Skip to content

Collaborative Editing

github-actions[bot] edited this page Aug 29, 2026 · 3 revisions

Collaborative Editing

SparkEngine's collaborative editing system enables multiple editor instances to work on the same scene simultaneously, inspired by HeroEngine's live collaborative editing. The system provides real-time peer presence awareness, node-level locking, edit broadcasting, and optional live push to running game servers.

Release boundary: Collaboration and its broker/client/service paths are experimental development implementations outside the blocked and uncertified stable-v1 service-free profile. This page is implementation guidance, not a production or deployment-support claim.

Architecture

[Editor A] ── local IPC ──► [SparkCollabServer] ◄── local IPC ── [Editor B]
       │                     presence / locks / edits                  │
       └──────── CollaborativeEditSession editor API ─────────────────┘
                                  │
                         LiveEditBridge (optional)
                                  │
                            [AreaServer]

SparkCollabServer is the development reference authority. The editor's existing CollaborativeEditSession API translates selection, camera presence, node locks, and serialized EditMessage objects through StandaloneCollaborationClient, so hierarchy and viewport callers do not need a second collaboration API. The older direct TCP peer-hosted mode remains available as an explicit fallback for ad-hoc sessions.

Three Layers

Layer System Purpose
Editor Collaboration CollaborativeEditSession + StandaloneCollaborationClient Editor API backed by the standalone broker (or explicit peer fallback)
Collaboration Authority SparkCollabServer Capability-authenticated presence, locks, ordered edit history, and snapshots
Editor ↔ Engine IPC EngineInterface Named pipe communication with local engine process
Live Push LiveEditBridge Forward edits to a running AreaServer for live game updates

These are intentionally separate systems. Editor collaboration uses TCP for reliable ordered delivery of edits. Game networking uses UDP for low-latency gameplay. The LiveEditBridge connects the two when live editing of a running game world is desired.

No Game Module Code Required

The current development path keeps collaborative editing logic in the editor-side integration, so the examples do not require game-module code. Coverage and release integration remain unverified:

  • Hosting/joining: Use the Collaboration panel (View > Collaboration) to host or join sessions
  • Locking: The editor automatically acquires/releases locks when you select and edit nodes
  • Edit broadcasting: Property changes in the Inspector and object operations in the Hierarchy are automatically broadcast to all connected editors
  • Presence: Other editors' selections and viewport positions are shown automatically in the Scene View

The C++ code examples below are internal API reference showing how the editor's subsystems work under the hood. They are provided for engine developers extending the collaborative editing system, not for game developers using it.

How to Use (No Code Needed)

  1. Start SparkCollabServer --socket spark-collab-project-a (the Service Topology panel can start it).
  2. Open the Collaboration panel from View > Collaboration.
  3. Leave Use standalone SparkCollabServer enabled and enter the matching endpoint and a project session ID.
  4. One editor clicks Create Broker Session; other editors click Join Broker Session.
  5. Edit the scene normally — locking, broadcasting, and presence are automatic.
  6. Disconnecting an editor releases its capabilities and locks without stopping the broker.

Internal API Reference

The following C++ examples show the editor's internal implementation. You do not need to write this code — it runs automatically when you use the Collaboration panel.

Hosting a Session

SparkEditor::CollaborativeEditSession session;
session.Host(27030, "Alice");  // Opens TCP listener on port 27030

Connecting to a Session

session.Connect("192.168.1.100", 27030, "Bob");  // TCP connect with 5s timeout

Node Locking

The editor automatically locks nodes when you select them for editing:

if (session.RequestLock("Entity_42"))
{
    // Lock acquired — safe to edit
    SparkEditor::EditMessage msg;
    msg.type = SparkEditor::EditMessageType::NodeModified;
    msg.sourceEditor = session.GetLocalPeerID();
    msg.nodeId = "Entity_42";
    msg.propertyName = "position";
    msg.newValue = "10.0, 5.0, 3.0";
    session.BroadcastEdit(msg);

    session.ReleaseLock("Entity_42");
}
else
{
    auto owner = session.GetLockOwner("Entity_42");
    auto* peer = session.GetPeer(owner);
    // Display: "Locked by Bob"
}

Presence Awareness

The editor broadcasts selection and camera state automatically:

// These are called internally by HierarchyPanel and SceneViewPanel
session.SetLocalSelection("Entity_42");
session.SetLocalViewportCamera(cameraPos, cameraDir);

// Called each frame by EditorUI::Update()
session.Update(deltaTime);

// SceneViewPanel renders peer overlays automatically
auto peers = session.GetConnectedPeers();
for (const auto& peer : peers)
{
    // Draw peer's name tag with color and selection info
}

Callbacks

These are wired automatically by EditorUI::WireCallbacks():

session.SetPeerConnectedCallback([](const SparkEditor::EditorPeer& peer) {
    LOG("Editor '%s' joined", peer.userName.c_str());
});

session.SetEditReceivedCallback([](const SparkEditor::EditMessage& msg) {
    // Apply the edit locally
});

session.SetLockChangedCallback([](const std::string& nodeId, SparkEditor::PeerID owner) {
    // Update lock indicators in the UI
});

Standalone Collaboration Broker

Run the isolated development broker without a GUI:

SparkCollabServer --socket spark-collab-project-a

This mode:

  • Uses an owner-local Windows named pipe or POSIX Unix-domain socket
  • Authenticates each peer with an expiring capability token
  • Arbitrates node locks and retains bounded, ordered edit history
  • Serves canonical peer/lock/edit snapshots to every connected editor
  • Supports graceful shutdown through the shared control protocol

The Collaboration panel defaults to this path. Uncheck Use standalone SparkCollabServer only when you intentionally want the legacy peer-hosted TCP mode.

Live Push to Running Games

The LiveEditBridge enables HeroEngine-style live editing where changes made by editors appear in the running game world in real-time. This is also handled within the editor — no game module code is needed. The editor's Collaboration panel will include a "Connect to AreaServer" option when an AreaServer is running.

Architecture

Editor → CollaborativeEditSession → LiveEditBridge → AreaServer → Game Clients

Internal API (called automatically by EditorUI)

// Created and managed by EditorUI — not user code
SparkEditor::LiveEditBridge bridge;
bridge.Connect("192.168.1.200", 27031, "EditorAlice");  // AreaServer inter-server port

// Edits are forwarded automatically when HierarchyPanel operations occur
bridge.PushEdit(editMessage);

// Called each frame by EditorUI::Update()
bridge.Update();

Custom Message Types

The bridge uses UserDefined message types (starting at 1000) to avoid collision with game messages:

Type Value Description
SceneEdit 1000 A scene edit (node/component change)
LockNotify 1001 Lock state change notification
EditorJoin 1002 Editor connecting as privileged client
EditorLeave 1003 Editor disconnecting

Editor UI — Collaboration Panel

The Collaboration panel (View → Collaboration) provides:

  • Connection controls: Host or join a session with username and port
  • Peer list: Shows all connected editors with their colors and current selections
  • Lock list: Active locks with owner names, durations, and release buttons
  • Edit log: Recent edit activity across all peers
  • Session stats: Peer count, lock count, edit counts, session duration

Viewport Peer Visualization

When a collaborative session is active, the Scene View panel shows:

  • Name tags: Each remote peer's username displayed in their assigned color
  • Selection info: What node each peer is currently editing
  • Tags appear in the top-right corner of the viewport

Edit Message Types

Type Description
NodeAdded A new node was added to the scene
NodeRemoved A node was removed from the scene
NodeModified A node's properties were changed
NodeMoved A node's transform was changed
NodeRenamed A node was renamed
ComponentAdded A component was added to an entity
ComponentRemoved A component was removed from an entity
ComponentModified A component's properties were changed

Lock Behavior

  • Locks are pessimistic — only one editor can lock a node at a time
  • Locks auto-expire after 5 minutes (configurable via NodeLock::maxDurationSeconds)
  • When an editor disconnects, all their locks are released automatically
  • Lock ownership is tracked with editor name for UI display
  • Double-locking the same node by the same peer is idempotent (succeeds)

Wire Protocol

Messages are sent as length-prefixed TCP frames:

[4 bytes: message length N] [N bytes: serialized InternalMessage]

The serialization uses big-endian integers and length-prefixed strings. Maximum message size is 16 MB.

Thread Safety

  • CollaborativeEditSession uses one network thread for socket I/O plus per-client handler threads on the host
  • Message queues (m_incomingMessages, m_outgoingMessages) are mutex-protected
  • m_connected and m_shuttingDown use std::atomic<bool> with appropriate memory ordering
  • Peer map and lock map have separate mutexes (m_peerMutex, m_lockMutex)
  • The main thread drains queues and fires callbacks — callbacks always run on the main thread

Session Statistics

auto stats = session.GetStats();
// stats.peerCount, stats.activeLocks, stats.editsBroadcast, stats.editsReceived, stats.sessionDuration

Design Decisions

  • Pessimistic locking over CRDTs/OT for simplicity and predictability in small teams (2-10 editors)
  • Node-level granularity (not property-level) reduces lock contention while keeping the protocol simple
  • Auto-expiry prevents forgotten locks from blocking other editors
  • TCP for editor collaboration (reliable, ordered) vs UDP for game networking (low-latency)
  • Separate networking stacks — editor collab and game networking serve fundamentally different needs
  • Peer-hosted by default — no infrastructure required; dedicated server mode available for larger teams

Source Files

File Description
SparkEditor/Source/Communication/CollaborativeEditSession.h Editor-facing session API and broker/peer mode ownership
SparkEditor/Source/Communication/CollaborativeEditSession.cpp Editor translation, snapshots, callbacks, and legacy TCP fallback
SparkEditor/Source/Communication/StandaloneCollaborationClient.* Typed development client for the broker protocol
SparkDaemon/src/CollaborationProtocol.h Bounded versioned collaboration wire DTOs and codecs
SparkDaemon/src/CollaborationService.* Authoritative presence, lock, and edit-history service
SparkDaemon/src/CollabServerMain.cpp Standalone process entry point
SparkEditor/Source/Communication/LiveEditBridge.h Live push bridge to AreaServer
SparkEditor/Source/Communication/LiveEditBridge.cpp Bridge implementation
SparkEditor/Source/Panels/CollaborationPanel.h Collaboration UI panel
SparkEditor/Source/Panels/CollaborationPanel.cpp Panel implementation
SparkEditor/Source/main.cpp --collab-server CLI mode
Tests/TestCollaborativeEditing.cpp Unit tests
SparkDaemon/tests/CollabProcessSmoke.cpp Black-box process/client startup, operation, and shutdown smoke test

Related Pages

SparkEngine Wiki

Website Entry Points

Getting Started

Engine Subsystems

Gameplay & Tools

Platform Support

Graphics

Advanced

Development & Process

Research & Analysis

Engineering Notes & Audits

Specifications

Reference

Clone this wiki locally