code wiki / (root) / nx_chunk_paginate.nx

nx_chunk_paginate.nx source

↩ module page · 213 lines · 6696 B

1// nx_chunk_paginate.nx -- flash-streamed world chunks. 2// 3// THE T1/MCU UNLOCK PRIMITIVE for worlds bigger than RAM. ESP32 has 4// 520 KiB SRAM but ~4 MB flash. A walkable Minecraft-equivalent world 5// at 32³ chunks (12 KiB each at 3bpp) means ~32 chunks comfortably 6// fit in flash; substrate streams them into RAM as player moves. 7// 8// LRU cache: keep N chunks resident in RAM; on miss, evict LRU and 9// load the requested chunk from flash. Caller supplies the "flash 10// storage" as a content-addressed byte buffer (the integration layer 11// maps the actual flash partition). 12// 13// Composes: 14// nx_tissue -- each chunk is a 32³ NxTissue 15// nx_palette -- chunks are palette-packed (3bpp typical for T1) 16// nx_aerobic -- evicted chunks go to aerobic pile for journaling 17// (we never silently lose data) 18// nx_metabolism -- chunk-page-miss is a substrate-level event; 19// metabolism tracks misses per call site 20 21import "nx_syscalls.nx" 22import "nx_tier.nx" 23import "nx_tissue.nx" 24const NX_MAGIC_1024: i64 = 1024 25 26const NX_CP_OK: nx_int = 0 27const NX_CP_ERR_NOT_FOUND: nx_int = 1 28const NX_CP_ERR_BAD_COORD: nx_int = 2 29const NX_CP_CACHE_HIT: nx_int = 3 30const NX_CP_CACHE_MISS: nx_int = 4 31 32// ===== Struct: NxChunkSlot ========================================= 33// 34// One LRU cache slot. chunk_id is the world-space chunk identifier 35// (e.g., 16-bit packed cx + cy + cz). tissue points to the loaded 36// NxTissue. last_access_us is monotonic for LRU eviction. 37 38struct NxChunkSlot { 39 chunk_id: nx_int, 40 tissue: *NxTissue, 41 last_access_us: nx_size, 42 is_loaded: nx_int, 43} 44 45struct NxChunkPaginator { 46 slots: *NxChunkSlot, 47 capacity: nx_size, 48 n_loaded: nx_size, 49 miss_count: nx_int, 50 hit_count: nx_int, 51 evict_count: nx_int, 52} 53 54const NX_CP_SLOT_BYTES: nx_size = 32 55 56func nx_chunk_paginator_new(capacity: nx_size) -> *NxChunkPaginator { 57 let p: *NxChunkPaginator = (sys_mmap(56)) as *NxChunkPaginator 58 let bytes: nx_size = capacity * NX_CP_SLOT_BYTES 59 p.slots = (sys_mmap(bytes)) as *NxChunkSlot 60 p.capacity = capacity 61 p.n_loaded = 0 62 p.miss_count = 0 63 p.hit_count = 0 64 p.evict_count = 0 65 // Zero slot states 66 var i: nx_size = 0 67 while i < capacity { 68 let s: *NxChunkSlot = (p.slots as i64 + (i as i64) * NX_CP_SLOT_BYTES) as *NxChunkSlot 69 s.chunk_id = 0 70 s.tissue = (0 as i64) as *NxTissue 71 s.last_access_us = 0 72 s.is_loaded = 0 73 i = i + 1 74 } 75 return p 76} 77 78func _cp_at(p: *NxChunkPaginator, idx: nx_size) -> *NxChunkSlot { 79 return (p.slots as i64 + (idx as i64) * NX_CP_SLOT_BYTES) as *NxChunkSlot 80} 81 82func _cp_find_loaded(p: *NxChunkPaginator, chunk_id: nx_int) -> nx_int { 83 var i: nx_size = 0 84 while i < p.capacity { 85 let s: *NxChunkSlot = _cp_at(p, i) 86 if s.is_loaded == 1 { 87 if s.chunk_id == chunk_id { return i as i64 } 88 } 89 i = i + 1 90 } 91 return -1 92} 93 94// ===== _cp_find_lru ================================================ 95// 96// Find the slot with the oldest last_access_us among LOADED slots. 97// Used for eviction when cache is full. 98 99func _cp_find_lru(p: *NxChunkPaginator) -> nx_int { 100 var oldest_idx: nx_int = -1 101 var oldest_ts: nx_size = 0 102 var first: nx_int = 1 103 var i: nx_size = 0 104 while i < p.capacity { 105 let s: *NxChunkSlot = _cp_at(p, i) 106 if s.is_loaded == 1 { 107 if first == 1 { 108 oldest_idx = i as i64 109 oldest_ts = s.last_access_us 110 first = 0 111 } else { 112 if s.last_access_us < oldest_ts { 113 oldest_ts = s.last_access_us 114 oldest_idx = i as i64 115 } 116 } 117 } 118 i = i + 1 119 } 120 return oldest_idx 121} 122 123func _cp_find_free(p: *NxChunkPaginator) -> nx_int { 124 var i: nx_size = 0 125 while i < p.capacity { 126 let s: *NxChunkSlot = _cp_at(p, i) 127 if s.is_loaded == 0 { return i as i64 } 128 i = i + 1 129 } 130 return -1 131} 132 133// ===== nx_chunk_request ============================================ 134// 135// Get a chunk by id. Cache HIT returns existing tissue + updates LRU 136// timestamp. MISS evicts LRU + caller is expected to load fresh 137// tissue into the returned slot (caller-driven actual flash read). 138// Returns the tissue pointer + verdict via out_verdict. 139 140func nx_chunk_request(p: *NxChunkPaginator, 141 chunk_id: nx_int, 142 fresh_tissue: *NxTissue, 143 now_us: nx_size, 144 out_verdict: *i64) -> *NxTissue { 145 let idx_loaded: nx_int = _cp_find_loaded(p, chunk_id) 146 if idx_loaded >= 0 { 147 let s: *NxChunkSlot = _cp_at(p, idx_loaded as nx_size) 148 s.last_access_us = now_us 149 p.hit_count = p.hit_count + 1 150 out_verdict[0] = NX_CP_CACHE_HIT 151 return s.tissue 152 } 153 154 // MISS: find a free slot or evict LRU 155 p.miss_count = p.miss_count + 1 156 var target_idx: nx_int = _cp_find_free(p) 157 if target_idx < 0 { 158 target_idx = _cp_find_lru(p) 159 if target_idx >= 0 { 160 p.evict_count = p.evict_count + 1 161 } 162 } 163 if target_idx < 0 { 164 out_verdict[0] = NX_CP_ERR_NOT_FOUND 165 return (0 as i64) as *NxTissue 166 } 167 168 let s: *NxChunkSlot = _cp_at(p, target_idx as nx_size) 169 let was_loaded: nx_int = s.is_loaded 170 s.chunk_id = chunk_id 171 s.tissue = fresh_tissue 172 s.last_access_us = now_us 173 s.is_loaded = 1 174 if was_loaded == 0 { p.n_loaded = p.n_loaded + 1 } 175 out_verdict[0] = NX_CP_CACHE_MISS 176 return fresh_tissue 177} 178 179// ===== nx_chunk_loaded_count ======================================= 180 181func nx_chunk_loaded_count(p: *NxChunkPaginator) -> nx_size { 182 return p.n_loaded 183} 184 185func nx_chunk_hit_count(p: *NxChunkPaginator) -> nx_int { 186 return p.hit_count 187} 188 189func nx_chunk_miss_count(p: *NxChunkPaginator) -> nx_int { 190 return p.miss_count 191} 192 193func nx_chunk_evict_count(p: *NxChunkPaginator) -> nx_int { 194 return p.evict_count 195} 196 197// ===== nx_chunk_hit_rate_q10 ======================================= 198// 199// Q10 cache effectiveness: hits / (hits + misses). High = good 200// locality; low = thrashing. 201 202func nx_chunk_hit_rate_q10(p: *NxChunkPaginator) -> nx_int { 203 let total: nx_int = p.hit_count + p.miss_count 204 if total <= 0 { return 0 } 205 return (p.hit_count * NX_MAGIC_1024) / total 206} 207 208// ===== nx_chunk_is_loaded ========================================== 209 210func nx_chunk_is_loaded(p: *NxChunkPaginator, chunk_id: nx_int) -> nx_int { 211 if _cp_find_loaded(p, chunk_id) >= 0 { return 1 } 212 return 0 213}