mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-08-17 09:21:41 +08:00
Sync copilot knowledge base
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 _
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,7 +195,7 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user