memo.gno
3.86 Kb ยท 134 lines
1// Package memo provides a simple memoization utility to cache function results.
2//
3// The package offers a Memoizer type that can cache function results based on keys,
4// with optional validation of cached values. This is useful for expensive computations
5// that need to be cached and potentially invalidated based on custom conditions.
6//
7// /!\ Important Warning for Gno Usage:
8// In Gno, storage updates only persist during transactions. This means:
9// - Cache entries created during queries will NOT persist
10// - Creating cache entries during queries will actually decrease performance
11// as it wastes resources trying to save data that won't be saved
12//
13// Best Practices:
14// - Use this pattern in transaction-driven contexts rather than query/render scenarios
15// - Consider controlled cache updates, e.g., by specific accounts (like oracles)
16// - Ideal for cases where cache updates happen every N blocks or on specific events
17// - Carefully evaluate if caching will actually improve performance in your use case
18//
19// Basic usage example:
20//
21// m := memo.New()
22//
23// // Cache expensive computation
24// result := m.Memoize("key", func() any {
25// // expensive operation
26// return "computed-value"
27// })
28//
29// // Subsequent calls with same key return cached result
30// result = m.Memoize("key", func() any {
31// // function won't be called, cached value is returned
32// return "computed-value"
33// })
34//
35// Example with validation:
36//
37// type TimestampedValue struct {
38// Value string
39// Timestamp time.Time
40// }
41//
42// m := memo.New()
43//
44// // Cache value with timestamp
45// result := m.MemoizeWithValidator(
46// "key",
47// func() any {
48// return TimestampedValue{
49// Value: "data",
50// Timestamp: time.Now(),
51// }
52// },
53// func(cached any) bool {
54// // Validate that the cached value is not older than 1 hour
55// if tv, ok := cached.(TimestampedValue); ok {
56// return time.Since(tv.Timestamp) < time.Hour
57// }
58// return false
59// },
60// )
61package memo
62
63import (
64 "gno.land/p/demo/btree"
65 "gno.land/p/demo/ufmt"
66)
67
68// Record implements the btree.Record interface for our cache entries
69type cacheEntry struct {
70 key any
71 value any
72}
73
74// Less implements btree.Record interface
75func (e cacheEntry) Less(than btree.Record) bool {
76 // Convert the other record to cacheEntry
77 other := than.(cacheEntry)
78 // Compare string representations of keys for consistent ordering
79 return ufmt.Sprintf("%v", e.key) < ufmt.Sprintf("%v", other.key)
80}
81
82// Memoizer is a structure to handle memoization of function results.
83type Memoizer struct {
84 cache *btree.BTree
85}
86
87// New creates a new Memoizer instance.
88func New() *Memoizer {
89 return &Memoizer{
90 cache: btree.New(),
91 }
92}
93
94// Memoize ensures the result of the given function is cached for the specified key.
95func (m *Memoizer) Memoize(key any, fn func() any) any {
96 entry := cacheEntry{key: key}
97 if found := m.cache.Get(entry); found != nil {
98 return found.(cacheEntry).value
99 }
100
101 value := fn()
102 m.cache.Insert(cacheEntry{key: key, value: value})
103 return value
104}
105
106// MemoizeWithValidator ensures the result is cached and valid according to the validator function.
107func (m *Memoizer) MemoizeWithValidator(key any, fn func() any, isValid func(any) bool) any {
108 entry := cacheEntry{key: key}
109 if found := m.cache.Get(entry); found != nil {
110 cachedEntry := found.(cacheEntry)
111 if isValid(cachedEntry.value) {
112 return cachedEntry.value
113 }
114 }
115
116 value := fn()
117 m.cache.Insert(cacheEntry{key: key, value: value})
118 return value
119}
120
121// Invalidate removes the cached value for the specified key.
122func (m *Memoizer) Invalidate(key any) {
123 m.cache.Delete(cacheEntry{key: key})
124}
125
126// Clear clears all cached values.
127func (m *Memoizer) Clear() {
128 m.cache.Clear(true)
129}
130
131// Size returns the number of items currently in the cache.
132func (m *Memoizer) Size() int {
133 return m.cache.Len()
134}