storage.gno
5.98 Kb ยท 218 lines
1package block
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/nt/avl"
8)
9
10// personalWorldInstalled stores installed blocks in personal worlds
11// Key: worldId (e.g., "1", "42")
12// Value: map[chunkCoord]map[blockIndex]map[string]string
13// - chunkCoord: "{xIndex}_{zIndex}" (e.g., "0_0", "-1_2")
14// - blockIndex: block position within chunk (e.g., "100", "200")
15var personalWorldInstalled = avl.NewTree()
16
17// systemChunkInstalled stores installed blocks in system chunks
18// Key: chunkId (e.g., "sc-chunk-1", "alexandria")
19// Value: map[chunkCoord]map[blockIndex]map[string]string
20// - chunkCoord: "{xIndex}_{zIndex}" (e.g., "0_0", "-1_2")
21// - blockIndex: block position within chunk (e.g., "100", "200")
22var systemChunkInstalled = avl.NewTree()
23
24// mustGetInstalled gets installed block with panic
25func mustGetInstalled(position string) map[string]string {
26 store := getStore(position)
27 storeKey, chunkCoord, blockIndex := parsePosition(position)
28
29 world, found := store.Get(storeKey)
30 if !found {
31 panic("block not found at position: " + position)
32 }
33
34 worldMap := world.(map[string]map[string]map[string]string)
35 chunkMap, found := worldMap[chunkCoord]
36 if !found {
37 panic("block not found at position: " + position)
38 }
39
40 info, found := chunkMap[blockIndex]
41 if !found {
42 panic("block not found at position: " + position)
43 }
44
45 return info
46}
47
48// getInstalled gets installed block without panic (internal)
49func getInstalled(position string) (map[string]string, bool) {
50 store := getStore(position)
51 storeKey, chunkCoord, blockIndex := parsePosition(position)
52
53 world, found := store.Get(storeKey)
54 if !found {
55 return nil, false
56 }
57
58 worldMap := world.(map[string]map[string]map[string]string)
59 chunkMap, found := worldMap[chunkCoord]
60 if !found {
61 return nil, false
62 }
63
64 info, found := chunkMap[blockIndex]
65 if !found {
66 return nil, false
67 }
68
69 return info, true
70}
71
72// saveInstalled stores installed block info
73// Structure: blockId, content, option, installer, shape, state
74func saveInstalled(position string, blockID uint32, content string, option string, installer address, shape string, state string) map[string]string {
75 store := getStore(position)
76 storeKey, chunkCoord, blockIndex := parsePosition(position)
77 blockIDStr := blockIDToString(blockID)
78
79 info := map[string]string{
80 "blockId": blockIDStr,
81 "content": content,
82 "option": option,
83 "installer": installer.String(),
84 "shape": shape,
85 "state": state,
86 }
87
88 // Get or create world map
89 world, found := store.Get(storeKey)
90 if !found {
91 world = make(map[string]map[string]map[string]string)
92 store.Set(storeKey, world)
93 }
94 worldMap := world.(map[string]map[string]map[string]string)
95
96 // Get or create chunk map
97 chunkMap, found := worldMap[chunkCoord]
98 if !found {
99 chunkMap = make(map[string]map[string]string)
100 worldMap[chunkCoord] = chunkMap
101 }
102
103 // Set block info
104 chunkMap[blockIndex] = info
105
106 return info
107}
108
109// removeInstalled deletes installed block
110func removeInstalled(position string) {
111 store := getStore(position)
112 storeKey, chunkCoord, blockIndex := parsePosition(position)
113
114 world, found := store.Get(storeKey)
115 if !found {
116 return
117 }
118
119 worldMap := world.(map[string]map[string]map[string]string)
120 chunkMap, found := worldMap[chunkCoord]
121 if !found {
122 return
123 }
124
125 delete(chunkMap, blockIndex)
126
127 // Remove chunk entry if no more blocks
128 if len(chunkMap) == 0 {
129 delete(worldMap, chunkCoord)
130 }
131
132 // Remove world entry if no more chunks
133 if len(worldMap) == 0 {
134 store.Remove(storeKey)
135 }
136}
137
138// getStore returns the appropriate store based on position type
139func getStore(position string) *avl.Tree {
140 if isPersonalWorld(position) {
141 return personalWorldInstalled
142 }
143 if isSystemChunk(position) {
144 return systemChunkInstalled
145 }
146 panic("unknown position type: " + position)
147}
148
149// parsePosition parses position into its components
150// Format: "type|worldId|owner|xIndex|zIndex|blockIndex"
151// Returns: (storeKey, chunkCoord, blockIndex)
152// Example: "personal|1|g1abc...|0|0|100" -> ("1", "0_0", "100")
153// Example: "system|sc-1|g1abc...|0|0|100" -> ("sc-1", "0_0", "100")
154func parsePosition(position string) (storeKey string, chunkCoord string, blockIndex string) {
155 parts := strings.Split(position, "|")
156 if len(parts) != 6 {
157 panic("invalid position format: must be 'type|worldId|owner|xIndex|zIndex|blockIndex'")
158 }
159 // worldType := parts[0] // Used for store selection via getStore
160 worldId := parts[1]
161 // owner := parts[2] // Used for permission check, not storage
162 xIndex := parts[3]
163 zIndex := parts[4]
164 blockIndex = parts[5]
165
166 // storeKey: worldId only (type is handled by getStore)
167 storeKey = worldId
168 // chunkCoord: "{xIndex}_{zIndex}"
169 chunkCoord = xIndex + "_" + zIndex
170 return storeKey, chunkCoord, blockIndex
171}
172
173// isPersonalWorld checks if position is personal world
174func isPersonalWorld(position string) bool {
175 return strings.HasPrefix(position, "personal|")
176}
177
178// isSystemChunk checks if position is system chunk
179func isSystemChunk(position string) bool {
180 return strings.HasPrefix(position, "system|")
181}
182
183// extractPersonalWorldID extracts world ID from personal world position
184// Format: "personal|worldId|owner|xIndex|zIndex|blockIndex"
185// Panics if not personal world
186func extractPersonalWorldID(position string) uint32 {
187 if !isPersonalWorld(position) {
188 panic("not a personal world position: " + position)
189 }
190
191 parts := strings.Split(position, "|")
192 if len(parts) != 6 {
193 panic("invalid position format: " + position)
194 }
195
196 id, err := strconv.ParseUint(parts[1], 10, 32)
197 if err != nil {
198 panic("invalid world ID: " + parts[1])
199 }
200 return uint32(id)
201}
202
203// extractSystemChunkID extracts chunk token ID from system chunk position
204// Format: "system|worldId|owner|xIndex|zIndex|blockIndex"
205// Returns: "worldId:xIndex_zIndex" (chunk NFT token ID format)
206// Panics if not system chunk
207func extractSystemChunkID(position string) string {
208 if !isSystemChunk(position) {
209 panic("not a system chunk position: " + position)
210 }
211
212 parts := strings.Split(position, "|")
213 if len(parts) != 6 {
214 panic("invalid position format: " + position)
215 }
216
217 return parts[1] + ":" + parts[3] + "_" + parts[4]
218}