ORMap

@Serializable
class ORMap<K, S : Quilted<S>> : Quilted<ORMap<K, S>>

An observed-remove map: keys K each carry a Quilted value S that merges via its own Quilted.piece. A concurrent put of the same key survives a remove (add-wins on the key), and a key's value is every surviving write to it, pieced together.

Built over Causal<DotMap<K, ORMapEntry<S>>>: an entry maps each of the key's presence tags to the value written under it, and the key's value is the join of the tags that are still live. A remove retires the tags it can see, so it takes their contributions with it; a write it never observed keeps both its tag and its value. That correspondence between a contribution and its tag is what makes piece associative — see ORMapEntry for why an entry-level value cannot be (#2086).

Every mutator returns the change rather than a new map: put and remove hand back a Patch holding just the key they touched, which is what belongs on the wire. piece absorbs one — and is also how a caller who wants the resulting whole map gets one: map.piece(map.put(replica, key, value)).

Serialization. An entry keys its contributions by Dot, so plain JSON needs Json { allowStructuredMapKeys = true } — the same flag MVRegister and ResettableCounter already require. CBOR and Protobuf encode it without any flag.

Samples

val a = ReplicaId("A")
val b = ReplicaId("B")

// Two peers have converged: "team" already holds a long roster, put there by B.
var alpha = ORMap.empty<String, GSet<String>>()
    .piece { it.put(b, "team", GSet.of("alice", "bob", "carol", "dan")) }
var bravo = alpha

// A adds one member and puts only the change on the wire. The delta carries A's one name —
// not the merged roster — because the receiver re-does that merge against its own copy.
val hire = alpha.put(a, "team", GSet.of("erin"))
check(hire.delta["team"] == GSet.of("erin"))

// A's tag joins B's rather than replacing it, so the key's value is both writes together.
alpha = alpha.piece(hire)
bravo = bravo.piece(hire)
check(alpha == bravo)
check(alpha["team"] == GSet.of("alice", "bob", "carol", "dan", "erin"))

// A concurrent put beats a concurrent remove: B's put mints a tag A's remove never saw.
val concurrent = alpha.put(b, "team", GSet.of("frank"))
check("team" in alpha.piece(alpha.remove("team")).piece(concurrent).keys)

// A remove lands everywhere, because both peers agree on which tags are live. Had A already
// held a tag on "team" and the delta kept quiet about it, that older tag would still be alive
// on bravo — and "team" would come back from the dead there.
val disband = alpha.remove("team")
alpha = alpha.piece(disband)
bravo = bravo.piece(disband)
check("team" !in alpha.keys)
check("team" !in bravo.keys)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard
val keys: Set<K>

Currently-present keys.

Functions

Link copied to clipboard
open fun causalDots(): Set<Dot>

The causal Dots this state has delivered — (author, author-seq) per op.

Link copied to clipboard

The per-author high-water of dots this state delivered and has since compacted away without retaining their identities.

Link copied to clipboard
open operator override fun equals(other: Any?): Boolean
Link copied to clipboard
operator fun get(key: K): S?

The value for key, or null if absent.

Link copied to clipboard
open override fun hashCode(): Int
Link copied to clipboard
open override fun piece(other: ORMap<K, S>): ORMap<K, S>

The causal merge.

Link copied to clipboard
fun put(replica: ReplicaId, key: K, value: S): Patch<ORMap<K, S>>

Put value under key, minting a fresh add-tag on behalf of replica — and return the change: one key, the value you supplied, and a short causal note, rather than the whole new map.

Link copied to clipboard
fun remove(key: K): Patch<ORMap<K, S>>

Remove key — and return the change: the tags currently on it, retired, and nothing else. Absorbing that patch drops the key; the retired tags stay witnessed, so the removal propagates on merge. To hold the resulting map locally, map.piece(map.remove(…)).

Link copied to clipboard
open override fun toString(): String