This document describes the WebSocket communication system used in the client.
The client uses WebSocket connections for real-time communication with the server, particularly for:
- Binary position updates for graph nodes
- Graph data synchronization
- Event notifications
- Connection status management
graph TB
subgraph ClientSide ["Client"]
App["Application UI/Logic"] --> GDM["GraphDataManager"]
App --> WSS["WebSocketService"]
WSS --> GDM
GDM --> RenderingEngine["Rendering Engine"]
end
subgraph ServerSide ["Server"]
ServerWSS["WebSocket Server Handler"]
ClientMgr["ClientManager"]
GraphSvc["GraphService"]
ServerWSS <--> ClientMgr
GraphSvc --> ClientMgr
end
WSS <--> ServerWSS
The WebSocket service (client/src/services/WebSocketService.ts) is implemented as a singleton that manages:
- WebSocket connection establishment and maintenance (including reconnection logic).
- Sending and receiving JSON and binary messages.
- Handling binary protocol specifics (like potential decompression if not handled by
binaryUtils.tsdirectly upon receipt). - Exposing connection status and readiness (see
websocket-readiness.md). - Error handling for the connection itself.
- Automatic reconnection with exponential backoff
- Binary message support
- Connection status monitoring
- Event-based message handling
The binary protocol is used for efficient transmission of node position updates.
The primary binary message format is for node position and velocity updates.
- Format per node:
- Node ID:
uint32(4 bytes) - Position (X, Y, Z): 3 x
float32(12 bytes) - Velocity (VX, VY, VZ): 3 x
float32(12 bytes)
- Node ID:
- Total per node: 28 bytes.
- A single binary WebSocket message can contain data for multiple nodes, packed consecutively.
- Important Clarification: The server-side
BinaryNodeDatastruct (insrc/utils/socket_flow_messages.rs) includes additional fields likemass,flags, andpadding. These are used for the server's internal physics simulation but are not part of the 28-byte wire format sent to the client. The client-side binary protocol handling correctly reflects the 28-byte structure (nodeId as uint32, position, velocity). - Server-side compression (zlib) is applied if the total binary message size exceeds
system.websocket.compressionThreshold(default 512 bytes). Client-side decompression is handled byclient/src/utils/binaryUtils.ts.
sequenceDiagram
participant Server
participant WebSocket
participant BinaryHandler
participant GraphManager
participant Visualisation
Server->>WebSocket: Binary Message
WebSocket->>BinaryHandler: Process ArrayBuffer
BinaryHandler->>GraphManager: Update Node Positions
GraphManager->>Visualisation: Trigger Update
The WebSocket service handles several types of messages:
-
Binary Position Updates (Server -> Client)
- Format:
ArrayBuffer(potentially zlib compressed). - Handler:
onBinaryMessagecallback provided toWebSocketService. - Content: Packed
BinaryNodeData(nodeId, position, velocity) for multiple nodes. - Usage: Real-time node position and velocity updates from the server's physics simulation.
- Format:
-
JSON Control Messages (Bidirectional)
- Format: JSON objects.
- Handler:
onMessagecallback provided toWebSocketService. - Examples:
- Server -> Client:
{"type": "connection_established"},{"type": "updatesStarted"},{"type": "loading"}. - Client -> Server:
{"type": "requestInitialData"},{"type": "ping"},{"type": "subscribe_position_updates", "binary": true, "interval": ...}. (Thesubscribe_position_updatesis effectively handled byrequestInitialDatalogic on the server).
- Server -> Client:
-
Connection Status Changes
- Not a message type per se, but an event emitted by
WebSocketService. - Handler:
onConnectionStatusChangecallback. - Provides status like
{ connected: boolean; error?: any }.
- Not a message type per se, but an event emitted by
The WebSocket service implements robust error handling, primarily by logging errors and attempting reconnection.
stateDiagram-v2
[*] --> Connected
Connected --> Disconnected: Connection Lost
Disconnected --> Retrying: Auto Reconnect
Retrying --> Connected: Success
Retrying --> Failed: Max Retries
Failed --> [*]: Fatal Error
Retrying --> Disconnected: Retry Failed
WebSocket behavior can be configured through settings:
// Relevant settings are found under 'system.websocket' in
// client/src/features/settings/config/settings.ts (ClientWebSocketSettings)
// and correspond to server-side settings in src/config/mod.rs (ServerFullWebSocketSettings).
interface ClientWebSocketSettings { // From settings.ts
updateRate: number; // Target FPS for client-side rendering of updates
reconnectAttempts: number;
reconnectDelay: number; // ms
compressionEnabled: boolean; // Client expects server to compress if true
compressionThreshold: number; // Matches server setting
// binaryChunkSize is not a direct client setting, more server-side or implicit.
// minUpdateRate, maxUpdateRate, motionThreshold, motionDamping are server-side physics/update rate controls.
}The client's WebSocketService uses reconnectAttempts and reconnectDelay. The compressionEnabled and compressionThreshold inform the client whether to expect compressed messages and how to handle them (though decompression logic is in binaryUtils.ts). The updateRate in client settings typically influences how often the client requests or processes updates, distinct from the server's actual send rate.
-
Binary Protocol
- Reduces message size by ~60% compared to JSON
- Minimizes parsing overhead
- Enables efficient batch updates
-
Message Batching
- Position updates are batched for efficiency
- Configurable batch size and update rate
- Automatic throttling under high load
-
Connection Management
- Heartbeat mechanism for connection health
- Automatic reconnection with backoff
- Connection status monitoring
// Initialization and usage typically happens within AppInitializer.tsx or similar.
// graphDataManager is an instance of GraphDataManager.
const wsService = WebSocketService.getInstance();
// Setup handlers
wsService.onConnectionStatusChange((status) => {
logger.info(`WebSocket connection status: ${status.connected ? 'Connected' : 'Disconnected'}`);
if (status.connected && wsService.isReady()) { // Check full readiness
// This might trigger graphDataManager to request initial data or enable binary updates
// if it hasn't already due to the adapter pattern.
graphDataManager.setBinaryUpdatesEnabled(true); // Or similar logic
} else if (!status.connected) {
graphDataManager.setBinaryUpdatesEnabled(false);
}
});
wsService.onMessage((jsonData) => {
logger.debug('WebSocket JSON message received:', jsonData);
// Handle JSON messages (e.g., connection_established, loading)
// This might also be handled by graphDataManager via the adapter.
});
wsService.onBinaryMessage((arrayBuffer) => {
// Pass the raw ArrayBuffer to graphDataManager, which will handle decompression
// (if needed, via binaryUtils) and parsing.
graphDataManager.updateNodePositions(arrayBuffer);
});
// Attempt to connect
wsService.connect().catch(error => {
logger.error('Failed to connect WebSocket initial attempt:', error);
});
// Later, graphDataManager, through its adapter, will use wsService.isReady()
// and wsService.sendRawBinaryData() or wsService.sendMessage().
// For example, when graphDataManager decides to enable binary updates:
// if (wsService.isReady()) {
// wsService.sendMessage({ type: 'subscribe_position_updates', binary: true, interval: 33 });
// }The graphDataManager.updateNodePositions method expects an ArrayBuffer which it will then process (potentially decompressing and then parsing into individual node updates). The call graphDataManager.setBinaryUpdatesEnabled(true) is a conceptual representation; the actual mechanism might involve graphDataManager sending a specific message via the WebSocket adapter to the server to start the flow of binary updates, or simply setting an internal flag to start processing them if they arrive.
- State Management - State management integration
- Graph Data - Graph data structure and updates
- Performance - Performance optimization details