watershed Collaborative data structures for Gleam

← watershed · Data structures / Counters

Counters

Numbers that many hands move at once.

Counters are the gentlest introduction to convergence, because addition does not care about order. Send each change as a signed delta instead of a new total, and simultaneous edits simply add up; there is nothing to overwrite.

They climb in guarantee: a server-sequenced scalar, then a grow-only lattice that stays correct even when a delta is delivered twice, then a signed counter built from two of those lattices so it can move in both directions offline.

Skip past the interactive demo

Watch counters converge

The structures from this family that fit the shared gauge rig run below on watershed’s Gleam kernels. Structures with dedicated interactions link to their own demos. Pending edits print in magenta; once the sequencer stamps an SN, every replica lands the same ink state. The picker switches among the loaded views, which share one ordered stream.

Nudge a value, race a concurrent write, or stretch the link latency to watch each merge rule hold.

  1. 1 · local edit prints in magenta
  2. 2 · the sequencer stamps it with an SN
  3. 3 · every client lands the same ink state
Data structure shown in all clients

Merge rule: increments commute. Race two increments and both apply. The counter converges on the sum; nothing is overwritten.

Client A

0 pending
Shared map replica on Client A
mill-race 24
kettle-run 61
low-ford 42
sandbags-placed 120
earthwork-balance · yd³ +44
fill Σ
74
cut Σ
30
inspection-count 18
A Σ
0
B Σ
0
C Σ
0
Claims replica on Client A
north-levee
spillway-gate
pump-house Survey
OR-map stockpile ledger replica on Client A
spoil-north 18
borrow-pit-7 -6
wash-fill 12
OR-set field marker roster replica on Client A
north-stake marked
sluice-tag marked
borrow-flag clear
G-set permanent benchmark registry replica on Client A
BM-17 recorded
BM-22 unrecorded
BM-31 unrecorded
2P-set retired marker ledger replica on Client A
stake-3 active
gate-pin unplaced
silt-flag retired
RegisterCollection replica on Client A
register atomic LWW revise
north-bench Survey Survey
gate-setpoint
pump-mode
OrderedCollection replica on Client A
sheet state op
queue grade-stakes, pump-check
held jobs none
PactMap replica on Client A
pact accepted pending op
datum-grid Survey datum
gate-policy
inspection-window
TaskManager replica on Client A
task assigned waiters op
sluice-inspection empty
pump-watch empty
crest-walk empty

Client B

0 pending
Shared map replica on Client B
mill-race 24
kettle-run 61
low-ford 42
sandbags-placed 120
earthwork-balance · yd³ +44
fill Σ
74
cut Σ
30
inspection-count 18
A Σ
0
B Σ
0
C Σ
0
Claims replica on Client B
north-levee
spillway-gate
pump-house Survey
OR-map stockpile ledger replica on Client B
spoil-north 18
borrow-pit-7 -6
wash-fill 12
OR-set field marker roster replica on Client B
north-stake marked
sluice-tag marked
borrow-flag clear
G-set permanent benchmark registry replica on Client B
BM-17 recorded
BM-22 unrecorded
BM-31 unrecorded
2P-set retired marker ledger replica on Client B
stake-3 active
gate-pin unplaced
silt-flag retired
RegisterCollection replica on Client B
register atomic LWW revise
north-bench Survey Survey
gate-setpoint
pump-mode
OrderedCollection replica on Client B
sheet state op
queue grade-stakes, pump-check
held jobs none
PactMap replica on Client B
pact accepted pending op
datum-grid Survey datum
gate-policy
inspection-window
TaskManager replica on Client B
task assigned waiters op
sluice-inspection empty
pump-watch empty
crest-walk empty

Client C

0 pending
Shared map replica on Client C
mill-race 24
kettle-run 61
low-ford 42
sandbags-placed 120
earthwork-balance · yd³ +44
fill Σ
74
cut Σ
30
inspection-count 18
A Σ
0
B Σ
0
C Σ
0
Claims replica on Client C
north-levee
spillway-gate
pump-house Survey
OR-map stockpile ledger replica on Client C
spoil-north 18
borrow-pit-7 -6
wash-fill 12
OR-set field marker roster replica on Client C
north-stake marked
sluice-tag marked
borrow-flag clear
G-set permanent benchmark registry replica on Client C
BM-17 recorded
BM-22 unrecorded
BM-31 unrecorded
2P-set retired marker ledger replica on Client C
stake-3 active
gate-pin unplaced
silt-flag retired
RegisterCollection replica on Client C
register atomic LWW revise
north-bench Survey Survey
gate-setpoint
pump-mode
OrderedCollection replica on Client C
sheet state op
queue grade-stakes, pump-check
held jobs none
PactMap replica on Client C
pact accepted pending op
datum-grid Survey datum
gate-policy
inspection-window
TaskManager replica on Client C
task assigned waiters op
sluice-inspection empty
pump-watch empty
crest-walk empty
Sequencer SN 0

    Loading booting watershed kernels…

    Latency and jitter affect simulated arrival order; animation speed changes playback only. “Ops in flight” counts every hop still travelling: one client → sequencer leg, then one sequencer → replica leg per client.

    SharedCounter

    DDS
    counter_kernel

    One number that many people can add to at once, without conflicts.

    A shared counter holds a single integer. Each client submits a signed delta (+5, −1) rather than a new absolute value. Addition commutes, so the server can sequence deltas in any order and every replica lands on the same total.

    Shipping the change instead of the result is the whole trick: two clients incrementing at the same instant never clobber one another. A local delta renders immediately as an unsequenced Δ beside the committed value. When the sequencer stamps it, the pending delta folds into the total.

    Best for

    • Live tallies: votes, reactions, attendees, items in a shared cart
    • Running totals where concurrent +/− must never be lost and order does not matter
    • Inventory or quota counters that many clients adjust at once
    Merge rule
    everyone sends +/− changes instead of overwriting, so simultaneous edits just add up
    Optimistic behavior
    your change shows next to the confirmed total right away
    Summary shape
    only one number needs saving, since every change is an add
    Model
    Distributed data structure: converges by server order

    GCounter

    CRDT
    lattice_counters/g_counter

    A count-up-only counter that stays correct even if an update arrives twice.

    A grow-only counter keeps one monotone count per replica. Client A only ever raises A’s slot; client B only B’s. The visible value is the sum of every slot.

    Merging two states takes the larger count for each replica. That pairwise maximum is idempotent, so a delta delivered twice (after a reconnect, say) changes nothing the second time. The price: a G-counter can only increase. It has no decrement.

    Best for

    • Idempotent counters over at-least-once or unreliable delivery
    • Distributed metrics where the same increment may arrive more than once
    • The building block beneath the PN counter and other lattice counters
    Merge rule
    each client keeps its own tally; the totals combine safely even if a change arrives twice
    Optimistic behavior
    your increment overlays the total in magenta until it’s confirmed
    Summary shape
    each client’s running tally reloads intact
    Model
    Conflict-free replicated data type: converges by merge

    PnCounter

    CRDT
    pn_counter_kernel

    A counter that goes up and down and survives duplicate or out-of-order updates.

    A single G-counter can only grow, which rules out decrements. A PN counter restores them by pairing two G-counters: a positive ledger and a negative ledger, each keyed by replica. The value you read is the positive sum minus the negative sum.

    Each half is a max-merge CRDT, so the whole thing is order- and duplicate-independent. A decrement re-delivered after reconnect is absorbed idempotently. The demo frames this as a cut-and-fill earthwork balance and lets you re-send a sequenced delta to watch the merge shrug it off. An op-based counter needs the runtime's sequence-number dedup to survive that; here the merge alone is enough.

    Best for

    • Counters that go up and down under unreliable delivery (reserve/release, like/unlike)
    • Collaborative budgets or capacity that both grows and shrinks concurrently
    • Offline-first counters that reconcile on reconnect without dropping edits
    Merge rule
    keeps separate up and down tallies, so it can move both ways and still shrug off duplicates
    Optimistic behavior
    adds and subtractions can be re-sent without being counted twice
    Summary shape
    per-client tallies survive reconnect and repeated delivery
    Model
    Conflict-free replicated data type: converges by merge