15 KiB
Remote Protocol Renderer and Serialization
The remote protocol renderer side receives protocol messages from the core side and translates them into native window operations and graphics rendering. The serialization and channel infrastructure provides composable layers that convert between typed protocol calls and transport-ready strings, enabling GacUI applications to run across process boundaries over any user-provided transport (named pipe, HTTP, WebSocket, etc.).
GuiRemoteRendererSingle
GuiRemoteRendererSingle (in Source/PlatformProviders/RemoteRenderer/) is the renderer-side implementation that bridges IGuiRemoteProtocol to a real native window with actual graphics rendering. It is not a full INativeController — it relies on a real platform provider (e.g., Windows Direct2D via SetupRawWindowsDirect2DRenderer()) already running, and registers itself as a listener on a native window.
Class Hierarchy
GuiRemoteRendererSingle inherits from:
Object— standard baseIGuiRemoteProtocol(virtual) — receives all protocol messages from the core sideINativeWindowListener(protected virtual) — listens to native window events (mouse, keyboard, window lifecycle)INativeControllerListener(protected virtual) — listens to global timer and shortcut key events
Key Members
window/screen/events: The native window handle, current screen, andIGuiRemoteProtocolEventscallback for firing events to the core side.availableElements:Dictionary<vint, Ptr<IGuiGraphicsElement>>mapping element IDs to actual graphics elements created by the real rendering engine (e.g.,GuiSolidBorderElement,GuiSolidLabelElement).availableImages:Dictionary<vint, Ptr<INativeImage>>mapping image IDs to loaded native images.renderingDom/renderingDomIndex: The rendering DOM tree received from the core side, used for on-screen rendering and hit testing.solidLabelMeasurings/fontHeightMeasurings: Caches and measurement tracking for text elements, supporting the measurement feedback loop.pendingMouseMove,pendingHWheel,pendingVWheel,pendingKeyAutoDown,pendingWindowBoundsUpdate: Accumulation fields for coalescing high-frequency events before sending.
Source File Organization
GuiRemoteRendererSingle.cpp: Construction, destruction, main window registration, connection lifecycle (Opened,BeforeClosing,Closed), screen/config management.GuiRemoteRendererSingle_Controller.cpp: Controller-level requests —RequestControllerGetFontConfig,RequestControllerGetScreenConfig,RequestControllerConnectionEstablished,RequestControllerConnectionStopped.GuiRemoteRendererSingle_MainWindow.cpp: Window style notifications —RequestWindowNotifySetBounds,RequestWindowNotifySetTitle,RequestWindowNotifySetEnabled,RequestWindowNotifyShow, etc.GuiRemoteRendererSingle_IO.cpp: IO requests (global shortcuts, mouse capture, key state queries) and native-to-protocol input event conversion. ContainsSendAccumulatedMessages()for batching high-frequency events.GuiRemoteRendererSingle_Rendering.cpp: Core rendering pipeline — element creation/destruction (RequestRendererCreated,RequestRendererDestroyed), begin/end rendering, DOM rendering (Renderrecursive traversal), hit testing, andGlobalTimer/Paintdriven refresh.GuiRemoteRendererSingle_Rendering_Elements.cpp: Property updates on ordinary graphics elements (solid border, sink border, splitter, background, gradient, inner shadow, polygon).GuiRemoteRendererSingle_Rendering_Label.cpp: Solid label measurement and property updates.GuiRemoteRendererSingle_Rendering_Image.cpp: Image creation, metadata, and image frame element updates.GuiRemoteRendererSingle_Rendering_Document.cpp: Complex document paragraph element rendering viaGuiRemoteDocumentParagraphElementinner class.
Rendering Pipeline
When the core sends RequestRendererBeginRendering(ElementBeginRendering), the updatedElements field contains a list of OrdinaryElementDescVariant. Each variant is dispatched via Apply(Overloading(...)) to the corresponding RequestRendererUpdateElement_* helper, which looks up the element by ID in availableElements and sets its properties.
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).
Event Forwarding
GuiRemoteRendererSingle implements all INativeWindowListener mouse/keyboard callbacks to forward OS events as protocol events:
- Discrete events (button clicks, key presses): Sent immediately via
events->OnIOMouseLeft,events->OnIOKeyDown, etc. - High-frequency events (mouse move, wheel, key auto-repeat): Accumulated and coalesced:
pendingMouseMove: Only the latest mouse position is kept.pendingHWheel/pendingVWheel: Wheel deltas are summed across frames.pendingKeyAutoDown: Only the latest auto-repeat key is kept.SendAccumulatedMessages()is called fromGlobalTimer()to flush these accumulated events.
- Window lifecycle events (
Opened,BeforeClosing,Moved,DpiChanged): Translated to protocol events likeOnControllerConnect,OnControllerRequestExit,OnWindowBoundsUpdated.
Hit Testing
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.
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.
Core Interfaces
IGuiRemoteProtocolChannel<TPackage>: A bidirectional channel that canWritepackages and receives packages viaIGuiRemoteProtocolChannelReceiver<TPackage>::OnReceive.IGuiRemoteProtocol: The high-level typed protocol interface with named methods (RequestNAME,OnNAME,RespondNAME).
Layer 1: IGuiRemoteProtocol ↔ JsonObject Channel
Two adapter classes handle bidirectional conversion between typed protocol calls and JSON objects:
GuiRemoteProtocolFromJsonChannel: WrapsIGuiRemoteProtocolChannel<Ptr<JsonObject>>and implementsIGuiRemoteProtocol. WhenRequestNAME(arguments)is called, it serializes arguments to JSON viaConvertCustomTypeToJson(), packs them into a JSON envelope withJsonChannelPack(), and callschannel->Write(package). When it receives a JSON package viaOnReceive, it unpacks withJsonChannelUnpack(), dispatches by name, deserializes viaConvertJsonToCustomType(), and callsevents->OnNAME()orevents->RespondNAME().GuiRemoteJsonChannelFromProtocol: The reverse — wrapsIGuiRemoteProtocoland implementsIGuiRemoteProtocolChannel<Ptr<JsonObject>>. Converts incoming JSON packages to protocol calls, and outgoing events/responses to JSON packages.
Layer 2: JsonObject ↔ WString Channel
JsonToStringSerializer handles JSON-to-string conversion:
Serialize: ConvertsPtr<JsonObject>toWStringusingJsonToStringwith compact formatting.Deserialize: ParsesWStringtoPtr<JsonObject>usingJsonParse.- Type aliases:
GuiRemoteJsonChannelStringSerializerandGuiRemoteJsonChannelStringDeserializerforGuiRemoteProtocolChannelSerializer<JsonToStringSerializer>andGuiRemoteProtocolChannelDeserializer<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>: CallsTSerialization::SerializeonWriteandTSerialization::DeserializeonOnReceive.GuiRemoteProtocolChannelDeserializer<TSerialization>: The reverse — callsDeserializeonWriteandSerializeonOnReceive.
The TSerialization concept requires: SourceType, DestType, ContextType, and static Serialize/Deserialize methods.
JSON Envelope Format
Every package is a JSON object with fields:
"semantic": One of"Message","Request","Response","Event"."id": (optional) An integer request/response ID, present only forRequestandResponse."name": The message/event name (e.g.,"ControllerGetFontConfig","IOMouseMoving")."arguments": (optional) The serialized arguments as a JSON value.
Messages (no response expected) use "Message". Requests (response expected) use "Request" with an ID. Responses use "Response" with the matching ID. Events use "Event".
JSON Serialization of Protocol Types
Protocol types are code-generated from Protocol/*.txt files into GuiRemoteProtocolSchema.h/.cpp. Each struct gets a JsonHelper<T> specialization with ToJson and FromJson methods. The shared infrastructure in GuiRemoteProtocolSchemaShared.h provides:
- Primitive type serializers:
bool,vint,float,double,WString,wchar_t,VKEY,Color,Ptr<MemoryStream>(Base64-encoded). - Generic container serializers:
Nullable<T>,Ptr<T>,List<T>,ArrayMap<K,V,F>,Dictionary<K,V>. Varianttypes are serialized with a type discriminator field.ConvertCustomTypeToJsonFieldadds a named field to a JSON object.
Async Channel
GuiRemoteProtocolAsyncChannelSerializer<TPackage> provides thread separation for real remote deployment:
- A channel thread handles all
Write/OnReceivecalls on the underlying channel (IO operations). - A UI thread runs the GacUI application logic.
Start(channel, uiMainProc, startingProc)launches both threads. ThestartingProcis 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
PendingRequestGroupwithconnectionCounterfor safe handling of disconnection/reconnection races. ExecuteInChannelThread()allows queueing work from any thread to the channel thread.
Demo Project Pair
Two projects in Test/GacUISrc/ demonstrate a full remote protocol deployment. They are paired: one is the core side (console application) and the other is the renderer side (Windows application).
RemotingTest_Core (Console Application)
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):
- Creates
CoreChannelwrapping the transport server — implementsIGuiRemoteProtocolChannel<WString>. - Creates
GuiRemoteJsonChannelStringSerializerfor JSON ↔ String conversion. - Creates
GuiRemoteProtocolAsyncJsonChannelSerializerfor async channel/UI thread separation. - Starts via
asyncChannelSender.Start()with a UI main proc that builds the protocol stack:GuiRemoteProtocolFromJsonChannel→GuiRemoteProtocolFilter→GuiRemoteProtocolDomDiffConverter→SetupRemoteNativeController. - Waits for a renderer client to connect, then waits for the async channel to stop, then waits for disconnection.
CoreChannel bridges async channel and network transport:
Write(WString): Accumulates messages in a pending list.Submit(): Dispatches accumulated messages vianetworkProtocol->SendStringArray().OnReadStringThreadUnsafe(): Called when strings arrive from the network, queues them for channel thread processing viaasyncChannel->ExecuteInChannelThread().- Detects
ControllerConnectevent JSON to track connection state. - On reconnection (
OnReconnectedUnsafe), injects aControllerDisconnectevent.
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):
- Creates
GuiRemoteRendererSingle— the renderer implementingIGuiRemoteProtocol. - Creates
GuiRemoteJsonChannelFromProtocolwrapping the renderer — converts protocol calls to JSON. - Creates
GuiRemoteJsonChannelStringDeserializerfor String ↔ JSON conversion. - Creates
RendererChannelconnecting renderer, transport, and channel. - Hooks
BeforeWrite/BeforeOnReceiveevents for request/response caching inRendererChannel. - Calls
SetupRawWindowsDirect2DRenderer()to run the native window event loop.
RendererChannel bridges network transport and JSON channel:
OnReadStringThreadUnsafe(): Receives strings from network, dispatches to UI thread viaInvokeInMainThread(). Handles error strings (prefixed with!) by displaying aMessageBoxand callingrenderer->ForceExitByFatelError().OnReceive(WString): Uses a caching mechanism (isCaching/cachedPackages) to batch responses — when aRequestsemantic is detected (viaBeforeWrite), caching is enabled; when aResponsesemantic is detected (viaBeforeOnReceive), caching is disabled and all cached packages are sent as a batch.
Protocol Stack Direction
Core side (messages flow outward):
SetupRemoteNativeController
→ GuiRemoteProtocolDomDiffConverter
→ GuiRemoteProtocolFilter
→ GuiRemoteProtocolFromJsonChannel
→ GuiRemoteProtocolAsyncJsonChannelSerializer
→ GuiRemoteJsonChannelStringSerializer
→ CoreChannel (IGuiRemoteProtocolChannel<WString>)
→ Network transport (user-implemented)
Renderer side (messages flow inward):
Network transport (user-implemented)
→ RendererChannel
→ GuiRemoteJsonChannelStringDeserializer
→ GuiRemoteJsonChannelFromProtocol
→ GuiRemoteRendererSingle (IGuiRemoteProtocol)
→ Native window rendering
Events flow in the reverse direction through the same stack.