Search Apps Documentation Source Content File Folder Download Copy Actions Download

world_store.gno

5.06 Kb · 192 lines
  1package chunk
  2
  3import "gno.land/p/akkadia/v0/ds/btree"
  4
  5// WorldStore owns chunk world records and secondary indexes.
  6//
  7// Data shape:
  8// - worlds: worldID(uint32) -> map[string]string
  9// - slugIndex: slug(string) -> worldID(uint32)
 10// - nameIndex: name(string) -> worldID(uint32)
 11//
 12// Responsibilities:
 13// - store canonical world records by world ID
 14// - keep slug/name indexes in sync with record mutations
 15// - protect store-owned invariants such as duplicate world IDs, names, and slugs
 16// - return defensive copies from public read methods
 17//
 18// Non-responsibilities:
 19// - validate external parameter shape such as slug format, seed range, CSV payloads, or pagination
 20// - interpret or semantically validate properties; properties are canonical map entries
 21// - perform auth, freeze, migration, or event handling
 22// - parse user-facing encoded input; public exported functions must pass canonical values
 23type WorldStore struct {
 24	worlds    *btree.Uint32BTree
 25	slugIndex *btree.StringBTree
 26	nameIndex *btree.StringBTree
 27}
 28
 29var worldStore = newWorldStore()
 30
 31func newWorldStore() *WorldStore {
 32	return &WorldStore{
 33		worlds:    btree.NewUint32BTree(32),
 34		slugIndex: btree.NewStringBTree(32),
 35		nameIndex: btree.NewStringBTree(32),
 36	}
 37}
 38
 39// ==================== DANGER: MUTABLE MIGRATION CAPABILITIES ====================
 40//
 41// The getters in this section expose mutable store internals.
 42// Only authorized migration exporter paths may depend on these capabilities.
 43// Runtime reads and test setup must use copy-returning methods or total helpers.
 44func (s *WorldStore) Worlds() *btree.Uint32BTree {
 45	return s.worlds
 46}
 47
 48func (s *WorldStore) SlugIndex() *btree.StringBTree {
 49	return s.slugIndex
 50}
 51
 52func (s *WorldStore) NameIndex() *btree.StringBTree {
 53	return s.nameIndex
 54}
 55
 56// ================= END DANGER: MUTABLE MIGRATION CAPABILITIES ==================
 57
 58func (s *WorldStore) Create(worldID uint32, world map[string]string) map[string]string {
 59	name := world["name"]
 60	slug := world["slug"]
 61	if _, found := s.worlds.Get(worldID); found {
 62		panic("world already exists: " + formatWorldID(worldID))
 63	}
 64	if s.nameIndex.Has(name) {
 65		panic("name already exists: " + name)
 66	}
 67	if s.slugIndex.Has(slug) {
 68		panic("slug already exists: " + slug)
 69	}
 70
 71	stored := copyStringMap(world)
 72	stored["id"] = formatWorldID(worldID)
 73	s.worlds.Set(worldID, stored)
 74	s.slugIndex.Set(slug, worldID)
 75	s.nameIndex.Set(name, worldID)
 76	return copyStringMap(stored)
 77}
 78
 79func (s *WorldStore) Update(worldID uint32, updates map[string]string) map[string]string {
 80	current := s.mustGetInternal(worldID)
 81	oldName := current["name"]
 82	newName := oldName
 83	if name, found := updates["name"]; found {
 84		newName = name
 85	}
 86	oldSlug := current["slug"]
 87	newSlug := oldSlug
 88	if slug, found := updates["slug"]; found {
 89		newSlug = slug
 90	}
 91	renameName := newName != oldName
 92	renameSlug := newSlug != oldSlug
 93
 94	if renameName {
 95		if s.nameIndex.Has(newName) {
 96			panic("name already exists: " + newName)
 97		}
 98	}
 99	if renameSlug {
100		if s.slugIndex.Has(newSlug) {
101			panic("slug already exists: " + newSlug)
102		}
103	}
104
105	updated := copyStringMap(current)
106	for key, value := range updates {
107		updated[key] = value
108	}
109	updated["id"] = formatWorldID(worldID)
110
111	if renameName {
112		s.nameIndex.Remove(oldName)
113		s.nameIndex.Set(newName, worldID)
114	}
115	if renameSlug {
116		s.slugIndex.Remove(oldSlug)
117		s.slugIndex.Set(newSlug, worldID)
118	}
119
120	s.worlds.Set(worldID, updated)
121	return copyStringMap(updated)
122}
123
124func (s *WorldStore) MustGet(worldID uint32) map[string]string {
125	return copyStringMap(s.mustGetInternal(worldID))
126}
127
128func (s *WorldStore) AssertExists(worldID uint32) {
129	if !s.worlds.Has(worldID) {
130		panic("world not found: " + formatWorldID(worldID))
131	}
132}
133
134func (s *WorldStore) Has(worldID uint32) bool {
135	return s.worlds.Has(worldID)
136}
137
138func (s *WorldStore) IsNameAvailable(name string) bool {
139	return !s.nameIndex.Has(name)
140}
141
142func (s *WorldStore) IsSlugAvailable(slug string) bool {
143	return !s.slugIndex.Has(slug)
144}
145
146func (s *WorldStore) mustGetInternal(worldID uint32) map[string]string {
147	s.AssertExists(worldID)
148	value, _ := s.worlds.Get(worldID)
149	return value.(map[string]string)
150}
151
152func (s *WorldStore) Get(worldID uint32) (map[string]string, bool) {
153	value, found := s.worlds.Get(worldID)
154	if !found {
155		return nil, false
156	}
157	return copyStringMap(value.(map[string]string)), true
158}
159
160func (s *WorldStore) MustGetBySlug(slug string) map[string]string {
161	worldID, found := s.slugIndex.Get(slug)
162	if !found {
163		panic("world not found by slug: " + slug)
164	}
165	return s.MustGet(worldID.(uint32))
166}
167
168func (s *WorldStore) Total() int {
169	return s.worlds.Size()
170}
171
172func (s *WorldStore) List(page int, count int) []map[string]string {
173	offset := (page - 1) * count
174	result := []map[string]string{}
175	s.worlds.IterateByOffset(offset, count, func(_ uint32, value any) bool {
176		result = append(result, copyStringMap(value.(map[string]string)))
177		return false
178	})
179	return result
180}
181
182func (s *WorldStore) ListByIDs(worldIDs ...uint32) []map[string]string {
183	result := []map[string]string{}
184	for _, worldID := range worldIDs {
185		world, found := s.Get(worldID)
186		if !found {
187			continue
188		}
189		result = append(result, world)
190	}
191	return result
192}