Sync copilot knowledge base

This commit is contained in:
vczh
2026-07-04 20:52:44 -07:00
parent 860a9ffefe
commit 03c48d4d27
22 changed files with 343 additions and 243 deletions
+6 -6
View File
@@ -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<Theme>` 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<T>` 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)
+5 -3
View File
@@ -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
+5 -5
View File
@@ -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)
+10 -7
View File
@@ -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<Class>(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)
+4 -4
View File
@@ -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_<T>` for pattern definition with `ObjectString<T>` encoding
- Use `MatchHead<U>` for finding longest prefix matching the pattern
- Use `Match<U>` for finding earliest substring matching the pattern
- Use `TestHead<U>` for boolean prefix matching without detailed results
- Use `Test<U>` for boolean substring matching without detailed results
- Use `Search<U>` for finding all non-overlapping matches
- Use `Split<U>` for using pattern as delimiter to split text
- Use `Cut<U>` for combined search and split operations
- Use `Search<U>` for appending all non-overlapping successful matches to `RegexMatch_<U>::List`
- Use `Split<U>` for appending delimiter-separated unmatched fragments to `RegexMatch_<U>::List`
- Use `Cut<U>` for appending both successful and failed fragments in order
[API Explanation](./KB_VlppRegex_PatternMatching.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
@@ -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<T>` provides a bidirectional channel abstraction. The typical stack:
1. `GuiRemoteProtocolFromJsonChannel` adapts a JSON channel to `IGuiRemoteProtocol`
2. `GuiRemoteProtocolAsyncChannelSerializer<Ptr<JsonObject>>` (aliased as `GuiRemoteProtocolAsyncJsonChannelSerializer`) runs channel IO on a separate thread
For real remote deployment, `channeling::IJsonChannel` is an alias of `inter_process::IChannel<Ptr<glr::json::JsonNode>>`. Network channels are built with `GuiRemoteProtocolNetworkChannelServer<TServerBase>`, `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
@@ -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<TPackage>`: A bidirectional channel that can `Write` packages and receives packages via `IGuiRemoteProtocolChannelReceiver<TPackage>::OnReceive`.
- `IGuiRemoteProtocol`: The high-level typed protocol interface with named methods (`RequestNAME`, `OnNAME`, `RespondNAME`).
- `channeling::JsonPackage`: Alias of `Ptr<glr::json::JsonNode>`.
- `channeling::IJsonChannel`: Alias of `inter_process::IChannel<JsonPackage>`, with `IJsonChannelReader`, `IJsonChannelClient`, and `IJsonChannelServer` aliases for the corresponding channel roles.
- `GuiRemoteProtocolNetworkChannelServer<TServerBase>`: Network channel server alias based on `inter_process::NetworkProtocolChannelServer<JsonPackage, glr::json::JsonNodeListSerializer, TServerBase>`.
- `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<Ptr<JsonObject>>` 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<Ptr<JsonObject>>`. 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<JsonObject>` to `WString` using `JsonToString` with compact formatting.
- `Deserialize`: Parses `WString` to `Ptr<JsonObject>` using `JsonParse`.
- Type aliases: `GuiRemoteJsonChannelStringSerializer` and `GuiRemoteJsonChannelStringDeserializer` for `GuiRemoteProtocolChannelSerializer<JsonToStringSerializer>` and `GuiRemoteProtocolChannelDeserializer<JsonToStringSerializer>`.
### Layer 3: WString ↔ Transport (User-Implemented)
The user provides an `IGuiRemoteProtocolChannel<WString>` 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<TFrom, TTo>` bridges two channel types:
- `GuiRemoteProtocolChannelSerializer<TSerialization>`: Calls `TSerialization::Serialize` on `Write` and `TSerialization::Deserialize` on `OnReceive`.
- `GuiRemoteProtocolChannelDeserializer<TSerialization>`: 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<TPackage>` 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<TServer>` in `GuiMain.cpp`):
1. Creates `CoreChannel` wrapping the transport server — implements `IGuiRemoteProtocolChannel<WString>`.
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<TServerBase>` and ultimately `GuiRemoteProtocolNetworkChannelServer<TServerBase>`.
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<TClient>` 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<WString>)
→ Network transport (user-implemented)
→ GuiRemoteProtocolCoreChannel
→ GuiRemoteProtocolAsyncJsonChannel
→ GuiRemoteProtocolLocalChannelClient / IJsonChannel
→ GuiRemoteProtocolNetworkChannelServer<TServerBase>
→ 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
```
@@ -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<wchar_t, char8_t>` — 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<wchar_t, char8_t>` — 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)`:
@@ -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.
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.
@@ -14,7 +14,7 @@ Use `UtfGeneralEncoder<Native, Expect>` and `UtfGeneralDecoder<Native, Expect>`
`UtfGeneralEncoder<Native, Expect>` encode from `Expect` to `Native`, `UtfGeneralDecoder<Native, Expect>` 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<wchar_t, char8_t> 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<wchar_t, char8_t> wtou8Decoder;
UtfGeneralDecoder<wchar_t, char8_t> 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.
This design provides flexibility for handling various data transformation scenarios efficiently.
@@ -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
- Mock file system behaviors for consistent testing across different environments
@@ -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.
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.
@@ -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
@@ -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:<AttributeTypeName>(<ArgTypeName>:<SerializedData>, ...)
@@ -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
@@ -31,13 +31,20 @@ VlppReflection provides extensive macros for registering classes and interfaces,
### Constructor Registration
- Use `CLASS_MEMBER_CONSTRUCTOR` for constructor registration with `Ptr<Class>(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<T>` 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
- Consider caching reflection results for frequently used operations
@@ -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
- Ensure all new types follow the established registration structure
@@ -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_<T>` accepts `ObjectString<T>` as the definition.
`MatchHead<U>`, `Match<U>`, `TestHead<U>`, `Test<U>`, `Search<U>`, `Split<U>` and `Cut<U>` accepts `ObjectString<U>` to match with the regular expression.
`MatchHead<U>`, `Match<U>`, `TestHead<U>`, `Test<U>`, `Search<U>`, `Split<U>` and `Cut<U>` accept `ObjectString<U>` or `const U*` text to match with the regular expression.
`Search<U>`, `Split<U>` and `Cut<U>` append results to a caller-provided `RegexMatch_<U>::List&`; they do not return a collection.
`Split<U>` and `Cut<U>` 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_<U>::List&`. When a match is found, searching continues after that match.
### Split<U>
`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_<U>` objects have `Success()` equal to `false`. `keepEmptyMatch` controls whether empty unmatched fragments are appended.
### Cut<U>
`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_<T>` 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<U>` 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_<T>`
- 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_<T>` construction
- `MatchHead<U>` and `Match<U>` return `nullptr` when no match is found
- `TestHead<U>` and `Test<U>` return `false` when no match is found
- `Search<U>` 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
```
Regex regex(L"/d+");
RegexMatch::List parts;
regex.Cut(mixedText, false, parts); // Appends both numbers and non-numbers
```
@@ -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<WString> 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<WString> 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_<T>` 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
- Consistent with the project's preference for `wchar_t` over other character types
@@ -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<typename T, typename Compare>
void Sort(T* begin, vint count, Compare compare);
template<typename T>
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<MyType> processor;
List<WString> 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<WString, WString> 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<T, T>` 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;
});
```
```
+9 -9
View File
@@ -35,17 +35,17 @@ The project provides convenient aliases instead of using `ObjectString<T>` 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.
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.
+22 -4
View File
@@ -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