1 import Base 2 import ./change_tick.bend as Tick 3 4 # Metadata is Data; payload ownership stays in the typed column. Identity is 5 # the caller's closed typed column lens, never the family's display name. 6 type Stamp is Data: 7 Stamp{added: Maybe<&2,Tick.Tick>,changed: Maybe<&2,Tick.Tick>} 8 type Entry is Data: 9 Entry{id: U32,stamp: Stamp} 10 11 def choose(found: Bool,stamp: Stamp,other: Stamp) -> Stamp: 12 match found: 13 case True{}: stamp 14 case False{}: other 15 16 def get_selected(entries: List<&2,Entry>,+id: U32,found: Bool,stamp: Stamp) -> Stamp: 17 match entries found: 18 case _ True{}: stamp 19 case Nil{} False{}: Stamp{None{},None{}} 20 case Con{Entry{key,current},rest} False{}: get_selected(rest,id,U32.is_eq(key,id),current) 21 22 def get(entries: List<&2,Entry>,id: U32) -> Stamp: 23 get_selected(entries,id,False{},Stamp{None{},None{}}) 24 25 26 def keep(equal: Bool,entry: Entry,rest: List<&2,Entry>) -> List<&2,Entry>: 27 match equal: 28 case True{}: rest 29 case False{}: entry <> rest 30 31 def entry_id(entry:Entry) -> U32: 32 match entry: 33 case Entry{id,_}:id 34 35 def clear_reversed(entries: List<&2,Entry>,+id: U32,acc: List<&2,Entry>) -> List<&2,Entry>: 36 match entries: 37 case []: List.reverse(&2,Entry,acc) 38 case +entry <> rest: clear_reversed(rest,id,keep(U32.is_eq(entry_id(entry),id),entry,acc)) 39 40 def clear(entries: List<&2,Entry>,id: U32) -> List<&2,Entry>: 41 clear_reversed(entries,id,[]) 42 43 def set(entries: List<&2,Entry>,+id: U32,stamp: Stamp) -> List<&2,Entry>: 44 Entry{id,stamp} <> clear(entries,id) 45 46 def written(wasPresent: Bool,stamp: Stamp,+tick: Tick.Tick) -> Stamp: 47 match wasPresent stamp: 48 case False{} _: Stamp{Some{tick},Some{tick}} 49 case True{} Stamp{added,_}: Stamp{added,Some{tick}} 50 51 def after_write(incoming: Bool,wasPresent: Bool,stamp: Stamp,tick: Tick.Tick) -> Stamp: 52 match incoming: 53 case False{}: Stamp{None{},None{}} 54 case True{}: written(wasPresent,stamp,tick) 55 56 # Presence is explicit: wrapped tick zero is a valid component timestamp. 57 def selected_age(+stamp:Tick.Tick,cursor:Maybe<&2,Tick.Tick>,+this:Tick.Tick) -> Bool: 58 match cursor: 59 case None{}:Bool.and(Tick.not_future(stamp,this),U32.is_lt(Tick.age(stamp,this),Tick.max_change_age())) 60 case Some{last}:Tick.newer(stamp,last,this) 61 def selected(stamp:Maybe<&2,Tick.Tick>,cursor:Maybe<&2,Tick.Tick>,this:Tick.Tick) -> Bool: 62 match stamp: 63 case None{}:False{} 64 case Some{tick}:selected_age(tick,cursor,this) 65 66 # Compare bounded true ages against this captured run; None is a first-run cursor. 67 def added(stamp:Stamp,cursor:Maybe<&2,Tick.Tick>,this:Tick.Tick) -> Bool: 68 match stamp: 69 case Stamp{value,_}:selected(value,cursor,this) 70 def changed(stamp:Stamp,cursor:Maybe<&2,Tick.Tick>,this:Tick.Tick) -> Bool: 71 match stamp: 72 case Stamp{_,value}:selected(value,cursor,this)