Shared (World-State) Views
One View instance, refreshed and diffed once per tick, fanned out to every subscriber.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ต Core ยท Category: Subscriptions
๐ฏ What it solves
World state โ NPCs, terrain objects, the player roster โ is the same for every client watching it. Computing that delta independently per subscriber wastes CPU proportional to subscriber count for data that doesn't vary by who's asking. Shared Views let many clients subscribe to the exact same query result: the engine refreshes and diffs it once per tick no matter how many clients are subscribed.
โ๏ธ How it works (in brief)
PublishView(name, view) registers a single ViewBase instance as the published target. During the Output
phase, the engine refreshes that one View, computes its Added/Removed/Modified delta once, and serializes the
resulting TickDeltaMessage once for steady-state clients โ the same byte buffer is then memcpy'd into every
subscriber's send buffer. Subscriber count only affects the fan-out step, not the delta computation or
serialization cost. The View instance must be dedicated to subscriptions โ it cannot also be wired as a
system's query input (see Published/System-Input View Separation).
๐ป Usage
[Component("Game.Npc", 1)]
public struct Npc
{
[Index] public int Health;
}
[Archetype]
public class NpcArch : Archetype<NpcArch>
{
public static readonly Comp<Npc> Data = Register<Npc>();
}
using var tx = dbe.CreateQuickTransaction();
// Dedicated View instance for subscriptions โ do not reuse a system's input View here.
var worldNpcsView = tx.Query<NpcArch>()
.WhereField<Npc>(n => n.Health > 0)
.ToView();
var worldNpcs = runtime.PublishView("world_npcs", worldNpcsView, SubscriptionPriority.Normal);
// Subscribe a client to it; the runtime diffs against their previous subscription set.
runtime.SetSubscriptions(clientContext, worldNpcs);
| Option | Default | Effect |
|---|---|---|
priority |
SubscriptionPriority.Normal |
Critical always pushed; Normal throttled at overload Level 1+; Low throttled aggressively, paused at Level 4 |
โ ๏ธ Guarantees & limits
- One delta, one serialization, N memcpys โ cost scales with View size and change volume, not with subscriber count, for the steady-state (no pending events, no sync) case.
- Must be a dedicated View โ using the same instance as a system's query input throws
InvalidOperationException; the View's change-tracking ring buffer has a single consumer. - New subscribers receive the View's current entity set via incremental sync (batched across ticks for large Views), not a free pass to the live delta stream โ see Incremental Sync in the design doc.
- All subscribers to a shared View see identical data; there is no per-client filtering within a shared View (use a per-client View factory for that).
๐งช Tests
- SubscriptionStressTests โ
MultiClient_50Clients_SharedView_AllReceiveDeltas: one shared View fanned out to 50 concurrent subscribers - ViewDeltaTests โ
DeltaBuilder_SharedView_ProducesCorrectDelta/DeltaBuilder_SecondCall_OnlyNewEntities: delta correctness across ticks for a sharedPublishedView
๐ Related
- Sibling: Published/System-Input View Separation
- Parent feature: Published Views