change_tick.bend
bendvy-ecs-core@0.1.0.0 · 90 lines · 0 definitions
1import Base
2
3# Logical change timestamp. low wraps modulo 2^32; epoch preserves the history
4# needed to age dormant components/readers across low wraps within runtime limits.
5# Raw constructors are trusted: execution must not supply future stamps/cursors.
6type Tick is Data:
7 Tick{epoch:Nat,low:U32}
8
9# Pinned Bevy ad678262 change_detection/mod.rs MAX_CHANGE_AGE.
10# Exact epochs replace periodic storage sweeps, not this observable age bound.
11def max_change_age() -> U32:3258167296
12
13# Initial administrative timestamp. Zero is valid, not component absence or a
14# fabricated first-run cursor; first-run reader policy is supplied separately.
15def zero() -> Tick:Tick{0n,0}
16
17def incremented(wrapped:Bool,epoch:Nat,low:U32) -> Tick:
18 match wrapped:
19 case False{}:Tick{epoch,U32.add(low,1)}
20 case True{}:Tick{1n+epoch,0}
21
22# Advance one logical execution boundary, carrying the epoch on low-word wrap.
23def next(tick:Tick) -> Tick:
24 match tick:
25 case Tick{epoch,+low}:incremented(U32.is_eq(low,4294967295),epoch,low)
26
27# Diagnostic low word only; never use this projection as an independent cursor.
28def low(tick:Tick) -> U32:
29 match tick:
30 case Tick{_,value}:value
31
32# Monotone logical ordinal for existing Nat-based observation/runtime domains.
33# Backend Nat representation limits still apply to consumers of this projection.
34def total(tick:Tick) -> Nat:
35 match tick:
36 case Tick{epoch,value}:Nat.add(Nat.mul(epoch,1n+4294967295n),U32.to_nat(value))
37
38def ordered_epochs(equal:Bool,earlier:Bool,stamp:U32,this:U32) -> Bool:
39 match equal:
40 case True{}:U32.is_le(stamp,this)
41 case False{}:earlier
42
43# Explicit validity of a timestamp relative to this execution. Future values
44# are not made recent by saturating Nat subtraction.
45def not_future(stamp:Tick,this:Tick) -> Bool:
46 match stamp this:
47 case Tick{+se,sl} Tick{+te,tl}:ordered_epochs(Nat.is_eq(se,te),Nat.is_lt(se,te),sl,tl)
48
49def crossing_age(lowWrapped:Bool,stamp:U32,this:U32) -> U32:
50 match lowWrapped:
51 case False{}:max_change_age()
52 case True{}:U32.min(U32.sub(this,stamp),max_change_age())
53
54def distance(same:Bool,adjacent:Bool,+stamp:U32,+this:U32) -> U32:
55 match same adjacent:
56 case True{} _:U32.min(U32.sub(this,stamp),max_change_age())
57 case False{} True{}:crossing_age(U32.is_lt(this,stamp),stamp,this)
58 case False{} False{}:max_change_age()
59
60def valid_age(valid:Bool,stamp:Tick,this:Tick) -> U32:
61 match valid stamp this:
62 case False{} _ _:max_change_age()
63 case True{} Tick{+se,sl} Tick{+te,tl}:distance(Nat.is_eq(se,te),Nat.is_eq(1n+se,te),sl,tl)
64
65# True elapsed age capped at Bevy's MAX_CHANGE_AGE, including multiple wraps.
66# Invalid future stamps return MAX; use not_future when distinguishing invalid
67# inputs from legitimately old data. No global storage maintenance is required.
68def age(+stamp:Tick,+this:Tick) -> U32:
69 valid_age(not_future(stamp,this),stamp,this)
70
71def valid_newer(valid:Bool,stamp:Tick,last:Tick,+this:Tick) -> Bool:
72 match valid:
73 case False{}:False{}
74 case True{}:U32.is_gt(age(last,this),age(stamp,this))
75
76# Compare component age with independent reader age. Equality and two maximally
77# old timestamps are not new. A future stamp OR reader cursor rejects as False;
78# callers separately establish component presence and first-run reader policy.
79def newer(+stamp:Tick,+last:Tick,+this:Tick) -> Bool:
80 valid_newer(Bool.and(not_future(stamp,this),not_future(last,this)),stamp,last,this)
81
82# Detached full-timestamp diagnostic; the epoch is never discarded.
83def show(tick:Tick) -> String:
84 match tick:
85 case Tick{epoch,low}:Nat.show(epoch) ++ ":" ++ U32.show(low)
86# Distinguish an uninitialized lifecycle cursor from the legitimate zero tick.
87def cursor_show(cursor:Maybe<&2,Tick>) -> String:
88 match cursor:
89 case None{}:"never"
90 case Some{tick}:show(tick)