PNCounter

@Serializable
class PNCounter : Quilted<PNCounter>

A positive/negative counter: two GCounter halves composed in a product lattice. One half tracks all increments, the other all decrements.

Intuition: each half is an independent GCounter whose piece is elementwise max. Joining the product of two such lattices is just joining each component separately. The observable value is inc − dec, which can be negative if decrements outpace increments.

Delta-state: increment and decrement do not mutate — each returns a Patch (a tiny PNCounter carrying only the changed slot in one half) that any replica absorbs with piece. This mirrors GCounter.inc.

Replica ownership: each replica increments/decrements its own slot. Two replicas must never mutate the same slot concurrently — that is the caller's responsibility, exactly as with GCounter.

Signed vs unsigned: the counters are Long-based throughout. A replica decrementing more than it (or any peer) has incremented is legal — value goes negative. This matches standard PNCounter semantics. If you need a floor at zero, use BoundedCounter instead.

No wraparound risk: both halves count upward monotonically; value is a subtraction of two non-negative Long sums. Overflow is theoretically possible at Long.MAX_VALUE increments per replica, which is not a practical concern.

Samples

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

var counter = PNCounter.ZERO
counter = counter.piece(counter.increment(a, 10))
counter = counter.piece(counter.decrement(b, 3))

check(counter.value == 7L)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

Total ever decremented across all replicas (monotonically non-decreasing).

Link copied to clipboard

Total ever incremented across all replicas (monotonically non-decreasing).

Link copied to clipboard
val value: Long

The counter's net value: total incremented across all replicas minus total decremented.

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
fun decrement(replica: ReplicaId, by: Long = 1): Patch<PNCounter>

Decrement replica's own slot by by (must be positive). Returns the delta to absorb with piece; the receiver is unchanged.

Link copied to clipboard
open operator override fun equals(other: Any?): Boolean
Link copied to clipboard
open override fun hashCode(): Int
Link copied to clipboard
fun increment(replica: ReplicaId, by: Long = 1): Patch<PNCounter>

Increment replica's own slot by by (must be positive). Returns the delta to absorb with piece; the receiver is unchanged.

Link copied to clipboard
open override fun piece(other: PNCounter): PNCounter

The join: per-component max on both halves.

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