RNet (short for Remote Net) is BlingoEngine's remote control and observation protocol. It lets a tooling process watch frames, sprites, sounds, and property changes coming from a running movie while sending commands such as property edits or frame jumps back to the host. Multiple transports are supported—today SignalR over HTTP and a low-latency pipe transport—so RNet can be embedded into desktop tools, headless renderers, or network services while reusing the same contracts and client logic.
The ecosystem is composed of a set of focused projects that you can mix and match:
- Contracts – The common DTOs, commands, and configuration abstractions that describe what flows across the wire.
- Publishers – Helpers that subscribe to a live
IBlingoPlayer, watch the engine's events, and push DTOs into channels. - Hosts – Servers that expose those channels over a specific transport (SignalR, pipes, etc.).
- Clients – Implementations of the shared client interface that consume the streams and send commands back.
- Runtimes & Tools – Higher-level helpers and applications that assemble the pieces (e.g., the RNet Terminal, the client player).
The following diagram summarizes the typical flow for the SignalR transport:
BlingoEngine runtime ──▶ Publisher (Project/Pipe) ──▶ Bus channels ──▶ Host (Hub/Server)
▲ │
│ ▼
Command queue ◀────────────────────────────── Clients (Terminal, custom apps)
The pipe transport mirrors the same shape but swaps the SignalR hub for a duplex pipe reader/writer. Both transports implement the same IBlingoRNetClient interface, so tooling code can stay transport-agnostic.
Defines the shared language spoken by all RNet components.
- DTOs describing frames, sprite deltas, film loops, transitions, tempo changes, sound events, text styles, and more live under this project. (see StageFrameDto.cs, SpriteDeltaDto.cs)
RNetCommandand its derived records (SetSpritePropCmd,SetMemberPropCmd,SetCastPropCmd,GoToFrameCmd,RewindCmd,PauseCmd,ResumeCmd) capture the write-side surface area for tooling commands alongside theRNetMemberTypeDtoandRNetSpriteTypeDtoenums used for member and sprite mutations. (see RNetCommand.cs)IRNetConfigurationandRNetConfigurationprovide a simple options object (port, autostart flag, client name) shared by both HTTP and pipe hosts/clients. (see IRNetConfiguration.cs, RNetConfiguration.cs)IRNetPublisherdefines the methods a publisher must expose for the engine to push updates into the transport-agnostic bus. (see IRNetPublisher.cs)
Infrastructure shared by the host implementations.
IRNetPublisherEngineBridgeextendsIRNetPublisherwithEnable/Disablehooks so a publisher can subscribe to anIBlingoPlayerat runtime. (see IRNetPublisherEngineBridge.cs)RNetPublisherBaseimplements the heavy lifting for tracking sprite/member/movie/stage property changes, queueing them, and flushing through the bus channels while also reacting to cast library and movie lifecycle events. (see RNetPublisherBase.cs)- Helpers such as
DtoExtensionsandIBlingoRNetServer(not shown) provide glue so the host servers can raise connection state events and expose the active publisher instance. (see IBlingoRNetServer.cs)
SignalR/HTTP host used by the Director tooling and most remote sessions.
BlingoRNetProjectHostSetup.WithRNetProjectHostServerwires up the host inside engine registration, registering the bus, publisher, and server and optionally autostarting once the engine is built. (see RNetProjectHostSetup.cs)RNetProjectServerself-hosts ASP.NET Core, exposing the hub at/director, managing connection state, and piping inbound commands back to the publisher via a bounded channel. (see RNetProjectServer.cs)RNetProjectBusis the set of channels linking the publisher to the hub; each DTO type has its own bounded queue tuned for that payload. (see RNetProjectBus.cs)BlingoRNetProjectHubis the SignalR hub that streams frames, deltas, and property updates to clients and accepts commands, heartbeats, and snapshot requests in return. (see RNetProjectHub.cs)RNetProjectPublisher(not shown) derives fromRNetPublisherBaseto push data into theIRNetProjectBusand drain command queues back into the activeIBlingoPlayer.
Pipe-based host for tooling scenarios where HTTP is undesirable.
WithRNetPipeHostServermirrors the SignalR setup helper but registersIRNetPipeServer,IRNetPipeBus, and the pipe publisher instead. (see BlingoRNetPipeHostSetup.cs)RNetPipeServerlistens on named pipes (Windows) or Unix domain sockets (macOS/Linux), decoding framed JSON messages, multiplexing streams, and raising connection state events just like the SignalR server. (see RNetPipeServer.cs)RNetPipeBusdefines the same set of bounded channels as the SignalR bus so the publishers can stay transport-agnostic. (see RNetPipeBus.cs)RNetPipePublisher(derived fromRNetPublisherBase) writes DTOs onto those channels and consumes commands coming back from the pipe reader.
A standalone ASP.NET Core relay used when hosts and clients cannot connect directly.
Program.csboots a minimal web application that maps a SignalR hub at/rnetand a simple health-check controller at/for diagnostics. (see Program.cs, HomeController.cs)ProjectRelayHubtracks active project hosts and their clients, forwards broadcast events from the host to every registered client, and relays commands back to the host connection ID. (see ProjectRelayHub.cs)ProjectRegistrystores the mapping between project names, the active host connection, and the connected client IDs. (see ProjectRegistry.cs)
This relay is optional; the standard RNetProjectServer already exposes /director. The relay becomes useful when multiple remote tools need to share a hosted movie through a central message broker.
Transport-agnostic client contract.
IBlingoRNetClientexpresses everything a client must do: connect with aHelloDto, stream the various DTO feeds, request snapshots, send commands, and emit heartbeats. (see IBlingoRNetClient.cs)- Tooling code written against this interface can swap in either the SignalR or pipe implementation without code changes.
SignalR client implementation.
BlingoRNetProjectClientbuilds aHubConnection, subscribes to connection state callbacks, exposes the stream APIs, and forwards commands viaInvokeAsync. It also implements automatic reconnection so transient network failures surface as state changes instead of exceptions. (see BlingoRNetProjectClient.cs)- Default configuration values (port 61699, sample client name) are provided for convenience, but you can inject your own
IRNetConfigurationto control these settings. (see BlingoRNetProjectClient.cs)
Named pipe / Unix socket client implementation.
RNetPipeClientconnects topipe://URIs, resolves the platform-specific endpoint (named pipes on Windows, Unix sockets elsewhere), and pumps JSON payloads through asynchronous channels mirroring the SignalR client APIs. (see RNetPipeClient.cs)- Each inbound payload type has a dedicated channel writer so back-pressure can be applied independently; commands and snapshots are handled via task completions that resolve when the matching response arrives. (see RNetPipeClient.cs, RNetPipeClient.cs)
- Heartbeats and commands share the same framing logic, keeping pipe sessions alive without relying on HTTP infrastructure. (see RNetPipeClient.cs)
Bridges an RNet client with a local IBlingoPlayer.
BlingoRNetClientPlayeraccepts anyIBlingoRNetProjectClient, subscribes to every stream (frames, deltas, film loops, sounds, tempos, transitions, properties, sprite events, text styles), and applies the updates throughRNetClientPlayerApplier. (see BlingoRNetClientPlayer.cs)- This class is ideal for building headless renderers or regression bots that need to stay synchronized with a remote host but run their own playback locally.
Interactive console tool for development and diagnostics.
RNetTerminalConnectioncentralizes connection management, background streaming tasks, heartbeat timers, and outgoing command queues. It exposesQueueGoToFrameCommand,QueueSpritePropertyChange, andQueueMemberPropertyChange(acceptingRNetSpriteTypeDto/BlingoMemberTypeDTO) so the UI can stay thin. (see RNetTerminalConnection.cs)- The terminal respects both HTTP and pipe transports through
RNetTerminalTransportand builds the correct URI automatically, keeping the rest of the UI agnostic to the transport mechanics. (see RNetTerminalConnection.cs) TerminalDataStore(not shown) coordinates sprite/member state, ensuring that in remote mode UI edits are deferred until the host confirms them, whileBlingoRNetTerminalwires everything into theTerminal.Guifront end.
- The
src/Net/cpp/BlingoEngine.RNetProjectClientfolder contains a minimal C++ client that exercises the same protocol from native code. Use it as a template when integrating RNet with legacy tools or custom engines.
using BlingoEngine.Net.RNetProjectHost;
using BlingoEngine.Setup;
var registration = BlingoEngineSetup.Create()
.WithRNetProjectHostServer(port: 7000, autoStart: true);
using var engine = registration.Build();
// The host starts automatically because of autoStart; otherwise call
// engine.Services.GetRequiredService<IRNetProjectServer>().StartAsync();- The helper registers
IRNetConfiguration,IRNetProjectServer, the publisher, and the bus, so nothing else is required beyond callingWithRNetProjectHostServerduring setup. (see RNetProjectHostSetup.cs) - When
autoStart(orIRNetConfiguration.AutoStartRNetHostOnStartup) istrue, the post-build action starts the server and enables the publisher as soon as the engine finishes building. (see RNetProjectHostSetup.cs) - To stop the host manually, resolve
IRNetProjectServerand callStopAsync(). Connection state changes are surfaced throughConnectionStatusChangedso you can update UI or logs accordingly. (see RNetProjectServer.cs)
using BlingoEngine.Net.RNetPipeServer;
using BlingoEngine.Setup;
var registration = BlingoEngineSetup.Create()
.WithRNetPipeHostServer(port: 9001, autoStart: true);
using var engine = registration.Build();- Pipes are ideal for local-only tooling or platforms where HTTP is too heavyweight. The helper registers
IRNetPipeServer,IRNetPipeBus, and the pipe publisher for you. (see BlingoRNetPipeHostSetup.cs) - Windows builds use named pipes derived from the port value; Unix platforms derive a socket path. From the client side you simply connect to
pipe://localhost:9001/and the implementation handles the OS-specific plumbing. (see RNetPipeClient.cs)
using BlingoEngine.Net.RNetProjectClient;
using BlingoEngine.Net.RNetContracts;
var client = new BlingoRNetProjectClient();
await client.ConnectAsync(new Uri("http://localhost:7000/director"),
new HelloDto("sample-project", "custom-tool", "1.0", "My tool"));
await foreach (var frame in client.StreamFramesAsync())
{
Console.WriteLine($"Frame {frame.FrameNumber} has {frame.Sprites.Length} sprites");
}- Every client starts by sending a
HelloDtoso the host knows who connected. (see BlingoRNetProjectClient.cs) - All stream methods return
IAsyncEnumerable<T>and accept cancellation tokens, so you can coordinate graceful shutdowns or back-pressure naturally. - Commands such as
SetSpritePropCmdare sent withSendCommandAsync, and heartbeats are optional but recommended to keep sessions alive behind proxies. (see IBlingoRNetClient.cs)
using BlingoEngine.Net.RNetPipeClient;
using BlingoEngine.Net.RNetContracts;
var client = new RNetPipeClient();
await client.ConnectAsync(new Uri("pipe://localhost:9001/"),
new HelloDto("sample-project", "pipe-tool", "1.0", "Pipe client"));- The remainder of the API mirrors the SignalR client. Because the transport is message-based, heartbeats (
SendHeartbeatAsync) are particularly important to detect broken pipe connections. (see RNetPipeClient.cs)
var player = /* resolve or create IBlingoPlayer */;
var client = new BlingoRNetProjectClient();
await using var remote = new BlingoRNetClientPlayer(client, player);
await remote.ConnectAsync(new Uri("http://localhost:7000/director"),
new HelloDto("project", "client-player", "1.0", "Sync bot"));BlingoRNetClientPlayerstarts a background pump that consumes every stream concurrently and applies the updates throughRNetClientPlayerApplier. This keeps the local player synchronized with the host's state without manual wiring. (see BlingoRNetClientPlayer.cs)
- Launch
BlingoEngine.Net.RNetTerminalfrom the command line; the startup dialog now offers dedicated buttons for HTTP or pipe connections. Transport choices are persisted viaRNetTerminalSettingsso the next session remembers your preference. (see RNetTerminalConnection.cs, RNetTerminalSettings.cs) - Once connected, the terminal streams frames, sprite deltas, and property updates through
RNetTerminalConnection. UI edits queue commands viaQueueSpritePropertyChange/QueueMemberPropertyChangeand wait for the host to echo the change before updating the display, ensuring the UI always reflects authoritative remote state. (see RNetTerminalConnection.cs, RNetTerminalConnection.cs) - Clicking in the score sends
GoToFrameCmdmessages so the host moves to the selected frame, keeping both sides in sync. (see RNetTerminalConnection.cs)
| Scenario | Recommended Transport | Reason |
|---|---|---|
| Cross-machine debugging, remote QA, or cloud-hosted projects | SignalR (BlingoEngine.Net.RNetProjectHost + BlingoRNetProjectClient) |
Works over HTTP/S, supports automatic reconnection, easy to deploy alongside existing web infrastructure. (see RNetProjectServer.cs) |
| Local editor tooling, high-frequency property scrubbing, or air-gapped machines | Pipe (BlingoEngine.Net.RNetPipeServer + RNetPipeClient) |
Avoids HTTP overhead, uses OS-level sockets/pipes for lower latency, no firewall configuration required. (see RNetPipeServer.cs, RNetPipeClient.cs) |
| Multi-tenant relay where hosts/clients discover each other dynamically | BlingoEngine.Net.RNetServer |
Provides a hub that keeps a registry of named projects and forwards payloads between them. (see ProjectRelayHub.cs) |
- Command processing – Publishers call
TryDrainCommandseach frame (or on a timer) to apply queued commands back into the engine. Implementations typically pass a delegate that switches onRNetCommandtypes and updates sprites, members, or playback state accordingly. (see RNetPublisherBase.cs) - Property coalescing –
RNetPublisherBasebatches sprite/member/movie/stage property notifications so flurries of property changes during a single tick collapse into a single DTO per unique key before being flushed to clients. (see RNetPublisherBase.cs) - Snapshots and project export – Clients can call
GetMovieSnapshotAsyncorGetCurrentProjectAsyncto retrieve the current playhead state or full serialized project. The SignalR hub answers these by interrogating the activeIBlingoMovieand usingJsonStateRepositoryto serialize the project graph. (see RNetProjectHub.cs) - Heartbeats – Both transports expose
SendHeartbeatAsyncto keep the session alive. The hub tracks the last heartbeat timestamp per connection and can disconnect idle sessions server-side if desired. (see RNetProjectHub.cs) - Extensibility – To add a new transport, implement
IBlingoRNetServer/IRNetPublisherEngineBridgeon the host side andIBlingoRNetClienton the client side. Because DTOs and commands are shared, your new transport immediately works with existing tooling like the RNet Terminal or client player.
src/Net/README.md– High-level overview of the RNet directory structure and a short connection example. (see README.md)docs/design/Architecture.md– Broader engine architecture context when embedding RNet in larger applications.src/Net/cpp/BlingoEngine.RNetProjectClient/README.md– Native interop notes for the C++ sample client.