diff --git a/.github/KnowledgeBase/Index_GacUI.md b/.github/KnowledgeBase/Index_GacUI.md index b7d03c5b..3c7d963e 100644 --- a/.github/KnowledgeBase/Index_GacUI.md +++ b/.github/KnowledgeBase/Index_GacUI.md @@ -13,7 +13,7 @@ Testing GacUI applications without real OS windows or rendering, using the remot - Use `GacUIUnitTest_LinkGuiMainProxy` for decorator-style proxy chaining to compose setup layers. - Use `GacUIUnitTest_StartFast_WithResourceAsText` for the most common entry point that compiles XML resources, registers themes, creates windows, and runs the application. - Use `GacUIUnitTest_Start` and `GacUIUnitTest_StartAsync` for synchronous and async protocol stack tests. -- Use `OnNextIdleFrame(name, callback)` on `UnitTestRemoteProtocol` to register frame callbacks; the name describes the rendering result, not the upcoming action. +- Use `OnNextIdleFrame(name, callback)` on `UnitTestRemoteProtocol` to register flat frame callbacks; the name describes the already-captured rendering result, and the callback should perform an observable UI-changing action. - Use `LocationOf(controlOrComposition)` to compute absolute screen coordinates for input simulation. - Use `LClick`, `RClick`, `MClick`, `LDBClick`, `MouseMove` for mouse input simulation. - Use `KeyPress`, `KeyDown`, `KeyUp`, `TypeString` for keyboard input simulation. @@ -124,7 +124,7 @@ Testing GacUI applications without real OS windows or rendering, using the remot #### Remote Protocol Core Architecture -- Remote protocol mode separates GacUI into a core side (application logic) and a renderer side (rendering and OS services), communicating through `IGuiRemoteProtocol`. +- Remote protocol mode separates GacUI into a core side (application logic) and a renderer side (rendering and OS services), communicating through `IGuiRemoteProtocol` over Parser2 JSON channel packages. - Messages flow core → renderer via `IGuiRemoteProtocolMessages`; events and responses flow renderer → core via `IGuiRemoteProtocolEvents`. - `GuiRemoteMessages` provides synchronous batched request-response with auto-incrementing IDs and blocking `Submit()`. - `GuiRemoteController` implements `INativeController` and all sub-services as virtual stubs: single window only, intentionally null clipboard/dialog services, synchronous key state queries. @@ -133,7 +133,7 @@ Testing GacUI applications without real OS windows or rendering, using the remot - `GuiRemoteGraphicsParagraph` handles rich text with run property system, incremental diff synchronization, delegated layout queries, and caret bounds caching. - DOM diff layer (`GuiRemoteProtocolDomDiffConverter`) converts per-frame command streams into diffed tree structures. - Protocol combinator and filter layers enable composable transformations and traffic optimization via `[@DropRepeat]`/`[@DropConsecutive]` annotations. -- Channel layer (`IGuiRemoteProtocolChannel`, `GuiRemoteProtocolAsyncChannelSerializer`) supports real remote deployment with async IO on a separate thread. +- Channel layer (`GuiRemoteProtocolCoreChannel`, `GuiRemoteProtocolRendererChannel`, `GuiRemoteProtocolAsyncJsonChannel`, `GuiRemoteProtocolAsyncJsonChannelRenderer`) supports real remote deployment, local clients, replacement renderers, and async main-thread dispatch. [Design Explanation](./KB_GacUI_Design_RemoteProtocolCoreArchitecture.md) @@ -141,11 +141,11 @@ Testing GacUI applications without real OS windows or rendering, using the remot - `GuiRemoteRendererSingle` is the renderer-side implementation that bridges `IGuiRemoteProtocol` to a real native window with actual graphics rendering, relying on an existing platform provider (e.g., Windows Direct2D). - It implements `IGuiRemoteProtocol` to receive protocol messages and translates them into native element operations, and implements `INativeWindowListener`/`INativeControllerListener` to forward OS events back as protocol events. -- Rendering pipeline: receives `RequestRendererBeginRendering` with `OrdinaryElementDescVariant` updates, applies them to real graphics elements, renders the DOM tree in `GlobalTimer()`, and returns measurement feedback via `RespondRendererEndRendering`. +- Rendering pipeline: receives `RequestRendererBeginRendering` with `OrdinaryElementDescVariant` updates, applies them to real graphics elements, updates the DOM through full DOM or DOM diff messages, refreshes completed frames, and returns measurement feedback via `RespondRendererEndRendering`. - Event forwarding coalesces high-frequency events (mouse move, wheel, key auto-repeat) and sends discrete events immediately; hit testing is performed locally by traversing the rendering DOM tree. -- Layered channel architecture for protocol serialization: `IGuiRemoteProtocol` ↔ `JsonObject` (via `GuiRemoteProtocolFromJsonChannel`/`GuiRemoteJsonChannelFromProtocol`) ↔ `WString` (via `JsonToStringSerializer`) ↔ user-implemented transport. +- Layered channel architecture for protocol serialization: `IGuiRemoteProtocol` is bridged by `GuiRemoteProtocolCoreChannel`/`GuiRemoteProtocolRendererChannel` over `IJsonChannel` packages, with network clients/servers using `glr::json::JsonNodeListSerializer`. - JSON envelope format with `semantic`, `id`, `name`, `arguments` fields; protocol types code-generated from `Protocol/*.txt` with `JsonHelper` specializations. -- `GuiRemoteProtocolAsyncChannelSerializer` provides thread separation (channel thread for IO, UI thread for application logic) with queued event delivery and connection-safe request matching. +- `GuiRemoteProtocolAsyncJsonChannel` and `GuiRemoteProtocolAsyncJsonChannelRenderer` provide async channel separation with queued events/responses, connection-safe request matching, renderer main-thread dispatch, and startup message caching. - Demo project pair (`RemotingTest_Core` and `RemotingTest_Rendering_Win32`) demonstrates full protocol stack assembly for both core and renderer sides with named-pipe/HTTP transport. [Design Explanation](./KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md) diff --git a/.github/KnowledgeBase/Index_Vlpp.md b/.github/KnowledgeBase/Index_Vlpp.md index c395497d..27342fea 100644 --- a/.github/KnowledgeBase/Index_Vlpp.md +++ b/.github/KnowledgeBase/Index_Vlpp.md @@ -117,9 +117,9 @@ Check out comments before `#ifndef VCZH_COLLECTIONS_OPERATION` for a full list o Algorithms for arranging data with support for both total and partial ordering relationships. -- Use `Sort(T*, vint)` for quick sort on raw pointer ranges -- Use lambda expressions returning `std::strong_ordering` or `std::weak_ordering` as comparators -- Use `PartialOrderingProcessor` for partial ordering scenarios where Sort doesn't work +- Use `Sort(T*, vint)` for quick sort on raw pointer ranges using `<=>` +- Use `Sort(T*, vint, comparer)` with comparators returning `std::strong_ordering`, `std::weak_ordering`, or usable `std::partial_ordering` +- Use `PartialOrderingProcessor` for dependency sorting by initializing relationships with `InitWithGroup`, `InitWithFunc`, or `InitWithSubClass`, then calling `Sort` - Use `<=>` operator to obtain ordering values for comparators [API Explanation](./KB_Vlpp_SortingOrdering.md) @@ -151,7 +151,9 @@ Testing infrastructure with hierarchical test organization and assertion capabil - Use `TEST_FILE` to define test file scope - Use `TEST_CATEGORY(name)` for grouping related tests - Use `TEST_CASE(name)` for individual test implementations +- Use `TEST_CASE_ASSERT(expression)` for a one-assertion test case - Use `TEST_ASSERT(expression)` for test assertions +- Use `TEST_ERROR(statement)` and `TEST_EXCEPTION(statement, exception, assertFunction)` for exception expectations - Use nested `TEST_CATEGORY` for hierarchical organization - Use `TEST_PRINT` for logging information to CLI in tests diff --git a/.github/KnowledgeBase/Index_VlppOS.md b/.github/KnowledgeBase/Index_VlppOS.md index 68e2ea51..8bda66b5 100644 --- a/.github/KnowledgeBase/Index_VlppOS.md +++ b/.github/KnowledgeBase/Index_VlppOS.md @@ -29,7 +29,7 @@ Cross-platform localization and globalization with culture-aware string operatio Cross-platform file and directory manipulation with path handling and content access. - Use `FilePath` for path representation and manipulation -- Use `GetName`, `GetFolder`, `GetFullPath`, `GetRelativePathFor` for path operations +- Use `GetPathDelimiter`, `operator/`, `GetName`, `GetFolder`, `GetFullPath`, `GetRelativePathFor` for path operations - Use `IsFile`, `IsFolder`, `IsRoot` to determine path object types - Use `File` class for file operations when `FilePath::IsFile` returns true - Use `ReadAllTextWithEncodingTesting`, `ReadAllTextByBom`, `ReadAllLinesByBom` for text reading @@ -37,7 +37,7 @@ Cross-platform file and directory manipulation with path handling and content ac - Use `Exists`, `Delete`, `Rename` for file operations - Use `Folder` class for directory operations when `FilePath::IsFolder` or `FilePath::IsRoot` returns true - Use `GetFolders`, `GetFiles` for directory content enumeration -- Use `Create` for creating new folders +- Use `Create(bool recursively)`, `Delete(bool recursively)`, `Rename` for folder operations - Use `InjectFileSystemImpl` to replace file system implementation for testing and customization - Use `EjectFileSystemImpl` to remove specific injected implementations or reset to default @@ -52,8 +52,8 @@ Unified stream interface for file, memory, and data transformation operations wi - Use `MemoryStream` for in-memory buffer operations - Use `MemoryWrapperStream` for operating on existing memory buffers - Use `EncoderStream` and `DecoderStream` for data transformation pipelines -- Use `IsAvailable`, `CanRead`, `CanWrite`, `CanSeek`, `IsLimited` for capability checking -- Use `Read`, `Write`, `Peek`, `Seek`, `Position`, `Size` for stream operations +- Use `IsAvailable`, `CanRead`, `CanWrite`, `CanSeek`, `CanPeek`, `IsLimited` for capability checking +- Use `Read`, `Write`, `Peek`, `Seek`, `SeekFromBegin`, `SeekFromEnd`, `Position`, `Size` for stream operations - Use `Close` for resource cleanup (automatic on destruction) [API Explanation](./KB_VlppOS_StreamOperations.md) @@ -106,7 +106,7 @@ Non-waitable synchronization objects for protecting shared resources in multi-th - Use `Enter`, `TryEnter`, `Leave` for manual lock management - Use `SPIN_LOCK`, `CS_LOCK`, `READER_LOCK`, `WRITER_LOCK` macros for exception-safe automatic locking - Use `ConditionVariable` with `SleepWith`, `SleepWithForTime` for conditional waiting -- Use `WakeOnePending`, `WaitAllPendings` for condition variable signaling +- Use `WakeOnePending`, `WakeAllPendings` for condition variable signaling [API Explanation](./KB_VlppOS_SynchronizationPrimitives.md) diff --git a/.github/KnowledgeBase/Index_VlppReflection.md b/.github/KnowledgeBase/Index_VlppReflection.md index dd3470c5..833837da 100644 --- a/.github/KnowledgeBase/Index_VlppReflection.md +++ b/.github/KnowledgeBase/Index_VlppReflection.md @@ -72,13 +72,16 @@ Comprehensive registration system for classes and interfaces with methods, prope - Use `CLASS_MEMBER_BASE` for reflectable base class declaration - Use `CLASS_MEMBER_FIELD` for member field registration - Use `CLASS_MEMBER_CONSTRUCTOR` for constructor registration with `Ptr(types...)` or `Class*(types...)` -- Use `CLASS_MEMBER_EXTERNALCTOR` for external function constructors -- Use `CLASS_MEMBER_METHOD` for method registration with parameter names -- Use `CLASS_MEMBER_METHOD_OVERLOAD` for overloaded method registration -- Use `CLASS_MEMBER_EXTERNALMETHOD` for external function methods -- Use `CLASS_MEMBER_STATIC_METHOD` for static method registration +- Use `CLASS_MEMBER_EXTERNALCTOR`, `CLASS_MEMBER_EXTERNALCTOR_TEMPLATE` for external function constructors +- Use `CLASS_MEMBER_METHOD`, `CLASS_MEMBER_METHOD_RENAME` for method registration with parameter names +- Use `CLASS_MEMBER_METHOD_OVERLOAD`, `CLASS_MEMBER_METHOD_OVERLOAD_RENAME` for overloaded method registration +- Use `CLASS_MEMBER_EXTERNALMETHOD`, `CLASS_MEMBER_EXTERNALMETHOD_TEMPLATE` for external function methods +- Use `CLASS_MEMBER_STATIC_METHOD`, `CLASS_MEMBER_STATIC_METHOD_OVERLOAD` for static method registration +- Use `CLASS_MEMBER_STATIC_EXTERNALMETHOD`, `CLASS_MEMBER_STATIC_EXTERNALMETHOD_TEMPLATE` for global functions registered as static methods - Use `CLASS_MEMBER_EVENT` for event registration - Use `CLASS_MEMBER_PROPERTY_READONLY`, `CLASS_MEMBER_PROPERTY` for property registration +- Use `CLASS_MEMBER_PROPERTY_EVENT_READONLY`, `CLASS_MEMBER_PROPERTY_EVENT` for properties with explicit getter/setter/event methods +- Use `CLASS_MEMBER_PROPERTY_REFERENCETEMPLATE` for properties with custom generated C++ reference code - Use `CLASS_MEMBER_PROPERTY_READONLY_FAST`, `CLASS_MEMBER_PROPERTY_FAST` for standard getter/setter patterns - Use `CLASS_MEMBER_PROPERTY_EVENT_READONLY_FAST`, `CLASS_MEMBER_PROPERTY_EVENT_FAST` for properties with change events - Use `NO_PARAMETER` for parameterless functions @@ -107,7 +110,7 @@ Proxy generation for interfaces to enable inheritance in Workflow scripts. Attach metadata attributes to types, members, and method parameters during reflection registration. -Attributes are instances of reflectable structs whose constructor arguments are serializable primitive values. +Attributes are instances of reflectable structs whose constructor arguments are serializable values, with `ITypeDescriptor*` as the explicit descriptor-reference exception. They are stored centrally in the owning type descriptor and can be queried at runtime via the `IAttributeBag` / `IAttributeInfo` interfaces. Attributes survive metaonly metadata serialization and deserialization, and appear in the logged text output. @@ -115,7 +118,7 @@ Attributes survive metaonly metadata serialization and deserialization, and appe - Use `ATTRIBUTE_MEMBER(TYPE, ...)` to attach an attribute to the most recently registered member (field, property, event, method, or constructor) - Use `ATTRIBUTE_PARAMETER(PARAMETER_NAME, TYPE, ...)` to attach an attribute to a named parameter of the most recently registered method or constructor - Use `IAttributeBag::GetAttributeCount` and `IAttributeBag::GetAttribute` to query attributes at runtime -- Use `IAttributeInfo::GetAttributeType`, `IAttributeInfo::GetAttributeValueCount`, `IAttributeInfo::GetAttributeValue` to inspect attribute content +- Use `IAttributeInfo::GetAttributeType`, `IAttributeInfo::GetAttributeValueCount`, `IAttributeInfo::GetAttributeValueType`, `IAttributeInfo::GetAttributeValue` to inspect attribute content [API Explanation](./KB_VlppReflection_AttributeRegistration.md) diff --git a/.github/KnowledgeBase/Index_VlppRegex.md b/.github/KnowledgeBase/Index_VlppRegex.md index d6c9a798..ef93a59f 100644 --- a/.github/KnowledgeBase/Index_VlppRegex.md +++ b/.github/KnowledgeBase/Index_VlppRegex.md @@ -6,16 +6,16 @@ Project introduction remains in [Index.md](./Index.md#vlppregex). #### Pattern Matching Operations -Text pattern matching and searching operations with support for different UTF encodings. +Text pattern matching and searching operations with support for different UTF encodings between pattern definitions and input text. - Use `Regex_` for pattern definition with `ObjectString` encoding - Use `MatchHead` for finding longest prefix matching the pattern - Use `Match` for finding earliest substring matching the pattern - Use `TestHead` for boolean prefix matching without detailed results - Use `Test` for boolean substring matching without detailed results -- Use `Search` for finding all non-overlapping matches -- Use `Split` for using pattern as delimiter to split text -- Use `Cut` for combined search and split operations +- Use `Search` for appending all non-overlapping successful matches to `RegexMatch_::List` +- Use `Split` for appending delimiter-separated unmatched fragments to `RegexMatch_::List` +- Use `Cut` for appending both successful and failed fragments in order [API Explanation](./KB_VlppRegex_PatternMatching.md) diff --git a/.github/KnowledgeBase/KB_GacUI_Design_ListControlArchitecture.md b/.github/KnowledgeBase/KB_GacUI_Design_ListControlArchitecture.md index 80d9a5cf..b3743bcf 100644 --- a/.github/KnowledgeBase/KB_GacUI_Design_ListControlArchitecture.md +++ b/.github/KnowledgeBase/KB_GacUI_Design_ListControlArchitecture.md @@ -23,6 +23,7 @@ - `IItemProviderCallback` receives notifications via `OnItemModified(start, count, newCount, itemReferenceUpdated)` - `itemReferenceUpdated` flag indicates whether item identity changed (requires recreation) or just content changed (can refresh in place) - Base implementation `ItemProviderBase` manages callbacks and editing counter +- `DetachCallback(IItemProviderCallback*)` is observable: when a registered callback is detached, the provider calls `OnAttached(nullptr)` on that callback before returning success. **View System**: Providers expose multiple view interfaces via `RequestView(identifier)` for specialized data access: - `ITextItemView`: Checkbox state for `GuiVirtualTextList` @@ -212,12 +213,15 @@ All predefined arrangers use `RangedItemArrangerBase` which delegates to `GuiVir - `ListViewItemBindableProvider`: Wraps `IValueObservableList` as list view items - `TreeViewItemBindableRootProvider`: Wraps object graph as tree - Uses reflection (`description::Value`) to access properties +- `TreeViewItemBindableRootProvider::UpdateBindingProperties(true)` is root-scoped: it unprepares and re-prepares the root node's immediate children, but it does not recursively process already-prepared descendants. **Converter Providers**: - `NodeItemProvider`: Converts `INodeRootProvider` (tree) to `IItemProvider` (flat list) - Implements `INodeItemView` for node access - Tracks expanded/collapsed state - Updates indices when tree structure changes +- `RequestNode(index)` returns the visible node for a valid flat index and returns `nullptr` for out-of-range indices. APIs that require a valid item, such as `GetTextValue` and `GetBindingValue`, validate the range with `CHECK_ERROR` before requesting the node. +- `CalculateNodeVisibilityIndex(node)` returns `-1` for nodes that belong to the tree but are invisible because an ancestor is collapsed. Passing a node from another tree is invalid and fails through `CHECK_ERROR` with an `ERROR_MESSAGE_PREFIX` diagnostic. **Base Class Hierarchy**: - `ItemProviderBase`: Manages callbacks and editing counter diff --git a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md index c26e0789..7eafc5f5 100644 --- a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md +++ b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md @@ -79,6 +79,8 @@ A connection begins when the renderer side fires `OnControllerConnect(Controller `GuiRemoteWindow::OnControllerConnect()` re-sends all current window styles when `applicationRunning` is true, enabling seamless reconnection — the renderer side experiences a fresh complete window state setup. +Channel servers distinguish the local core client from renderer clients through the `localClient` argument in `OnClientConnected`. A new renderer client is accepted even if an older renderer is still connected: the server records the new renderer as current, detaches the old renderer from `GuiRemoteProtocolCoreChannel`, sends a raw `ControllerConnectionStopped` package when possible, and disconnects the old transport only as a fallback. + ### Disconnection The renderer side fires `OnControllerDisconnect()`. Each subsystem (`GuiRemoteWindow`, `GuiRemoteGraphicsImageService`, `GuiRemoteGraphicsResourceManager`) marks itself disconnected and suspends protocol communication until reconnection. @@ -219,7 +221,7 @@ Unlike ordinary elements (borders, backgrounds, labels) that send their visual s ### Lifecycle -Paragraphs are created by `GuiRemoteGraphicsResourceManager::CreateParagraph()`. Each paragraph gets its own element ID (shared ID space with element renderers), registered via `RegisterParagraph()` instead of `RegisterRenderer()`. Creations are batched in `pendingParagraphCreations` alongside element creates in `EnsureRequestedRenderersCreated()`. +Paragraphs are created by `GuiRemoteGraphicsResourceManager::CreateParagraph()`. Each paragraph gets its own element ID (shared ID space with element renderers), registered on `GuiRemoteGraphicsRenderTarget` via `RegisterParagraph()` instead of `RegisterRenderer()`. Creations are batched in `pendingParagraphCreations` alongside element creates in `EnsureRequestedRenderersCreated()`. The paragraph treats `id == -1` as the single "not registered / unavailable" state, and `UnregisterParagraph(id)` removes either a pending creation or an active paragraph before queuing renderer destruction. ### Run Property System @@ -232,26 +234,28 @@ Paragraph styling uses a run-based system with three layers: ### Core Synchronization (EnsureRemoteParagraphSynced) -This is the core synchronization method, called before any query: +This is the core synchronization method, called before any query. It returns `false` when the paragraph is unavailable (`id == -1` or no render target) or when a synchronous submit reports disconnection, allowing callers to return conservative defaults instead of using stale remote state: 1. `EnsureRequestedRenderersCreated()` — ensures the paragraph element exists on the renderer side 2. Merge text and inline object runs into `stagedRuns` 3. `DiffRuns(committedRuns, stagedRuns, desc)` — compute diff between last committed and current state 4. Send `RequestRendererUpdateElement_DocumentParagraph(desc)` with the diff 5. `Submit()` synchronously — the response includes the new `documentSize` -6. Store `cachedSize` from response, swap `stagedRuns` to `committedRuns` +6. If still connected, store `cachedSize` from response, remove stale cached inline-object bounds reported by `removedInlineObjects`, move `stagedRuns` to `committedRuns`, and mark `remoteParagraphCreated = true` The first sync sends full text content; subsequent syncs send only run property diffs. This is efficient for scenarios like syntax highlighting where only styling changes between frames. +On controller reconnection, `GuiRemoteGraphicsRenderTarget::OnControllerConnect()` re-registers all paragraph IDs, calls `ResetRemoteParagraphSyncState()` on each paragraph, and immediately re-syncs them so the new renderer receives complete paragraph state. + ### Layout Queries (Delegated to Renderer Side) All layout-dependent operations require synchronous round-trips or cached data: -- **`GetSize()`**: returns `cachedSize` from the last sync -- **`GetCaret(comparingCaret, position, preferFrontSide)`**: sends `RequestDocumentParagraph_GetCaret` — the renderer side performs text layout-aware caret navigation -- **`GetCaretBounds(caret, frontSide)`**: uses `GetCaretBoundsInternal()` which lazily fetches ALL caret bounds (front and back arrays) in one request via `RequestDocumentParagraph_GetCaretBounds` and caches them in `cachedCaretBounds`. Subsequent calls use the cache. -- **`GetCaretFromPoint(point)`**: iterates all caret positions locally using cached bounds from `GetCaretBoundsInternal()`, finding the nearest by Manhattan distance — no additional remote call if bounds are cached -- **`GetInlineObjectFromPoint(point)`**: sends `RequestDocumentParagraph_GetInlineObjectFromPoint` for hit-test, then looks up `inlineObjectProperties` locally -- **`GetNearestCaretFromTextPos(textPos, frontSide)`**: sends `RequestDocumentParagraph_GetNearestCaretFromTextPos` -- **`IsValidCaret(caret)`**: sends `RequestDocumentParagraph_IsValidCaret` +- **`GetSize()`**: calls `EnsureRemoteParagraphSynced()` and returns `cachedSize`; when sync fails, the current cached value is returned. +- **`GetCaret(comparingCaret, position, preferFrontSide)`**: sends `RequestDocumentParagraph_GetCaret`; if sync or submit fails, it returns `comparingCaret` and resets `preferFrontSide` to false. +- **`GetCaretBounds(caret, frontSide)`**: uses `GetCaretBoundsInternal()` which lazily fetches ALL caret bounds (front and back arrays) in one request via `RequestDocumentParagraph_GetCaretBounds` and caches them in `cachedCaretBounds`. Subsequent calls use the cache; failed sync returns an empty rectangle. +- **`GetCaretFromPoint(point)`**: iterates all caret positions locally using cached bounds from `GetCaretBoundsInternal()`, finding the nearest by Manhattan distance; if bounds cannot be fetched, it returns the best caret found so far. +- **`GetInlineObjectFromPoint(point)`**: sends `RequestDocumentParagraph_GetInlineObjectFromPoint` for hit-test, then looks up `inlineObjectProperties` locally; failed sync or submit returns null. +- **`GetNearestCaretFromTextPos(textPos, frontSide)`**: sends `RequestDocumentParagraph_GetNearestCaretFromTextPos`; failed sync or submit returns `textPos`. +- **`IsValidCaret(caret)`**: sends `RequestDocumentParagraph_IsValidCaret`; failed sync or submit returns false. - **`IsValidTextPos(textPos)`**: purely local — checks bounds against `text.Length()` ### Caret Display @@ -263,7 +267,7 @@ All layout-dependent operations require synchronous round-trips or cached data: ### Paragraph Rendering `GuiRemoteGraphicsParagraph::Render(bounds)`: -1. `EnsureRemoteParagraphSynced()` — ensure current state sent to renderer side +1. Return immediately if there is no render target or if `EnsureRemoteParagraphSynced()` fails. 2. For each inline object with cached bounds, call `callback->OnRenderInlineObject()` — if the inline object's size changed, update `inlineObjectRuns` and mark dirty 3. Send `RequestRendererRenderElement` — same as ordinary elements @@ -274,7 +278,7 @@ All layout-dependent operations require synchronous round-trips or cached data: - `cachedSize = {0,0}` if `invalidateSize` is true (forcing re-measurement from renderer side) - `needUpdateCaretBoundsCache = true` if `invalidateCaretBoundsCache` is true (forcing caret bounds refetch) -Size-affecting changes (font, size, text content, wrap line, max width) set both invalidation flags. Color-only changes skip size invalidation because they don't affect layout. +Size-affecting changes (font, size, text content, wrap line, max width) set both invalidation flags. Text color and background color changes still mark the paragraph dirty and clear `cachedSize`, but they do not force the caret-bounds cache to be refreshed. ## DOM Diff Layer @@ -290,6 +294,7 @@ Without DOM diff, each frame sends per-element rendering commands (`RenderElemen - First frame: sends `RequestRendererRenderDom` with the full tree - Subsequent frames: `DiffDom(lastDom, lastDomIndex, newDom, newDomIndex, diffs)` computes structural diffs, sends `RequestRendererRenderDomDiff` - `lastDom` is stored for next frame; `OnControllerConnect` clears it to force a full DOM send on reconnection +- Renderer-side diff application uses `UpdateDomInplace(renderingDom, renderingDomIndex, diffs)`, then marks `needRefresh = true`. `RequestRendererEndRendering` can immediately force a repaint for a completed frame, so empty or measurement-only frame traffic still reaches the native render target when the DOM path reports a frame. DOM node IDs encode their type: element IDs use `(elementId << 2) + 0`, hit test compositions use `(compositionId << 2) + 2`, with parent variants at `+1` and `+3`. @@ -309,11 +314,15 @@ The filter queues messages internally and applies drop logic in `ProcessRequests ## Channel Layer -For real remote deployment, `IGuiRemoteProtocolChannel` provides a bidirectional channel abstraction. The typical stack: -1. `GuiRemoteProtocolFromJsonChannel` adapts a JSON channel to `IGuiRemoteProtocol` -2. `GuiRemoteProtocolAsyncChannelSerializer>` (aliased as `GuiRemoteProtocolAsyncJsonChannelSerializer`) runs channel IO on a separate thread +For real remote deployment, `channeling::IJsonChannel` is an alias of `inter_process::IChannel>`. Network channels are built with `GuiRemoteProtocolNetworkChannelServer`, `GuiRemoteProtocolChannelClient`, and `glr::json::JsonNodeListSerializer`, so protocol packages move as Parser2 JSON node lists instead of ad-hoc string serialization. -The async serializer's design: the UI thread batches messages and blocks on `Submit()` until the channel thread completes the round-trip. Events from the channel thread are queued and processed on the UI thread during `ProcessRemoteEvents()`. Connection/disconnection is tracked via a `connectionCounter` to handle race conditions when the channel thread delivers events after disconnection. +The core-side stack uses: +1. `GuiRemoteProtocolCoreChannel` to implement `IGuiRemoteProtocol` over an `IJsonChannel`, pack requests/events with `JsonChannelPack`, dispatch responses/events by protocol name, queue packages until a renderer client is known, and detach stale renderer clients. +2. `GuiRemoteProtocolAsyncJsonChannel` when core/channel separation is needed. It wraps an `IJsonChannel`, queues outgoing packages, queues received events for `ProcessRemoteEvents()`, stores responses by request id, and uses `connectionCounter` plus `PendingRequestGroup` to keep blocking `Submit()` calls consistent across disconnects. + +The renderer-side stack uses: +1. `GuiRemoteProtocolAsyncJsonChannelRenderer` when network packages can arrive before the native GacUI window is ready. It queues received packages until `SetInvokeInMainThread(...)` installs an `IGuiRemoteProtocolAsyncRendererInvoker`, then drains them on the renderer UI thread. Its message version prevents callbacks captured by an old reader from running after the reader is replaced. +2. `GuiRemoteProtocolRendererChannel` to bridge the renderer JSON channel to a concrete renderer `IGuiRemoteProtocol` implementation and to serialize renderer events/responses back to the channel. ## Image Service diff --git a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md index 67d31cc1..c5f6b119 100644 --- a/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md +++ b/.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md @@ -41,7 +41,9 @@ When the core sends `RequestRendererBeginRendering(ElementBeginRendering)`, the The core sends `RequestRendererRenderDom` or `RequestRendererRenderDomDiff` to update the rendering DOM tree. The legacy command-based rendering path (`RequestRendererBeginBoundary`/`RequestRendererEndBoundary`/`RequestRendererRenderElement`) is disabled with `CHECK_FAIL` — the DOM-diff approach is required. -Actual painting happens in `GlobalTimer()`: if `needRefresh` is true, it calls `Render(renderingDom, rt)` which recursively traverses the DOM tree, rendering each element in order with clipping. After rendering, it checks if label measurements changed and populates `elementMeasurings`. The response is returned via `RespondRendererEndRendering(id, elementMeasurings)`. +`RequestRendererRenderDom` installs a full `RenderingDom`, rebuilds `renderingDomIndex`, and sets `needRefresh = true`. `RequestRendererRenderDomDiff` requires an existing DOM, applies `UpdateDomInplace(renderingDom, renderingDomIndex, diffs)`, and also sets `needRefresh = true`. + +Actual painting happens in either `RequestRendererEndRendering` or `GlobalTimer()`: when `needRefresh` is true and refresh is no longer suppressed, `ForceRender()` recursively traverses `renderingDom`, renders each element in order with clipping, redraws the native window, and handles render-target resize/lost-device failures by resizing/recreating the target and scheduling another refresh. `RequestRendererEndRendering` clears suppression and can force the completed frame to render before returning `RespondRendererEndRendering(id, elementMeasurings)`. `GlobalTimer()` also flushes accumulated IO events, drives caret blinking, and renders any pending refresh not already handled at frame end. ### Event Forwarding @@ -58,44 +60,36 @@ Actual painting happens in `GlobalTimer()`: if `needRefresh` is true, it calls ` Hit testing is performed locally in the renderer by traversing the rendering DOM tree via `HitTestInternal`. Each DOM node may have `hitTestResult` and `cursor` attributes set by the core side. The renderer walks the tree and finds the matching node for a given point. This avoids round-trips — hit testing stays entirely renderer-side. +### Document Paragraph Rendering + +Document paragraph elements are represented by the renderer-local `GuiRemoteDocumentParagraphElement` wrapper. The wrapper is both an `IGuiGraphicsElement` and its own renderer/factory, but it delegates actual text layout to an `IGuiGraphicsParagraph` created from the active render target. + +The wrapper caches full paragraph state, including text, wrapping/alignment/max-width, caret state, text runs, inline-object runs, merged runs, inline-object bounds, inline-object properties, and callback ranges. This cache is required because `SetRenderTarget(nullptr)` destroys the native paragraph object; when a new render target arrives, `TryRecreateParagraph()` creates a fresh paragraph and reapplies properties, runs, and caret state. + +`ApplyUpdateAndFillResponse(arguments, response)` accepts incremental `runsDiff` updates from the core side. The first update must contain `text`; later updates must omit `text` and only update properties/runs. Removed inline objects clear cached bounds/properties/ranges and reset native paragraph ranges when the paragraph exists. Inline-object hit testing calls `TryGetInlineObjectRunProperty(callbackId, outProp)` instead of directly reading the wrapper dictionaries. + ## Protocol Serialization and Channel Architecture -The serialization system uses a layered channel architecture to convert between typed protocol calls and transport-ready data. Each layer is a composable building block. +The channel system converts typed protocol calls to Parser2 JSON node packages and moves those packages through `VlppOS` inter-process channels. It no longer uses a separate JSON-string transformer stack for GacUI remoting. ### Core Interfaces -- `IGuiRemoteProtocolChannel`: A bidirectional channel that can `Write` packages and receives packages via `IGuiRemoteProtocolChannelReceiver::OnReceive`. - `IGuiRemoteProtocol`: The high-level typed protocol interface with named methods (`RequestNAME`, `OnNAME`, `RespondNAME`). +- `channeling::JsonPackage`: Alias of `Ptr`. +- `channeling::IJsonChannel`: Alias of `inter_process::IChannel`, with `IJsonChannelReader`, `IJsonChannelClient`, and `IJsonChannelServer` aliases for the corresponding channel roles. +- `GuiRemoteProtocolNetworkChannelServer`: Network channel server alias based on `inter_process::NetworkProtocolChannelServer`. +- `GuiRemoteProtocolChannelClient` and `GuiRemoteProtocolLocalChannelClient`: Client helpers that expose the `GacUIRemoteProtocol` JSON channel through `GetProtocolChannel()`. -### Layer 1: IGuiRemoteProtocol ↔ JsonObject Channel +### Typed Protocol to JSON Channel -Two adapter classes handle bidirectional conversion between typed protocol calls and JSON objects: +Two channel adapters bridge typed protocol calls and JSON packages: -- `GuiRemoteProtocolFromJsonChannel`: Wraps `IGuiRemoteProtocolChannel>` and implements `IGuiRemoteProtocol`. When `RequestNAME(arguments)` is called, it serializes arguments to JSON via `ConvertCustomTypeToJson()`, packs them into a JSON envelope with `JsonChannelPack()`, and calls `channel->Write(package)`. When it receives a JSON package via `OnReceive`, it unpacks with `JsonChannelUnpack()`, dispatches by name, deserializes via `ConvertJsonToCustomType()`, and calls `events->OnNAME()` or `events->RespondNAME()`. -- `GuiRemoteJsonChannelFromProtocol`: The reverse — wraps `IGuiRemoteProtocol` and implements `IGuiRemoteProtocolChannel>`. Converts incoming JSON packages to protocol calls, and outgoing events/responses to JSON packages. +- `GuiRemoteProtocolCoreChannel`: Implements `IGuiRemoteProtocol` and reads from an `IJsonChannel`. `RequestNAME(...)` serializes arguments with `ConvertCustomTypeToJson()`, packs an envelope with `JsonChannelPack()`, and sends it to the current renderer client. Incoming packages are unpacked with `JsonChannelUnpack()`, dispatched by name, deserialized with `ConvertJsonToCustomType()`, and delivered as `events->OnNAME(...)` or `events->RespondNAME(...)`. It also queues outgoing packages before a renderer is known and exposes `DetachRenderer(clientId)` for renderer replacement. +- `GuiRemoteProtocolRendererChannel`: Reads renderer-side JSON packages and calls the wrapped `IGuiRemoteProtocol`. It also implements `IGuiRemoteProtocolEvents` so renderer events and responses are serialized back to JSON packages and flushed through the renderer channel. -### Layer 2: JsonObject ↔ WString Channel +### Transport Layer -`JsonToStringSerializer` handles JSON-to-string conversion: -- `Serialize`: Converts `Ptr` to `WString` using `JsonToString` with compact formatting. -- `Deserialize`: Parses `WString` to `Ptr` using `JsonParse`. -- Type aliases: `GuiRemoteJsonChannelStringSerializer` and `GuiRemoteJsonChannelStringDeserializer` for `GuiRemoteProtocolChannelSerializer` and `GuiRemoteProtocolChannelDeserializer`. - -### Layer 3: WString ↔ Transport (User-Implemented) - -The user provides an `IGuiRemoteProtocolChannel` implementation that sends and receives strings over any transport mechanism (named pipe, HTTP, WebSocket, etc.). GacUI does not provide transport implementations directly. - -### Layer 4: UTF String Conversion (Optional) - -`GuiRemoteUtfStringChannelSerializer` / `GuiRemoteUtfStringChannelDeserializer` can convert between `WString` and other UTF string types if the transport requires a specific encoding. - -### Channel Transformer Pattern - -`GuiRemoteProtocolChannelTransformerBase` bridges two channel types: -- `GuiRemoteProtocolChannelSerializer`: Calls `TSerialization::Serialize` on `Write` and `TSerialization::Deserialize` on `OnReceive`. -- `GuiRemoteProtocolChannelDeserializer`: The reverse — calls `Deserialize` on `Write` and `Serialize` on `OnReceive`. - -The `TSerialization` concept requires: `SourceType`, `DestType`, `ContextType`, and static `Serialize`/`Deserialize` methods. +Named-pipe and HTTP remoting use `VlppOS` network protocol clients/servers underneath the JSON channel. The channel serializer for transport package lists is `glr::json::JsonNodeListSerializer`, shared with Parser2 JSON infrastructure. GacUI remoting code therefore passes `JsonPackage` values through channel clients and servers instead of converting each package through a dedicated `WString` protocol layer. ### JSON Envelope Format @@ -117,13 +111,9 @@ Protocol types are code-generated from `Protocol/*.txt` files into `GuiRemotePro ### Async Channel -`GuiRemoteProtocolAsyncChannelSerializer` provides thread separation for real remote deployment: -- A **channel thread** handles all `Write`/`OnReceive` calls on the underlying channel (IO operations). -- A **UI thread** runs the GacUI application logic. -- `Start(channel, uiMainProc, startingProc)` launches both threads. The `startingProc` is responsible for creating the threads. -- Events received asynchronously are queued and dispatched on the UI thread during `Submit()`. -- Responses are matched to pending requests using a `PendingRequestGroup` with `connectionCounter` for safe handling of disconnection/reconnection races. -- `ExecuteInChannelThread()` allows queueing work from any thread to the channel thread. +`GuiRemoteProtocolAsyncJsonChannel` is the core-side async wrapper around an `IJsonChannel`. It queues outgoing packages, queues incoming events for `ProcessRemoteEvents()`, stores incoming responses by request id, and blocks `BatchWrite(disconnected)` until the current `PendingRequestGroup` is satisfied or disconnected. `connectionCounter` and `connectionClientId` protect pending requests when channel events arrive after disconnect/reconnect boundaries. + +`GuiRemoteProtocolAsyncJsonChannelRenderer` is the renderer-side async wrapper. It queues received packages and schedules `ProcessRemoteMessages()` through an `IGuiRemoteProtocolAsyncRendererInvoker`. Before `SetInvokeInMainThread(...)` is called by renderer `GuiMain`, packages are cached. After the invoker is installed, they are drained on the renderer UI thread. A `messageVersion` stamp prevents messages captured by an old reader from running after the channel reader is replaced or detached. ## Demo Project Pair @@ -134,34 +124,28 @@ Two projects in `Test/GacUISrc/` demonstrate a full remote protocol deployment. Located at `Test/GacUISrc/RemotingTest_Core/`. Accepts `/Pipe` or `/Http` arguments to start either a named-pipe server or HTTP server. **Protocol stack setup** (`StartServer` in `GuiMain.cpp`): -1. Creates `CoreChannel` wrapping the transport server — implements `IGuiRemoteProtocolChannel`. -2. Creates `GuiRemoteJsonChannelStringSerializer` for JSON ↔ String conversion. -3. Creates `GuiRemoteProtocolAsyncJsonChannelSerializer` for async channel/UI thread separation. -4. Starts via `asyncChannelSender.Start()` with a UI main proc that builds the protocol stack: `GuiRemoteProtocolFromJsonChannel` → `GuiRemoteProtocolFilter` → `GuiRemoteProtocolDomDiffConverter` → `SetupRemoteNativeController`. -5. Waits for a renderer client to connect, then waits for the async channel to stop, then waits for disconnection. +1. Creates `NamedPipeRemotingChannelServer` or `HttpRemotingChannelServer`, both derived from `RemotingChannelServerBase` and ultimately `GuiRemoteProtocolNetworkChannelServer`. +2. Starts the server, creates a local core client with `GuiRemoteProtocolLocalChannelClient`, and connects it to the server. +3. Waits for the first non-local renderer client with a manual-reset `EventObject` signaled from `OnClientConnected`. +4. Wraps the core client's protocol channel in `GuiRemoteProtocolAsyncJsonChannel`. +5. Creates `GuiRemoteProtocolCoreChannel`, then builds `GuiRemoteProtocolFilter` -> `GuiRemoteProtocolDomDiffConverter` -> `SetupRemoteNativeController`. +6. On shutdown, disconnects the named-pipe renderer client when needed, clears core channel/server pointers, and stops the channel server before stack-owned wrappers are destroyed. -**CoreChannel** bridges async channel and network transport: -- `Write(WString)`: Accumulates messages in a pending list. -- `Submit()`: Dispatches accumulated messages via `networkProtocol->SendStringArray()`. -- `OnReadStringThreadUnsafe()`: Called when strings arrive from the network, queues them for channel thread processing via `asyncChannel->ExecuteInChannelThread()`. -- Detects `ControllerConnect` event JSON to track connection state. -- On reconnection (`OnReconnectedUnsafe`), injects a `ControllerDisconnect` event. +`RemotingChannelServerBase::OnClientConnected` accepts replacement renderers. If a different renderer is already current, it calls `GuiRemoteProtocolCoreChannel::DetachRenderer(oldClientId)`, tries to send a raw `ControllerConnectionStopped` package to the old renderer, and disconnects the old transport only when notification fails. Fatal exceptions from `GuiMain()` are sent to connected clients via `BroadcastError(...)` before the error is printed. ### RemotingTest_Rendering_Win32 (Windows Application) Located at `Test/GacUISrc/RemotingTest_Rendering_Win32/`. Accepts `/Pipe` or `/Http` arguments to start as a named-pipe or HTTP client. **Protocol stack setup** (`StartClient` in `GuiMain.cpp`): -1. Creates `GuiRemoteRendererSingle` — the renderer implementing `IGuiRemoteProtocol`. -2. Creates `GuiRemoteJsonChannelFromProtocol` wrapping the renderer — converts protocol calls to JSON. -3. Creates `GuiRemoteJsonChannelStringDeserializer` for String ↔ JSON conversion. -4. Creates `RendererChannel` connecting renderer, transport, and channel. -5. Hooks `BeforeWrite`/`BeforeOnReceive` events for request/response caching in `RendererChannel`. -6. Calls `SetupRawWindowsDirect2DRenderer()` to run the native window event loop. +1. Creates `RemotingTestChannelClient`, derived from `GuiRemoteProtocolChannelClient`, over a named-pipe or HTTP `INetworkProtocolClient`. +2. Creates `GuiRemoteProtocolAsyncJsonChannelRenderer` over the client's protocol channel. +3. Creates `GuiRemoteRendererSingle` and `GuiRemoteProtocolRendererChannel(&asyncRendererChannel, &remoteRenderer)`. +4. Waits for the server, then calls `SetupRawWindowsDirect2DRenderer()` to run the native window event loop. +5. In `GuiMain()`, creates the native window, registers it with `GuiRemoteRendererSingle`, installs `GuiMainAsyncRendererInvoker` through `asyncChannel->SetInvokeInMainThread(&invoker)`, and runs the window service. +6. On exit, clears the invoker, unregisters the main window, stops the network connection, and clears stack-owned renderer/channel pointers. -**RendererChannel** bridges network transport and JSON channel: -- `OnReadStringThreadUnsafe()`: Receives strings from network, dispatches to UI thread via `InvokeInMainThread()`. Handles error strings (prefixed with `!`) by displaying a `MessageBox` and calling `renderer->ForceExitByFatelError()`. -- `OnReceive(WString)`: Uses a caching mechanism (`isCaching`/`cachedPackages`) to batch responses — when a `Request` semantic is detected (via `BeforeWrite`), caching is enabled; when a `Response` semantic is detected (via `BeforeOnReceive`), caching is disabled and all cached packages are sent as a batch. +`RemotingTestChannelClient` handles remote fatal errors by showing a native error message and calling `GuiRemoteRendererSingle::ForceExitByFatelError()`. On disconnect, it detaches the async renderer channel with `Initialize(nullptr)` and forces renderer exit unless the disconnect was already caused by a fatal error. ### Protocol Stack Direction @@ -170,19 +154,19 @@ Located at `Test/GacUISrc/RemotingTest_Rendering_Win32/`. Accepts `/Pipe` or `/H SetupRemoteNativeController → GuiRemoteProtocolDomDiffConverter → GuiRemoteProtocolFilter - → GuiRemoteProtocolFromJsonChannel - → GuiRemoteProtocolAsyncJsonChannelSerializer - → GuiRemoteJsonChannelStringSerializer - → CoreChannel (IGuiRemoteProtocolChannel) - → Network transport (user-implemented) + → GuiRemoteProtocolCoreChannel + → GuiRemoteProtocolAsyncJsonChannel + → GuiRemoteProtocolLocalChannelClient / IJsonChannel + → GuiRemoteProtocolNetworkChannelServer + → Named pipe or HTTP transport ``` **Renderer side** (messages flow inward): ``` -Network transport (user-implemented) - → RendererChannel - → GuiRemoteJsonChannelStringDeserializer - → GuiRemoteJsonChannelFromProtocol +Named pipe or HTTP transport + → GuiRemoteProtocolChannelClient / IJsonChannel + → GuiRemoteProtocolAsyncJsonChannelRenderer + → GuiRemoteProtocolRendererChannel → GuiRemoteRendererSingle (IGuiRemoteProtocol) → Native window rendering ``` diff --git a/.github/KnowledgeBase/KB_GacUI_RemoteProtocolUnitTestFramework.md b/.github/KnowledgeBase/KB_GacUI_RemoteProtocolUnitTestFramework.md index 4322132a..20cfeffb 100644 --- a/.github/KnowledgeBase/KB_GacUI_RemoteProtocolUnitTestFramework.md +++ b/.github/KnowledgeBase/KB_GacUI_RemoteProtocolUnitTestFramework.md @@ -151,31 +151,29 @@ It also saves the compiled Workflow script text as a snapshot file (`[x64].txt` ### Synchronous Mode (GacUIUnitTest_Start) -`GacUIUnitTest_Start` constructs the full protocol stack in-process: +`GacUIUnitTest_Start` constructs the protocol stack in-process. When `useChannel == UnitTestRemoteChannel::Sync`, it creates a local Parser2 JSON channel server and two local channel clients: -**Renderer side (deserialization direction):** -- `UnitTestRemoteProtocol` — mock `IGuiRemoteProtocol` implementation. -- `GuiRemoteJsonChannelFromProtocol` — converts protocol calls to JSON. -- `GuiRemoteJsonChannelStringDeserializer` — JSON to String. -- `GuiRemoteUtfStringChannelDeserializer` — String to UTF-8. +**Renderer side:** +- `UnitTestRemoteProtocol` — mock `IGuiRemoteProtocol` implementation and `IGuiRemoteEventProcessor`. +- `GuiRemoteProtocolLocalChannelClient` — local renderer-side JSON channel client. +- `GuiRemoteProtocolRendererChannel` — bridges the renderer client's `IJsonChannel` to `UnitTestRemoteProtocol`. -**Core side (serialization direction, mirrors back):** -- `GuiRemoteUtfStringChannelSerializer` — UTF-8 to String. -- `GuiRemoteJsonChannelStringSerializer` — String to JSON. -- `GuiRemoteProtocolFromJsonChannel` — JSON to typed protocol calls. +**Core side:** +- `GuiRemoteProtocolLocalChannelClient` — local core-side JSON channel client. +- `GuiRemoteProtocolCoreChannel` — implements `IGuiRemoteProtocol` over the core client's `IJsonChannel`, using the unit-test executable path and `UnitTestRemoteProtocol` event processor. **Protocol filter layers on core side:** - `GuiRemoteProtocolFilterVerifier` — validates repeat-filtering invariants. - `GuiRemoteProtocolFilter` — filters redundant messages. - `GuiRemoteProtocolDomDiffConverter` — (optional, when `useDomDiff` is true) converts full DOM to DOM diffs. -When `useChannel == UnitTestRemoteChannel::None`, the verifier directly wraps `UnitTestRemoteProtocol`'s `IGuiRemoteProtocol`, bypassing the serialization layers for speed. Otherwise, the full serialization channel is used for testing round-trip fidelity. +When `useChannel == UnitTestRemoteChannel::None`, the verifier directly wraps `UnitTestRemoteProtocol`'s `IGuiRemoteProtocol`, bypassing the channel layer for speed. When `useChannel == UnitTestRemoteChannel::Sync`, the channel path tests round-trip JSON package fidelity through the local channel server. Finally, `SetupRemoteNativeController(protocol)` creates the runtime stack: `GuiRemoteController` → `GuiHostedController` → resource managers, then calls `GuiApplicationMain()` → `GuiMain()` → the registered test proxy. ### Async Mode (GacUIUnitTest_StartAsync) -`GacUIUnitTest_StartAsync` inserts `GuiRemoteProtocolAsyncJsonChannelSerializer` into the channel, placing the core and renderer on separate threads. Two threads are spawned via `RunInNewThread`: a channel thread for serialization I/O and a UI thread for the GacUI application. The call waits for `asyncChannelSender.WaitForStopped()` before writing snapshots. +`GacUIUnitTest_StartAsync` uses the same local channel server/client setup, but wraps the core client's protocol channel in `GuiRemoteProtocolAsyncJsonChannel`. The renderer still uses `GuiRemoteProtocolRendererChannel`; the core thread creates `GuiRemoteProtocolCoreChannel` over the async JSON channel and then runs the normal verifier/filter/DOM-diff stack. `GuiRemoteProtocolAsyncJsonChannel` queues channel events and responses, exposes its `IGuiRemoteEventProcessor`, and uses connection counters to keep pending request groups consistent across disconnects. ## UnitTestRemoteProtocol Class Hierarchy @@ -223,6 +221,9 @@ The frame name is assigned to the **already-committed snapshot** (the rendering - The `frameName` describes what **led to** the current visual state, not what the callback will do next. - The first frame is conventionally named `"Ready"`, representing the initial rendering state after the window opens. - Subsequent frame names describe the action taken in the previous callback (e.g., `"Hover"` means the previous callback moved the mouse, and this snapshot shows the hover state). +- Register all `OnNextIdleFrame` callbacks flatly in the test proxy; do not register a new idle frame from inside another idle-frame callback. +- A frame callback should usually perform an action that can trigger a rendering update, such as typing, clicking, changing selection, showing/closing a dialog, or hiding the window. Avoid standalone verification-only frames, because the framework expects progress toward a new settled rendering frame. +- Re-find windows and controls inside each frame callback instead of caching GUI control pointers across frames. The sequence is: 1. Application renders → DOM/elements captured as `candidateFrame`. @@ -287,6 +288,8 @@ Overloads accept either `GuiGraphicsComposition*` or `GuiControl*`. - `KeyPress(key, ctrl, shift, alt)` — wraps the key press with modifier key down/up events. - `TypeString(text)` — sends a sequence of `OnIOChar` events for each character via `MakeCharInfo`, without synthesizing key down/up events. +Keyboard helpers target the currently focused control. Focus the target control in the frame (for example with `SetFocused()` or a click) before using `TypeString`, `KeyPress`, `KeyDown`, or `KeyUp`. + ### Event Flow Through the Pipeline When a test calls `protocol->LClick(location)`: diff --git a/.github/KnowledgeBase/KB_VlppOS_AdditionalStreams.md b/.github/KnowledgeBase/KB_VlppOS_AdditionalStreams.md index 0ab23bb2..50ee10c0 100644 --- a/.github/KnowledgeBase/KB_VlppOS_AdditionalStreams.md +++ b/.github/KnowledgeBase/KB_VlppOS_AdditionalStreams.md @@ -16,6 +16,7 @@ or when the same part of the data needs to be modified repeatly. - Readable when the underlying stream is readable - Writable when the underlying stream is writable - Seekable when the underlying stream is seekable +- Peekable when the underlying stream is peekable - Limited/finite when the underlying stream is limited/finite ### Use Cases @@ -35,6 +36,8 @@ Use `RecorderStream` for copying data from one stream to another during reading. ### RecorderStream Behavior - It is a read-only stream that wraps another readable stream +- It is not seekable or peekable +- It is finite only when the input stream is finite - Every read operation is simultaneously written to a target stream - Useful for creating backups or logs of data as it's being processed - The recorded data can be written to any writable stream (file, memory, etc.) @@ -108,4 +111,4 @@ Each specialized stream type has specific error scenarios: ### Thread Safety -These stream types inherit the thread safety characteristics of their underlying streams. Additional synchronization may be needed when using them in multi-threaded scenarios, especially with `BroadcastStream` where multiple targets might be accessed concurrently. \ No newline at end of file +These stream types inherit the thread safety characteristics of their underlying streams. Additional synchronization may be needed when using them in multi-threaded scenarios, especially with `BroadcastStream` where multiple targets might be accessed concurrently. diff --git a/.github/KnowledgeBase/KB_VlppOS_EncodingDecoding.md b/.github/KnowledgeBase/KB_VlppOS_EncodingDecoding.md index d4360a04..17ebf471 100644 --- a/.github/KnowledgeBase/KB_VlppOS_EncodingDecoding.md +++ b/.github/KnowledgeBase/KB_VlppOS_EncodingDecoding.md @@ -14,7 +14,7 @@ Use `UtfGeneralEncoder` and `UtfGeneralDecoder` `UtfGeneralEncoder` encode from `Expect` to `Native`, `UtfGeneralDecoder` decode from `Native` to `Expect`. They should be one of `wchar_t`, `char8_t`, `char16_t`, `char32_t` and `char16be_t`. -Unlike `BomEncoder` and `BomDecoder`, `UtfGeneralEncoder` and `UtfGeneralDecodes` is without BOM. +Unlike `BomEncoder` and `BomDecoder`, `UtfGeneralEncoder` and `UtfGeneralDecoder` are without BOM. `char16be_t` means UTF-16 Big Endian, which is not a C++ native type, it can't be used with any string literal. @@ -47,7 +47,7 @@ There is a function `TestEncoding` to scan a binary data and guess the most poss Use `Utf8Base64Encoder` and `Utf8Base64Decoder` for Base64 encoding in UTF-8. -`Utf8Base64Encoder` and `Utf6Base64Decoder` convert between binary data to Base64 in UTF8 encoding. +`Utf8Base64Encoder` and `Utf8Base64Decoder` convert between binary data and Base64 in UTF-8 encoding. They can work with `UtfGeneralEncoder` and `UtfGeneralDecoder` to convert binary data to Base64 in a `WString`. ### Example: Converting Binary Data to Base64 WString @@ -58,27 +58,27 @@ MemoryStream memoryStream; UtfGeneralEncoder u8towEncoder; EncoderStream u8towStream(memoryStream, u8towEncoder); Utf8Base64Encoder base64Encoder; - EncoderStream base64Stream(u8t0wStream, base64Encoder); + EncoderStream base64Stream(u8towStream, base64Encoder); base64Stream.Write(binary ...); } memoryStream.SeekFromBegin(0); { StreamReader reader(memoryStream); - auto base64 = reader.ReadToEnd(reader); + auto base64 = reader.ReadToEnd(); } ``` ### Example: Converting Base64 WString to Binary Data ```cpp -MemoryStream memoryStreamn; +MemoryStream memoryStream; { StreamWriter writer(memoryStream); writer.WriteString(base64); } memoryStream.SeekFromBegin(0); { - UtfGeneralEncoder wtou8Decoder; + UtfGeneralDecoder wtou8Decoder; DecoderStream wtou8Stream(memoryStream, wtou8Decoder); Utf8Base64Decoder base64Decoder; DecoderStream base64Stream(wtou8Stream, base64Decoder); @@ -130,4 +130,4 @@ The encoder/decoder system is designed for pipeline composition. You can chain m 1. Base64 decode → UTF-8 decode → String processing 2. String processing → UTF-8 encode → Compression → File output -This design provides flexibility for handling various data transformation scenarios efficiently. \ No newline at end of file +This design provides flexibility for handling various data transformation scenarios efficiently. diff --git a/.github/KnowledgeBase/KB_VlppOS_FileSystemOperations.md b/.github/KnowledgeBase/KB_VlppOS_FileSystemOperations.md index 7be11927..417d21ec 100644 --- a/.github/KnowledgeBase/KB_VlppOS_FileSystemOperations.md +++ b/.github/KnowledgeBase/KB_VlppOS_FileSystemOperations.md @@ -6,7 +6,8 @@ Cross-platform file and directory manipulation with path handling and content ac `FilePath` is a string representation of file path. -- Use `GetName`, `GetFolder`, `GetFullPath` and `GetRelativePathFor` for path manipulation. +- Use `GetPathDelimiter` to get the platform path delimiter. +- Use `operator/`, `GetName`, `GetFolder`, `GetFullPath` and `GetRelativePathFor` for path manipulation. - Use `IsFile`, `IsFolder` and `IsRoot` to tell the object represented by the path. ## File Class @@ -39,7 +40,7 @@ Use `Rename` to change the name or move a file to a different location. When `FilePath::IsFolder` or `FilePath::IsRoot` return true, `Folder` could be initialized with such path. It offers: - Content enumerations by `GetFolders` and `GetFiles` to enumerate the content. -- Folder operation by `Exists`, `Delete` and `Rename`. +- Folder operation by `Exists`, `Create`, `Delete` and `Rename`. ### Content Enumeration @@ -49,13 +50,20 @@ Use `GetFiles` to retrieve all files within the folder. ### Folder Operations Use `Exists` to check if a folder exists at the specified path. -Use `Delete` to remove an existing folder and its contents. +Use `Create(false)` to create the folder directly, and `Create(true)` to create missing containing folders first. +Use `Delete(false)` to remove an existing folder directly, and `Delete(true)` to remove its contents recursively. Use `Rename` to change the name or move a folder to a different location. ### Creating Folders `Folder::Create` is special, it creates a new folder, which means you have to initialize `Folder` with an unexisting `FilePath` before doing that. In such case `FilePath::IsFolder` would return false before calling `Create`. +Pass `false` to `Create` when only the final folder should be created. Pass `true` when missing containing folders should be created recursively. + +### Deleting Folders + +Pass `false` to `Delete` when only the specified folder should be removed. Pass `true` when the folder tree should be removed recursively. + ## Root Directory Handling Initializing a `Folder` with a file path with `IsRoot` returning true, is just calling `Folder`'s default constructors. @@ -117,4 +125,4 @@ Implementation injection is particularly valuable for unit testing file system o - Create isolated test environments without affecting the real file system - Simulate file system errors and edge cases - Test file operations with predictable directory structures -- Mock file system behaviors for consistent testing across different environments \ No newline at end of file +- Mock file system behaviors for consistent testing across different environments diff --git a/.github/KnowledgeBase/KB_VlppOS_StreamOperations.md b/.github/KnowledgeBase/KB_VlppOS_StreamOperations.md index ec5a5ebc..8dde4799 100644 --- a/.github/KnowledgeBase/KB_VlppOS_StreamOperations.md +++ b/.github/KnowledgeBase/KB_VlppOS_StreamOperations.md @@ -16,11 +16,11 @@ Usually we don't need to call `Close` explicitly, it will be called internally w ### Stream Capabilities -Use `IsAvailable`, `CanRead`, `CanWrite`, `CanSeek`, `IsLimited` for capability checking. +Use `IsAvailable`, `CanRead`, `CanWrite`, `CanSeek`, `CanPeek`, `IsLimited` for capability checking. #### Readable Streams -A stream is readable when `CanRead` returns true. `Read` and `Peek` can only be used in this case. +A stream is readable when `CanRead` returns true. `Read` can only be used in this case. Here are all streams that guaranteed to be readable so no further checking is needed: - `FileStream` with `FileStream::ReadOnly` or `FileStream::ReadWrite` in the constructor. @@ -31,6 +31,17 @@ Here are all streams that guaranteed to be readable so no further checking is ne - The following streams are readable when their underlying streams are readable - `CacheStream` +#### Peekable Streams + +A stream is peekable when `CanPeek` returns true. `Peek` can only be used in this case. + +Here are all streams that guaranteed to be peekable so no further checking is needed: +- `FileStream` with `FileStream::ReadOnly` or `FileStream::ReadWrite` in the constructor. +- `MemoryStream` +- `MemoryWrapperStream` +- The following streams are peekable when their underlying streams are peekable + - `CacheStream` + #### Writable Streams A stream is writable when `CanWrite` returns true. `Write` can only be used in this case. @@ -41,7 +52,7 @@ Here are all streams that guaranteed to be writable so no further checking is ne - `MemoryWrapperStream` - `EncoderStream` - `BroadcastStream` -- The following streams are readable when their underlying streams are writable +- The following streams are writable when their underlying streams are writable - `CacheStream` #### Seekable Streams @@ -54,7 +65,7 @@ Here are all streams that guaranteed to be seekable so no further checking is ne - `FileStream` - `MemoryStream` - `MemoryWrapperStream` -- The following streams are readable when their underlying streams are seekable +- The following streams are seekable when their underlying streams are seekable - `CacheStream` #### Limited/Finite Streams @@ -65,24 +76,20 @@ The `Size` and `SeekFromEnd` method only make sense for a finite stream. Here are all streams that guaranteed to be limited/finite so no further checking is needed: - `FileStream` with `FileStream::ReadOnly` in the constructor. - `MemoryWrapperStream` -- The following streams are readable when their underlying streams are limited/finite +- The following streams are limited/finite when their underlying streams are limited/finite - `DecoderStream` - `EncoderStream` - `CacheStream` - `RecorderStream` -Here are all streams that guaranteed to be infinite so no further checking is needed: +Here are all streams that guaranteed to be unlimited so no further checking is needed: - `FileStream` with `FileStream::WriteOnly` or `FileStream::ReadWrite` in the constructor. - `MemoryStream` -- The following streams are readable when their underlying streams are limited/finite - - `DecoderStream` - - `EncoderStream` - - `CacheStream` - - `RecorderStream` +- `BroadcastStream` ### Basic Stream Operations -Use `Read`, `Write`, `Peek`, `Seek`, `Position`, `Size` for stream operations. +Use `Read`, `Write`, `Peek`, `Seek`, `SeekFromBegin`, `SeekFromEnd`, `Position`, `Size` for stream operations. Use `Close` for resource cleanup (automatic on destruction). ## FileStream @@ -98,7 +105,7 @@ The buffer will be deleted when `MemoryStream` is destroyed. ## MemoryWrapperStream -`MemoryWrapperStream` operates on a given memory buffer, `MemoryWrapperStream` will be delete the buffer. +`MemoryWrapperStream` operates on a given memory buffer. It does not own or delete the buffer. ## EncoderStream and DecoderStream @@ -148,4 +155,4 @@ Stream operations may throw exceptions when encountering I/O errors, permission ### Resource Management -Streams automatically manage their resources through RAII. The `Close()` method is called automatically in destructors, but can be called explicitly when immediate resource cleanup is needed. \ No newline at end of file +Streams automatically manage their resources through RAII. The `Close()` method is called automatically in destructors, but can be called explicitly when immediate resource cleanup is needed. diff --git a/.github/KnowledgeBase/KB_VlppOS_SynchronizationPrimitives.md b/.github/KnowledgeBase/KB_VlppOS_SynchronizationPrimitives.md index 798cb6bb..6b018aef 100644 --- a/.github/KnowledgeBase/KB_VlppOS_SynchronizationPrimitives.md +++ b/.github/KnowledgeBase/KB_VlppOS_SynchronizationPrimitives.md @@ -101,7 +101,7 @@ A `ConditionVariable` works with a `CriticalSection` or a `ReaderWriterLock`. ### ConditionVariable with ReaderWriterLock -- Call `SleepWithReader`, `SleepWithReaderForTime`, `SleepWriter` or `SleepWriterForTime` to work with a `ReaderWriterLock`. They only work on Windows. +- Call `SleepWithReader`, `SleepWithReaderForTime`, `SleepWithWriter` or `SleepWithWriterForTime` to work with a `ReaderWriterLock`. They only work on Windows. ### ConditionVariable Behavior @@ -109,15 +109,15 @@ The `Sleep*` function temporarily releases the lock from the current thread, and - Before calling the `Sleep*` function, the current thread must own the lock. - Calling the `Sleep*` function releases the lock from the current thread, and block the current thread. -- The `Sleep*` function returns when `WakeOnePending` or `WaitAllPendings` is called. +- The `Sleep*` function returns when `WakeOnePending` or `WakeAllPendings` is called. - The `Sleep*ForTime` function could also return when it reaches the timeout. But this will not always happen, because: - - `WaitOnePending` only activates one thread pending on the condition variable. - - `WaitAllPendings` activates all thread but they are also controlled by the lock. + - `WakeOnePending` only activates one thread pending on the condition variable. + - `WakeAllPendings` activates all thread but they are also controlled by the lock. - When `Sleep*` returns, the current thread owns the lock. ### ConditionVariable Signaling -Use `WakeOnePending`, `WaitAllPendings` for condition variable signaling. +Use `WakeOnePending`, `WakeAllPendings` for condition variable signaling. ## Extra Content diff --git a/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md b/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md index 93b96dff..77f34a07 100644 --- a/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md +++ b/.github/KnowledgeBase/KB_VlppReflection_AttributeRegistration.md @@ -3,7 +3,7 @@ ## Attach metadata attributes to types, members, and method parameters during reflection registration. VlppReflection provides three macros for attaching attribute metadata to reflected types and their members. -An attribute is an instance of a reflectable struct whose constructor arguments are serializable primitive values (e.g., `WString`, `vint`, `bool`, `float`, `double`). +An attribute is an instance of a reflectable struct whose constructor arguments are serializable values (e.g., `WString`, `vint`, `bool`, `float`, `double`), with `ITypeDescriptor*` as the explicit descriptor-reference exception. Attributes are stored centrally in the owning type descriptor and can be queried at runtime or through metaonly metadata. ## Attribute Macros @@ -31,7 +31,9 @@ Must appear after a method or constructor registration macro. - The attribute type must be a reflected struct (`TypeDescriptorFlags::Struct`). - It must be registered via `BEGIN_STRUCT_MEMBER` / `END_STRUCT_MEMBER` before use. - `TYPE{ ARG1, ARG2, ... }` must be a valid C++ aggregate initialization expression. -- Each argument is boxed as a `Value` and its type descriptor must have a non-null `GetSerializableType()`. +- Each argument is boxed as a `Value`. +- Ordinary argument type descriptors must have a non-null `GetSerializableType()`. +- `ITypeDescriptor*` arguments are the supported non-serializable exception. They must be boxed as a raw pointer or null, not as a shared pointer or arbitrary boxed object. ## Argument Type Inference @@ -59,12 +61,14 @@ Use the `IAttributeBag` interface (inherited by `ITypeDescriptor`, `IMemberInfo` Use `IAttributeInfo` to inspect an attribute: - `GetAttributeType()` — returns the `ITypeDescriptor` of the attribute struct. - `GetAttributeValueCount()` — returns the number of constructor argument values. +- `GetAttributeValueType(index)` — returns the reflected type descriptor used to serialize or interpret the argument value. - `GetAttributeValue(index)` — returns the boxed `Value` of the argument at the given index. ## Metaonly Metadata Attributes are serialized into metaonly binary metadata by `GenerateMetaonlyTypes` and deserialized by `LoadMetaonlyTypes`. -Each attribute value is serialized through `ISerializableType::Serialize` and deserialized through `ISerializableType::Deserialize`. +Ordinary attribute values are serialized through `ISerializableType::Serialize` and deserialized through `ISerializableType::Deserialize`. +`ITypeDescriptor*` attribute values are encoded separately as referenced type-descriptor indices with empty serialized data. Attributes appear in the logged text output (`.txt` baseline files) in the format: ``` @Attribute:(:, ...) @@ -151,7 +155,7 @@ END_CLASS_MEMBER(MyClass) - `ATTRIBUTE_PARAMETER` raises `CHECK_ERROR` if the last registered member is not a method or constructor. - `ATTRIBUTE_PARAMETER` raises `CHECK_ERROR` if the named parameter does not exist or is ambiguous. - A `static_assert` fires at compile time if `TYPE{ ARG1, ... }` is not a valid expression. -- `CHECK_ERROR` is raised if the attribute type is not a reflected struct or if an argument is not serializable. +- `CHECK_ERROR` is raised if the attribute type is not a reflected struct, if an ordinary argument is not serializable, or if an `ITypeDescriptor*` argument is not a raw pointer or null. ## Workflow Script Attributes diff --git a/.github/KnowledgeBase/KB_VlppReflection_ClassInterfaceRegistration.md b/.github/KnowledgeBase/KB_VlppReflection_ClassInterfaceRegistration.md index 2a54fa4f..be45901b 100644 --- a/.github/KnowledgeBase/KB_VlppReflection_ClassInterfaceRegistration.md +++ b/.github/KnowledgeBase/KB_VlppReflection_ClassInterfaceRegistration.md @@ -31,13 +31,20 @@ VlppReflection provides extensive macros for registering classes and interfaces, ### Constructor Registration - Use `CLASS_MEMBER_CONSTRUCTOR` for constructor registration with `Ptr(types...)` or `Class*(types...)` - Use `CLASS_MEMBER_EXTERNALCTOR` for external function constructors +- Use `CLASS_MEMBER_EXTERNALCTOR_TEMPLATE` when an external constructor needs custom generated C++ code templates - Constructor type determines whether instances are boxed in `Ptr` or not ### Method Registration - Use `CLASS_MEMBER_METHOD` for method registration with parameter names +- Use `CLASS_MEMBER_METHOD_RENAME` to register a non-overloaded member function under a different reflected name - Use `CLASS_MEMBER_METHOD_OVERLOAD` for overloaded method registration +- Use `CLASS_MEMBER_METHOD_OVERLOAD_RENAME` to register a specific overload under a different reflected name - Use `CLASS_MEMBER_EXTERNALMETHOD` for external function methods +- Use `CLASS_MEMBER_EXTERNALMETHOD_TEMPLATE` when an external method needs custom generated C++ code templates - Use `CLASS_MEMBER_STATIC_METHOD` for static method registration +- Use `CLASS_MEMBER_STATIC_METHOD_OVERLOAD` for overloaded static method registration +- Use `CLASS_MEMBER_STATIC_EXTERNALMETHOD` for global functions registered as static methods +- Use `CLASS_MEMBER_STATIC_EXTERNALMETHOD_TEMPLATE` when a static external method needs custom generated C++ code templates ### Event Registration - Use `CLASS_MEMBER_EVENT` for event registration @@ -46,6 +53,8 @@ VlppReflection provides extensive macros for registering classes and interfaces, ### Property Registration - Use `CLASS_MEMBER_PROPERTY_READONLY`, `CLASS_MEMBER_PROPERTY` for property registration +- Use `CLASS_MEMBER_PROPERTY_EVENT_READONLY`, `CLASS_MEMBER_PROPERTY_EVENT` for properties with explicit getter/setter/event methods +- Use `CLASS_MEMBER_PROPERTY_REFERENCETEMPLATE` when generated C++ needs custom reference code for the property - Use `CLASS_MEMBER_PROPERTY_READONLY_FAST`, `CLASS_MEMBER_PROPERTY_FAST` for standard getter/setter patterns - Use `CLASS_MEMBER_PROPERTY_EVENT_READONLY_FAST`, `CLASS_MEMBER_PROPERTY_EVENT_FAST` for properties with change events @@ -99,7 +108,7 @@ END_CLASS_MEMBER(MyClass) - Use `ATTRIBUTE_MEMBER(TYPE, ...)` after any member registration to attach an attribute to that member - Use `ATTRIBUTE_PARAMETER(PARAMETER_NAME, TYPE, ...)` after a method or constructor registration to attach an attribute to a named parameter - The attribute type must be a reflected struct -- Each argument must be a serializable primitive value +- Each ordinary argument must be serializable; `ITypeDescriptor*` is supported as a raw-pointer descriptor reference - Multiple attributes can be attached to the same target - See [Attribute Registration](./KB_VlppReflection_AttributeRegistration.md) for full details @@ -140,19 +149,30 @@ There is no constructor in an interface registration - only classes support cons For overloaded methods, use specific macros: - `CLASS_MEMBER_METHOD_OVERLOAD(name, parameter, function-type)` - `CLASS_MEMBER_METHOD_OVERLOAD_RENAME(new-name, name, parameter, function-type)` +- `CLASS_MEMBER_METHOD_RENAME(new-name, name, parameters)` for non-overloaded methods that need a different reflected name - Function type must be a pointer to member function #### External Methods For methods implemented as external functions: - `CLASS_MEMBER_EXTERNALMETHOD(name, parameters, function-type, source)` +- `CLASS_MEMBER_EXTERNALMETHOD_TEMPLATE(name, parameters, function-type, source, invoke-template, closure-template)` - First parameter acts as `this` pointer - Should not appear in parameters or function-type +#### Static Methods +For static methods and global functions registered as static methods: +- `CLASS_MEMBER_STATIC_METHOD(name, parameters)` for non-overloaded static member functions +- `CLASS_MEMBER_STATIC_METHOD_OVERLOAD(name, parameters, function-type)` for overloaded static member functions +- `CLASS_MEMBER_STATIC_EXTERNALMETHOD(name, parameters, function-type, source)` for global functions +- `CLASS_MEMBER_STATIC_EXTERNALMETHOD_TEMPLATE(name, parameters, function-type, source, invoke-template, closure-template)` when generated C++ needs custom templates + #### Property Shortcuts Fast property registration shortcuts: - `CLASS_MEMBER_PROPERTY_READONLY_FAST(X)` for `GetX()` getter and property `X` - `CLASS_MEMBER_PROPERTY_FAST(X)` for `GetX()` getter, `SetX()` setter, and property `X` -- `CLASS_MEMBER_PROPERTY_EVENT_FAST(X)` includes `XChanged` event +- `CLASS_MEMBER_PROPERTY_EVENT_READONLY_FAST(X, XChanged)` for `GetX()` and an existing `XChanged` event +- `CLASS_MEMBER_PROPERTY_EVENT_FAST(X, XChanged)` for `GetX()`, `SetX()`, and an existing `XChanged` event +- `CLASS_MEMBER_PROPERTY_REFERENCETEMPLATE(X, GetX, SetX, template)` when generated C++ needs a custom property reference expression ### Best Practices @@ -167,4 +187,4 @@ Fast property registration shortcuts: - Registration occurs at startup time with minimal runtime overhead - Dynamic method calls have slight performance cost compared to direct calls - Property access through reflection is slower than direct field access -- Consider caching reflection results for frequently used operations \ No newline at end of file +- Consider caching reflection results for frequently used operations diff --git a/.github/KnowledgeBase/KB_VlppReflection_TypeRegistrationStructure.md b/.github/KnowledgeBase/KB_VlppReflection_TypeRegistrationStructure.md index 9230470a..c28a763d 100644 --- a/.github/KnowledgeBase/KB_VlppReflection_TypeRegistrationStructure.md +++ b/.github/KnowledgeBase/KB_VlppReflection_TypeRegistrationStructure.md @@ -42,8 +42,16 @@ namespace vl::reflection::description #pragma warning(push) #pragma warning(disable:4250) - BEGIN_INTERFACE_PROXY...(::my::namespaces::ISecond) - ... + BEGIN_INTERFACE_PROXY_NOPARENT_SHAREDPTR(::my::namespaces::ISecond) + vint ThisFunction() override + { + INVOKEGET_INTERFACE_PROXY_NOPARAMS(ThisFunction); + } + + vint ThatFunction(vint arg1, vint arg2) override + { + INVOKEGET_INTERFACE_PROXY(ThatFunction, arg1, arg2); + } END_INTERFACE_PROXY(::my::namespaces::ISecond) #pragma warning(pop) @@ -66,18 +74,9 @@ namespace vl::reflection::description #ifdef VCZH_DESCRIPTABLEOBJECT_WITH_METADATA #define _ , - BEGIN_CLASS_MEMBER(::my::namespaces::ISecond) + BEGIN_INTERFACE_MEMBER(::my::namespaces::ISecond) CLASS_MEMBER_METHOD(ThisFunction, NO_PARAMETER) CLASS_MEMBER_METHOD(ThatFunction, { L"arg1" _ L"arg2" }) - ... - END_CLASS_MEMBER(::my::namespaces::ISecond) - - BEGIN_INTERFACE_MEMBER(::my::namespaces::ISecond) - vint Func(vint a, vint b) override - { - INVOKEGET_INTERFACE_PROXY_NOPARAMS(Func, a, b); - } - ... END_INTERFACE_MEMBER(::my::namespaces::ISecond) #undef _ @@ -149,4 +148,4 @@ Define macros that enumerate all types to be registered: - Regularly review registration files for consistency - Update registration patterns when new reflection features are added -- Ensure all new types follow the established registration structure \ No newline at end of file +- Ensure all new types follow the established registration structure diff --git a/.github/KnowledgeBase/KB_VlppRegex_PatternMatching.md b/.github/KnowledgeBase/KB_VlppRegex_PatternMatching.md index 25f6b505..f3cd2c61 100644 --- a/.github/KnowledgeBase/KB_VlppRegex_PatternMatching.md +++ b/.github/KnowledgeBase/KB_VlppRegex_PatternMatching.md @@ -2,9 +2,11 @@ Text pattern matching and searching operations with support for different UTF encodings. -The definition and the string to match could be in different UTF encoding. +The definition and the string to match can use different UTF encodings. `Regex_` accepts `ObjectString` as the definition. -`MatchHead`, `Match`, `TestHead`, `Test`, `Search`, `Split` and `Cut` accepts `ObjectString` to match with the regular expression. +`MatchHead`, `Match`, `TestHead`, `Test`, `Search`, `Split` and `Cut` accept `ObjectString` or `const U*` text to match with the regular expression. +`Search`, `Split` and `Cut` append results to a caller-provided `RegexMatch_::List&`; they do not return a collection. +`Split` and `Cut` also take `keepEmptyMatch` to decide whether empty unmatched fragments are included. ## Core Pattern Matching Methods @@ -38,19 +40,19 @@ This is an optimization when you only need to know whether the string contains a `Search` finds all substrings which match the regular expression. All results do not overlap with each other. -This method returns a collection of all non-overlapping matches found in the input string. When multiple matches are possible at the same position, it will choose one and continue searching from after that match. +This method appends all non-overlapping successful matches found in the input string to `RegexMatch_::List&`. When a match is found, searching continues after that match. ### Split -`Split` use the regular expression as a splitter, finding all remaining substrings. +`Split` uses the regular expression as a splitter, finding all remaining substrings. -This method treats the pattern as a delimiter and splits the input string wherever the pattern matches, returning the parts between the matches. This is similar to string split operations but with the power of regular expressions. +This method treats the pattern as a delimiter and appends the parts between successful matches. The appended `RegexMatch_` objects have `Success()` equal to `false`. `keepEmptyMatch` controls whether empty unmatched fragments are appended. ### Cut `Cut` combines both `Search` and `Split`, finding all substrings in order, regardless if one matches or not. -This method returns all parts of the string in sequence, both the parts that match the pattern and the parts that don't match. This gives you a complete decomposition of the input string. +This method appends all parts of the string in sequence, both the parts that match the pattern and the parts that do not match. `Success()` distinguishes successful pattern matches from unmatched fragments, and `keepEmptyMatch` controls empty unmatched fragments. ## Extra Content @@ -59,7 +61,7 @@ This method returns all parts of the string in sequence, both the parts that mat One of the key features of VlppRegex is its support for different UTF encodings between the pattern definition and the input text: - The regex pattern is defined using `Regex_` where `T` is the character type for the pattern -- The input text uses type `U` in the matching methods +- The input text uses type `U` in the matching methods, through `ObjectString` or `const U*` - This allows patterns defined in one encoding to match text in another encoding - Supported character types include `wchar_t`, `char8_t`, `char16_t`, `char32_t` @@ -67,39 +69,42 @@ One of the key features of VlppRegex is its support for different UTF encodings The VlppRegex engine has specific performance characteristics: -- **DFA Compatible vs Incompatible**: Features that break DFA compatibility (like backreferences) significantly impact performance +- **DFA Compatible vs Incompatible**: Features that break DFA compatibility, including captures, backreferences, lookahead, and lazy loops, require rich mode and significantly impact performance - **Escaping Optimization**: Using `/` instead of `\` for escaping can improve readability in C++ code - **Method Selection**: Choose simpler methods like `Test` or `TestHead` when you only need boolean results +- **Mode Inspection**: Use `IsPureMatch()` and `IsPureTest()` to check whether DFA mode is used for matching and testing ### Syntax Differences from .NET While mostly compatible with .NET regex syntax, VlppRegex has important differences: -- **Dot Character**: `.` matches literal '.' character, while `/.` or `\.` matches all characters +- **Dot Character**: `.` matches literal '.' character, while `/.` or `\.` matches any character - **Escaping**: Both `/` and `\` perform escaping (prefer `/` for C++ compatibility) -- **Character Classes**: Standard character classes work the same as .NET +- **Character Classes**: `\s`, `\S`, `\d`, `\D`, `\l`, `\L`, `\w` and `\W` are supported, and each one can also be written with `/` - **Quantifiers**: Standard quantifiers (`*`, `+`, `?`, `{n,m}`) work as expected ### Error Handling When using regex operations: -- Invalid patterns will cause compilation errors when constructing `Regex_` -- Invalid input strings generally won't cause errors but may produce no matches -- Method calls on empty or invalid regex objects may result in undefined behavior +- Invalid patterns trigger `CHECK_ERROR` during `Regex_` construction +- `MatchHead` and `Match` return `nullptr` when no match is found +- `TestHead` and `Test` return `false` when no match is found +- `Search` appends no items when no successful match is found ### Common Usage Patterns -**Simple validation**: (note that `^` is not required and `$` does not make sense here) +**Prefix test**: ```cpp -Regex regex(L"[0-9]+"); -bool isNumber = regex.TestHead(input); +Regex regex(L"/d+"); +bool hasNumberPrefix = regex.TestHead(input); ``` **Extracting all matches**: ```cpp -Regex regex(L"\\w+"); -auto matches = regex.Search(text); +Regex regex(L"/w+"); +RegexMatch::List matches; +regex.Search(text, matches); for (auto match : matches) { // Process each word } @@ -108,11 +113,13 @@ for (auto match : matches) { **Splitting text**: ```cpp Regex regex(L"[,;]"); -auto parts = regex.Split(csvLine); +RegexMatch::List parts; +regex.Split(csvLine, false, parts); ``` **Complete decomposition**: ```cpp -Regex regex(L"\\d+"); -auto parts = regex.Cut(mixedText); // Returns both numbers and non-numbers -``` \ No newline at end of file +Regex regex(L"/d+"); +RegexMatch::List parts; +regex.Cut(mixedText, false, parts); // Appends both numbers and non-numbers +``` diff --git a/.github/KnowledgeBase/KB_VlppRegex_TypeAliases.md b/.github/KnowledgeBase/KB_VlppRegex_TypeAliases.md index 91964e0a..768a32b7 100644 --- a/.github/KnowledgeBase/KB_VlppRegex_TypeAliases.md +++ b/.github/KnowledgeBase/KB_VlppRegex_TypeAliases.md @@ -111,16 +111,16 @@ Using aliases vs explicit templates has no performance impact: **Basic pattern matching**: ```cpp -Regex pattern(L"\\d+"); +Regex pattern(L"/d+"); auto match = pattern.Match(text); ``` **Lexical analysis**: ```cpp List tokenDefs; -tokenDefs.Add(L"\\b(if|else|while)\\b"); // keywords -tokenDefs.Add(L"\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"); // identifiers -tokenDefs.Add(L"\\d+"); // numbers +tokenDefs.Add(L"if|else|while"); // keywords +tokenDefs.Add(L"[a-zA-Z_]/w*"); // identifiers +tokenDefs.Add(L"/d+"); // numbers RegexLexer lexer(tokenDefs); auto tokens = lexer.Parse(sourceCode); @@ -129,7 +129,7 @@ auto tokens = lexer.Parse(sourceCode); **Syntax highlighting**: ```cpp List tokenDefs; -tokenDefs.Add(L"\\bclass\\b"); // token 0: keywords +tokenDefs.Add(L"class"); // token 0: keywords tokenDefs.Add(L"\"[^\"]*\""); // token 1: strings RegexProc proc; @@ -161,4 +161,4 @@ These aliases exist because VlppRegex is built on a template system: - Base templates like `Regex_` support any character type - Most applications only need wide character support - Aliases eliminate template parameter repetition -- Consistent with the project's preference for `wchar_t` over other character types \ No newline at end of file +- Consistent with the project's preference for `wchar_t` over other character types diff --git a/.github/KnowledgeBase/KB_Vlpp_SortingOrdering.md b/.github/KnowledgeBase/KB_Vlpp_SortingOrdering.md index 1bbf104d..82188249 100644 --- a/.github/KnowledgeBase/KB_Vlpp_SortingOrdering.md +++ b/.github/KnowledgeBase/KB_Vlpp_SortingOrdering.md @@ -6,31 +6,38 @@ Vlpp provides algorithms for arranging data with support for both total and part ## Quick Sort Implementation -### Sort(T*, vint) Function +### `Sort(T*, vint)` Functions -The primary sorting function performs quick sort on raw pointer ranges with custom comparators. +The sorting functions perform quick sort on raw pointer ranges. Use the two-argument overload when the element type has a usable `<=>` operator, or the three-argument overload when custom ordering is needed. ```cpp // Sort an array of integers vint numbers[] = {5, 2, 8, 1, 9}; -Sort(numbers, 5, [](vint a, vint b) { return a <=> b; }); +Sort(numbers, 5); + +// Sort with a custom comparator +Sort(numbers, 5, [](vint a, vint b) { return b <=> a; }); ``` -**Function signature:** +**Function signatures:** ```cpp template void Sort(T* begin, vint count, Compare compare); + +template +void Sort(T* begin, vint count); ``` **Parameters:** - **`T* begin`**: Pointer to the first element of the array to sort - **`vint count`**: Number of elements in the array -- **`Compare compare`**: Lambda expression or function object for comparison +- **`Compare compare`**: Lambda expression or function object returning an ordering value **Key characteristics:** - **In-place sorting**: Modifies the original array - **Quick sort algorithm**: Efficient O(n log n) average case performance -- **Custom comparators**: Flexible comparison logic through lambda expressions +- **Default comparator**: Uses `a <=> b` when no comparator is supplied +- **Custom comparators**: Flexible comparison logic through lambda expressions or function objects ## Modern C++ Comparison Support @@ -50,7 +57,7 @@ Sort(data, count, [](const MyType& a, const MyType& b) { ### Comparison Return Types -#### std::strong_ordering +#### `std::strong_ordering` Use for types with total ordering where all elements can be compared: ```cpp Sort(numbers, count, [](vint a, vint b) -> std::strong_ordering { @@ -58,7 +65,7 @@ Sort(numbers, count, [](vint a, vint b) -> std::strong_ordering { }); ``` -#### std::weak_ordering +#### `std::weak_ordering` Use for types where equivalent elements may not be identical: ```cpp Sort(strings, count, [](const WString& a, const WString& b) -> std::weak_ordering { @@ -66,6 +73,15 @@ Sort(strings, count, [](const WString& a, const WString& b) -> std::weak_orderin }); ``` +#### `std::partial_ordering` +Use only when every pair that reaches `Sort` is still comparable. If the comparator returns `std::partial_ordering::unordered`, `Sort` raises an `Error`. + +```cpp +Sort(items, count, [](const MyType& a, const MyType& b) -> std::partial_ordering { + return a.PartialCompare(b); +}); +``` + ### Lambda Expression Comparators Sorting relies on lambda expressions returning ordering values rather than boolean comparisons: @@ -86,56 +102,69 @@ Sort(items, count, [](const Item& a, const Item& b) { ## Partial Ordering Support -### PartialOrderingProcessor +### `PartialOrderingProcessor` -For scenarios where not all elements can be compared (partial ordering), use `PartialOrderingProcessor` instead of the standard `Sort` function. +For dependency sorting, use `PartialOrderingProcessor` instead of `Sort`. `PartialOrderingProcessor` is not a template. Initialize it with one relationship source, call `Sort()`, then read `components` and `nodes`. ```cpp -// Example: Dependency sorting where some items have no ordering relationship -PartialOrderingProcessor processor; +List items; +items.Add(L"compile"); +items.Add(L"link"); +items.Add(L"package"); -// Add items and their relationships -processor.AddItem(item1); -processor.AddItem(item2); -processor.AddItem(item3); +Group dependencies; +dependencies.Add(L"link", L"compile"); +dependencies.Add(L"package", L"link"); -// Define partial ordering relationships -processor.AddDependency(item2, item1); // item2 depends on item1 -processor.AddDependency(item3, item1); // item3 depends on item1 +PartialOrderingProcessor processor; +processor.InitWithGroup(items, dependencies); +processor.Sort(); -// Process to get topologically sorted result -auto sortedItems = processor.Process(); +for (vint i = 0; i < processor.components.Count(); i++) +{ + auto& component = processor.components[i]; + // component.firstNode points to indexes in processor.nodes. +} ``` -**Use cases for partial ordering:** +**Initialization APIs:** +- **`InitWithGroup(items, depGroup)`**: Use a `Group` where `depGroup.Add(a, b)` means `a` depends on `b` +- **`InitWithFunc(items, depFunc)`**: Use a callback where `depFunc(a, b)` returns true when `a` depends on `b` +- **`InitWithSubClass(items, depGroup, subClasses)`**: Group items into subclasses before sorting dependencies + +**Result fields:** +- **`components`**: Sorted components. A component can contain multiple nodes when dependencies form a cycle. +- **`nodes`**: Node data referenced by components. With subclass sorting, a node can represent a subclass and exposes `firstSubClassItem` and `subClassItemCount`. + +**Use cases for dependency sorting:** - **Dependency resolution**: When items have prerequisites - **Task scheduling**: When some tasks must complete before others -- **Type hierarchies**: When comparing types with inheritance relationships -- **Version constraints**: When some versions are incomparable +- **Type hierarchies**: When subclass groups must be ordered from relationships among original objects +- **Cycle grouping**: When mutually dependent items should be reported in the same component ### When Sort Doesn't Work -The standard `Sort` function requires total ordering - every pair of elements must be comparable. Use `PartialOrderingProcessor` when: +The standard `Sort` function requires every pair it compares to produce an ordered result. Use `PartialOrderingProcessor` when: - Some elements cannot be meaningfully compared -- Circular dependencies need to be detected - Topological sorting is required -- The comparison relationship is not transitive across all elements +- Dependency cycles need to be grouped into components +- Relationships are expressed as dependencies instead of pairwise ordering ## Best Practices ### Choosing the Right Approach 1. **Use `Sort()` when:** - - All elements have a clear total ordering + - All element pairs that may be compared produce ordered results - Performance is critical (quick sort is very efficient) - Working with simple data types (numbers, strings) 2. **Use `PartialOrderingProcessor` when:** - - Not all elements can be compared - - Dealing with dependency graphs + - Working with dependency graphs - Need topological sorting - - Circular dependency detection is important + - Cycles should be represented as grouped components + - Items should be sorted through subclass groups ### Comparator Best Practices @@ -199,7 +228,7 @@ sortedNumbers.Add(8); ### Performance Characteristics -- **Quick Sort**: Average O(n log n), worst case O(n²) +- **Quick Sort**: Average O(n log n), worst case O(n^2) - **In-place**: No additional memory allocation for sorting - **Comparison-based**: Performance depends on comparison function complexity - **Cache-friendly**: Works with contiguous memory arrays @@ -216,7 +245,7 @@ The sorting functions are not thread-safe. For concurrent access: For custom types used in sorting: - Implement appropriate comparison operators (`<=>` recommended) - Ensure comparison is consistent and transitive -- Consider providing both strong and weak ordering overloads as needed +- Consider providing strong, weak, or partial ordering overloads as needed ### Debugging Comparators @@ -233,4 +262,4 @@ Sort(items, count, [](const Item& a, const Item& b) { // Add debug output if needed return result; }); -``` \ No newline at end of file +``` diff --git a/.github/KnowledgeBase/KB_Vlpp_StringTypes.md b/.github/KnowledgeBase/KB_Vlpp_StringTypes.md index bda0fcfa..34ecd0d3 100644 --- a/.github/KnowledgeBase/KB_Vlpp_StringTypes.md +++ b/.github/KnowledgeBase/KB_Vlpp_StringTypes.md @@ -35,17 +35,17 @@ The project provides convenient aliases instead of using `ObjectString` direc Use these static functions to create string instances: -#### Unmanaged(L"string-literal") +#### `Unmanaged(externally-owned-buffer)` ```cpp auto str = WString::Unmanaged(L"Hello World"); ``` -- **Use case**: String literals only -- **Performance**: Zero-copy for string literals -- **Safety**: Only works with compile-time string literals +- **Use case**: String literals or other externally owned zero-terminated buffers +- **Performance**: Zero-copy because the string points at the supplied buffer +- **Safety**: The buffer must outlive the string and is not released by the string -#### CopyFrom(wchar_t*, vint) +#### `CopyFrom(const wchar_t*, vint)` ```cpp -wchar_t* buffer = GetSomeBuffer(); +const wchar_t* buffer = GetSomeBuffer(); vint length = GetBufferLength(); auto str = WString::CopyFrom(buffer, length); ``` @@ -171,7 +171,7 @@ U8String u16to8 = u16tou8(utf16); // UTF-16 to UTF-8 3. **Avoid `char` and `std::string`** - use project's string types instead ### Initialization Best Practices -1. **Use `WString::Unmanaged(L"...")`** for string literals +1. **Use `WString::Unmanaged(L"...")`** for string literals and only use it with other buffers when their lifetime is externally guaranteed 2. **Use constructor or `CopyFrom`** when you need to copy external data 3. **Use `TakeOver`** only when you want to transfer ownership of allocated memory @@ -195,10 +195,10 @@ The `wchar_t` type behaves differently across platforms: The string conversion system automatically handles these differences internally. ### Performance Considerations -- **String literals with `Unmanaged`**: Zero-copy initialization +- **Externally owned buffers with `Unmanaged`**: Zero-copy initialization without ownership transfer - **Immutable strings**: Thread-safe sharing but creates new instances for modifications - **UTF conversions**: May involve memory allocation and encoding conversion overhead - **Case conversions**: Create new string instances rather than modifying in-place ### Thread Safety -All string types are immutable and thread-safe for reading. Multiple threads can safely access the same string instance simultaneously. However, string creation and conversion operations may allocate memory and should be considered for performance in highly concurrent scenarios. \ No newline at end of file +All string types are immutable and thread-safe for reading. Multiple threads can safely access the same string instance simultaneously. However, string creation and conversion operations may allocate memory and should be considered for performance in highly concurrent scenarios. diff --git a/.github/KnowledgeBase/KB_Vlpp_UnitTesting.md b/.github/KnowledgeBase/KB_Vlpp_UnitTesting.md index d4350ca0..35f7c173 100644 --- a/.github/KnowledgeBase/KB_Vlpp_UnitTesting.md +++ b/.github/KnowledgeBase/KB_Vlpp_UnitTesting.md @@ -33,11 +33,11 @@ If a test case contains only one call to `TEST_ASSERT`, it can be simplified to ## Hierarchical Organization -### TEST_FILE +### `TEST_FILE` `TEST_FILE` defines the test file scope and serves as the root container for all test cases and categories within a source file. There can be only one `TEST_FILE` per source file. -### TEST_CATEGORY +### `TEST_CATEGORY` `TEST_CATEGORY(L"CATEGORY-NAME")` groups related tests under a descriptive category name. Key characteristics: @@ -46,7 +46,7 @@ If a test case contains only one call to `TEST_ASSERT`, it can be simplified to - Categories help organize tests logically by functionality or feature area - Category names should be descriptive and use wide character string literals -### TEST_CASE +### `TEST_CASE` `TEST_CASE(L"TOPIC-NAME")` defines individual test implementations. Key characteristics: @@ -55,9 +55,13 @@ If a test case contains only one call to `TEST_ASSERT`, it can be simplified to - Each test case should focus on testing a specific behavior or functionality - Test case names should clearly describe what is being tested +### `TEST_CASE_ASSERT` + +`TEST_CASE_ASSERT(EXPRESSION-TO-VERIFY)` is shorthand for a `TEST_CASE` containing one `TEST_ASSERT`. The generated test case name is the expression text. + ## Test Assertions -### TEST_ASSERT +### `TEST_ASSERT` `TEST_ASSERT(EXPRESSION-TO-VERIFY)` performs test assertions within test cases. Key characteristics: @@ -66,6 +70,18 @@ If a test case contains only one call to `TEST_ASSERT`, it can be simplified to - When the expression evaluates to false, the test fails and reports the failure - Multiple assertions can be used within a single test case +### `TEST_ERROR` + +`TEST_ERROR(STATEMENT)` executes one statement and passes only when it throws `vl::Error`. + +### `TEST_EXCEPTION` + +`TEST_EXCEPTION(STATEMENT, EXCEPTION, ASSERT_FUNCTION)` executes one statement and passes only when it throws the specified exception type. The assertion callback receives the caught exception for additional checks. + +### `TEST_PRINT` + +`TEST_PRINT(MESSAGE)` writes an informational message through the unit-test output channel. + ## Test Execution Integration ### Integration with Main Function @@ -90,7 +106,9 @@ The unit test framework supports various command line options for controlling te - `/D`: Disable failure suppression (for debugging) - `/R`: Enable failure suppression (for release mode) +- `/C`: Copilot mode. Stop immediately on the first failure for automated testing. - `/F:TestFile`: Run only specific test files +- `/DebugOutput:File`: Redirect debug output to the specified file ### Test Output and Reporting