Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

Design Philosophy

Relevant source files

Purpose and Scope

This document details the fundamental design principles that guide the rust-muxio architecture. It explains the non-async runtime model, binary protocol design choices, transport abstraction strategy, and core goals that shape the system. For information about how these principles manifest in the layered architecture, see Layered Architecture. For practical implementation details of the binary protocol, see Binary Framing Protocol.


Non-Async Runtime Model

The core muxio library is implemented using a callback-driven, synchronous control flow rather than async/await. This design choice enables maximum portability and minimal runtime dependencies while still supporting concurrent operations, streaming, and cancellation.

Callback-Driven Architecture

The fundamental mechanism is the on_message_bytes callback pattern. The core dispatcher accepts a closure that will be invoked whenever bytes need to be sent:

Sources: DRAFT.md:48-52 README.md34

graph LR
    subgraph "Application Code"
        AppLogic["Application Logic"]
end
    
    subgraph "Core Dispatcher"
        RpcDispatcher["RpcDispatcher"]
CallbackRegistry["on_message_bytes callback"]
end
    
    subgraph "Transport Layer (Async or Sync)"
        AsyncTransport["Tokio WebSocket"]
SyncTransport["Standard Library TCP"]
WasmTransport["WASM JS Bridge"]
end
    
 
   AppLogic --> RpcDispatcher
 
   RpcDispatcher --> CallbackRegistry
 
   CallbackRegistry --> AsyncTransport
 
   CallbackRegistry --> SyncTransport
 
   CallbackRegistry --> WasmTransport
    
 
   AsyncTransport --> RpcDispatcher
 
   SyncTransport --> RpcDispatcher
 
   WasmTransport --> RpcDispatcher

Runtime Independence Benefits

This model provides several critical advantages:

BenefitDescription
WASM CompatibilityWorks in single-threaded JavaScript environments where async tasks are limited
Runtime FlexibilitySame core code runs on Tokio, async-std, or synchronous runtimes
Deterministic ExecutionNo hidden async state machines or yield points
FFI-FriendlyCallbacks can cross language boundaries more easily than async functions
Zero Runtime OverheadNo async runtime machinery in the core library

The following diagram maps this philosophy to actual code entities:

Sources: muxio/src/rpc_dispatcher.rs DRAFT.md:48-52 README.md34


Binary Protocol Foundation

Muxio uses a low-overhead binary framing protocol for all communication. This is a deliberate architectural choice prioritizing performance over human readability.

graph LR
    subgraph "Text-Based Approach (JSON/XML)"
        TextData["Human-readable strings"]
TextParsing["Complex parsing\nTokenization\nString allocation"]
TextSize["Larger payload size\nQuotes, brackets, keys"]
TextCPU["High CPU cost\nUTF-8 validation\nEscape sequences"]
end
    
    subgraph "Binary Approach (Muxio)"
        BinaryData["Raw byte arrays"]
BinaryParsing["Simple framing\nFixed header offsets\nZero-copy reads"]
BinarySize["Minimal payload\nCompact encoding\nNo metadata"]
BinaryCPU["Low CPU cost\nDirect memory access\nNo parsing"]
end
    
 
   TextData -->
 TextParsing -->
 TextSize --> TextCPU
 
   BinaryData -->
 BinaryParsing -->
 BinarySize --> BinaryCPU

Why Binary Over Text

Performance Impact:

MetricText-BasedBinary (Muxio)Improvement
Serialization overheadHigh (string formatting)Minimal (bitcode)~10-100x faster
Payload sizeVerboseCompact~2-5x smaller
Parse complexityO(n) with allocationsO(1) header readsConstant time
CPU cache efficiencyPoor (scattered strings)Good (contiguous bytes)Better locality

Sources: README.md32 README.md45 DRAFT.md11

Binary Protocol Stack

The following diagram shows how binary data flows through the protocol layers:

Sources: README.md:32-33 muxio/src/rpc_request_response.rs Cargo.toml (bitcode dependency)


Transport and Runtime Agnosticism

A core principle is that the muxio core makes zero assumptions about the transport or runtime. This is enforced through careful API design.

graph TB
    subgraph "Muxio Core (Transport-Agnostic)"
        CoreDispatcher["RpcDispatcher\nGeneric over callback\nNo transport dependencies"]
CoreTypes["RpcRequest\nRpcResponse\nRpcHeader"]
end
    
    subgraph "Transport Abstraction Layer"
        CallerInterface["RpcServiceCallerInterface\n(muxio-rpc-service-caller)\nAbstract trait"]
EndpointInterface["RpcServiceEndpointInterface\n(muxio-rpc-service-endpoint)\nAbstract trait"]
end
    
    subgraph "Concrete Implementations"
        TokioImpl["MuxioRpcServer\nmuxio-tokio-rpc-server\nUses: tokio-tungstenite"]
TokioClientImpl["RpcClient\nmuxio-tokio-rpc-client\nUses: tokio-tungstenite"]
WasmImpl["RpcWasmClient\nmuxio-wasm-rpc-client\nUses: wasm-bindgen"]
CustomImpl["Custom implementations\n(IPC, gRPC, etc.)"]
end
    
 
   CoreDispatcher --> CoreTypes
 
   CoreTypes --> CallerInterface
 
   CoreTypes --> EndpointInterface
    
 
   CallerInterface --> TokioClientImpl
 
   CallerInterface --> WasmImpl
 
   CallerInterface --> CustomImpl
    
 
   EndpointInterface --> TokioImpl

Transport Abstraction Strategy

Key Abstraction Points:

The RpcServiceCallerInterface trait provides transport abstraction for clients:

  • Method: call_prebuffered() - Send request, receive response
  • Implementation: Each transport provides its own RpcServiceCallerInterface impl
  • Portability: Application code depends only on the trait, not concrete implementations

Sources: README.md47 extensions/muxio-rpc-service-caller/src/caller_interface.rs README.md34

Runtime Environment Support Matrix

Runtime EnvironmentServer SupportClient SupportImplementation Crate
Tokio (async)muxio-tokio-rpc-server, muxio-tokio-rpc-client
async-std✗ (possible)✗ (possible)Not implemented
Standard Library (sync)✗ (possible)✗ (possible)Not implemented
WASM/BrowserN/Amuxio-wasm-rpc-client
Node.js/Deno✗ (possible)✗ (possible)Not implemented

The core's agnosticism means new runtime support requires only implementing the appropriate wrapper crates, not modifying core logic.

Sources: README.md:34-40 extensions/README.md


graph TB
    subgraph "Layer 4: Application"
        AppCode["Application Logic\nBusiness rules"]
end
    
    subgraph "Layer 3: RPC Abstraction"
        ServiceDef["RpcMethodPrebuffered\n(muxio-rpc-service)\nDefines API contract"]
Caller["RpcServiceCallerInterface\n(muxio-rpc-service-caller)\nClient-side calls"]
Endpoint["RpcServiceEndpointInterface\n(muxio-rpc-service-endpoint)\nServer-side dispatch"]
end
    
    subgraph "Layer 2: Multiplexing"
        Dispatcher["RpcDispatcher\n(muxio/rpc_dispatcher.rs)\nRequest correlation\nFrame multiplexing"]
end
    
    subgraph "Layer 1: Binary Framing"
        Framing["Binary Protocol\n(muxio/framing.rs)\nChunk/reassemble frames"]
end
    
    subgraph "Layer 0: Transport"
        Transport["WebSocket/TCP/IPC\nExternal implementations"]
end
    
 
   AppCode --> ServiceDef
 
   ServiceDef --> Caller
 
   ServiceDef --> Endpoint
 
   Caller --> Dispatcher
 
   Endpoint --> Dispatcher
 
   Dispatcher --> Framing
 
   Framing --> Transport
    
    Transport -.bytes up.-> Framing
    Framing -.frames up.-> Dispatcher
    Dispatcher -.responses up.-> Caller
    Dispatcher -.requests up.-> Endpoint

Layered Separation of Concerns

Muxio enforces strict separation between system layers, with each layer unaware of layers above it:

Layer Independence Guarantees:

LayerKnowledgeIgnorance
Binary FramingBytes, frame headersNo knowledge of RPC, methods, or requests
MultiplexingRequest IDs, correlationNo knowledge of method semantics or serialization
RPC AbstractionMethod IDs, request/response patternNo knowledge of specific transports
ApplicationBusiness logicNo knowledge of framing or multiplexing

This enables:

  • Testing: Each layer can be unit tested independently
  • Extensibility: New transports don't affect RPC logic
  • Reusability: Same multiplexing layer works for non-RPC protocols
  • Maintainability: Changes isolated to single layers

Sources: README.md:16-17 README.md22 DRAFT.md:9-26


graph TB
    subgraph "Shared Service Definition Crate"
        ServiceTrait["RpcMethodPrebuffered\n(muxio-rpc-service)"]
AddMethod["impl RpcMethodPrebuffered for Add\nMETHOD_ID = xxhash('Add')\nRequest = Vec<f64>\nResponse = f64"]
MultMethod["impl RpcMethodPrebuffered for Mult\nMETHOD_ID = xxhash('Mult')\nRequest = Vec<f64>\nResponse = f64"]
end
    
    subgraph "Server Code"
        ServerHandler["endpoint.register_prebuffered(\n Add::METHOD_ID,\n /bytes, ctx/ async move {\n let params = Add::decode_request(&bytes)?;\n let sum = params.iter().sum();\n Add::encode_response(sum)\n }\n)"]
end
    
    subgraph "Client Code"
        ClientCall["Add::call(\n &rpc_client,\n vec![1.0, 2.0, 3.0]\n).await"]
end
    
    subgraph "Compile-Time Guarantees"
        TypeCheck["Type mismatch → Compiler error"]
MethodIDCheck["Duplicate METHOD_ID → Compiler error"]
SerdeCheck["Serde incompatibility → Compiler error"]
end
    
 
   ServiceTrait --> AddMethod
 
   ServiceTrait --> MultMethod
    
    AddMethod -.defines.-> ServerHandler
    AddMethod -.defines.-> ClientCall
    
 
   AddMethod --> TypeCheck
 
   AddMethod --> MethodIDCheck
 
   AddMethod --> SerdeCheck

Type Safety Through Shared Definitions

The system enforces compile-time API contracts between client and server via shared service definitions.

Compile-Time Contract Enforcement

What Gets Checked at Compile Time:

  1. Type Consistency: Client and server must agree on Request and Response types
  2. Method ID Uniqueness: Hash collisions in method names are detected
  3. Serialization Compatibility: Both sides use identical encode/decode implementations
  4. API Changes: Modifying service definition breaks both client and server simultaneously

Example Service Definition Structure:

The trait RpcMethodPrebuffered requires:

  • METHOD_ID: u64 - Compile-time constant generated from method name hash
  • encode_request() / decode_request() - Serialization for parameters
  • encode_response() / decode_response() - Serialization for results

Sources: README.md49 extensions/muxio-rpc-service/src/prebuffered.rs README.md:69-117 (example code)


Core Design Goals

The following table summarizes the fundamental design goals that drive all architectural decisions:

GoalRationaleImplementation Approach
Binary ProtocolMinimize overhead, maximize performanceRaw byte arrays, bitcode serialization
Framed TransportDiscrete, ordered chunks enable multiplexingFixed-size frame headers, variable payloads
BidirectionalClient/server symmetrySame RpcDispatcher logic for both directions
WASM-CompatibleDeploy to browsersNon-async core, callback-driven model
StreamableSupport large payloadsChunked transmission via framing protocol
CancelableAbort in-flight requestsRequest ID tracking in RpcDispatcher
Metrics-CapableObservability for productionHooks for latency, throughput measurement
Transport-AgnosticFlexible deploymentCallback-based abstraction, no hard transport deps
Runtime-AgnosticWork with any async runtimeNon-async core, async wrappers
Type-SafeEliminate runtime API mismatchesShared service definitions, compile-time checks

Sources: DRAFT.md:9-26 README.md:41-52

sequenceDiagram
    participant App as "Application\n(Type-safe)"
    participant Service as "Add::call()\n(Shared definition)"
    participant Client as "RpcClient\n(Transport wrapper)"
    participant Dispatcher as "RpcDispatcher\n(Core, non-async)"
    participant Callback as "on_message_bytes\n(Callback)"
    participant Transport as "WebSocket\n(Async transport)"
    
    App->>Service: Add::call(&client, vec![1.0, 2.0])
    Note over Service: Compile-time type check
    Service->>Service: encode_request(vec![1.0, 2.0])\n→ bytes
    Service->>Client: call_prebuffered(METHOD_ID, bytes)
    Client->>Dispatcher: send_request(METHOD_ID, bytes)
    Note over Dispatcher: Assign request ID\nStore pending request
    Dispatcher->>Dispatcher: Serialize to binary frames
    Dispatcher->>Callback: callback(frame_bytes)
    Note over Callback: Synchronous invocation
    Callback->>Transport: send_websocket_frame(frame_bytes)
    Note over Transport: Async I/O (external)

Design Philosophy in Practice

The following sequence shows how these principles work together in a single RPC call:

This demonstrates:

  • Type safety: Add::call() enforces parameter types
  • Shared definitions: Same encode_request() on both sides
  • Transport agnostic: RpcDispatcher knows nothing about WebSocket
  • Non-async core: RpcDispatcher is synchronous, invokes callback
  • Binary protocol: Everything becomes bytes before transmission

Sources: README.md:69-161 muxio/src/rpc_dispatcher.rs extensions/muxio-rpc-service-caller/src/caller_interface.rs

Dismiss

Refresh this wiki

Enter email to refresh