blueprint_store.gno
13.05 Kb · 478 lines
1package blueprint
2
3import (
4 "gno.land/p/akkadia/v0/ds/btree"
5 "gno.land/p/akkadia/v0/ds/btreeset"
6)
7
8var blueprintStore *BlueprintStore = newBlueprintStore()
9
10// BlueprintStore owns blueprint records, opaque metadata, and secondary indexes.
11//
12// Data shape:
13// - blueprints: blueprintID(uint32) -> map[string]string
14// - metadatas: blueprintID(uint32) -> metadata(string)
15// - nameIndex: name(string) -> blueprintID(uint32)
16// - ownerIndex: owner(address string) -> *Uint32BTreeSet(blueprintID)
17// - biomeIndex: biome(string) -> *Uint32BTreeSet(blueprintID)
18// - stateIndex: state(string) -> *Uint32BTreeSet(blueprintID)
19// - biomeStateIndex: biome(string) -> *StringBTree(state(string) -> *Uint32BTreeSet(blueprintID))
20//
21// Responsibilities:
22// - allocate blueprint IDs and store canonical blueprint records
23// - keep name/owner/biome/state/biome-state indexes in sync with record mutations
24// - store per-blueprint admin-managed opaque metadata text
25// - protect store-owned invariants such as duplicate names
26// - return defensive copies from public read methods
27//
28// Non-responsibilities:
29// - validate external parameter shape such as name format, CSV payloads, or pagination
30// - interpret or semantically validate metadata; metadata is off-chain decoded admin text
31// - perform auth, payment, freeze, migration, or event handling
32// - parse user-facing encoded input; public exported functions must pass canonical values
33type BlueprintStore struct {
34 nextBlueprintID uint32
35 blueprints *btree.Uint32BTree
36 metadatas *btree.Uint32BTree
37 nameIndex *btree.StringBTree
38 ownerIndex *btree.StringBTree
39 biomeIndex *btree.StringBTree
40 stateIndex *btree.StringBTree
41 biomeStateIndex *btree.StringBTree
42}
43
44func newBlueprintStore() *BlueprintStore {
45 return &BlueprintStore{
46 nextBlueprintID: 1,
47 blueprints: btree.NewUint32BTree(32),
48 metadatas: btree.NewUint32BTree(32),
49 nameIndex: btree.NewStringBTree(32),
50 ownerIndex: btree.NewStringBTree(32),
51 biomeIndex: btree.NewStringBTree(32),
52 stateIndex: btree.NewStringBTree(32),
53 biomeStateIndex: btree.NewStringBTree(32),
54 }
55}
56
57func (s *BlueprintStore) NextID() uint32 {
58 return s.nextBlueprintID
59}
60
61// ==================== DANGER: MUTABLE MIGRATION CAPABILITIES ====================
62//
63// The getters in this section expose mutable store internals.
64// Only authorized migration exporter paths may depend on these capabilities.
65// Runtime reads and test setup must use copy-returning methods or total helpers.
66func (s *BlueprintStore) Blueprints() *btree.Uint32BTree {
67 return s.blueprints
68}
69
70func (s *BlueprintStore) Metadatas() *btree.Uint32BTree {
71 return s.metadatas
72}
73
74func (s *BlueprintStore) NameIndex() *btree.StringBTree {
75 return s.nameIndex
76}
77
78func (s *BlueprintStore) OwnerIndex() *btree.StringBTree {
79 return s.ownerIndex
80}
81
82func (s *BlueprintStore) BiomeIndex() *btree.StringBTree {
83 return s.biomeIndex
84}
85
86func (s *BlueprintStore) StateIndex() *btree.StringBTree {
87 return s.stateIndex
88}
89
90func (s *BlueprintStore) BiomeStateIndex() *btree.StringBTree {
91 return s.biomeStateIndex
92}
93
94// ================= END DANGER: MUTABLE MIGRATION CAPABILITIES ==================
95
96func (s *BlueprintStore) Create(blueprint map[string]string) uint32 {
97 name := blueprint["name"]
98 if s.nameIndex.Has(name) {
99 panic("name already exists")
100 }
101
102 id := s.nextID()
103 stored := copyStringMap(blueprint)
104 stored["id"] = formatBlueprintID(id)
105 s.blueprints.Set(id, stored)
106 s.nameIndex.Set(name, id)
107 s.addOwnerIndex(id, address(stored["owner"]))
108 s.addBiomeIndex(id, stored["biome"])
109 s.addStateIndex(id, stored["state"])
110 s.addBiomeStateIndex(id, stored["biome"], stored["state"])
111 return id
112}
113
114func (s *BlueprintStore) Update(blueprintID uint32, updates map[string]string) map[string]string {
115 current := s.mustGetInternal(blueprintID)
116 oldName := current["name"]
117 if name, found := updates["name"]; found {
118 if oldName != name && s.nameIndex.Has(name) {
119 panic("name already exists")
120 }
121 }
122
123 updated := copyStringMap(current)
124 for key, value := range updates {
125 updated[key] = value
126 }
127 updated["id"] = formatBlueprintID(blueprintID)
128
129 oldOwner := current["owner"]
130 newOwner := updated["owner"]
131 newName := updated["name"]
132 oldBiome := current["biome"]
133 newBiome := updated["biome"]
134 oldState := current["state"]
135 newState := updated["state"]
136
137 if oldOwner != newOwner {
138 s.removeOwnerIndex(blueprintID, address(oldOwner))
139 s.addOwnerIndex(blueprintID, address(newOwner))
140 }
141 if oldName != newName {
142 s.nameIndex.Remove(oldName)
143 s.nameIndex.Set(newName, blueprintID)
144 }
145 if oldBiome != newBiome {
146 s.removeBiomeIndex(blueprintID, oldBiome)
147 s.addBiomeIndex(blueprintID, newBiome)
148 }
149 if oldState != newState {
150 s.removeStateIndex(blueprintID, oldState)
151 s.addStateIndex(blueprintID, newState)
152 }
153 if oldBiome != newBiome || oldState != newState {
154 s.removeBiomeStateIndex(blueprintID, oldBiome, oldState)
155 s.addBiomeStateIndex(blueprintID, newBiome, newState)
156 }
157
158 s.blueprints.Set(blueprintID, updated)
159 return copyStringMap(updated)
160}
161
162func (s *BlueprintStore) Delete(blueprintID uint32) {
163 blueprint := s.mustGetInternal(blueprintID)
164 s.blueprints.Remove(blueprintID)
165 s.nameIndex.Remove(blueprint["name"])
166 s.removeOwnerIndex(blueprintID, address(blueprint["owner"]))
167 s.removeBiomeIndex(blueprintID, blueprint["biome"])
168 s.removeStateIndex(blueprintID, blueprint["state"])
169 s.removeBiomeStateIndex(blueprintID, blueprint["biome"], blueprint["state"])
170 s.metadatas.Remove(blueprintID)
171}
172
173func (s *BlueprintStore) SetMetadata(blueprintID uint32, metadata string) {
174 s.mustGetInternal(blueprintID)
175 s.metadatas.Set(blueprintID, metadata)
176}
177
178func (s *BlueprintStore) GetMetadata(blueprintID uint32) string {
179 s.mustGetInternal(blueprintID)
180 metadata, found := s.metadatas.Get(blueprintID)
181 if !found {
182 return ""
183 }
184 return metadata.(string)
185}
186
187func (s *BlueprintStore) ListMetadataByIDs(blueprintIDs ...uint32) []string {
188 result := []string{}
189 for _, blueprintID := range blueprintIDs {
190 if !s.blueprints.Has(blueprintID) {
191 continue
192 }
193 metadata, found := s.metadatas.Get(blueprintID)
194 if !found {
195 result = append(result, "")
196 continue
197 }
198 result = append(result, metadata.(string))
199 }
200 return result
201}
202
203func (s *BlueprintStore) MustGet(blueprintID uint32) map[string]string {
204 return copyStringMap(s.mustGetInternal(blueprintID))
205}
206
207func (s *BlueprintStore) Get(blueprintID uint32) (map[string]string, bool) {
208 blueprint, found := s.blueprints.Get(blueprintID)
209 if !found {
210 return nil, false
211 }
212 return copyStringMap(blueprint.(map[string]string)), true
213}
214
215func (s *BlueprintStore) AssertBlueprintExists(blueprintID uint32) {
216 if !s.blueprints.Has(blueprintID) {
217 panic("blueprint not found: " + formatBlueprintID(blueprintID))
218 }
219}
220
221func (s *BlueprintStore) mustGetInternal(blueprintID uint32) map[string]string {
222 properties, found := s.blueprints.Get(blueprintID)
223 if !found {
224 panic("blueprint not found: " + formatBlueprintID(blueprintID))
225 }
226 return properties.(map[string]string)
227}
228
229func (s *BlueprintStore) MustGetIDByName(name string) uint32 {
230 id, found := s.nameIndex.Get(name)
231 if !found {
232 panic("blueprint not found by name: " + name)
233 }
234 return id.(uint32)
235}
236
237func (s *BlueprintStore) Total() int {
238 return s.blueprints.Size()
239}
240
241func (s *BlueprintStore) IsNameAvailable(name string) bool {
242 return !s.nameIndex.Has(name)
243}
244
245func (s *BlueprintStore) ListIDs(page int, count int) []uint32 {
246 offset := (page - 1) * count
247 result := []uint32{}
248 s.blueprints.IterateByOffset(offset, count, func(blueprintID uint32, _ any) bool {
249 result = append(result, blueprintID)
250 return false
251 })
252 return result
253}
254
255func (s *BlueprintStore) ListByIDs(blueprintIDs ...uint32) []map[string]string {
256 result := []map[string]string{}
257 for _, blueprintID := range blueprintIDs {
258 blueprint, found := s.Get(blueprintID)
259 if !found {
260 continue
261 }
262 result = append(result, blueprint)
263 }
264 return result
265}
266
267func (s *BlueprintStore) OwnerCount(owner address) int {
268 val, found := s.ownerIndex.Get(owner.String())
269 if !found {
270 return 0
271 }
272 return val.(*btreeset.Uint32BTreeSet).Size()
273}
274
275func (s *BlueprintStore) ListIDsByOwner(owner address, page int, count int) []uint32 {
276 val, found := s.ownerIndex.Get(owner.String())
277 if !found {
278 return []uint32{}
279 }
280 offset := (page - 1) * count
281 result := []uint32{}
282 val.(*btreeset.Uint32BTreeSet).IterateByOffset(offset, count, func(id uint32) bool {
283 result = append(result, id)
284 return false
285 })
286 return result
287}
288
289func (s *BlueprintStore) BiomeCount(biomeName string) int {
290 val, found := s.biomeIndex.Get(biomeName)
291 if !found {
292 return 0
293 }
294 return val.(*btreeset.Uint32BTreeSet).Size()
295}
296
297func (s *BlueprintStore) ListIDsByBiome(biomeName string, page int, count int) []uint32 {
298 val, found := s.biomeIndex.Get(biomeName)
299 if !found {
300 return []uint32{}
301 }
302 offset := (page - 1) * count
303 result := []uint32{}
304 val.(*btreeset.Uint32BTreeSet).IterateByOffset(offset, count, func(id uint32) bool {
305 result = append(result, id)
306 return false
307 })
308 return result
309}
310
311func (s *BlueprintStore) StateCount(state string) int {
312 val, found := s.stateIndex.Get(state)
313 if !found {
314 return 0
315 }
316 return val.(*btreeset.Uint32BTreeSet).Size()
317}
318
319func (s *BlueprintStore) ListIDsByState(state string, page int, count int) []uint32 {
320 val, found := s.stateIndex.Get(state)
321 if !found {
322 return []uint32{}
323 }
324 offset := (page - 1) * count
325 result := []uint32{}
326 val.(*btreeset.Uint32BTreeSet).IterateByOffset(offset, count, func(id uint32) bool {
327 result = append(result, id)
328 return false
329 })
330 return result
331}
332
333func (s *BlueprintStore) BiomeStateCount(biomeName string, state string) int {
334 stateTree, found := s.biomeStateTree(biomeName)
335 if !found {
336 return 0
337 }
338 val, found := stateTree.Get(state)
339 if !found {
340 return 0
341 }
342 return val.(*btreeset.Uint32BTreeSet).Size()
343}
344
345func (s *BlueprintStore) ListIDsByBiomeState(biomeName string, state string, page int, count int) []uint32 {
346 stateTree, found := s.biomeStateTree(biomeName)
347 if !found {
348 return []uint32{}
349 }
350 val, found := stateTree.Get(state)
351 if !found {
352 return []uint32{}
353 }
354
355 offset := (page - 1) * count
356 result := []uint32{}
357 val.(*btreeset.Uint32BTreeSet).IterateByOffset(offset, count, func(blueprintID uint32) bool {
358 result = append(result, blueprintID)
359 return false
360 })
361 return result
362}
363
364func (s *BlueprintStore) IsOwner(blueprintID uint32, user address) bool {
365 blueprint := s.mustGetInternal(blueprintID)
366 return address(blueprint["owner"]) == user
367}
368
369func (s *BlueprintStore) nextID() uint32 {
370 id := s.nextBlueprintID
371 s.nextBlueprintID++
372 return id
373}
374
375func (s *BlueprintStore) addOwnerIndex(blueprintID uint32, owner address) {
376 ownerKey := owner.String()
377 var tree *btreeset.Uint32BTreeSet
378 val, found := s.ownerIndex.Get(ownerKey)
379 if !found {
380 tree = btreeset.NewUint32BTreeSet(16)
381 s.ownerIndex.Set(ownerKey, tree)
382 } else {
383 tree = val.(*btreeset.Uint32BTreeSet)
384 }
385 tree.Set(blueprintID)
386}
387
388func (s *BlueprintStore) removeOwnerIndex(blueprintID uint32, owner address) {
389 val, found := s.ownerIndex.Get(owner.String())
390 if !found {
391 return
392 }
393 val.(*btreeset.Uint32BTreeSet).Remove(blueprintID)
394}
395
396func (s *BlueprintStore) addBiomeIndex(blueprintID uint32, biomeName string) {
397 var tree *btreeset.Uint32BTreeSet
398 val, found := s.biomeIndex.Get(biomeName)
399 if !found {
400 tree = btreeset.NewUint32BTreeSet(16)
401 s.biomeIndex.Set(biomeName, tree)
402 } else {
403 tree = val.(*btreeset.Uint32BTreeSet)
404 }
405 tree.Set(blueprintID)
406}
407
408func (s *BlueprintStore) removeBiomeIndex(blueprintID uint32, biomeName string) {
409 val, found := s.biomeIndex.Get(biomeName)
410 if !found {
411 return
412 }
413 val.(*btreeset.Uint32BTreeSet).Remove(blueprintID)
414}
415
416func (s *BlueprintStore) addStateIndex(blueprintID uint32, state string) {
417 var tree *btreeset.Uint32BTreeSet
418 val, found := s.stateIndex.Get(state)
419 if !found {
420 tree = btreeset.NewUint32BTreeSet(16)
421 s.stateIndex.Set(state, tree)
422 } else {
423 tree = val.(*btreeset.Uint32BTreeSet)
424 }
425 tree.Set(blueprintID)
426}
427
428func (s *BlueprintStore) removeStateIndex(blueprintID uint32, state string) {
429 val, found := s.stateIndex.Get(state)
430 if !found {
431 return
432 }
433 val.(*btreeset.Uint32BTreeSet).Remove(blueprintID)
434}
435
436func (s *BlueprintStore) addBiomeStateIndex(blueprintID uint32, biomeName string, state string) {
437 stateTree := s.getOrCreateBiomeStateTree(biomeName)
438
439 var set *btreeset.Uint32BTreeSet
440 val, found := stateTree.Get(state)
441 if !found {
442 set = btreeset.NewUint32BTreeSet(16)
443 stateTree.Set(state, set)
444 } else {
445 set = val.(*btreeset.Uint32BTreeSet)
446 }
447 set.Set(blueprintID)
448}
449
450func (s *BlueprintStore) removeBiomeStateIndex(blueprintID uint32, biomeName string, state string) {
451 stateTree, found := s.biomeStateTree(biomeName)
452 if !found {
453 return
454 }
455 val, found := stateTree.Get(state)
456 if !found {
457 return
458 }
459 val.(*btreeset.Uint32BTreeSet).Remove(blueprintID)
460}
461
462func (s *BlueprintStore) biomeStateTree(biomeName string) (*btree.StringBTree, bool) {
463 val, found := s.biomeStateIndex.Get(biomeName)
464 if !found {
465 return nil, false
466 }
467 return val.(*btree.StringBTree), true
468}
469
470func (s *BlueprintStore) getOrCreateBiomeStateTree(biomeName string) *btree.StringBTree {
471 tree, found := s.biomeStateTree(biomeName)
472 if found {
473 return tree
474 }
475 tree = btree.NewStringBTree(16)
476 s.biomeStateIndex.Set(biomeName, tree)
477 return tree
478}