diff options
Diffstat (limited to 'internal/cache/wrappers.go')
-rw-r--r-- | internal/cache/wrappers.go | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/internal/cache/wrappers.go b/internal/cache/wrappers.go new file mode 100644 index 000000000..edeea9bcd --- /dev/null +++ b/internal/cache/wrappers.go @@ -0,0 +1,214 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package cache + +import ( + "slices" + + "codeberg.org/gruf/go-cache/v3/simple" + "codeberg.org/gruf/go-structr" +) + +// SliceCache wraps a simple.Cache to provide simple loader-callback +// functions for fetching + caching slices of objects (e.g. IDs). +type SliceCache[T any] struct { + cache simple.Cache[string, []T] +} + +// Init initializes the cache with given length + capacity. +func (c *SliceCache[T]) Init(len, cap int) { + c.cache = simple.Cache[string, []T]{} + c.cache.Init(len, cap) +} + +// Load will attempt to load an existing slice from cache for key, else calling load function and caching the result. +func (c *SliceCache[T]) Load(key string, load func() ([]T, error)) ([]T, error) { + // Look for cached values. + data, ok := c.cache.Get(key) + + if !ok { + var err error + + // Not cached, load! + data, err = load() + if err != nil { + return nil, err + } + + // Store the data. + c.cache.Set(key, data) + } + + // Return data clone for safety. + return slices.Clone(data), nil +} + +// Invalidate: see simple.Cache{}.InvalidateAll(). +func (c *SliceCache[T]) Invalidate(keys ...string) { + _ = c.cache.InvalidateAll(keys...) +} + +// Trim: see simple.Cache{}.Trim(). +func (c *SliceCache[T]) Trim(perc float64) { + c.cache.Trim(perc) +} + +// Clear: see simple.Cache{}.Clear(). +func (c *SliceCache[T]) Clear() { + c.cache.Clear() +} + +// Len: see simple.Cache{}.Len(). +func (c *SliceCache[T]) Len() int { + return c.cache.Len() +} + +// Cap: see simple.Cache{}.Cap(). +func (c *SliceCache[T]) Cap() int { + return c.cache.Cap() +} + +// StructCache wraps a structr.Cache{} to simple index caching +// by name (also to ease update to library version that introduced +// this). (in the future it may be worth embedding these indexes by +// name under the main database caches struct which would reduce +// time required to access cached values). +type StructCache[StructType any] struct { + cache structr.Cache[StructType] + index map[string]*structr.Index +} + +// Init initializes the cache with given structr.CacheConfig{}. +func (c *StructCache[T]) Init(config structr.CacheConfig[T]) { + c.index = make(map[string]*structr.Index, len(config.Indices)) + c.cache = structr.Cache[T]{} + c.cache.Init(config) + for _, cfg := range config.Indices { + c.index[cfg.Fields] = c.cache.Index(cfg.Fields) + } +} + +// GetOne calls structr.Cache{}.GetOne(), using a cached structr.Index{} by 'index' name. +// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}. +func (c *StructCache[T]) GetOne(index string, key ...any) (T, bool) { + i := c.index[index] + return c.cache.GetOne(i, i.Key(key...)) +} + +// Get calls structr.Cache{}.Get(), using a cached structr.Index{} by 'index' name. +// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}. +func (c *StructCache[T]) Get(index string, keys ...[]any) []T { + i := c.index[index] + return c.cache.Get(i, i.Keys(keys...)...) +} + +// Put: see structr.Cache{}.Put(). +func (c *StructCache[T]) Put(values ...T) { + c.cache.Put(values...) +} + +// LoadOne calls structr.Cache{}.LoadOne(), using a cached structr.Index{} by 'index' name. +// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}. +func (c *StructCache[T]) LoadOne(index string, load func() (T, error), key ...any) (T, error) { + i := c.index[index] + return c.cache.LoadOne(i, i.Key(key...), load) +} + +// LoadIDs calls structr.Cache{}.Load(), using a cached structr.Index{} by 'index' name. Note: this also handles +// conversion of the ID strings to structr.Key{} via structr.Index{}. Strong typing is used for caller convenience. +// +// If you need to load multiple cache keys other than by ID strings, please create another convenience wrapper. +func (c *StructCache[T]) LoadIDs(index string, ids []string, load func([]string) ([]T, error)) ([]T, error) { + i := c.index[index] + if i == nil { + // we only perform this check here as + // we're going to use the index before + // passing it to cache in main .Load(). + panic("missing index for cache type") + } + + // Generate cache keys for ID types. + keys := make([]structr.Key, len(ids)) + for x, id := range ids { + keys[x] = i.Key(id) + } + + // Pass loader callback with wrapper onto main cache load function. + return c.cache.Load(i, keys, func(uncached []structr.Key) ([]T, error) { + uncachedIDs := make([]string, len(uncached)) + for i := range uncached { + uncachedIDs[i] = uncached[i].Values()[0].(string) + } + return load(uncachedIDs) + }) +} + +// Store: see structr.Cache{}.Store(). +func (c *StructCache[T]) Store(value T, store func() error) error { + return c.cache.Store(value, store) +} + +// Invalidate calls structr.Cache{}.Invalidate(), using a cached structr.Index{} by 'index' name. +// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}. +func (c *StructCache[T]) Invalidate(index string, key ...any) { + i := c.index[index] + c.cache.Invalidate(i, i.Key(key...)) +} + +// InvalidateIDs calls structr.Cache{}.Invalidate(), using a cached structr.Index{} by 'index' name. Note: this also +// handles conversion of the ID strings to structr.Key{} via structr.Index{}. Strong typing is used for caller convenience. +// +// If you need to invalidate multiple cache keys other than by ID strings, please create another convenience wrapper. +func (c *StructCache[T]) InvalidateIDs(index string, ids []string) { + i := c.index[index] + if i == nil { + // we only perform this check here as + // we're going to use the index before + // passing it to cache in main .Load(). + panic("missing index for cache type") + } + + // Generate cache keys for ID types. + keys := make([]structr.Key, len(ids)) + for x, id := range ids { + keys[x] = i.Key(id) + } + + // Pass to main invalidate func. + c.cache.Invalidate(i, keys...) +} + +// Trim: see structr.Cache{}.Trim(). +func (c *StructCache[T]) Trim(perc float64) { + c.cache.Trim(perc) +} + +// Clear: see structr.Cache{}.Clear(). +func (c *StructCache[T]) Clear() { + c.cache.Clear() +} + +// Len: see structr.Cache{}.Len(). +func (c *StructCache[T]) Len() int { + return c.cache.Len() +} + +// Cap: see structr.Cache{}.Cap(). +func (c *StructCache[T]) Cap() int { + return c.cache.Cap() +} |