Assert, Patch, and Retract
ConvergeDB has three write operations. All three are fire-and-forget: the server does not send a per-operation acknowledgement. Writes are buffered on the server and flushed atomically at the end of each coalescing window.
ASSERT
Section titled “ASSERT”“This source declares the full current state of entity E.”
await players.AssertAsync(new Player{ EntityId = EntityId.FromGuid(playerId), Id = 42, Health = 100, PosX = 1.5f, PosY = -2.3f, Name = "Alice",});What happens on the server
Section titled “What happens on the server”- The source’s bit is set in the entity’s source bitset.
- The submitted field data is compared byte-for-byte against the stored state.
- If the data differs, the entity’s version is incremented and subscribers are notified.
- If the data is identical, no version bump occurs and no notification is sent. This is deduplication and it is essential for making re-assertion safe and cheap.
Creation
Section titled “Creation”If the entity does not exist, ASSERT creates it. There is no separate “create” operation. The first ASSERT for a given entity ID creates the entity, sets the source bit, assigns version 1, and sends a Created notification to subscribers.
Idempotency
Section titled “Idempotency”Asserting the same data for the same entity is always safe. Identical re-assertions produce no version bump and no subscriber notification. This makes it possible to periodically re-assert an entity’s state (for example, on a timer) without generating spurious updates.
“This source updates only specific fields of entity E.”
await recipes.PatchAsync(new RecipePatch{ EntityId = EntityId.FromGuid(recipeId), CraftTimeMs = 5000,});PATCH sends only the fields that are set on the patch object. Fields left as null are not included in the wire payload and are untouched on the server.
How it works
Section titled “How it works”- The server reads the entity’s current field data.
- For each field in the PATCH presence mask, the server overwrites the stored value with the new value.
- The merged result is compared byte-for-byte against the previous state.
- If any field changed, the version is incremented and subscribers are notified with a
ChangedFieldsbitmask indicating which fields differ. - The source’s bit is set in the entity’s source bitset (PATCH implies assertion).
The presence mask
Section titled “The presence mask”The PATCH operation uses a u64 presence mask where bit N is set if field N is included in the patch. The source generator automatically builds this mask based on which properties are set on the {Name}Patch type. You do not need to construct it manually.
PATCH and field ownership
Section titled “PATCH and field ownership”PATCH is the primary tool for multi-service field ownership. Each service PATCHes only the fields it owns, while the server maintains the complete merged state.
PATCH on non-existent entities
Section titled “PATCH on non-existent entities”If the target entity does not exist, PATCH creates it. The fields included in the PATCH are set to the provided values; all other fields get zero defaults.
RETRACT
Section titled “RETRACT”“This source withdraws its assertion of entity E.”
await players.RetractAsync(entityId);What happens on the server
Section titled “What happens on the server”- The source’s bit is cleared from the entity’s source bitset.
- If any other source bits remain set, the entity stays alive. No version bump. No subscriber notification. The entity’s field data is unchanged.
- If all source bits are now cleared, the entity is tombstoned: its version is incremented and subscribers receive a
Deletednotification.
Source-scoped semantics
Section titled “Source-scoped semantics”Retraction is always scoped to the calling source. Retracting from source A has no effect on source B’s assertion. This enables clean entity handoff between sources: source B asserts the entity, then source A retracts, and the entity remains alive throughout.
Tombstone lifecycle
Section titled “Tombstone lifecycle”Tombstoned entities remain visible to queries (returning QueryStatus.Tombstone) and in subscriber notifications for a configurable retention period (default: 5 minutes). After the retention period, the entity is removed during compaction.
Subscribers who were offline longer than the tombstone retention period must re-bootstrap to discover missed deletions.
Operation ordering within a coalescing window
Section titled “Operation ordering within a coalescing window”Multiple operations on the same entity within a single coalescing window are merged into a net result:
| Sequence | Net result |
|---|---|
| ASSERT then ASSERT | Second ASSERT wins (last-write-wins). |
| ASSERT then RETRACT | Net retraction. Source bit cleared. |
| RETRACT then ASSERT | Entity stays alive with new data. Source bit set. |
| ASSERT then PATCH | PATCH fields are applied on top of the ASSERT. |
| PATCH then PATCH | Second PATCH overwrites fields from the first for overlapping fields. |
Only the net result of all operations within the window is flushed to the WAL and published to subscribers.
When does the version bump?
Section titled “When does the version bump?”A version bump (and corresponding subscriber notification) occurs only when:
- The entity’s field data changes (byte-level comparison after applying the operation).
- The entity transitions from alive to tombstoned, or from tombstoned to alive.
Source-set-only changes do not bump the version. For example, a second source asserting an entity with identical data sets that source’s bit but does not increment the version and does not notify subscribers.