world_config_store.gno
4.27 Kb · 167 lines
1package personal_world
2
3import "gno.land/p/akkadia/v0/ds/btree"
4
5var worldConfigStore *WorldConfigStore = newWorldConfigStore()
6
7// WorldConfigStore owns personal world biome and size configuration records.
8//
9// Data shape:
10// - biomeInfos: biome(string) -> map[string]string
11// - sizeInfos: sizeID(string) -> map[string]string
12type WorldConfigStore struct {
13 biomeInfos *btree.StringBTree
14 defaultBiome string
15 sizeInfos *btree.StringBTree
16}
17
18func newWorldConfigStore() *WorldConfigStore {
19 return &WorldConfigStore{
20 biomeInfos: btree.NewStringBTree(32),
21 defaultBiome: "",
22 sizeInfos: btree.NewStringBTree(32),
23 }
24}
25
26// ==================== DANGER: MUTABLE MIGRATION CAPABILITIES ====================
27//
28// The getters in this section expose mutable store internals.
29// Only authorized migration exporter paths may depend on these capabilities.
30// Runtime reads and test setup must use copy-returning methods or total helpers.
31func (s *WorldConfigStore) BiomeInfos() *btree.StringBTree {
32 return s.biomeInfos
33}
34
35func (s *WorldConfigStore) SizeInfos() *btree.StringBTree {
36 return s.sizeInfos
37}
38
39// ================= END DANGER: MUTABLE MIGRATION CAPABILITIES ==================
40
41func (s *WorldConfigStore) SetBiomeInfo(info map[string]string) map[string]string {
42 biomeName := info["biome"]
43 s.biomeInfos.Set(biomeName, copyStringMap(info))
44 return copyStringMap(info)
45}
46
47func (s *WorldConfigStore) MustGetBiomeInfo(biomeName string) map[string]string {
48 info, found := s.GetBiomeInfo(biomeName)
49 if !found {
50 panic("biome not found: " + biomeName)
51 }
52 return info
53}
54
55func (s *WorldConfigStore) GetBiomeInfo(biomeName string) (map[string]string, bool) {
56 info, found := s.biomeInfos.Get(biomeName)
57 if !found {
58 return nil, false
59 }
60 return copyStringMap(info.(map[string]string)), true
61}
62
63func (s *WorldConfigStore) ListBiomeInfos() []map[string]string {
64 result := []map[string]string{}
65 s.biomeInfos.Iterate(nil, nil, func(_ string, info any) bool {
66 result = append(result, copyStringMap(info.(map[string]string)))
67 return false
68 })
69 return result
70}
71
72func (s *WorldConfigStore) HasBiomeInfo(biomeName string) bool {
73 return s.biomeInfos.Has(biomeName)
74}
75
76func (s *WorldConfigStore) SetDefaultBiome(biomeName string) string {
77 oldDefault := s.defaultBiome
78 s.defaultBiome = biomeName
79 return oldDefault
80}
81
82func (s *WorldConfigStore) DefaultBiome() string {
83 return s.defaultBiome
84}
85
86func (s *WorldConfigStore) TotalBiomes() int {
87 return s.biomeInfos.Size()
88}
89
90func (s *WorldConfigStore) SetSizeInfo(info map[string]string) map[string]string {
91 key := info["id"]
92 s.sizeInfos.Set(key, copyStringMap(info))
93 return copyStringMap(info)
94}
95
96func (s *WorldConfigStore) MustGetSizeInfo(sizeID string) map[string]string {
97 info, found := s.GetSizeInfo(sizeID)
98 if !found {
99 panic("size info not found: " + sizeID)
100 }
101 return info
102}
103
104func (s *WorldConfigStore) GetSizeInfo(sizeID string) (map[string]string, bool) {
105 info, found := s.sizeInfos.Get(sizeID)
106 if !found {
107 return nil, false
108 }
109 return copyStringMap(info.(map[string]string)), true
110}
111
112func (s *WorldConfigStore) ListSizeInfos() []map[string]string {
113 result := []map[string]string{}
114 s.sizeInfos.Iterate(nil, nil, func(_ string, info any) bool {
115 result = append(result, copyStringMap(info.(map[string]string)))
116 return false
117 })
118 return result
119}
120
121func (s *WorldConfigStore) TotalSizes() int {
122 return s.sizeInfos.Size()
123}
124
125func calculateExpansionCost(baseCost int64, priceMultiplierBPS int64) (int64, bool) {
126 if baseCost <= 0 {
127 return 0, true
128 }
129 if priceMultiplierBPS < 0 {
130 return maxInt64, false
131 }
132
133 denominator := int64(10000)
134 wholeAmount := baseCost / denominator
135 remainingAmount := baseCost % denominator
136 first, ok := checkedMultiply(wholeAmount, priceMultiplierBPS)
137 if !ok {
138 return maxInt64, false
139 }
140 second, ok := checkedMultiply(remainingAmount, priceMultiplierBPS)
141 if !ok {
142 return maxInt64, false
143 }
144 second = second / denominator
145 total, ok := checkedAdd(first, second)
146 if !ok {
147 return maxInt64, false
148 }
149 return total, true
150}
151
152func checkedMultiply(a int64, b int64) (int64, bool) {
153 if a == 0 || b == 0 {
154 return 0, true
155 }
156 if a < 0 || b < 0 || a > maxInt64/b {
157 return maxInt64, false
158 }
159 return a * b, true
160}
161
162func checkedAdd(a int64, b int64) (int64, bool) {
163 if a < 0 || b < 0 || a > maxInt64-b {
164 return maxInt64, false
165 }
166 return a + b, true
167}