use "collections"
class ref GCounter[A: (Integer[A] val & Unsigned) = U64]
is (Comparable[GCounter[A]] & Convergent[GCounter[A]] & Replicated)
"""
A mutable grow-only counter. That is, the value can only be increased.
It is limited by the maximum value of the used unsigned integer datatype.
Any operation that would lead to an overflow (if the maximum is the
maximum value for the used unsigned integer type) will result in the value
being set to the maximum. So once the maximum is reached, the GCounter will
never change.
This data type tracks the state seen from each replica, thus the size of the
state will grow proportionally with the number of total replicas. New replicas
may be added as peers at any time, provided that they use unique ids.
Read-only replicas which never change state and only observe need not use
unique ids, and should use an id of zero, by convention.
The state of each replica represents the value incremented so far by that
particular replica. This local value may only ever increase, never decrease.
The total value of the counter is the sum of the local value of all replicas.
When converging state from other replicas, we retain the maximum observed
value from each replica id. Because a higher value always implies later
logical time for that replica, and we only keep the highest value seen from
each replica, we will always retain the latest value seen from each replica.
Because the view of values from each other replica is eventually consistent,
the summed value of the overall counter is also eventually consistent.
All mutator methods accept and return a convergent delta-state.
"""
var _id: ID
embed _data: Map[ID, A]
let _checklist: (DotChecklist | None)
new ref create(id': ID) =>
"""
Instantiate the GCounter under the given unique replica id.
"""
_id = id'
_data = Map[ID, A]
_checklist = None
new ref _create_in(ctx: DotContext) =>
_id = ctx.id()
_data = _data.create()
_checklist = DotChecklist(ctx)
fun ref _checklist_write() =>
match _checklist
| let c: DotChecklist => c.write()
end
fun ref _converge_empty_in(ctx: DotContext box): Bool => // ignore the context
false
fun is_empty(): Bool =>
"""
Return true if the data structure contains no information (bottom state).
"""
_data.size() == 0
fun apply(): A =>
"""
Return the current value of the counter (the sum of all replica values).
"""
value()
fun value(): A =>
"""
Return the current value of the counter (the sum of all replica values).
"""
var sum = A(0)
for v in _data.values() do sum = _Math.saturated_sum[A](sum, v) end
sum
fun ref _data_update(id': ID, value': A) => _data(id') = value'
fun ref increment[D: GCounter[A] ref = GCounter[A]](
value': A = 1,
delta': D = recover GCounter[A](0) end)
: D^
=>
"""
Increment the counter by the given value.
Accepts and returns a convergent delta-state.
"""
let v' =
_data.upsert(
_id,
value',
{(x: A, y: A): A => _Math.saturated_sum[A](x, y) })
_checklist_write()
delta'._data_update(_id, v')
consume delta'
fun ref converge(that: GCounter[A] box): Bool =>
"""
Converge from the given GCounter into this one.
For each replica state, we select the maximum value seen so far (grow-only).
Returns true if the convergence added new information to the data structure.
"""
var changed = false
for (id, value') in that._data.pairs() do
// TODO: introduce a stateful upsert in ponyc Map?
if try value' > _data(id)? else true end then
_data(id) = value'
changed = true
end
end
changed
fun string(): String iso^ =>
"""
Return a best effort at printing the register. If A is Stringable, use
the string representation of the value; otherwise print as a question mark.
"""
iftype A <: Stringable val then
value().string()
else
"?".clone()
end
fun eq(that: GCounter[A] box): Bool => value().eq(that.value())
fun ne(that: GCounter[A] box): Bool => value().ne(that.value())
fun lt(that: GCounter[A] box): Bool => value().lt(that.value())
fun le(that: GCounter[A] box): Bool => value().le(that.value())
fun gt(that: GCounter[A] box): Bool => value().gt(that.value())
fun ge(that: GCounter[A] box): Bool => value().ge(that.value())
fun ref from_tokens(that: TokensIterator) ? =>
"""
Deserialize an instance of this data structure from a stream of tokens.
"""
var count = that.next[USize]()?
if count < 1 then error end
count = count - 1
_id = that.next[ID]()?
if (count % 2) != 0 then error end
count = count / 2
// TODO: _data.reserve(count)
while (count = count - 1) > 0 do
_data.update(that.next[ID]()?, that.next[A]()?)
end
fun ref each_token(tokens: Tokens) =>
"""
Serialize the data structure, capturing each token into the given Tokens.
"""
tokens.push(1 + (_data.size() * 2))
tokens.push(_id)
for (id, v) in _data.pairs() do
tokens.push(id)
tokens.push(v)
end