Rendering Canvas on Web Service Application
Overview
The web application's 3D canvas is rendered on the server and streamed to the browser over a
SignalR hub connection at /renderingHub. Disp/RenderingHub.cs serves that hub, Program.cs
maps it, and the singleton in Disp/RenderingService.cs owns one native display engine per hub
connection. The browser paints the frames that arrive and forwards the user's input back.
Core Component
- Location:
wwwroot-src/src/components/RenderingCanvas.vue - Purpose: owns one hub connection, forwards input to the server engine, and paints the frames the server streams back
Its hubUrl prop defaults to /renderingHub, and it exposes connect, disconnect, the eight
view commands and clearCache — the surface through which
wwwroot-src/src/components/RenderingCanvasToolBar.vue drives the canvas instance it is bound to.
Connection Management
SignalR Hub Connection
Each RenderingCanvas instance opens its own hub connection and therefore owns one connection ID;
the server keeps one display engine per connection ID, so several canvases can be live at the same
time. On initialization the hub sends the connection ID back to the caller as CanvasInitialized,
and the component re-emits it as serverInitialized. It is re-emitted after every reconnect,
because the server disposes the engine on disconnect and a reconnect gets a new ID — the parent
page answers by registering its content against that new ID.
The connection ID is the index for every canvas operation. Disp/RenderingService.cs keys the
engine dictionary, the last-input time, the last lossless-frame hash and the negotiated frame
format by it, and Disp/RenderingHub.cs keys its own sketch-view cache the same way.
Engine Lifetime
The engine's life is the connection's: GetOrCreateEngine creates it when the canvas initializes,
and RemoveEngine — called from OnDisconnectedAsync — disposes it and raises EngineRemoved.
An HTTP endpoint therefore resolves an engine through the non-creating GetEngine; creating one
for an ID that has already disconnected would orphan an engine no disconnect can ever clean up.
Disp/StlPreviewService.cs subscribes to EngineRemoved to release the per-connection native
topology the engine itself does not own.
Frame Encoding
The frame encoding is negotiated at initialization. InitializeCanvasV2(width, height, formats)
with jpeg in the accepted list selects SkiaSharp JPEG frames delivered on ImageUpdateV2 at an
adaptive quality — interactive frames compress harder and a still frame refines once — and adding
png lets that refine be encoded lossless. A browser without createImageBitmap, or a server that
does not carry the V2 method, falls back to InitializeCanvas and the gzip-RGBA frames delivered
on ImageUpdate.
Connection ID Naming Convention
One value carries two names on the frontend:
renderingConnectionId— the parameter name used throughout the API wrappers underwwwroot-src/src/api, and the route-parameter name on most display controllersrenderingConnId— how the Execution page holds it (wwwroot-src/src/pages/ExecutionPage.vue), filled from the canvas's@server-initializedand passed to the typed wrappers inwwwroot-src/src/api/execution.ts, whose parameter isrenderingConnectionId
Data Flow Architecture
Frontend Responsibilities
The component owns the connection and its reconnect schedule, forwards pointer, wheel, key, touch, resize and visibility events to the hub, and paints each frame as it arrives. A canvas the layout has hidden keeps its connection open and tells the engine to pause rendering instead, so a hidden canvas costs nothing while a simulation runs.
Backend Integration
Rendering and frame encoding happen on the server; a controller decides only what the engine shows, by resolving the engine from the connection ID and assigning its displayee. That is why any feature can put its own content on a canvas without touching the transport.
Example: Execution Controller
- File:
Execution/ExecutionController.cs - Method:
InitializeExecution(POST /api/Execution/initialize/{connectionId}) - Purpose: resolves the engine with
RenderingService.GetEngine(connectionId)and assigns the Execution displayee to it
Six further controllers bind content the same way — the STL preview, the Tool House, the General Setup equipment, the machine tool, the Mech Builder general mechanism and the legacy HardNcEnv controller — each with its own route, all reaching the engine through the same connection ID. The first five bind a displayee of their own; the last binds the shared Execution displayee, and it has had no caller since the Legacy Controller page was removed on 2026-09-11 — the route is still mounted, but no canvas in the SPA initializes against it.
Key Points
- Every canvas data-stream operation is indexed by the connection ID.
- Rendering and frame encoding happen on the server; the frontend owns the connection, the input events and the painting of received frames.
- The engine is created and destroyed with the hub connection, so anything else that holds
per-connection resources releases them from
EngineRemoved.
Source Code Path
See HiNC App Anatomy for git repository links.
Web Service Source Code Path
Disp/RenderingHub.cs— the hub: creates the per-connection engine, encodes and pumps frames, handles mouse, wheel, key, touch, resize and visibility, and disposes the engine on disconnect.Disp/RenderingService.cs— the singleton holding the per-connection maps, the playback render throttle and theEngineRemovedevent.Disp/StlPreviewService.cs— theEngineRemovedsubscriber that frees the File Explorer preview's native topology.Program.cs— registers SignalR and maps the hub at/renderingHub.Execution/ExecutionController.cs— the worked example above:InitializeExecutionassigns the Execution displayee to the engine named by the connection ID.Disp/StlPreviewController.cs— binds an STL preview to a canvas (api/stl-preview).Mech/ToolHouseDisplayController.cs— the Tool House canvas binding (api/mech/tool-house-display).Mech/EquipmentSetupDisplayController.cs— the General Setup canvas binding (api/mech/equipment-setup-display).Mech/MachineToolDisplayController.cs— the machine-tool canvas binding (api/mech/machine-tool/display).Mech/MechBuilder/GeneralMechanismDisplayController.cs— the Mech Builder canvas binding (api/general-mechanism/display).Controller/ControllerController.cs— the legacy controller surface's canvas binding (initialize-display/{connectionId}), which assigns the shared Execution displayee; still mounted, with no SPA caller since the Legacy Controller page was removed.
Web Page Application Source Code Path
wwwroot-src/src/components/RenderingCanvas.vue— the client half: one hub connection per instance, encoding negotiation, frame painting, input forwarding and the reconnect loop.wwwroot-src/src/components/RenderingCanvasToolBar.vue— the view-control bar bound to one canvas instance through its exposed methods.wwwroot-src/src/pages/ExecutionPage.vue— mounts the main canvas, stores the ID asrenderingConnIdand initializes the Execution content from@server-initialized.wwwroot-src/src/api/execution.ts— the typed wrappers whoserenderingConnectionIdparameter addresses that canvas.wwwroot-src/src/components/execution/StepVolumePanel.vue— a second canvas on the Execution page, with its own connection and its own engine.wwwroot-src/src/components/mech/EquipmentSetupPanel.vue— the General Setup page's canvas column.wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue— the Tool House page's canvas column.wwwroot-src/src/components/StlPreviewPane.vue— the File Explorer STL preview canvas.
See Also
- RenderingCanvas Tool Bar — the
Viewmenu that drives the hub, and theScenemenu beside it - Program and Hosting — the host that maps this hub and registers the services behind it
- STL Preview Pane — the one caller that opens a rendering connection outside a project