query.go raw
1 package memory
2
3 import (
4 "bytes"
5 "encoding/json"
6
7 "git.mleku.dev/mleku/dendrite/pkg/ratio"
8 "github.com/dgraph-io/badger/v4"
9 )
10
11 // TypePoint is a single generation's type count for a tag.
12 type TypePoint struct {
13 Gen uint32
14 Count uint32
15 }
16
17 // MissingPoint is a single generation's missing count for a tag.
18 type MissingPoint struct {
19 Gen uint32
20 Count uint32
21 }
22
23 // FitnessPoint is a single generation's fitness score.
24 type FitnessPoint struct {
25 Gen uint32
26 Score ratio.Ratio
27 }
28
29 // HealthPoint is a single generation's health snapshot.
30 type HealthPoint struct {
31 Gen uint32
32 Occupied uint32
33 Total uint32
34 AvgLockIn ratio.Ratio
35 }
36
37 // HexPoint is a single generation's operation count.
38 type HexPoint struct {
39 Gen uint32
40 Count uint32
41 }
42
43 // QueryTypeTrend returns type counts for a tag across the last N generations,
44 // ordered by generation (ascending).
45 func (d *DB) QueryTypeTrend(tag string, lastN int) []TypePoint {
46 h := TagHash(tag)
47 prefix := TypPrefix(h)
48 return collectTypPoints(d.db, prefix, lastN)
49 }
50
51 // QueryMissingTrend returns missing counts for a tag across the last N
52 // generations, ordered by generation (ascending).
53 func (d *DB) QueryMissingTrend(tag string, lastN int) []MissingPoint {
54 h := TagHash(tag)
55 prefix := MisPrefix(h)
56 return collectMisPoints(d.db, prefix, lastN)
57 }
58
59 // QueryFitnessTrajectory returns overall fitness scores across the last N
60 // generations, ordered by generation (ascending).
61 func (d *DB) QueryFitnessTrajectory(lastN int) []FitnessPoint {
62 prefix := FitDimPrefix(DimOverall)
63 return collectFitPoints(d.db, prefix, lastN)
64 }
65
66 // QueryFitnessDimension returns scores for a specific fitness dimension
67 // across the last N generations.
68 func (d *DB) QueryFitnessDimension(dim byte, lastN int) []FitnessPoint {
69 prefix := FitDimPrefix(dim)
70 return collectFitPoints(d.db, prefix, lastN)
71 }
72
73 // QueryHealthHistory returns health snapshots across the last N generations,
74 // ordered by generation (ascending).
75 func (d *DB) QueryHealthHistory(lastN int) []HealthPoint {
76 prefix := PrefixHlt[:]
77 var points []HealthPoint
78
79 _ = d.db.View(func(txn *badger.Txn) error {
80 opts := badger.DefaultIteratorOptions
81 opts.Reverse = true
82 opts.Prefix = prefix
83 it := txn.NewIterator(opts)
84 defer it.Close()
85
86 // Seek to end of prefix range.
87 end := PrefixEnd(prefix)
88 it.Seek(end)
89
90 for it.Valid() {
91 item := it.Item()
92 key := item.Key()
93 if !bytes.HasPrefix(key, prefix) {
94 break
95 }
96 gen := DecodeHlt(key)
97 var occ, tot uint32
98 var num, denom int64
99 _ = item.Value(func(val []byte) error {
100 occ, tot, num, denom = DecodeHltValue(val)
101 return nil
102 })
103 points = append(points, HealthPoint{
104 Gen: gen,
105 Occupied: occ,
106 Total: tot,
107 AvgLockIn: ratio.New(num, denom),
108 })
109 if lastN > 0 && len(points) >= lastN {
110 break
111 }
112 it.Next()
113 }
114 return nil
115 })
116
117 // Reverse to ascending order.
118 reverse(points)
119 return points
120 }
121
122 // QueryBondCount returns the number of bonds for a tag in a specific generation.
123 func (d *DB) QueryBondCount(tag string, gen uint32) int {
124 h := TagHash(tag)
125 prefix := BndGenPrefix(h, gen)
126 count := 0
127
128 _ = d.db.View(func(txn *badger.Txn) error {
129 opts := badger.DefaultIteratorOptions
130 opts.PrefetchValues = false
131 opts.Prefix = prefix
132 it := txn.NewIterator(opts)
133 defer it.Close()
134
135 for it.Seek(prefix); it.Valid(); it.Next() {
136 if !bytes.HasPrefix(it.Item().Key(), prefix) {
137 break
138 }
139 count++
140 }
141 return nil
142 })
143 return count
144 }
145
146 // QueryBondHistory returns bond counts for a tag across the last N generations.
147 // Uses the bnd prefix to scan all generations and count per-gen entries.
148 func (d *DB) QueryBondHistory(tag string, lastN int) []TypePoint {
149 h := TagHash(tag)
150 prefix := BndPrefix(h)
151
152 // Collect all (gen → count) by scanning all bonds for this tag.
153 genCounts := make(map[uint32]uint32)
154
155 _ = d.db.View(func(txn *badger.Txn) error {
156 opts := badger.DefaultIteratorOptions
157 opts.PrefetchValues = false
158 opts.Prefix = prefix
159 it := txn.NewIterator(opts)
160 defer it.Close()
161
162 for it.Seek(prefix); it.Valid(); it.Next() {
163 key := it.Item().Key()
164 if !bytes.HasPrefix(key, prefix) {
165 break
166 }
167 _, gen, _ := DecodeBnd(key)
168 genCounts[gen]++
169 }
170 return nil
171 })
172
173 // Sort by generation descending, take lastN.
174 var points []TypePoint
175 for gen, count := range genCounts {
176 points = append(points, TypePoint{Gen: gen, Count: count})
177 }
178 // Sort ascending by gen.
179 sortTypePoints(points)
180
181 if lastN > 0 && len(points) > lastN {
182 points = points[len(points)-lastN:]
183 }
184 return points
185 }
186
187 // QueryHexagramOps returns operation counts for a specific operation across
188 // the last N generations.
189 func (d *DB) QueryHexagramOps(op byte, lastN int) []HexPoint {
190 prefix := make([]byte, 4)
191 copy(prefix, PrefixHex[:])
192 prefix[3] = op
193
194 var points []HexPoint
195
196 _ = d.db.View(func(txn *badger.Txn) error {
197 opts := badger.DefaultIteratorOptions
198 opts.Reverse = true
199 opts.Prefix = prefix
200 it := txn.NewIterator(opts)
201 defer it.Close()
202
203 end := PrefixEnd(prefix)
204 it.Seek(end)
205
206 for it.Valid() {
207 item := it.Item()
208 key := item.Key()
209 if !bytes.HasPrefix(key, prefix) {
210 break
211 }
212 _, gen := DecodeHex(key)
213 var count uint32
214 _ = item.Value(func(val []byte) error {
215 count = DecodeU32Value(val)
216 return nil
217 })
218 points = append(points, HexPoint{Gen: gen, Count: count})
219 if lastN > 0 && len(points) >= lastN {
220 break
221 }
222 it.Next()
223 }
224 return nil
225 })
226
227 reverse(points)
228 return points
229 }
230
231 // QueryGeneration returns generation metadata. Returns nil if not found.
232 func (d *DB) QueryGeneration(gen uint32) *genMeta {
233 key := GenKey(gen)
234 var meta genMeta
235 err := d.db.View(func(txn *badger.Txn) error {
236 item, err := txn.Get(key)
237 if err != nil {
238 return err
239 }
240 return item.Value(func(val []byte) error {
241 return json.Unmarshal(val, &meta)
242 })
243 })
244 if err != nil {
245 return nil
246 }
247 return &meta
248 }
249
250 // ADSRPoint is a single generation's ADSR phase distribution.
251 type ADSRPoint struct {
252 Gen uint32
253 Counts [4]uint32 // [Attack, Decay, Sustain, Release]
254 }
255
256 // QueryADSRHistory returns ADSR distributions across the last N generations,
257 // ordered by generation (ascending).
258 func (d *DB) QueryADSRHistory(lastN int) []ADSRPoint {
259 prefix := PrefixAdr[:]
260 var points []ADSRPoint
261
262 _ = d.db.View(func(txn *badger.Txn) error {
263 opts := badger.DefaultIteratorOptions
264 opts.Reverse = true
265 opts.Prefix = prefix
266 it := txn.NewIterator(opts)
267 defer it.Close()
268
269 end := PrefixEnd(prefix)
270 it.Seek(end)
271
272 for it.Valid() {
273 item := it.Item()
274 key := item.Key()
275 if !bytes.HasPrefix(key, prefix) {
276 break
277 }
278 gen := DecodeGen(key)
279 var counts [4]uint32
280 _ = item.Value(func(val []byte) error {
281 counts = DecodeAdrValue(val)
282 return nil
283 })
284 points = append(points, ADSRPoint{Gen: gen, Counts: counts})
285 if lastN > 0 && len(points) >= lastN {
286 break
287 }
288 it.Next()
289 }
290 return nil
291 })
292
293 reverse(points)
294 return points
295 }
296
297 // --- internal helpers ---
298
299 func collectTypPoints(db *badger.DB, prefix []byte, lastN int) []TypePoint {
300 var points []TypePoint
301
302 _ = db.View(func(txn *badger.Txn) error {
303 opts := badger.DefaultIteratorOptions
304 opts.Reverse = true
305 opts.PrefetchValues = false
306 opts.Prefix = prefix
307 it := txn.NewIterator(opts)
308 defer it.Close()
309
310 end := PrefixEnd(prefix)
311 it.Seek(end)
312
313 for it.Valid() {
314 key := it.Item().Key()
315 if !bytes.HasPrefix(key, prefix) {
316 break
317 }
318 _, count, gen := DecodeTyp(key)
319 points = append(points, TypePoint{Gen: gen, Count: count})
320 if lastN > 0 && len(points) >= lastN {
321 break
322 }
323 it.Next()
324 }
325 return nil
326 })
327
328 reverse(points)
329 return points
330 }
331
332 func collectMisPoints(db *badger.DB, prefix []byte, lastN int) []MissingPoint {
333 var points []MissingPoint
334
335 _ = db.View(func(txn *badger.Txn) error {
336 opts := badger.DefaultIteratorOptions
337 opts.Reverse = true
338 opts.PrefetchValues = false
339 opts.Prefix = prefix
340 it := txn.NewIterator(opts)
341 defer it.Close()
342
343 end := PrefixEnd(prefix)
344 it.Seek(end)
345
346 for it.Valid() {
347 key := it.Item().Key()
348 if !bytes.HasPrefix(key, prefix) {
349 break
350 }
351 _, gen, count := DecodeMis(key)
352 points = append(points, MissingPoint{Gen: gen, Count: count})
353 if lastN > 0 && len(points) >= lastN {
354 break
355 }
356 it.Next()
357 }
358 return nil
359 })
360
361 reverse(points)
362 return points
363 }
364
365 func collectFitPoints(db *badger.DB, prefix []byte, lastN int) []FitnessPoint {
366 var points []FitnessPoint
367
368 _ = db.View(func(txn *badger.Txn) error {
369 opts := badger.DefaultIteratorOptions
370 opts.Reverse = true
371 opts.Prefix = prefix
372 it := txn.NewIterator(opts)
373 defer it.Close()
374
375 end := PrefixEnd(prefix)
376 it.Seek(end)
377
378 for it.Valid() {
379 item := it.Item()
380 key := item.Key()
381 if !bytes.HasPrefix(key, prefix) {
382 break
383 }
384 _, gen := DecodeFit(key)
385 var num, denom int64
386 _ = item.Value(func(val []byte) error {
387 num, denom = DecodeFitValue(val)
388 return nil
389 })
390 points = append(points, FitnessPoint{
391 Gen: gen,
392 Score: ratio.New(num, denom),
393 })
394 if lastN > 0 && len(points) >= lastN {
395 break
396 }
397 it.Next()
398 }
399 return nil
400 })
401
402 reverse(points)
403 return points
404 }
405
406 // reverse reverses a slice in place. Works with any slice type via generics.
407 func reverse[T any](s []T) {
408 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
409 s[i], s[j] = s[j], s[i]
410 }
411 }
412
413 // sortTypePoints sorts by generation ascending (insertion sort, small slices).
414 func sortTypePoints(pts []TypePoint) {
415 for i := 1; i < len(pts); i++ {
416 for j := i; j > 0 && pts[j].Gen < pts[j-1].Gen; j-- {
417 pts[j], pts[j-1] = pts[j-1], pts[j]
418 }
419 }
420 }
421