summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2022-11-11 12:18:38 +0100
committerLibravatar GitHub <noreply@github.com>2022-11-11 12:18:38 +0100
commitedcee14d07bae129e2d1a06d99c30fc6f659ff5e (patch)
tree5b9d605654347fe104c55bf4b0e7fb1e1533e2a0 /vendor/codeberg.org/gruf
parent[feature] S3: add config flag to proxy S3 media (#1014) (diff)
downloadgotosocial-edcee14d07bae129e2d1a06d99c30fc6f659ff5e.tar.xz
[feature] Read + Write tombstones for deleted Actors (#1005)
* [feature] Read + Write tombstones for deleted Actors * copyTombstone * update to use resultcache instead of old ttl cache Signed-off-by: kim <grufwub@gmail.com> * update go-cache library to fix result cache capacity / ordering bugs Signed-off-by: kim <grufwub@gmail.com> * bump go-cache/v3 to v3.1.6 to fix bugs Signed-off-by: kim <grufwub@gmail.com> * switch on status code * better explain ErrGone reasoning Signed-off-by: kim <grufwub@gmail.com> Co-authored-by: kim <grufwub@gmail.com>
Diffstat (limited to 'vendor/codeberg.org/gruf')
-rw-r--r--vendor/codeberg.org/gruf/go-cache/v3/LICENSE9
-rw-r--r--vendor/codeberg.org/gruf/go-cache/v3/result/cache.go341
-rw-r--r--vendor/codeberg.org/gruf/go-cache/v3/result/error.go22
-rw-r--r--vendor/codeberg.org/gruf/go-cache/v3/result/key.go184
-rw-r--r--vendor/codeberg.org/gruf/go-cache/v3/ttl/schedule.go20
-rw-r--r--vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go412
-rw-r--r--vendor/codeberg.org/gruf/go-mangler/LICENSE9
-rw-r--r--vendor/codeberg.org/gruf/go-mangler/README.md40
-rw-r--r--vendor/codeberg.org/gruf/go-mangler/helpers.go97
-rw-r--r--vendor/codeberg.org/gruf/go-mangler/load.go354
-rw-r--r--vendor/codeberg.org/gruf/go-mangler/mangle.go132
-rw-r--r--vendor/codeberg.org/gruf/go-mangler/manglers.go264
-rw-r--r--vendor/codeberg.org/gruf/go-maps/LICENSE9
-rw-r--r--vendor/codeberg.org/gruf/go-maps/README.md7
-rw-r--r--vendor/codeberg.org/gruf/go-maps/common.go289
-rw-r--r--vendor/codeberg.org/gruf/go-maps/list.go154
-rw-r--r--vendor/codeberg.org/gruf/go-maps/lru.go153
-rw-r--r--vendor/codeberg.org/gruf/go-maps/ordered.go159
18 files changed, 2655 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/LICENSE b/vendor/codeberg.org/gruf/go-cache/v3/LICENSE
new file mode 100644
index 000000000..e4163ae35
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-cache/v3/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2022 gruf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/result/cache.go b/vendor/codeberg.org/gruf/go-cache/v3/result/cache.go
new file mode 100644
index 000000000..69f5593e3
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-cache/v3/result/cache.go
@@ -0,0 +1,341 @@
+package result
+
+import (
+ "reflect"
+ "time"
+
+ "codeberg.org/gruf/go-cache/v3/ttl"
+)
+
+// Cache ...
+type Cache[Value any] struct {
+ cache ttl.Cache[int64, result[Value]] // underlying result cache
+ lookups structKeys // pre-determined struct lookups
+ copy func(Value) Value // copies a Value type
+ next int64 // update key counter
+}
+
+// New returns a new initialized Cache, with given lookups and underlying value copy function.
+func New[Value any](lookups []string, copy func(Value) Value) *Cache[Value] {
+ return NewSized(lookups, copy, 64)
+}
+
+// NewSized returns a new initialized Cache, with given lookups, underlying value copy function and provided capacity.
+func NewSized[Value any](lookups []string, copy func(Value) Value, cap int) *Cache[Value] {
+ var z Value
+
+ // Determine generic type
+ t := reflect.TypeOf(z)
+
+ // Iteratively deref pointer type
+ for t.Kind() == reflect.Pointer {
+ t = t.Elem()
+ }
+
+ // Ensure that this is a struct type
+ if t.Kind() != reflect.Struct {
+ panic("generic parameter type must be struct (or ptr to)")
+ }
+
+ // Allocate new cache object
+ c := &Cache[Value]{copy: copy}
+ c.lookups = make([]keyFields, len(lookups))
+
+ for i, lookup := range lookups {
+ // Generate keyed field info for lookup
+ c.lookups[i].pkeys = make(map[string]int64, cap)
+ c.lookups[i].lookup = lookup
+ c.lookups[i].populate(t)
+ }
+
+ // Create and initialize underlying cache
+ c.cache.Init(0, cap, 0)
+ c.SetEvictionCallback(nil)
+ c.SetInvalidateCallback(nil)
+ return c
+}
+
+// Start will start the cache background eviction routine with given sweep frequency. If already
+// running or a freq <= 0 provided, this is a no-op. This will block until eviction routine started.
+func (c *Cache[Value]) Start(freq time.Duration) bool {
+ return c.cache.Start(freq)
+}
+
+// Stop will stop cache background eviction routine. If not running this
+// is a no-op. This will block until the eviction routine has stopped.
+func (c *Cache[Value]) Stop() bool {
+ return c.cache.Stop()
+}
+
+// SetTTL sets the cache item TTL. Update can be specified to force updates of existing items
+// in the cache, this will simply add the change in TTL to their current expiry time.
+func (c *Cache[Value]) SetTTL(ttl time.Duration, update bool) {
+ c.cache.SetTTL(ttl, update)
+}
+
+// SetEvictionCallback sets the eviction callback to the provided hook.
+func (c *Cache[Value]) SetEvictionCallback(hook func(Value)) {
+ if hook == nil {
+ // Ensure non-nil hook.
+ hook = func(Value) {}
+ }
+ c.cache.SetEvictionCallback(func(item *ttl.Entry[int64, result[Value]]) {
+ for _, key := range item.Value.Keys {
+ // Delete key->pkey lookup
+ pkeys := key.fields.pkeys
+ delete(pkeys, key.value)
+ }
+
+ if item.Value.Error != nil {
+ // Skip error hooks
+ return
+ }
+
+ // Call user hook.
+ hook(item.Value.Value)
+ })
+}
+
+// SetInvalidateCallback sets the invalidate callback to the provided hook.
+func (c *Cache[Value]) SetInvalidateCallback(hook func(Value)) {
+ if hook == nil {
+ // Ensure non-nil hook.
+ hook = func(Value) {}
+ }
+ c.cache.SetInvalidateCallback(func(item *ttl.Entry[int64, result[Value]]) {
+ for _, key := range item.Value.Keys {
+ if key.fields != nil {
+ // Delete key->pkey lookup
+ pkeys := key.fields.pkeys
+ delete(pkeys, key.value)
+ }
+ }
+
+ if item.Value.Error != nil {
+ // Skip error hooks
+ return
+ }
+
+ // Call user hook.
+ hook(item.Value.Value)
+ })
+}
+
+// Load ...
+func (c *Cache[Value]) Load(lookup string, load func() (Value, error), keyParts ...any) (Value, error) {
+ var (
+ zero Value
+ res result[Value]
+ )
+
+ // Get lookup map by name.
+ kfields := c.getFields(lookup)
+ lmap := kfields.pkeys
+
+ // Generate cache key string.
+ ckey := genkey(keyParts...)
+
+ // Acquire cache lock
+ c.cache.Lock()
+
+ // Look for primary key
+ pkey, ok := lmap[ckey]
+
+ if ok {
+ // Fetch the result for primary key
+ entry, _ := c.cache.Cache.Get(pkey)
+ res = entry.Value
+ }
+
+ // Done with lock
+ c.cache.Unlock()
+
+ if !ok {
+ // Generate new result from fresh load.
+ res.Value, res.Error = load()
+
+ if res.Error != nil {
+ // This load returned an error, only
+ // store this item under provided key.
+ res.Keys = []cacheKey{{
+ value: ckey,
+ fields: kfields,
+ }}
+ } else {
+ // This was a successful load, generate keys.
+ res.Keys = c.lookups.generate(res.Value)
+ }
+
+ // Acquire cache lock.
+ c.cache.Lock()
+ defer c.cache.Unlock()
+
+ // Attempt to cache this result.
+ if key, ok := c.storeResult(res); !ok {
+ return zero, ConflictError{key}
+ }
+ }
+
+ // Catch and return error
+ if res.Error != nil {
+ return zero, res.Error
+ }
+
+ // Return a copy of value from cache
+ return c.copy(res.Value), nil
+}
+
+// Store ...
+func (c *Cache[Value]) Store(value Value, store func() error) error {
+ // Attempt to store this value.
+ if err := store(); err != nil {
+ return err
+ }
+
+ // Prepare cached result.
+ result := result[Value]{
+ Keys: c.lookups.generate(value),
+ Value: c.copy(value),
+ Error: nil,
+ }
+
+ // Acquire cache lock.
+ c.cache.Lock()
+ defer c.cache.Unlock()
+
+ // Attempt to cache result, only return conflict
+ // error if the appropriate flag has been set.
+ if key, ok := c.storeResult(result); !ok {
+ return ConflictError{key}
+ }
+
+ return nil
+}
+
+// Has ...
+func (c *Cache[Value]) Has(lookup string, keyParts ...any) bool {
+ var res result[Value]
+
+ // Get lookup map by name.
+ kfields := c.getFields(lookup)
+ lmap := kfields.pkeys
+
+ // Generate cache key string.
+ ckey := genkey(keyParts...)
+
+ // Acquire cache lock
+ c.cache.Lock()
+
+ // Look for primary key
+ pkey, ok := lmap[ckey]
+
+ if ok {
+ // Fetch the result for primary key
+ entry, _ := c.cache.Cache.Get(pkey)
+ res = entry.Value
+ }
+
+ // Done with lock
+ c.cache.Unlock()
+
+ // Check for non-error result.
+ return ok && (res.Error == nil)
+}
+
+// Invalidate ...
+func (c *Cache[Value]) Invalidate(lookup string, keyParts ...any) {
+ // Get lookup map by name.
+ kfields := c.getFields(lookup)
+ lmap := kfields.pkeys
+
+ // Generate cache key string.
+ ckey := genkey(keyParts...)
+
+ // Look for primary key
+ c.cache.Lock()
+ pkey, ok := lmap[ckey]
+ c.cache.Unlock()
+
+ if !ok {
+ return
+ }
+
+ // Invalid by primary key
+ c.cache.Invalidate(pkey)
+}
+
+// Clear empties the cache, calling the invalidate callback.
+func (c *Cache[Value]) Clear() {
+ c.cache.Clear()
+}
+
+// Len ...
+func (c *Cache[Value]) Len() int {
+ return c.cache.Cache.Len()
+}
+
+// Cap ...
+func (c *Cache[Value]) Cap() int {
+ return c.cache.Cache.Cap()
+}
+
+func (c *Cache[Value]) getFields(name string) *keyFields {
+ for _, k := range c.lookups {
+ // Find key fields with name
+ if k.lookup == name {
+ return &k
+ }
+ }
+ panic("invalid lookup: " + name)
+}
+
+func (c *Cache[Value]) storeResult(res result[Value]) (string, bool) {
+ for _, key := range res.Keys {
+ pkeys := key.fields.pkeys
+
+ // Look for cache primary key
+ pkey, ok := pkeys[key.value]
+
+ if ok {
+ // Look for overlap with non error keys,
+ // as an overlap for some but not all keys
+ // could produce inconsistent results.
+ entry, _ := c.cache.Cache.Get(pkey)
+ if entry.Value.Error == nil {
+ return key.value, false
+ }
+ }
+ }
+
+ // Get primary key
+ pkey := c.next
+ c.next++
+
+ // Store all primary key lookups
+ for _, key := range res.Keys {
+ pkeys := key.fields.pkeys
+ pkeys[key.value] = pkey
+ }
+
+ // Store main entry under primary key, using evict hook if needed
+ c.cache.Cache.SetWithHook(pkey, &ttl.Entry[int64, result[Value]]{
+ Expiry: time.Now().Add(c.cache.TTL),
+ Key: pkey,
+ Value: res,
+ }, func(_ int64, item *ttl.Entry[int64, result[Value]]) {
+ c.cache.Evict(item)
+ })
+
+ return "", true
+}
+
+type result[Value any] struct {
+ // keys accessible under
+ Keys []cacheKey
+
+ // cached value
+ Value Value
+
+ // cached error
+ Error error
+}
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/result/error.go b/vendor/codeberg.org/gruf/go-cache/v3/result/error.go
new file mode 100644
index 000000000..fa26083bf
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-cache/v3/result/error.go
@@ -0,0 +1,22 @@
+package result
+
+import "errors"
+
+// ErrUnkownLookup ...
+var ErrUnknownLookup = errors.New("unknown lookup identifier")
+
+// IsConflictErr returns whether error is due to key conflict.
+func IsConflictErr(err error) bool {
+ _, ok := err.(ConflictError)
+ return ok
+}
+
+// ConflictError is returned on cache key conflict.
+type ConflictError struct {
+ Key string
+}
+
+// Error returns the message for this key conflict error.
+func (c ConflictError) Error() string {
+ return "cache conflict for key \"" + c.Key + "\""
+}
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/result/key.go b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go
new file mode 100644
index 000000000..ec58e0ef9
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-cache/v3/result/key.go
@@ -0,0 +1,184 @@
+package result
+
+import (
+ "reflect"
+ "strings"
+ "sync"
+ "unicode"
+ "unicode/utf8"
+
+ "codeberg.org/gruf/go-byteutil"
+ "codeberg.org/gruf/go-mangler"
+)
+
+// structKeys provides convience methods for a list
+// of struct field combinations used for cache keys.
+type structKeys []keyFields
+
+// get fetches the key-fields for given lookup (else, panics).
+func (sk structKeys) get(lookup string) *keyFields {
+ for i := range sk {
+ if sk[i].lookup == lookup {
+ return &sk[i]
+ }
+ }
+ panic("unknown lookup: \"" + lookup + "\"")
+}
+
+// generate will calculate the value string for each required
+// cache key as laid-out by the receiving structKeys{}.
+func (sk structKeys) generate(a any) []cacheKey {
+ // Get reflected value in order
+ // to access the struct fields
+ v := reflect.ValueOf(a)
+
+ // Iteratively deref pointer value
+ for v.Kind() == reflect.Pointer {
+ if v.IsNil() {
+ panic("nil ptr")
+ }
+ v = v.Elem()
+ }
+
+ // Preallocate expected slice of keys
+ keys := make([]cacheKey, len(sk))
+
+ // Acquire byte buffer
+ buf := bufpool.Get().(*byteutil.Buffer)
+ defer bufpool.Put(buf)
+
+ for i := range sk {
+ // Reset buffer
+ buf.B = buf.B[:0]
+
+ // Set the key-fields reference
+ keys[i].fields = &sk[i]
+
+ // Calculate cache-key value
+ keys[i].populate(buf, v)
+ }
+
+ return keys
+}
+
+// cacheKey represents an actual cache key.
+type cacheKey struct {
+ // value is the actual string representing
+ // this cache key for hashmap lookups.
+ value string
+
+ // fieldsRO is a read-only slice (i.e. we should
+ // NOT be modifying them, only using for reference)
+ // of struct fields encapsulated by this cache key.
+ fields *keyFields
+}
+
+// populate will calculate the cache key's value string for given
+// value's reflected information. Passed encoder is for string building.
+func (k *cacheKey) populate(buf *byteutil.Buffer, v reflect.Value) {
+ // Append each field value to buffer.
+ for _, idx := range k.fields.fields {
+ fv := v.Field(idx)
+ fi := fv.Interface()
+ buf.B = mangler.Append(buf.B, fi)
+ buf.B = append(buf.B, '.')
+ }
+
+ // Drop last '.'
+ buf.Truncate(1)
+
+ // Create string copy from buf
+ k.value = string(buf.B)
+}
+
+// keyFields represents a list of struct fields
+// encompassed in a single cache key, the string name
+// of the lookup, and the lookup map to primary keys.
+type keyFields struct {
+ // lookup is the calculated (well, provided)
+ // cache key lookup, consisting of dot sep'd
+ // struct field names.
+ lookup string
+
+ // fields is a slice of runtime struct field
+ // indices, of the fields encompassed by this key.
+ fields []int
+
+ // pkeys is a lookup of stored struct key values
+ // to the primary cache lookup key (int64).
+ pkeys map[string]int64
+}
+
+// populate will populate this keyFields{} object's .fields member by determining
+// the field names from the given lookup, and querying given reflected type to get
+// the runtime field indices for each of the fields. this speeds-up future value lookups.
+func (kf *keyFields) populate(t reflect.Type) {
+ // Split dot-separated lookup to get
+ // the individual struct field names
+ names := strings.Split(kf.lookup, ".")
+ if len(names) == 0 {
+ panic("no key fields specified")
+ }
+
+ // Pre-allocate slice of expected length
+ kf.fields = make([]int, len(names))
+
+ for i, name := range names {
+ // Get field info for given name
+ ft, ok := t.FieldByName(name)
+ if !ok {
+ panic("no field found for name: \"" + name + "\"")
+ }
+
+ // Check field is usable
+ if !isExported(name) {
+ panic("field must be exported")
+ }
+
+ // Set the runtime field index
+ kf.fields[i] = ft.Index[0]
+ }
+}
+
+// genkey generates a cache key for given key values.
+func genkey(parts ...any) string {
+ if len(parts) < 1 {
+ // Panic to prevent annoying usecase
+ // where user forgets to pass lookup
+ // and instead only passes a key part,
+ // e.g. cache.Get("key")
+ // which then always returns false.
+ panic("no key parts provided")
+ }
+
+ // Acquire buffer and reset
+ buf := bufpool.Get().(*byteutil.Buffer)
+ defer bufpool.Put(buf)
+ buf.Reset()
+
+ // Encode each key part
+ for _, part := range parts {
+ buf.B = mangler.Append(buf.B, part)
+ buf.B = append(buf.B, '.')
+ }
+
+ // Drop last '.'
+ buf.Truncate(1)
+
+ // Return string copy
+ return string(buf.B)
+}
+
+// isExported checks whether function name is exported.
+func isExported(fnName string) bool {
+ r, _ := utf8.DecodeRuneInString(fnName)
+ return unicode.IsUpper(r)
+}
+
+// bufpool provides a memory pool of byte
+// buffers use when encoding key types.
+var bufpool = sync.Pool{
+ New: func() any {
+ return &byteutil.Buffer{B: make([]byte, 0, 512)}
+ },
+}
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/ttl/schedule.go b/vendor/codeberg.org/gruf/go-cache/v3/ttl/schedule.go
new file mode 100644
index 000000000..111de0757
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-cache/v3/ttl/schedule.go
@@ -0,0 +1,20 @@
+package ttl
+
+import (
+ "time"
+
+ "codeberg.org/gruf/go-sched"
+)
+
+// scheduler is the global cache runtime scheduler
+// for handling regular cache evictions.
+var scheduler sched.Scheduler
+
+// schedule will given sweep routine to the global scheduler, and start global scheduler.
+func schedule(sweep func(time.Time), freq time.Duration) func() {
+ if !scheduler.Running() {
+ // ensure running
+ _ = scheduler.Start()
+ }
+ return scheduler.Schedule(sched.NewJob(sweep).Every(freq))
+}
diff --git a/vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go b/vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go
new file mode 100644
index 000000000..f830ed3d2
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go
@@ -0,0 +1,412 @@
+package ttl
+
+import (
+ "sync"
+ "time"
+
+ "codeberg.org/gruf/go-maps"
+)
+
+// Entry represents an item in the cache, with it's currently calculated Expiry time.
+type Entry[Key comparable, Value any] struct {
+ Key Key
+ Value Value
+ Expiry time.Time
+}
+
+// Cache is the underlying Cache implementation, providing both the base Cache interface and unsafe access to underlying map to allow flexibility in building your own.
+type Cache[Key comparable, Value any] struct {
+ // TTL is the cache item TTL.
+ TTL time.Duration
+
+ // Evict is the hook that is called when an item is evicted from the cache, includes manual delete.
+ Evict func(*Entry[Key, Value])
+
+ // Invalid is the hook that is called when an item's data in the cache is invalidated.
+ Invalid func(*Entry[Key, Value])
+
+ // Cache is the underlying hashmap used for this cache.
+ Cache maps.LRUMap[Key, *Entry[Key, Value]]
+
+ // stop is the eviction routine cancel func.
+ stop func()
+
+ // pool is a memory pool of entry objects.
+ pool []*Entry[Key, Value]
+
+ // Embedded mutex.
+ sync.Mutex
+}
+
+// New returns a new initialized Cache with given initial length, maximum capacity and item TTL.
+func New[K comparable, V any](len, cap int, ttl time.Duration) *Cache[K, V] {
+ c := new(Cache[K, V])
+ c.Init(len, cap, ttl)
+ return c
+}
+
+// Init will initialize this cache with given initial length, maximum capacity and item TTL.
+func (c *Cache[K, V]) Init(len, cap int, ttl time.Duration) {
+ if ttl <= 0 {
+ // Default duration
+ ttl = time.Second * 5
+ }
+ c.TTL = ttl
+ c.SetEvictionCallback(nil)
+ c.SetInvalidateCallback(nil)
+ c.Cache.Init(len, cap)
+}
+
+// Start: implements cache.Cache's Start().
+func (c *Cache[K, V]) Start(freq time.Duration) (ok bool) {
+ // Nothing to start
+ if freq <= 0 {
+ return false
+ }
+
+ // Safely start
+ c.Lock()
+
+ if ok = c.stop == nil; ok {
+ // Not yet running, schedule us
+ c.stop = schedule(c.Sweep, freq)
+ }
+
+ // Done with lock
+ c.Unlock()
+
+ return
+}
+
+// Stop: implements cache.Cache's Stop().
+func (c *Cache[K, V]) Stop() (ok bool) {
+ // Safely stop
+ c.Lock()
+
+ if ok = c.stop != nil; ok {
+ // We're running, cancel evicts
+ c.stop()
+ c.stop = nil
+ }
+
+ // Done with lock
+ c.Unlock()
+
+ return
+}
+
+// Sweep attempts to evict expired items (with callback!) from cache.
+func (c *Cache[K, V]) Sweep(now time.Time) {
+ var after int
+
+ // Sweep within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Sentinel value
+ after = -1
+
+ // The cache will be ordered by expiry date, we iterate until we reach the index of
+ // the youngest item that hsa expired, as all succeeding items will also be expired.
+ c.Cache.RangeIf(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) bool {
+ if now.After(item.Expiry) {
+ after = i
+
+ // All older than this (including) can be dropped
+ return false
+ }
+
+ // Continue looping
+ return true
+ })
+
+ if after == -1 {
+ // No Truncation needed
+ return
+ }
+
+ // Truncate items, calling eviction hook
+ c.truncate(c.Cache.Len()-after, c.Evict)
+}
+
+// SetEvictionCallback: implements cache.Cache's SetEvictionCallback().
+func (c *Cache[K, V]) SetEvictionCallback(hook func(*Entry[K, V])) {
+ // Ensure non-nil hook
+ if hook == nil {
+ hook = func(*Entry[K, V]) {}
+ }
+
+ // Update within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Update hook
+ c.Evict = hook
+}
+
+// SetInvalidateCallback: implements cache.Cache's SetInvalidateCallback().
+func (c *Cache[K, V]) SetInvalidateCallback(hook func(*Entry[K, V])) {
+ // Ensure non-nil hook
+ if hook == nil {
+ hook = func(*Entry[K, V]) {}
+ }
+
+ // Update within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Update hook
+ c.Invalid = hook
+}
+
+// SetTTL: implements cache.Cache's SetTTL().
+func (c *Cache[K, V]) SetTTL(ttl time.Duration, update bool) {
+ if ttl < 0 {
+ panic("ttl must be greater than zero")
+ }
+
+ // Update within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Set updated TTL
+ diff := ttl - c.TTL
+ c.TTL = ttl
+
+ if update {
+ // Update existing cache entries with new expiry time
+ c.Cache.Range(0, c.Cache.Len(), func(i int, key K, item *Entry[K, V]) {
+ item.Expiry = item.Expiry.Add(diff)
+ })
+ }
+}
+
+// Get: implements cache.Cache's Get().
+func (c *Cache[K, V]) Get(key K) (V, bool) {
+ // Read within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Check for item in cache
+ item, ok := c.Cache.Get(key)
+ if !ok {
+ var value V
+ return value, false
+ }
+
+ // Update item expiry and return
+ item.Expiry = time.Now().Add(c.TTL)
+ return item.Value, true
+}
+
+// Add: implements cache.Cache's Add().
+func (c *Cache[K, V]) Add(key K, value V) bool {
+ // Write within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // If already cached, return
+ if c.Cache.Has(key) {
+ return false
+ }
+
+ // Alloc new item
+ item := c.alloc()
+ item.Key = key
+ item.Value = value
+ item.Expiry = time.Now().Add(c.TTL)
+
+ var hook func(K, *Entry[K, V])
+
+ if c.Evict != nil {
+ // Pass evicted entry to user hook
+ hook = func(_ K, item *Entry[K, V]) {
+ c.Evict(item)
+ }
+ }
+
+ // Place new item in the map with hook
+ c.Cache.SetWithHook(key, item, hook)
+
+ return true
+}
+
+// Set: implements cache.Cache's Set().
+func (c *Cache[K, V]) Set(key K, value V) {
+ // Write within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Check if already exists
+ item, ok := c.Cache.Get(key)
+
+ if ok {
+ if c.Invalid != nil {
+ // Invalidate existing
+ c.Invalid(item)
+ }
+ } else {
+ // Allocate new item
+ item = c.alloc()
+ item.Key = key
+ c.Cache.Set(key, item)
+ }
+
+ // Update the item value + expiry
+ item.Expiry = time.Now().Add(c.TTL)
+ item.Value = value
+}
+
+// CAS: implements cache.Cache's CAS().
+func (c *Cache[K, V]) CAS(key K, old V, new V, cmp func(V, V) bool) bool {
+ // CAS within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Check for item in cache
+ item, ok := c.Cache.Get(key)
+ if !ok || !cmp(item.Value, old) {
+ return false
+ }
+
+ if c.Invalid != nil {
+ // Invalidate item
+ c.Invalid(item)
+ }
+
+ // Update item + Expiry
+ item.Value = new
+ item.Expiry = time.Now().Add(c.TTL)
+
+ return ok
+}
+
+// Swap: implements cache.Cache's Swap().
+func (c *Cache[K, V]) Swap(key K, swp V) V {
+ // Swap within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Check for item in cache
+ item, ok := c.Cache.Get(key)
+ if !ok {
+ var value V
+ return value
+ }
+
+ if c.Invalid != nil {
+ // invalidate old
+ c.Invalid(item)
+ }
+
+ old := item.Value
+
+ // update item + Expiry
+ item.Value = swp
+ item.Expiry = time.Now().Add(c.TTL)
+
+ return old
+}
+
+// Has: implements cache.Cache's Has().
+func (c *Cache[K, V]) Has(key K) bool {
+ c.Lock()
+ ok := c.Cache.Has(key)
+ c.Unlock()
+ return ok
+}
+
+// Invalidate: implements cache.Cache's Invalidate().
+func (c *Cache[K, V]) Invalidate(key K) bool {
+ // Delete within lock
+ c.Lock()
+ defer c.Unlock()
+
+ // Check if we have item with key
+ item, ok := c.Cache.Get(key)
+ if !ok {
+ return false
+ }
+
+ // Remove from cache map
+ _ = c.Cache.Delete(key)
+
+ if c.Invalid != nil {
+ // Invalidate item
+ c.Invalid(item)
+ }
+
+ // Return item to pool
+ c.free(item)
+
+ return true
+}
+
+// Clear: implements cache.Cache's Clear().
+func (c *Cache[K, V]) Clear() {
+ c.Lock()
+ defer c.Unlock()
+ c.truncate(c.Cache.Len(), c.Invalid)
+}
+
+// Len: implements cache.Cache's Len().
+func (c *Cache[K, V]) Len() int {
+ c.Lock()
+ l := c.Cache.Len()
+ c.Unlock()
+ return l
+}
+
+// Cap: implements cache.Cache's Cap().
+func (c *Cache[K, V]) Cap() int {
+ c.Lock()
+ l := c.Cache.Cap()
+ c.Unlock()
+ return l
+}
+
+// truncate will call Cache.Truncate(sz), and if provided a hook will temporarily store deleted items before passing them to the hook. This is required in order to prevent cache writes during .Truncate().
+func (c *Cache[K, V]) truncate(sz int, hook func(*Entry[K, V])) {
+ if hook == nil {
+ // No hook was provided, we can simply truncate and free items immediately.
+ c.Cache.Truncate(sz, func(_ K, item *Entry[K, V]) { c.free(item) })
+ return
+ }
+
+ // Store list of deleted items for later callbacks
+ deleted := make([]*Entry[K, V], 0, sz)
+
+ // Truncate and store list of deleted items
+ c.Cache.Truncate(sz, func(_ K, item *Entry[K, V]) {
+ deleted = append(deleted, item)
+ })
+
+ // Pass each deleted to hook, then free
+ for _, item := range deleted {
+ hook(item)
+ c.free(item)
+ }
+}
+
+// alloc will acquire cache entry from pool, or allocate new.
+func (c *Cache[K, V]) alloc() *Entry[K, V] {
+ if len(c.pool) == 0 {
+ return &Entry[K, V]{}
+ }
+ idx := len(c.pool) - 1
+ e := c.pool[idx]
+ c.pool = c.pool[:idx]
+ return e
+}
+
+// free will reset entry fields and place back in pool.
+func (c *Cache[K, V]) free(e *Entry[K, V]) {
+ var (
+ zk K
+ zv V
+ )
+ e.Key = zk
+ e.Value = zv
+ e.Expiry = time.Time{}
+ c.pool = append(c.pool, e)
+}
diff --git a/vendor/codeberg.org/gruf/go-mangler/LICENSE b/vendor/codeberg.org/gruf/go-mangler/LICENSE
new file mode 100644
index 000000000..e4163ae35
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-mangler/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2022 gruf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/codeberg.org/gruf/go-mangler/README.md b/vendor/codeberg.org/gruf/go-mangler/README.md
new file mode 100644
index 000000000..15bbf57c4
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-mangler/README.md
@@ -0,0 +1,40 @@
+# go-mangler
+
+[Documentation](https://pkg.go.dev/codeberg.org/gruf/go-mangler).
+
+To put it simply is a bit of an odd library. It aims to provide incredibly fast, unique string outputs for all default supported input data types during a given runtime instance.
+
+It is useful, for example, for use as part of larger abstractions involving hashmaps. That was my particular usecase anyways...
+
+This package does make liberal use of the "unsafe" package.
+
+Benchmarks are below. Those with missing values panicked during our set of benchmarks, usually a case of not handling nil values elegantly. Please note the more important thing to notice here is the relative difference in benchmark scores, the actual `ns/op`,`B/op`,`allocs/op` accounts for running through over 80 possible test cases, including some not-ideal situations.
+
+The choice of libraries in the benchmark are just a selection of libraries that could be used in a similar manner to this one, i.e. serializing in some manner.
+
+```
+goos: linux
+goarch: amd64
+pkg: codeberg.org/gruf/go-mangler
+cpu: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
+BenchmarkMangle
+BenchmarkMangle-8 723278 1593 ns/op 1168 B/op 120 allocs/op
+BenchmarkMangleHash
+BenchmarkMangleHash-8 405380 2788 ns/op 4496 B/op 214 allocs/op
+BenchmarkJSON
+BenchmarkJSON-8 199360 6116 ns/op 4243 B/op 142 allocs/op
+BenchmarkBinary
+BenchmarkBinary-8 ------ ---- ns/op ---- B/op --- allocs/op
+BenchmarkFmt
+BenchmarkFmt-8 168500 7111 ns/op 2256 B/op 161 allocs/op
+BenchmarkKelindarBinary
+BenchmarkKelindarBinary-8 ------ ---- ns/op ---- B/op --- allocs/op
+BenchmarkFxmackerCbor
+BenchmarkFxmackerCbor-8 361416 3255 ns/op 1495 B/op 122 allocs/op
+BenchmarkMitchellhHashStructure
+BenchmarkMitchellhHashStructure-8 117672 10493 ns/op 8443 B/op 961 allocs/op
+BenchmarkCnfStructhash
+BenchmarkCnfStructhash-8 7078 161926 ns/op 288644 B/op 5843 allocs/op
+PASS
+ok codeberg.org/gruf/go-mangler 14.377s
+``` \ No newline at end of file
diff --git a/vendor/codeberg.org/gruf/go-mangler/helpers.go b/vendor/codeberg.org/gruf/go-mangler/helpers.go
new file mode 100644
index 000000000..0ca4498af
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-mangler/helpers.go
@@ -0,0 +1,97 @@
+package mangler
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+func deref_ptr_mangler(mangle Mangler, count int) rMangler {
+ return func(buf []byte, v reflect.Value) []byte {
+ for i := 0; i < count; i++ {
+ // Check for nil
+ if v.IsNil() {
+ buf = append(buf, '0')
+ return buf
+ }
+
+ // Further deref ptr
+ buf = append(buf, '1')
+ v = v.Elem()
+ }
+
+ // Mangle fully deref'd ptr
+ return mangle(buf, v.Interface())
+ }
+}
+
+func deref_ptr_rmangler(mangle rMangler, count int) rMangler {
+ return func(buf []byte, v reflect.Value) []byte {
+ for i := 0; i < count; i++ {
+ // Check for nil
+ if v.IsNil() {
+ buf = append(buf, '0')
+ return buf
+ }
+
+ // Further deref ptr
+ buf = append(buf, '1')
+ v = v.Elem()
+ }
+
+ // Mangle fully deref'd ptr
+ return mangle(buf, v)
+ }
+}
+
+func iter_array_mangler(mangle Mangler) rMangler {
+ return func(buf []byte, v reflect.Value) []byte {
+ n := v.Len()
+ for i := 0; i < n; i++ {
+ buf = mangle(buf, v.Index(i).Interface())
+ buf = append(buf, ',')
+ }
+ if n > 0 {
+ buf = buf[:len(buf)-1]
+ }
+ return buf
+ }
+}
+
+func iter_array_rmangler(mangle rMangler) rMangler {
+ return func(buf []byte, v reflect.Value) []byte {
+ n := v.Len()
+ for i := 0; i < n; i++ {
+ buf = mangle(buf, v.Index(i))
+ buf = append(buf, ',')
+ }
+ if n > 0 {
+ buf = buf[:len(buf)-1]
+ }
+ return buf
+ }
+}
+
+func iter_map_rmangler(kMangle, vMangle rMangler) rMangler {
+ return func(buf []byte, v reflect.Value) []byte {
+ r := v.MapRange()
+ for r.Next() {
+ buf = kMangle(buf, r.Key())
+ buf = append(buf, ':')
+ buf = vMangle(buf, r.Value())
+ buf = append(buf, '.')
+ }
+ if v.Len() > 0 {
+ buf = buf[:len(buf)-1]
+ }
+ return buf
+ }
+}
+
+// iface_value returns the raw value ptr for input boxed within interface{} type.
+func iface_value(a any) unsafe.Pointer {
+ type eface struct {
+ Type unsafe.Pointer
+ Value unsafe.Pointer
+ }
+ return (*eface)(unsafe.Pointer(&a)).Value
+}
diff --git a/vendor/codeberg.org/gruf/go-mangler/load.go b/vendor/codeberg.org/gruf/go-mangler/load.go
new file mode 100644
index 000000000..fd742c17b
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-mangler/load.go
@@ -0,0 +1,354 @@
+package mangler
+
+import (
+ "encoding"
+ "net/url"
+ "reflect"
+ "time"
+)
+
+// loadMangler is the top-most Mangler load function. It guarantees that a Mangler
+// function will be returned for given value interface{} and reflected type. Else panics.
+func loadMangler(a any, t reflect.Type) Mangler {
+ // Load mangler function
+ mng, rmng := load(a, t)
+
+ if rmng != nil {
+ // Wrap reflect mangler to handle iface
+ return func(buf []byte, a any) []byte {
+ return rmng(buf, reflect.ValueOf(a))
+ }
+ }
+
+ if mng == nil {
+ // No mangler function could be determined
+ panic("cannot mangle type: " + t.String())
+ }
+
+ return mng
+}
+
+// load will load a Mangler or reflect Mangler for given type and iface 'a'.
+// Note: allocates new interface value if nil provided, i.e. if coming via reflection.
+func load(a any, t reflect.Type) (Mangler, rMangler) {
+ if t == nil {
+ // There is no reflect type to search by
+ panic("cannot mangle nil interface{} type")
+ }
+
+ if a == nil {
+ // Alloc new iface instance
+ v := reflect.New(t).Elem()
+ a = v.Interface()
+ }
+
+ // Check in fast iface type switch
+ if mng := loadIface(a); mng != nil {
+ return mng, nil
+ }
+
+ // Search by reflection
+ return loadReflect(t)
+}
+
+// loadIface is used as a first-resort interface{} type switcher loader
+// for types implementing Mangled and providing performant alternative
+// Mangler functions for standard library types to avoid reflection.
+func loadIface(a any) Mangler {
+ switch a.(type) {
+ case Mangled:
+ return mangle_mangled
+
+ case time.Time:
+ return mangle_time
+
+ case *time.Time:
+ return mangle_time_ptr
+
+ case *url.URL:
+ return mangle_stringer
+
+ case encoding.BinaryMarshaler:
+ return mangle_binary
+
+ // NOTE:
+ // we don't just handle ALL fmt.Stringer types as often
+ // the output is large and unwieldy and this interface
+ // switch is for types it would be faster to avoid reflection.
+ // If they want better performance they can implement Mangled{}.
+
+ default:
+ return nil
+ }
+}
+
+// loadReflect will load a Mangler (or rMangler) function for the given reflected type info.
+// NOTE: this is used as the top level load function for nested reflective searches.
+func loadReflect(t reflect.Type) (Mangler, rMangler) {
+ switch t.Kind() {
+ case reflect.Pointer:
+ return loadReflectPtr(t.Elem())
+
+ case reflect.String:
+ return mangle_string, nil
+
+ case reflect.Array:
+ return nil, loadReflectArray(t.Elem())
+
+ case reflect.Slice:
+ // Element type
+ et := t.Elem()
+
+ // Preferably look for known slice mangler func
+ if mng := loadReflectKnownSlice(et); mng != nil {
+ return mng, nil
+ }
+
+ // Else handle as array elements
+ return nil, loadReflectArray(et)
+
+ case reflect.Map:
+ return nil, loadReflectMap(t.Key(), t.Elem())
+
+ case reflect.Bool:
+ return mangle_bool, nil
+
+ case reflect.Int,
+ reflect.Uint,
+ reflect.Uintptr:
+ return mangle_platform_int, nil
+
+ case reflect.Int8,
+ reflect.Uint8:
+ return mangle_8bit, nil
+
+ case reflect.Int16,
+ reflect.Uint16:
+ return mangle_16bit, nil
+
+ case reflect.Int32,
+ reflect.Uint32:
+ return mangle_32bit, nil
+
+ case reflect.Int64,
+ reflect.Uint64:
+ return mangle_64bit, nil
+
+ case reflect.Float32:
+ return mangle_32bit, nil
+
+ case reflect.Float64:
+ return mangle_64bit, nil
+
+ case reflect.Complex64:
+ return mangle_64bit, nil
+
+ case reflect.Complex128:
+ return mangle_128bit, nil
+
+ default:
+ return nil, nil
+ }
+}
+
+// loadReflectPtr loads a Mangler (or rMangler) function for a ptr's element type.
+// This also handles further dereferencing of any further ptr indrections (e.g. ***int).
+func loadReflectPtr(et reflect.Type) (Mangler, rMangler) {
+ count := 1
+
+ // Iteratively dereference ptrs
+ for et.Kind() == reflect.Pointer {
+ et = et.Elem()
+ count++
+ }
+
+ if et.Kind() == reflect.Array {
+ // Special case of addressable (sliceable) array
+ if mng := loadReflectKnownSlice(et); mng != nil {
+ if count == 1 {
+ return mng, nil
+ }
+ return nil, deref_ptr_mangler(mng, count-1)
+ }
+
+ // Look for an array mangler function, this will
+ // access elements by index using reflect.Value and
+ // pass each one to a separate mangler function.
+ if rmng := loadReflectArray(et); rmng != nil {
+ return nil, deref_ptr_rmangler(rmng, count)
+ }
+
+ return nil, nil
+ }
+
+ // Try remove a layer of derefs by loading a mangler
+ // for a known ptr kind. The less reflection the better!
+ if mng := loadReflectKnownPtr(et); mng != nil {
+ if count == 1 {
+ return mng, nil
+ }
+ return nil, deref_ptr_mangler(mng, count-1)
+ }
+
+ // Search for ptr elemn type mangler
+ if mng, rmng := load(nil, et); mng != nil {
+ return nil, deref_ptr_mangler(mng, count)
+ } else if rmng != nil {
+ return nil, deref_ptr_rmangler(rmng, count)
+ }
+
+ return nil, nil
+}
+
+// loadReflectKnownPtr loads a Mangler function for a known ptr-of-element type (in this case, primtive ptrs).
+func loadReflectKnownPtr(et reflect.Type) Mangler {
+ switch et.Kind() {
+ case reflect.String:
+ return mangle_string_ptr
+
+ case reflect.Bool:
+ return mangle_bool_ptr
+
+ case reflect.Int,
+ reflect.Uint,
+ reflect.Uintptr:
+ return mangle_platform_int_ptr
+
+ case reflect.Int8,
+ reflect.Uint8:
+ return mangle_8bit_ptr
+
+ case reflect.Int16,
+ reflect.Uint16:
+ return mangle_16bit_ptr
+
+ case reflect.Int32,
+ reflect.Uint32:
+ return mangle_32bit_ptr
+
+ case reflect.Int64,
+ reflect.Uint64:
+ return mangle_64bit_ptr
+
+ case reflect.Float32:
+ return mangle_32bit_ptr
+
+ case reflect.Float64:
+ return mangle_64bit_ptr
+
+ case reflect.Complex64:
+ return mangle_64bit_ptr
+
+ case reflect.Complex128:
+ return mangle_128bit_ptr
+
+ default:
+ return nil
+ }
+}
+
+// loadReflectKnownSlice loads a Mangler function for a known slice-of-element type (in this case, primtives).
+func loadReflectKnownSlice(et reflect.Type) Mangler {
+ switch et.Kind() {
+ case reflect.String:
+ return mangle_string_slice
+
+ case reflect.Bool:
+ return mangle_bool_slice
+
+ case reflect.Int,
+ reflect.Uint,
+ reflect.Uintptr:
+ return mangle_platform_int_slice
+
+ case reflect.Int8,
+ reflect.Uint8:
+ return mangle_8bit_slice
+
+ case reflect.Int16,
+ reflect.Uint16:
+ return mangle_16bit_slice
+
+ case reflect.Int32,
+ reflect.Uint32:
+ return mangle_32bit_slice
+
+ case reflect.Int64,
+ reflect.Uint64:
+ return mangle_64bit_slice
+
+ case reflect.Float32:
+ return mangle_32bit_slice
+
+ case reflect.Float64:
+ return mangle_64bit_slice
+
+ case reflect.Complex64:
+ return mangle_64bit_slice
+
+ case reflect.Complex128:
+ return mangle_128bit_slice
+
+ default:
+ return nil
+ }
+}
+
+// loadReflectArray loads an rMangler function for an array (or slice) or given element type.
+func loadReflectArray(et reflect.Type) rMangler {
+ // Search via reflected array element type
+ if mng, rmng := load(nil, et); mng != nil {
+ return iter_array_mangler(mng)
+ } else if rmng != nil {
+ return iter_array_rmangler(rmng)
+ }
+ return nil
+}
+
+// loadReflectMap ...
+func loadReflectMap(kt, vt reflect.Type) rMangler {
+ var kmng, vmng rMangler
+
+ // Search for mangler for key type
+ mng, rmng := load(nil, kt)
+
+ switch {
+ // Wrap key mangler to reflect
+ case mng != nil:
+ mng := mng // take our own ptr
+ kmng = func(buf []byte, v reflect.Value) []byte {
+ return mng(buf, v.Interface())
+ }
+
+ // Use reflect key mangler as-is
+ case rmng != nil:
+ kmng = rmng
+
+ // No mangler found
+ default:
+ return nil
+ }
+
+ // Search for mangler for value type
+ mng, rmng = load(nil, vt)
+
+ switch {
+ // Wrap key mangler to reflect
+ case mng != nil:
+ mng := mng // take our own ptr
+ vmng = func(buf []byte, v reflect.Value) []byte {
+ return mng(buf, v.Interface())
+ }
+
+ // Use reflect key mangler as-is
+ case rmng != nil:
+ vmng = rmng
+
+ // No mangler found
+ default:
+ return nil
+ }
+
+ // Wrap key/value manglers in map iter
+ return iter_map_rmangler(kmng, vmng)
+}
diff --git a/vendor/codeberg.org/gruf/go-mangler/mangle.go b/vendor/codeberg.org/gruf/go-mangler/mangle.go
new file mode 100644
index 000000000..7158893ae
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-mangler/mangle.go
@@ -0,0 +1,132 @@
+package mangler
+
+import (
+ "encoding/binary"
+ "reflect"
+ "unsafe"
+
+ "github.com/cespare/xxhash"
+ "github.com/cornelk/hashmap"
+)
+
+var (
+ // manglers is a map of runtime type ptrs => Mangler functions.
+ manglers = hashmap.New[uintptr, Mangler]()
+
+ // bin is a short-hand for our chosen byteorder encoding.
+ bin = binary.LittleEndian
+)
+
+// Mangled is an interface that allows any type to implement a custom
+// Mangler function to improve performance when mangling this type.
+type Mangled interface {
+ Mangle(buf []byte) []byte
+}
+
+// Mangler is a function that will take an input interface value of known
+// type, and append it in mangled serialized form to the given byte buffer.
+// While the value type is an interface, the Mangler functions are accessed
+// by the value's runtime type pointer, allowing the input value type to be known.
+type Mangler func(buf []byte, value any) []byte
+
+// rMangler is functionally the same as a Mangler function, but it
+// takes the value input in reflected form. By specifying these differences
+// in mangler function types, it allows us to cut back on new calls to
+// `reflect.ValueOf()` and instead pass by existing reflected values.
+type rMangler func(buf []byte, value reflect.Value) []byte
+
+// Get will fetch the Mangler function for given runtime type.
+func Get(t reflect.Type) (Mangler, bool) {
+ if t == nil {
+ return nil, false
+ }
+ uptr := uintptr(iface_value(t))
+ return manglers.Get(uptr)
+}
+
+// Register will register the given Mangler function for use with vars of given runtime type. This allows
+// registering performant manglers for existing types not implementing Mangled (e.g. std library types).
+// NOTE: panics if there already exists a Mangler function for given type. Register on init().
+func Register(t reflect.Type, m Mangler) {
+ if t == nil {
+ // Nil interface{} types cannot be searched by, do not accept
+ panic("cannot register mangler for nil interface{} type")
+ }
+
+ // Get raw runtime type ptr
+ uptr := uintptr(iface_value(t))
+
+ // Ensure this is a unique encoder
+ if _, ok := manglers.Get(uptr); ok {
+ panic("already registered mangler for type: " + t.String())
+ }
+
+ // Cache this encoder func
+ manglers.Set(uptr, m)
+}
+
+// Append will append the mangled form of input value 'a' to buffer 'b'.
+// See mangler.String() for more information on mangled output.
+func Append(b []byte, a any) []byte {
+ // Get reflect type of 'a'
+ t := reflect.TypeOf(a)
+
+ // Get raw runtime type ptr
+ uptr := uintptr(iface_value(t))
+
+ // Look for a cached mangler
+ mng, ok := manglers.Get(uptr)
+
+ if !ok {
+ // Load mangler into cache
+ mng = loadMangler(a, t)
+ manglers.Set(uptr, mng)
+ }
+
+ // First write the type ptr (this adds
+ // a unique prefix for each runtime type).
+ b = mangle_platform_int(b, uptr)
+
+ // Finally, mangle value
+ return mng(b, a)
+}
+
+// String will return the mangled format of input value 'a'. This
+// mangled output will be unique for all default supported input types
+// during a single runtime instance. Uniqueness cannot be guaranteed
+// between separate runtime instances (whether running concurrently, or
+// the same application running at different times).
+//
+// The exact formatting of the output data should not be relied upon,
+// only that it is unique given the above constraints. Generally though,
+// the mangled output is the binary formatted text of given input data.
+//
+// Uniqueness is guaranteed for similar input data of differing types
+// (e.g. string("hello world") vs. []byte("hello world")) by prefixing
+// mangled output with the input data's runtime type pointer.
+//
+// Default supported types include:
+// - string
+// - bool
+// - int,int8,int16,int32,int64
+// - uint,uint8,uint16,uint32,uint64,uintptr
+// - float32,float64
+// - complex64,complex128
+// - all type aliases of above
+// - time.Time{}, *url.URL{}
+// - mangler.Mangled{}
+// - encoding.BinaryMarshaler{}
+// - all pointers to the above
+// - all slices / arrays of the above
+// - all map keys / values of the above
+func String(a any) string {
+ b := Append(make([]byte, 0, 32), a)
+ return *(*string)(unsafe.Pointer(&b))
+}
+
+// Hash returns the xxHash digest of the result of mangler.Append(nil, 'a').
+func Hash(a any) uint64 {
+ b := make([]byte, 0, 32)
+ b = Append(b, a)
+ return xxhash.Sum64(b)
+}
diff --git a/vendor/codeberg.org/gruf/go-mangler/manglers.go b/vendor/codeberg.org/gruf/go-mangler/manglers.go
new file mode 100644
index 000000000..52f9f0082
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-mangler/manglers.go
@@ -0,0 +1,264 @@
+package mangler
+
+import (
+ "encoding"
+ "fmt"
+ "math/bits"
+ "time"
+ _ "unsafe"
+)
+
+// Notes:
+// the use of unsafe conversion from the direct interface values to
+// the chosen types in each of the below functions allows us to convert
+// not only those types directly, but anything type-aliased to those
+// types. e.g. `time.Duration` directly as int64.
+
+func mangle_string(buf []byte, a any) []byte {
+ return append(buf, *(*string)(iface_value(a))...)
+}
+
+func mangle_string_ptr(buf []byte, a any) []byte {
+ if ptr := (*string)(iface_value(a)); ptr != nil {
+ buf = append(buf, '1')
+ return append(buf, *ptr...)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_string_slice(buf []byte, a any) []byte {
+ s := *(*[]string)(iface_value(a))
+ for _, s := range s {
+ buf = append(buf, s...)
+ buf = append(buf, ',')
+ }
+ if len(s) > 0 {
+ buf = buf[:len(buf)-1]
+ }
+ return buf
+}
+
+func mangle_bool(buf []byte, a any) []byte {
+ if *(*bool)(iface_value(a)) {
+ return append(buf, '1')
+ }
+ return append(buf, '0')
+}
+
+func mangle_bool_ptr(buf []byte, a any) []byte {
+ if ptr := (*bool)(iface_value(a)); ptr != nil {
+ buf = append(buf, '1')
+ if *ptr {
+ return append(buf, '1')
+ }
+ return append(buf, '0')
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_bool_slice(buf []byte, a any) []byte {
+ for _, b := range *(*[]bool)(iface_value(a)) {
+ if b {
+ buf = append(buf, '1')
+ } else {
+ buf = append(buf, '0')
+ }
+ }
+ return buf
+}
+
+func mangle_8bit(buf []byte, a any) []byte {
+ return append(buf, *(*uint8)(iface_value(a)))
+}
+
+func mangle_8bit_ptr(buf []byte, a any) []byte {
+ if ptr := (*uint8)(iface_value(a)); ptr != nil {
+ buf = append(buf, '1')
+ return append(buf, *ptr)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_8bit_slice(buf []byte, a any) []byte {
+ return append(buf, *(*[]uint8)(iface_value(a))...)
+}
+
+func mangle_16bit(buf []byte, a any) []byte {
+ return bin.AppendUint16(buf, *(*uint16)(iface_value(a)))
+}
+
+func mangle_16bit_ptr(buf []byte, a any) []byte {
+ if ptr := (*uint16)(iface_value(a)); ptr != nil {
+ buf = append(buf, '1')
+ return bin.AppendUint16(buf, *ptr)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_16bit_slice(buf []byte, a any) []byte {
+ for _, u := range *(*[]uint16)(iface_value(a)) {
+ buf = bin.AppendUint16(buf, u)
+ }
+ return buf
+}
+
+func mangle_32bit(buf []byte, a any) []byte {
+ return bin.AppendUint32(buf, *(*uint32)(iface_value(a)))
+}
+
+func mangle_32bit_ptr(buf []byte, a any) []byte {
+ if ptr := (*uint32)(iface_value(a)); ptr != nil {
+ buf = append(buf, '1')
+ return bin.AppendUint32(buf, *ptr)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_32bit_slice(buf []byte, a any) []byte {
+ for _, u := range *(*[]uint32)(iface_value(a)) {
+ buf = bin.AppendUint32(buf, u)
+ }
+ return buf
+}
+
+func mangle_64bit(buf []byte, a any) []byte {
+ return bin.AppendUint64(buf, *(*uint64)(iface_value(a)))
+}
+
+func mangle_64bit_ptr(buf []byte, a any) []byte {
+ if ptr := (*uint64)(iface_value(a)); ptr != nil {
+ buf = append(buf, '1')
+ return bin.AppendUint64(buf, *ptr)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_64bit_slice(buf []byte, a any) []byte {
+ for _, u := range *(*[]uint64)(iface_value(a)) {
+ buf = bin.AppendUint64(buf, u)
+ }
+ return buf
+}
+
+// mangle_platform_int contains the correct iface mangler on runtime for platform int size.
+var mangle_platform_int = func() Mangler {
+ switch bits.UintSize {
+ case 32:
+ return mangle_32bit
+ case 64:
+ return mangle_64bit
+ default:
+ panic("unexpected platform int size")
+ }
+}()
+
+// mangle_platform_int_ptr contains the correct iface mangler on runtime for platform int size.
+var mangle_platform_int_ptr = func() Mangler {
+ switch bits.UintSize {
+ case 32:
+ return mangle_32bit_ptr
+ case 64:
+ return mangle_64bit_ptr
+ default:
+ panic("unexpected platform int size")
+ }
+}()
+
+// mangle_platform_int_slice contains the correct iface mangler on runtime for platform int size.
+var mangle_platform_int_slice = func() Mangler {
+ switch bits.UintSize {
+ case 32:
+ return mangle_32bit_slice
+ case 64:
+ return mangle_64bit_slice
+ default:
+ panic("unexpected platform int size")
+ }
+}()
+
+// uint128 provides an easily mangleable data type for 128bit data types to be cast into.
+type uint128 [2]uint64
+
+func mangle_128bit(buf []byte, a any) []byte {
+ u2 := *(*uint128)(iface_value(a))
+ buf = bin.AppendUint64(buf, u2[0])
+ buf = bin.AppendUint64(buf, u2[1])
+ return buf
+}
+
+func mangle_128bit_ptr(buf []byte, a any) []byte {
+ if ptr := (*uint128)(iface_value(a)); ptr != nil {
+ buf = append(buf, '1')
+ buf = bin.AppendUint64(buf, (*ptr)[0])
+ buf = bin.AppendUint64(buf, (*ptr)[1])
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_128bit_slice(buf []byte, a any) []byte {
+ for _, u2 := range *(*[]uint128)(iface_value(a)) {
+ buf = bin.AppendUint64(buf, u2[0])
+ buf = bin.AppendUint64(buf, u2[1])
+ }
+ return buf
+}
+
+func mangle_time(buf []byte, a any) []byte {
+ t := *(*time.Time)(iface_value(a))
+ b, err := t.MarshalBinary()
+ if err != nil {
+ panic("marshal_time: " + err.Error())
+ }
+ return append(buf, b...)
+}
+
+func mangle_time_ptr(buf []byte, a any) []byte {
+ if ptr := (*time.Time)(iface_value(a)); ptr != nil {
+ b, err := ptr.MarshalBinary()
+ if err != nil {
+ panic("marshal_time: " + err.Error())
+ }
+ buf = append(buf, '1')
+ return append(buf, b...)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_mangled(buf []byte, a any) []byte {
+ if v := a.(Mangled); v != nil {
+ buf = append(buf, '1')
+ return v.Mangle(buf)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_binary(buf []byte, a any) []byte {
+ if v := a.(encoding.BinaryMarshaler); v != nil {
+ b, err := v.MarshalBinary()
+ if err != nil {
+ panic("mangle_binary: " + err.Error())
+ }
+ buf = append(buf, '1')
+ return append(buf, b...)
+ }
+ buf = append(buf, '0')
+ return buf
+}
+
+func mangle_stringer(buf []byte, a any) []byte {
+ if v := a.(fmt.Stringer); v != nil {
+ buf = append(buf, '1')
+ return append(buf, v.String()...)
+ }
+ buf = append(buf, '0')
+ return buf
+}
diff --git a/vendor/codeberg.org/gruf/go-maps/LICENSE b/vendor/codeberg.org/gruf/go-maps/LICENSE
new file mode 100644
index 000000000..e4163ae35
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2022 gruf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/codeberg.org/gruf/go-maps/README.md b/vendor/codeberg.org/gruf/go-maps/README.md
new file mode 100644
index 000000000..cf1aea644
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/README.md
@@ -0,0 +1,7 @@
+# go-maps
+
+Provides a selection of hashmaps (or, "dictionaries") with features exceeding that of the default Go runtime hashmap.
+
+Includes:
+- OrderedMap
+- LRUMap
diff --git a/vendor/codeberg.org/gruf/go-maps/common.go b/vendor/codeberg.org/gruf/go-maps/common.go
new file mode 100644
index 000000000..f5877ee3a
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/common.go
@@ -0,0 +1,289 @@
+package maps
+
+import (
+ "fmt"
+ "reflect"
+
+ "codeberg.org/gruf/go-byteutil"
+ "codeberg.org/gruf/go-kv"
+)
+
+// ordered provides a common ordered hashmap base, storing order in a doubly-linked list.
+type ordered[K comparable, V any] struct {
+ hmap map[K]*elem[K, V]
+ list list[K, V]
+ pool []*elem[K, V]
+ rnly bool
+}
+
+// write_check panics if map is not in a safe-state to write to.
+func (m ordered[K, V]) write_check() {
+ if m.rnly {
+ panic("map write during read loop")
+ }
+}
+
+// Has returns whether key exists in map.
+func (m *ordered[K, V]) Has(key K) bool {
+ _, ok := m.hmap[key]
+ return ok
+}
+
+// Delete will delete given key from map, returns false if not found.
+func (m *ordered[K, V]) Delete(key K) bool {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+ if !ok {
+ return false
+ }
+
+ // Drop from list
+ m.list.Unlink(elem)
+
+ // Delete from map
+ delete(m.hmap, key)
+
+ // Return to pool
+ m.free(elem)
+
+ return true
+}
+
+// Range passes given function over the requested range of the map.
+func (m *ordered[K, V]) Range(start, length int, fn func(int, K, V)) {
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ // Nil check
+ _ = fn
+
+ switch end := start + length; {
+ // No loop to iterate
+ case length == 0:
+ if start < 0 || (m.list.len > 0 && start >= m.list.len) {
+ panic("index out of bounds")
+ }
+
+ // Step backwards
+ case length < 0:
+ // Check loop indices are within map bounds
+ if end < -1 || start >= m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i > end; i-- {
+ fn(i, elem.K, elem.V)
+ elem = elem.prev
+ }
+
+ // Step forwards
+ case length > 0:
+ // Check loop indices are within map bounds
+ if start < 0 || end > m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i < end; i++ {
+ fn(i, elem.K, elem.V)
+ elem = elem.next
+ }
+ }
+}
+
+// RangeIf passes given function over the requested range of the map. Returns early on 'fn' -> false.
+func (m *ordered[K, V]) RangeIf(start, length int, fn func(int, K, V) bool) {
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ // Nil check
+ _ = fn
+
+ switch end := start + length; {
+ // No loop to iterate
+ case length == 0:
+ if start < 0 || (m.list.len > 0 && start >= m.list.len) {
+ panic("index out of bounds")
+ }
+
+ // Step backwards
+ case length < 0:
+ // Check loop indices are within map bounds
+ if end < -1 || start >= m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i > end; i-- {
+ if !fn(i, elem.K, elem.V) {
+ return
+ }
+ elem = elem.prev
+ }
+
+ // Step forwards
+ case length > 0:
+ // Check loop indices are within map bounds
+ if start < 0 || end > m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i < end; i++ {
+ if !fn(i, elem.K, elem.V) {
+ return
+ }
+ elem = elem.next
+ }
+ }
+}
+
+// Truncate will truncate the map from the back by given amount, passing dropped elements to given function.
+func (m *ordered[K, V]) Truncate(sz int, fn func(K, V)) {
+ // Check size withing bounds
+ if sz > m.list.len {
+ panic("index out of bounds")
+ }
+
+ if fn == nil {
+ // move nil check out of loop
+ fn = func(K, V) {}
+ }
+
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ for i := 0; i < sz; i++ {
+ // Pop current tail
+ elem := m.list.tail
+ m.list.Unlink(elem)
+
+ // Delete from map
+ delete(m.hmap, elem.K)
+
+ // Pass dropped to func
+ fn(elem.K, elem.V)
+
+ // Release to pool
+ m.free(elem)
+ }
+}
+
+// Len returns the current length of the map.
+func (m *ordered[K, V]) Len() int {
+ return m.list.len
+}
+
+// format implements fmt.Formatter, allowing performant string formatting of map.
+func (m *ordered[K, V]) format(rtype reflect.Type, state fmt.State, verb rune) {
+ var (
+ kvbuf byteutil.Buffer
+ field kv.Field
+ vbose bool
+ )
+
+ switch {
+ // Only handle 'v' verb
+ case verb != 'v':
+ panic("invalid verb '" + string(verb) + "' for map")
+
+ // Prefix with type when verbose
+ case state.Flag('#'):
+ state.Write([]byte(rtype.String()))
+ }
+
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ // Write map opening brace
+ state.Write([]byte{'{'})
+
+ if m.list.len > 0 {
+ // Preallocate buffer
+ kvbuf.Guarantee(64)
+
+ // Start at index 0
+ elem := m.list.head
+
+ for i := 0; i < m.list.len-1; i++ {
+ // Append formatted key-val pair to state
+ field.K = fmt.Sprint(elem.K)
+ field.V = elem.V
+ field.AppendFormat(&kvbuf, vbose)
+ _, _ = state.Write(kvbuf.B)
+ kvbuf.Reset()
+
+ // Prepare buffer with comma separator
+ kvbuf.B = append(kvbuf.B, `, `...)
+
+ // Jump to next in list
+ elem = elem.next
+ }
+
+ // Append formatted key-val pair to state
+ field.K = fmt.Sprint(elem.K)
+ field.V = elem.V
+ field.AppendFormat(&kvbuf, vbose)
+ _, _ = state.Write(kvbuf.B)
+ }
+
+ // Write map closing brace
+ state.Write([]byte{'}'})
+}
+
+// Std returns a clone of map's data in the standard library equivalent map type.
+func (m *ordered[K, V]) Std() map[K]V {
+ std := make(map[K]V, m.list.len)
+ for _, elem := range m.hmap {
+ std[elem.K] = elem.V
+ }
+ return std
+}
+
+// alloc will acquire list element from pool, or allocate new.
+func (m *ordered[K, V]) alloc() *elem[K, V] {
+ if len(m.pool) == 0 {
+ return &elem[K, V]{}
+ }
+ idx := len(m.pool) - 1
+ elem := m.pool[idx]
+ m.pool = m.pool[:idx]
+ return elem
+}
+
+// free will reset elem fields and place back in pool.
+func (m *ordered[K, V]) free(elem *elem[K, V]) {
+ var (
+ zk K
+ zv V
+ )
+ elem.K = zk
+ elem.V = zv
+ elem.next = nil
+ elem.prev = nil
+ m.pool = append(m.pool, elem)
+}
diff --git a/vendor/codeberg.org/gruf/go-maps/list.go b/vendor/codeberg.org/gruf/go-maps/list.go
new file mode 100644
index 000000000..2d960976b
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/list.go
@@ -0,0 +1,154 @@
+package maps
+
+// list is a doubly-linked list containing elemnts with key-value pairs of given generic parameter types.
+type list[K comparable, V any] struct {
+ head *elem[K, V]
+ tail *elem[K, V]
+ len int
+}
+
+// Index returns the element at index from list.
+func (l *list[K, V]) Index(idx int) *elem[K, V] {
+ switch {
+ // Idx in first half
+ case idx < l.len/2:
+ elem := l.head
+ for i := 0; i < idx; i++ {
+ elem = elem.next
+ }
+ return elem
+
+ // Index in last half
+ default:
+ elem := l.tail
+ for i := l.len - 1; i > idx; i-- {
+ elem = elem.prev
+ }
+ return elem
+ }
+}
+
+// PushFront will push the given element to the front of the list.
+func (l *list[K, V]) PushFront(elem *elem[K, V]) {
+ if l.len == 0 {
+ // Set new tail + head
+ l.head = elem
+ l.tail = elem
+
+ // Link elem to itself
+ elem.next = elem
+ elem.prev = elem
+ } else {
+ oldHead := l.head
+
+ // Link to old head
+ elem.next = oldHead
+ oldHead.prev = elem
+
+ // Link up to tail
+ elem.prev = l.tail
+ l.tail.next = elem
+
+ // Set new head
+ l.head = elem
+ }
+
+ // Incr count
+ l.len++
+}
+
+// PushBack will push the given element to the back of the list.
+func (l *list[K, V]) PushBack(elem *elem[K, V]) {
+ if l.len == 0 {
+ // Set new tail + head
+ l.head = elem
+ l.tail = elem
+
+ // Link elem to itself
+ elem.next = elem
+ elem.prev = elem
+ } else {
+ oldTail := l.tail
+
+ // Link up to head
+ elem.next = l.head
+ l.head.prev = elem
+
+ // Link to old tail
+ elem.prev = oldTail
+ oldTail.next = elem
+
+ // Set new tail
+ l.tail = elem
+ }
+
+ // Incr count
+ l.len++
+}
+
+// PopTail will pop the current tail of the list, returns nil if empty.
+func (l *list[K, V]) PopTail() *elem[K, V] {
+ if l.len == 0 {
+ return nil
+ }
+ elem := l.tail
+ l.Unlink(elem)
+ return elem
+}
+
+// Unlink will unlink the given element from the doubly-linked list chain.
+func (l *list[K, V]) Unlink(elem *elem[K, V]) {
+ if l.len <= 1 {
+ // Drop elem's links
+ elem.next = nil
+ elem.prev = nil
+
+ // Only elem in list
+ l.head = nil
+ l.tail = nil
+ l.len = 0
+ return
+ }
+
+ // Get surrounding elems
+ next := elem.next
+ prev := elem.prev
+
+ // Relink chain
+ next.prev = prev
+ prev.next = next
+
+ switch elem {
+ // Set new head
+ case l.head:
+ l.head = next
+
+ // Set new tail
+ case l.tail:
+ l.tail = prev
+ }
+
+ // Drop elem's links
+ elem.next = nil
+ elem.prev = nil
+
+ // Decr count
+ l.len--
+}
+
+// elem represents an element in a doubly-linked list.
+type elem[K comparable, V any] struct {
+ next *elem[K, V]
+ prev *elem[K, V]
+ K K
+ V V
+}
+
+// allocElems will allocate a slice of empty elements of length.
+func allocElems[K comparable, V any](i int) []*elem[K, V] {
+ s := make([]*elem[K, V], i)
+ for i := range s {
+ s[i] = &elem[K, V]{}
+ }
+ return s
+}
diff --git a/vendor/codeberg.org/gruf/go-maps/lru.go b/vendor/codeberg.org/gruf/go-maps/lru.go
new file mode 100644
index 000000000..06ea2ab10
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/lru.go
@@ -0,0 +1,153 @@
+package maps
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// LRU provides an ordered hashmap implementation that keeps elements ordered according to last recently used (hence, LRU).
+type LRUMap[K comparable, V any] struct {
+ ordered[K, V]
+ size int
+}
+
+// NewLRU returns a new instance of LRUMap with given initializing length and maximum capacity.
+func NewLRU[K comparable, V any](len, cap int) *LRUMap[K, V] {
+ m := new(LRUMap[K, V])
+ m.Init(len, cap)
+ return m
+}
+
+// Init will initialize this map with initial length and maximum capacity.
+func (m *LRUMap[K, V]) Init(len, cap int) {
+ if cap <= 0 {
+ panic("lru cap must be greater than zero")
+ } else if m.pool != nil {
+ panic("lru map already initialized")
+ }
+ m.ordered.hmap = make(map[K]*elem[K, V], len)
+ m.ordered.pool = allocElems[K, V](len)
+ m.size = cap
+}
+
+// Get will fetch value for given key from map, in the process pushing it to the front of the map. Returns false if not found.
+func (m *LRUMap[K, V]) Get(key K) (V, bool) {
+ if elem, ok := m.hmap[key]; ok {
+ // Ensure safe
+ m.write_check()
+
+ // Unlink elem from list
+ m.list.Unlink(elem)
+
+ // Push to front of list
+ m.list.PushFront(elem)
+
+ return elem.V, true
+ }
+ var z V // zero value
+ return z, false
+}
+
+// Add will add the given key-value pair to the map, pushing them to the front of the map. Returns false if already exists. Evicts old at maximum capacity.
+func (m *LRUMap[K, V]) Add(key K, value V) bool {
+ return m.AddWithHook(key, value, nil)
+}
+
+// AddWithHook performs .Add() but passing any evicted entry to given hook function.
+func (m *LRUMap[K, V]) AddWithHook(key K, value V, evict func(K, V)) bool {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+ if ok {
+ return false
+ }
+
+ if m.list.len >= m.size {
+ // We're at capacity, sir!
+ // Pop current tail elem
+ elem = m.list.PopTail()
+
+ if evict != nil {
+ // Pass to evict hook
+ evict(elem.K, elem.V)
+ }
+
+ // Delete key from map
+ delete(m.hmap, elem.K)
+ } else {
+ // Allocate elem
+ elem = m.alloc()
+ }
+
+ // Set elem
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Push to front of list
+ m.list.PushFront(elem)
+ return true
+}
+
+// Set will ensure that given key-value pair exists in the map, by either adding new or updating existing, pushing them to the front of the map. Evicts old at maximum capacity.
+func (m *LRUMap[K, V]) Set(key K, value V) {
+ m.SetWithHook(key, value, nil)
+}
+
+// SetWithHook performs .Set() but passing any evicted entry to given hook function.
+func (m *LRUMap[K, V]) SetWithHook(key K, value V, evict func(K, V)) {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+
+ if ok {
+ // Unlink elem from list
+ m.list.Unlink(elem)
+
+ // Update existing
+ elem.V = value
+ } else {
+ if m.list.len >= m.size {
+ // We're at capacity, sir!
+ // Pop current tail elem
+ elem = m.list.PopTail()
+
+ if evict != nil {
+ // Pass to evict hook
+ evict(elem.K, elem.V)
+ }
+
+ // Delete key from map
+ delete(m.hmap, elem.K)
+ } else {
+ // Allocate elem
+ elem = m.alloc()
+ }
+
+ // Set elem
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+ }
+
+ // Push to front of list
+ m.list.PushFront(elem)
+}
+
+// Cap returns the maximum capacity of this LRU map.
+func (m *LRUMap[K, V]) Cap() int {
+ return m.size
+}
+
+// Format implements fmt.Formatter, allowing performant string formatting of map.
+func (m *LRUMap[K, V]) Format(state fmt.State, verb rune) {
+ m.format(reflect.TypeOf(m), state, verb)
+}
diff --git a/vendor/codeberg.org/gruf/go-maps/ordered.go b/vendor/codeberg.org/gruf/go-maps/ordered.go
new file mode 100644
index 000000000..ca8ebe8a0
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/ordered.go
@@ -0,0 +1,159 @@
+package maps
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// OrderedMap provides a hashmap implementation that tracks the order in which keys are added.
+type OrderedMap[K comparable, V any] struct {
+ ordered[K, V]
+}
+
+// NewOrdered returns a new instance of LRUMap with given initializing length and maximum capacity.
+func NewOrdered[K comparable, V any](len int) *OrderedMap[K, V] {
+ m := new(OrderedMap[K, V])
+ m.Init(len)
+ return m
+}
+
+// Init will initialize this map with initial length.
+func (m *OrderedMap[K, V]) Init(len int) {
+ if m.pool != nil {
+ panic("ordered map already initialized")
+ }
+ m.ordered.hmap = make(map[K]*elem[K, V], len)
+ m.ordered.pool = allocElems[K, V](len)
+}
+
+// Get will fetch value for given key from map. Returns false if not found.
+func (m *OrderedMap[K, V]) Get(key K) (V, bool) {
+ if elem, ok := m.hmap[key]; ok {
+ return elem.V, true
+ }
+ var z V // zero value
+ return z, false
+}
+
+// Add will add the given key-value pair to the map, returns false if already exists.
+func (m *OrderedMap[K, V]) Add(key K, value V) bool {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+ if ok {
+ return false
+ }
+
+ // Allocate elem
+ elem = m.alloc()
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Push to back of list
+ m.list.PushBack(elem)
+ return true
+}
+
+// Set will ensure that given key-value pair exists in the map, by either adding new or updating existing.
+func (m *OrderedMap[K, V]) Set(key K, value V) {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+
+ if ok {
+ // Update existing
+ elem.V = value
+ } else {
+ // Allocate elem
+ elem = m.alloc()
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Push to back of list
+ m.list.PushBack(elem)
+ }
+}
+
+// Index returns the key-value pair at index from map. Returns false if index out of range.
+func (m *OrderedMap[K, V]) Index(idx int) (K, V, bool) {
+ if idx < 0 || idx >= m.list.len {
+ var (
+ zk K
+ zv V
+ ) // zero values
+ return zk, zv, false
+ }
+ elem := m.list.Index(idx)
+ return elem.K, elem.V, true
+}
+
+// Push will insert the given key-value pair at index in the map. Panics if index out of range.
+func (m *OrderedMap[K, V]) Push(idx int, key K, value V) {
+ // Check index within bounds of map
+ if idx < 0 || idx >= m.list.len {
+ panic("index out of bounds")
+ }
+
+ // Ensure safe
+ m.write_check()
+
+ // Get element at index
+ next := m.list.Index(idx)
+
+ // Allocate new elem
+ elem := m.alloc()
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Move next forward
+ elem.next = next
+ elem.prev = next.prev
+
+ // Link up elem in chain
+ next.prev.next = elem
+ next.prev = elem
+}
+
+// Pop will remove and return the key-value pair at index in the map. Panics if index out of range.
+func (m *OrderedMap[K, V]) Pop(idx int) (K, V) {
+ // Check index within bounds of map
+ if idx < 0 || idx >= m.list.len {
+ panic("index out of bounds")
+ }
+
+ // Ensure safe
+ m.write_check()
+
+ // Get element at index
+ elem := m.list.Index(idx)
+
+ // Unlink elem from list
+ m.list.Unlink(elem)
+
+ // Get elem values
+ k := elem.K
+ v := elem.V
+
+ // Release to pool
+ m.free(elem)
+
+ return k, v
+}
+
+// Format implements fmt.Formatter, allowing performant string formatting of map.
+func (m *OrderedMap[K, V]) Format(state fmt.State, verb rune) {
+ m.format(reflect.TypeOf(m), state, verb)
+}