concinnity_core/ecs/payload_store.rs
1// The payload-access seam a `PipelineContext` exposes to systems.
2//
3// Systems reach compiled binary payloads through this trait, not a concrete
4// store, so the ECS mechanism carries no dependency on blob file I/O or the
5// state root's layout. The runtime implementor is the `BlobData` that
6// `concinnity_host::store` builds over `crate::blob`; tests supply their own.
7
8use crate::ecs::PayloadLocator;
9use crate::result::CnResult;
10
11/// A source of compiled payload bytes addressed by `PayloadLocator`.
12pub trait PayloadStore {
13 /// Read the bytes a locator points at. `&mut self` because a disk-backed
14 /// store may load an overflow section lazily on first access. Errors when
15 /// the payload was released, the locator is out of range, or a lazy load
16 /// fails.
17 fn read(&mut self, locator: &PayloadLocator) -> Result<&[u8], CnResult>;
18
19 /// Release an entire blob's in-memory payload once every system that needs
20 /// it has finished (e.g. after GPU upload). A store with nothing resident
21 /// for `blob_index` treats this as a no-op.
22 fn release(&mut self, blob_index: u32);
23
24 /// Whether the payloads are backed by files still on disk, so a released
25 /// payload can be re-read on demand rather than kept RAM-resident.
26 fn disk_backed(&self) -> bool;
27
28 /// Release every resident section at once, returning the bytes freed.
29 /// `World::start` calls this after init: systems read compiled payloads
30 /// only while initing and cache what they keep, so nothing consults the
31 /// store again. A store with nothing resident frees nothing.
32 fn release_all_resident(&mut self) -> usize {
33 0
34 }
35}
36
37/// A payload store holding nothing: every read errors and every release is a
38/// no-op. What a [`World`](crate::ecs::World) built without a blob runs on --
39/// unit tests, and worlds assembled entirely from runtime-only components.
40pub struct NoPayloads;
41
42impl PayloadStore for NoPayloads {
43 fn read(&mut self, _locator: &PayloadLocator) -> Result<&[u8], CnResult> {
44 Err(CnResult::FileIo)
45 }
46
47 fn release(&mut self, _blob_index: u32) {}
48
49 fn disk_backed(&self) -> bool {
50 false
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 // A world built without a blob runs on this: nothing is resident, so every
59 // read fails rather than handing back bytes it does not have, and every
60 // release is a no-op.
61 #[test]
62 fn a_store_holding_nothing_reads_nothing_and_frees_nothing() {
63 let mut store = NoPayloads;
64 let locator = PayloadLocator {
65 blob_index: 0,
66 offset: 0,
67 len: 4,
68 };
69 assert_eq!(store.read(&locator), Err(CnResult::FileIo));
70 store.release(0);
71 assert!(!store.disk_backed());
72 assert_eq!(store.release_all_resident(), 0);
73 }
74}