1 import Base 2 import ./world.bend as W 3 import ./system.bend as Sys 4 import ./registration.bend as Registration 5 6 # Removal streams are retained metadata, not component owners or ordinary events. 7 # Schema/family are phantom indices; each closed selector defines its typed stream. 8 type Record is Data: 9 Removed{entity: W.Handle,family: F} 10 Despawned{entity: W.Handle} 11 type Reader Maybe<&2,Record>> is Type: 12 Reader{namespace: U32,id: U32,name: String,cursor: Nat} 13 type Registered Maybe<&2,Record>> is Type: 14 Registered{world: W.World,reader: Reader} 15 Rejected{world: W.World} 16 type Run Maybe<&2,Record>,Error: Data,Out: Type> is Type: 17 Completed{reader: Reader,outcome: Sys.Outcome,Error,Out>} 18 Invalid{reader: Reader,world: W.World} 19 20 type Disposal Maybe<&2,Record>> is Type: 21 Disposed{world: W.World} 22 DisposalRejected{world: W.World,reader: Reader} 23 24 def count(~E: Data,xs: List<&2,E>) -> Nat: 25 match xs: 26 case []: 0n 27 case _ <> rest: 1n+count(~E,rest) 28 29 def include(~S: Data,~F: Data,item: Maybe<&2,Record>,rest: List<&2,Record>) -> List<&2,Record>: 30 match item: 31 case None{}: rest 32 case Some{value}: value <> rest 33 34 def since(~S: Data,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,xs: List<&2,E>,cursor: Nat) -> List<&2,Record>: 35 match xs cursor: 36 case [] _: [] 37 case _ <> rest 1n+p: since(~S,~E,~F,~select,rest,p) 38 case value <> rest 0n: include(~S,~F,select(value),since(~S,~E,~F,~select,rest,0n)) 39 40 def register_result(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,registered: W.World & W.Registration,+namespace: U32,+name: String,cursor: Nat) -> Registered: 41 match registered: 42 case (world,W.Registered{id}): Registered{world,Reader{namespace,id,name,cursor}} 43 case (world,W.RegistrationRejected{_}): Rejected{world} 44 45 def register_view(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,viewed: W.World & List<&2,E>,+namespace: U32,+name: String) -> Registered: 46 (world,values) = viewed 47 register_result(~S,~C,~R,~E,~F,~select,W.register(~S,~C,~R,~E,world,name,["removal",name]),namespace,name,0n) 48 49 def register_named(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,named: W.World & U32,+name: String) -> Registered: 50 (world,+namespace) = named 51 register_view(~S,~C,~R,~E,~F,~select,W.events_view(~S,~C,~R,~E,world),namespace,name) 52 53 def register(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,world: W.World,name: String) -> Registered: 54 register_named(~S,~C,~R,~E,~F,~select,W.namespace(~S,~C,~R,~E,world),name) 55 56 def finished(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,~Error: Data,~Out: Type,reader: Reader,next: Nat,outcome: Sys.Outcome,Error,Out>) -> Run: 57 match reader outcome: 58 case reader Sys.Failed{world,error}: Completed{reader,Sys.Failed{world,error}} 59 case Reader{namespace,id,name,_} Sys.Succeeded{world,out}: Completed{Reader{namespace,id,name,next},Sys.Succeeded{world,out}} 60 61 def consumed(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,~Error: Data,~Out: Type,reader: Reader,next: Nat,world: W.World,result: Result<&2,&1,Error,Out>) -> Run: 62 match result: 63 case Done{out}: finished(~S,~C,~R,~E,~F,~select,~Error,~Out,reader,next,Sys.Succeeded{world,out}) 64 case Fail{error}: finished(~S,~C,~R,~E,~F,~select,~Error,~Out,reader,next,Sys.Failed{world,error}) 65 66 def viewed(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,~Error: Data,~Out: Type,~consume: List<&2,Record> -> Result<&2,&1,Error,Out>,reader: Reader,cursor: Nat,viewed: W.World & List<&2,E>) -> Run: 67 (world,+values) = viewed 68 result = consume(since(~S,~E,~F,~select,values,cursor)) 69 consumed(~S,~C,~R,~E,~F,~select,~Error,~Out,reader,count(~E,values),world,result) 70 71 def checked(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,~Error: Data,~Out: Type,~consume: List<&2,Record> -> Result<&2,&1,Error,Out>,allowed: Bool,reader: Reader,world: W.World,cursor: Nat) -> Run: 72 match allowed: 73 case False{}: Invalid{reader,world} 74 case True{}: viewed(~S,~C,~R,~E,~F,~select,~Error,~Out,~consume,reader,cursor,W.events_view(~S,~C,~R,~E,world)) 75 76 def named(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,~Error: Data,~Out: Type,~consume: List<&2,Record> -> Result<&2,&1,Error,Out>,reader: Reader,world: W.World,actual: U32) -> Run: 77 match reader world: 78 case Reader{+namespace,+id,+name,+cursor} W.World{+ns,allocator,store,resource,events,pending,+registrations,nextSystemId,clock}: 79 checked(~S,~C,~R,~E,~F,~select,~Error,~Out,~consume,Bool.and(U32.is_eq(namespace,actual),W.registration_list_matches(registrations,id,name,["removal",name])),Reader{namespace,id,name,cursor},W.World{ns,allocator,store,resource,events,pending,registrations,nextSystemId,clock},cursor) 80 81 def run(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,~Error: Data,~Out: Type,~consume: List<&2,Record> -> Result<&2,&1,Error,Out>,reader: Reader,world: W.World) -> Run: 82 match world: 83 case W.World{+ns,allocator,store,resource,events,pending,registrations,nextSystemId,clock}: named(~S,~C,~R,~E,~F,~select,~Error,~Out,~consume,reader,W.World{ns,allocator,store,resource,events,pending,registrations,nextSystemId,clock},ns) 84 85 # Skipped removal readers keep their original cursor; unlike ordinary events. 86 def skip(~S: Data,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,reader: Reader) -> Reader: 87 reader 88 89 def removal_access(access: List<&2,String>) -> Bool: 90 match access: 91 case []: False{} 92 case first <> _: String.eq(first,"removal") 93 94 def readers_remain(entries: List<&2,W.RegistrationMeta>) -> Bool: 95 match entries: 96 case []: False{} 97 case W.RegistrationMeta{_,_,access} <> rest: Bool.or(removal_access(access),readers_remain(rest)) 98 99 def collect_checked(~S: Data,~C: Type,~R: Type,~E: Data,retain: Bool,world: W.World) -> W.World: 100 match retain world: 101 case True{} world: world 102 case False{} W.World{namespace,allocator,store,resource,_,pending,registrations,nextSystemId,clock}: W.World{namespace,allocator,store,resource,[],pending,registrations,nextSystemId,clock} 103 104 def collected(~S: Data,~C: Type,~R: Type,~E: Data,result: W.World & Bool) -> W.World & Bool: 105 match result: 106 case (world,False{}): (world,False{}) 107 case (W.World{namespace,allocator,store,resource,events,pending,+registrations,nextSystemId,clock},True{}): (collect_checked(~S,~C,~R,~E,readers_remain(registrations),W.World{namespace,allocator,store,resource,events,pending,registrations,nextSystemId,clock}),True{}) 108 109 def disposal_collected(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,result: W.World & Bool) -> Disposal: 110 (world,removed) = result 111 Disposed{world} 112 113 def disposal_result(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,reader: Reader,result: W.World & Bool) -> Disposal: 114 match result: 115 case (world,False{}): DisposalRejected{world,reader} 116 case (world,True{}): disposal_collected(~S,~C,~R,~E,~F,~select,collected(~S,~C,~R,~E,(world,True{}))) 117 118 # This owner is removal-only: ordinary event retention uses its own Runtime. 119 # Disposing the last registered reader releases the retained metadata log. 120 def dispose(~S: Data,~C: Type,~R: Type,~E: Data,~F: Data,~select: E -> Maybe<&2,Record>,reader: Reader,world: W.World) -> Disposal: 121 match reader: 122 case Reader{+namespace,+id,+name,+cursor}: disposal_result(~S,~C,~R,~E,~F,~select,Reader{namespace,id,name,cursor},Registration.remove(~S,~C,~R,~E,world,namespace,id,name,["removal",name]))