mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 22:52:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			708 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			708 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package structr
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"sync"
 | |
| )
 | |
| 
 | |
| // DefaultIgnoreErr is the default function used to
 | |
| // ignore (i.e. not cache) incoming error results during
 | |
| // Load() calls. By default ignores context pkg errors.
 | |
| func DefaultIgnoreErr(err error) bool {
 | |
| 	return errors.Is(err, context.Canceled) ||
 | |
| 		errors.Is(err, context.DeadlineExceeded)
 | |
| }
 | |
| 
 | |
| // Config defines config variables
 | |
| // for initializing a struct cache.
 | |
| type Config[StructType any] struct {
 | |
| 
 | |
| 	// Indices defines indices to create
 | |
| 	// in the Cache for the receiving
 | |
| 	// generic struct type parameter.
 | |
| 	Indices []IndexConfig
 | |
| 
 | |
| 	// MaxSize defines the maximum number
 | |
| 	// of results allowed in the Cache at
 | |
| 	// one time, before old results start
 | |
| 	// getting evicted.
 | |
| 	MaxSize int
 | |
| 
 | |
| 	// IgnoreErr defines which errors to
 | |
| 	// ignore (i.e. not cache) returned
 | |
| 	// from load function callback calls.
 | |
| 	// This may be left as nil, on which
 | |
| 	// DefaultIgnoreErr will be used.
 | |
| 	IgnoreErr func(error) bool
 | |
| 
 | |
| 	// CopyValue provides a means of copying
 | |
| 	// cached values, to ensure returned values
 | |
| 	// do not share memory with those in cache.
 | |
| 	CopyValue func(StructType) StructType
 | |
| 
 | |
| 	// Invalidate is called when cache values
 | |
| 	// (NOT errors) are invalidated, either
 | |
| 	// as the values passed to Put() / Store(),
 | |
| 	// or by the keys by calls to Invalidate().
 | |
| 	Invalidate func(StructType)
 | |
| }
 | |
| 
 | |
| // Cache provides a structure cache with automated
 | |
| // indexing and lookups by any initialization-defined
 | |
| // combination of fields (as long as serialization is
 | |
| // supported by codeberg.org/gruf/go-mangler). This
 | |
| // also supports caching of negative results by errors
 | |
| // returned from the LoadOne() series of functions.
 | |
| type Cache[StructType any] struct {
 | |
| 
 | |
| 	// indices used in storing passed struct
 | |
| 	// types by user defined sets of fields.
 | |
| 	indices []Index[StructType]
 | |
| 
 | |
| 	// keeps track of all indexed results,
 | |
| 	// in order of last recently used (LRU).
 | |
| 	lruList list
 | |
| 
 | |
| 	// max cache size, imposes size
 | |
| 	// limit on the lruList in order
 | |
| 	// to evict old entries.
 | |
| 	maxSize int
 | |
| 
 | |
| 	// hook functions.
 | |
| 	ignore  func(error) bool
 | |
| 	copy    func(StructType) StructType
 | |
| 	invalid func(StructType)
 | |
| 
 | |
| 	// protective mutex, guards:
 | |
| 	// - Cache{}.lruList
 | |
| 	// - Index{}.data
 | |
| 	// - Cache{} hook fns
 | |
| 	mutex sync.Mutex
 | |
| }
 | |
| 
 | |
| // Init initializes the cache with given configuration
 | |
| // including struct fields to index, and necessary fns.
 | |
| func (c *Cache[T]) Init(config Config[T]) {
 | |
| 	if len(config.Indices) == 0 {
 | |
| 		panic("no indices provided")
 | |
| 	}
 | |
| 
 | |
| 	if config.IgnoreErr == nil {
 | |
| 		config.IgnoreErr = DefaultIgnoreErr
 | |
| 	}
 | |
| 
 | |
| 	if config.CopyValue == nil {
 | |
| 		panic("copy value function must be provided")
 | |
| 	}
 | |
| 
 | |
| 	if config.MaxSize < 2 {
 | |
| 		panic("minimum cache size is 2 for LRU to work")
 | |
| 	}
 | |
| 
 | |
| 	// Safely copy over
 | |
| 	// provided config.
 | |
| 	c.mutex.Lock()
 | |
| 	c.indices = make([]Index[T], len(config.Indices))
 | |
| 	for i, cfg := range config.Indices {
 | |
| 		init_index(&c.indices[i], cfg, config.MaxSize)
 | |
| 	}
 | |
| 	c.ignore = config.IgnoreErr
 | |
| 	c.copy = config.CopyValue
 | |
| 	c.invalid = config.Invalidate
 | |
| 	c.maxSize = config.MaxSize
 | |
| 	c.mutex.Unlock()
 | |
| }
 | |
| 
 | |
| // Index selects index with given name from cache, else panics.
 | |
| func (c *Cache[T]) Index(name string) *Index[T] {
 | |
| 	for i := range c.indices {
 | |
| 		if c.indices[i].name == name {
 | |
| 			return &c.indices[i]
 | |
| 		}
 | |
| 	}
 | |
| 	panic("unknown index: " + name)
 | |
| }
 | |
| 
 | |
| // GetOne fetches one value from the cache stored under index, using key generated from key parts.
 | |
| // Note that given number of key parts MUST match expected number and types of the given index name.
 | |
| func (c *Cache[T]) GetOne(index string, key ...any) (T, bool) {
 | |
| 	return c.GetOneBy(c.Index(index), key...)
 | |
| }
 | |
| 
 | |
| // GetOneBy fetches value from cache stored under index, using precalculated index key.
 | |
| func (c *Cache[T]) GetOneBy(index *Index[T], key ...any) (T, bool) {
 | |
| 	if index == nil {
 | |
| 		panic("no index given")
 | |
| 	} else if !is_unique(index.flags) {
 | |
| 		panic("cannot get one by non-unique index")
 | |
| 	}
 | |
| 	values := c.GetBy(index, key)
 | |
| 	if len(values) == 0 {
 | |
| 		var zero T
 | |
| 		return zero, false
 | |
| 	}
 | |
| 	return values[0], true
 | |
| }
 | |
| 
 | |
| // Get fetches values from the cache stored under index, using keys generated from given key parts.
 | |
| // Note that each number of key parts MUST match expected number and types of the given index name.
 | |
| func (c *Cache[T]) Get(index string, keys ...[]any) []T {
 | |
| 	return c.GetBy(c.Index(index), keys...)
 | |
| }
 | |
| 
 | |
| // GetBy fetches values from the cache stored under index, using precalculated index keys.
 | |
| func (c *Cache[T]) GetBy(index *Index[T], keys ...[]any) []T {
 | |
| 	if index == nil {
 | |
| 		panic("no index given")
 | |
| 	}
 | |
| 
 | |
| 	// Acquire hasher.
 | |
| 	h := get_hasher()
 | |
| 
 | |
| 	// Acquire lock.
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Check cache init.
 | |
| 	if c.copy == nil {
 | |
| 		c.mutex.Unlock()
 | |
| 		panic("not initialized")
 | |
| 	}
 | |
| 
 | |
| 	// Preallocate expected ret slice.
 | |
| 	values := make([]T, 0, len(keys))
 | |
| 
 | |
| 	for _, key := range keys {
 | |
| 
 | |
| 		// Generate sum from provided key.
 | |
| 		sum, ok := index_hash(index, h, key)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Get indexed results list at key.
 | |
| 		list := index_get(index, sum, key)
 | |
| 		if list == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Concatenate all *values* from non-err cached results.
 | |
| 		list_rangefn(list, func(e *list_elem) {
 | |
| 			entry := (*index_entry)(e.data)
 | |
| 			res := entry.result
 | |
| 
 | |
| 			switch value := res.data.(type) {
 | |
| 			case T:
 | |
| 				// Append value COPY.
 | |
| 				value = c.copy(value)
 | |
| 				values = append(values, value)
 | |
| 
 | |
| 			case error:
 | |
| 				// Don't bump
 | |
| 				// for errors.
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			// Push to front of LRU list, USING
 | |
| 			// THE RESULT'S LRU ENTRY, NOT THE
 | |
| 			// INDEX KEY ENTRY. VERY IMPORTANT!!
 | |
| 			list_move_front(&c.lruList, &res.elem)
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// Done with lock.
 | |
| 	c.mutex.Unlock()
 | |
| 
 | |
| 	// Done with h.
 | |
| 	hash_pool.Put(h)
 | |
| 
 | |
| 	return values
 | |
| }
 | |
| 
 | |
| // Put will insert the given values into cache,
 | |
| // calling any invalidate hook on each value.
 | |
| func (c *Cache[T]) Put(values ...T) {
 | |
| 	var z Hash
 | |
| 
 | |
| 	// Acquire lock.
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Get func ptrs.
 | |
| 	invalid := c.invalid
 | |
| 
 | |
| 	// Check cache init.
 | |
| 	if c.copy == nil {
 | |
| 		c.mutex.Unlock()
 | |
| 		panic("not initialized")
 | |
| 	}
 | |
| 
 | |
| 	// Store all the passed values.
 | |
| 	for _, value := range values {
 | |
| 		c.store_value(nil, z, nil, value)
 | |
| 	}
 | |
| 
 | |
| 	// Done with lock.
 | |
| 	c.mutex.Unlock()
 | |
| 
 | |
| 	if invalid != nil {
 | |
| 		// Pass all invalidated values
 | |
| 		// to given user hook (if set).
 | |
| 		for _, value := range values {
 | |
| 			invalid(value)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // LoadOne fetches one result from the cache stored under index, using key generated from key parts.
 | |
| // In the case that no result is found, the provided load callback will be used to hydrate the cache.
 | |
| // Note that given number of key parts MUST match expected number and types of the given index name.
 | |
| func (c *Cache[T]) LoadOne(index string, load func() (T, error), key ...any) (T, error) {
 | |
| 	return c.LoadOneBy(c.Index(index), load, key...)
 | |
| }
 | |
| 
 | |
| // LoadOneBy fetches one result from the cache stored under index, using precalculated index key.
 | |
| // In the case that no result is found, provided load callback will be used to hydrate the cache.
 | |
| func (c *Cache[T]) LoadOneBy(index *Index[T], load func() (T, error), key ...any) (T, error) {
 | |
| 	if index == nil {
 | |
| 		panic("no index given")
 | |
| 	} else if !is_unique(index.flags) {
 | |
| 		panic("cannot get one by non-unique index")
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		// whether a result was found
 | |
| 		// (and so val / err are set).
 | |
| 		ok bool
 | |
| 
 | |
| 		// separate value / error ptrs
 | |
| 		// as the result is liable to
 | |
| 		// change outside of lock.
 | |
| 		val T
 | |
| 		err error
 | |
| 	)
 | |
| 
 | |
| 	// Acquire hasher.
 | |
| 	h := get_hasher()
 | |
| 
 | |
| 	// Generate sum from provided key.
 | |
| 	sum, _ := index_hash(index, h, key)
 | |
| 
 | |
| 	// Done with h.
 | |
| 	hash_pool.Put(h)
 | |
| 
 | |
| 	// Acquire lock.
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Get func ptrs.
 | |
| 	ignore := c.ignore
 | |
| 
 | |
| 	// Check init'd.
 | |
| 	if c.copy == nil ||
 | |
| 		ignore == nil {
 | |
| 		c.mutex.Unlock()
 | |
| 		panic("not initialized")
 | |
| 	}
 | |
| 
 | |
| 	// Get indexed list at hash key.
 | |
| 	list := index_get(index, sum, key)
 | |
| 
 | |
| 	if ok = (list != nil); ok {
 | |
| 		entry := (*index_entry)(list.head.data)
 | |
| 		res := entry.result
 | |
| 
 | |
| 		switch data := res.data.(type) {
 | |
| 		case T:
 | |
| 			// Return value COPY.
 | |
| 			val = c.copy(data)
 | |
| 		case error:
 | |
| 			// Return error.
 | |
| 			err = data
 | |
| 		}
 | |
| 
 | |
| 		// Push to front of LRU list, USING
 | |
| 		// THE RESULT'S LRU ENTRY, NOT THE
 | |
| 		// INDEX KEY ENTRY. VERY IMPORTANT!!
 | |
| 		list_move_front(&c.lruList, &res.elem)
 | |
| 	}
 | |
| 
 | |
| 	// Done with lock.
 | |
| 	c.mutex.Unlock()
 | |
| 
 | |
| 	if ok {
 | |
| 		// result found!
 | |
| 		return val, err
 | |
| 	}
 | |
| 
 | |
| 	// Load new result.
 | |
| 	val, err = load()
 | |
| 
 | |
| 	// Check for ignored
 | |
| 	// (transient) errors.
 | |
| 	if ignore(err) {
 | |
| 		return val, err
 | |
| 	}
 | |
| 
 | |
| 	// Acquire lock.
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Index this new loaded result.
 | |
| 	// Note this handles copying of
 | |
| 	// the provided value, so it is
 | |
| 	// safe for us to return as-is.
 | |
| 	if err != nil {
 | |
| 		c.store_error(index, sum, key, err)
 | |
| 	} else {
 | |
| 		c.store_value(index, sum, key, val)
 | |
| 	}
 | |
| 
 | |
| 	// Done with lock.
 | |
| 	c.mutex.Unlock()
 | |
| 
 | |
| 	return val, err
 | |
| }
 | |
| 
 | |
| // Load fetches values from the cache stored under index, using keys generated from given key parts. The provided get callback is used
 | |
| // to load groups of values from the cache by the key generated from the key parts provided to the inner callback func, where the returned
 | |
| // boolean indicates whether any values are currently stored. After the get callback has returned, the cache will then call provided load
 | |
| // callback to hydrate the cache with any other values. Example usage here is that you may see which values are cached using 'get', and load
 | |
| // the remaining uncached values using 'load', to minimize database queries. Cached error results are not included or returned by this func.
 | |
| // Note that given number of key parts MUST match expected number and types of the given index name, in those provided to the get callback.
 | |
| func (c *Cache[T]) Load(index string, get func(load func(key ...any) bool), load func() ([]T, error)) (values []T, err error) {
 | |
| 	return c.LoadBy(c.Index(index), get, load)
 | |
| }
 | |
| 
 | |
| // LoadBy fetches values from the cache stored under index, using precalculated index key. The provided get callback is used to load
 | |
| // groups of values from the cache by the key generated from the key parts provided to the inner callback func, where the returned boolea
 | |
| // indicates whether any values are currently stored. After the get callback has returned, the cache will then call provided load callback
 | |
| // to hydrate the cache with any other values. Example usage here is that you may see which values are cached using 'get', and load the
 | |
| // remaining uncached values using 'load', to minimize database queries. Cached error results are not included or returned by this func.
 | |
| // Note that given number of key parts MUST match expected number and types of the given index name, in those provided to the get callback.
 | |
| func (c *Cache[T]) LoadBy(index *Index[T], get func(load func(key ...any) bool), load func() ([]T, error)) (values []T, err error) {
 | |
| 	if index == nil {
 | |
| 		panic("no index given")
 | |
| 	}
 | |
| 
 | |
| 	// Acquire hasher.
 | |
| 	h := get_hasher()
 | |
| 
 | |
| 	// Acquire lock.
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Check init'd.
 | |
| 	if c.copy == nil {
 | |
| 		c.mutex.Unlock()
 | |
| 		panic("not initialized")
 | |
| 	}
 | |
| 
 | |
| 	var unlocked bool
 | |
| 	defer func() {
 | |
| 		// Deferred unlock to catch
 | |
| 		// any user function panics.
 | |
| 		if !unlocked {
 | |
| 			c.mutex.Unlock()
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// Pass loader to user func.
 | |
| 	get(func(key ...any) bool {
 | |
| 
 | |
| 		// Generate sum from provided key.
 | |
| 		sum, ok := index_hash(index, h, key)
 | |
| 		if !ok {
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		// Get indexed results at hash key.
 | |
| 		list := index_get(index, sum, key)
 | |
| 		if list == nil {
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		// Value length before
 | |
| 		// any below appends.
 | |
| 		before := len(values)
 | |
| 
 | |
| 		// Concatenate all *values* from non-err cached results.
 | |
| 		list_rangefn(list, func(e *list_elem) {
 | |
| 			entry := (*index_entry)(e.data)
 | |
| 			res := entry.result
 | |
| 
 | |
| 			switch value := res.data.(type) {
 | |
| 			case T:
 | |
| 				// Append value COPY.
 | |
| 				value = c.copy(value)
 | |
| 				values = append(values, value)
 | |
| 
 | |
| 			case error:
 | |
| 				// Don't bump
 | |
| 				// for errors.
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			// Push to front of LRU list, USING
 | |
| 			// THE RESULT'S LRU ENTRY, NOT THE
 | |
| 			// INDEX KEY ENTRY. VERY IMPORTANT!!
 | |
| 			list_move_front(&c.lruList, &res.elem)
 | |
| 		})
 | |
| 
 | |
| 		// Only if values changed did
 | |
| 		// we actually find anything.
 | |
| 		return len(values) != before
 | |
| 	})
 | |
| 
 | |
| 	// Done with lock.
 | |
| 	c.mutex.Unlock()
 | |
| 	unlocked = true
 | |
| 
 | |
| 	// Done with h.
 | |
| 	hash_pool.Put(h)
 | |
| 
 | |
| 	// Load uncached values.
 | |
| 	uncached, err := load()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Insert uncached.
 | |
| 	c.Put(uncached...)
 | |
| 
 | |
| 	// Append uncached to return values.
 | |
| 	values = append(values, uncached...)
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // Store will call the given store callback, on non-error then
 | |
| // passing the provided value to the Put() function. On error
 | |
| // return the value is still passed to stored invalidate hook.
 | |
| func (c *Cache[T]) Store(value T, store func() error) error {
 | |
| 	// Store value.
 | |
| 	err := store()
 | |
| 
 | |
| 	if err != nil {
 | |
| 		// Get func ptrs.
 | |
| 		c.mutex.Lock()
 | |
| 		invalid := c.invalid
 | |
| 		c.mutex.Unlock()
 | |
| 
 | |
| 		// On error don't store
 | |
| 		// value, but still pass
 | |
| 		// to invalidate hook.
 | |
| 		if invalid != nil {
 | |
| 			invalid(value)
 | |
| 		}
 | |
| 
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Store value.
 | |
| 	c.Put(value)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Invalidate generates index key from parts and invalidates all stored under it.
 | |
| func (c *Cache[T]) Invalidate(index string, key ...any) {
 | |
| 	c.InvalidateBy(c.Index(index), key...)
 | |
| }
 | |
| 
 | |
| // InvalidateBy invalidates all results stored under index key.
 | |
| func (c *Cache[T]) InvalidateBy(index *Index[T], key ...any) {
 | |
| 	if index == nil {
 | |
| 		panic("no index given")
 | |
| 	}
 | |
| 
 | |
| 	// Acquire hasher.
 | |
| 	h := get_hasher()
 | |
| 
 | |
| 	// Generate sum from provided key.
 | |
| 	sum, ok := index_hash(index, h, key)
 | |
| 
 | |
| 	// Done with h.
 | |
| 	hash_pool.Put(h)
 | |
| 
 | |
| 	if !ok {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var values []T
 | |
| 
 | |
| 	// Acquire lock.
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Get func ptrs.
 | |
| 	invalid := c.invalid
 | |
| 
 | |
| 	// Delete all results under key from index, collecting
 | |
| 	// value results and dropping them from all their indices.
 | |
| 	index_delete(c, index, sum, key, func(del *result) {
 | |
| 		switch value := del.data.(type) {
 | |
| 		case T:
 | |
| 			// Append value COPY.
 | |
| 			value = c.copy(value)
 | |
| 			values = append(values, value)
 | |
| 		case error:
 | |
| 		}
 | |
| 		c.delete(del)
 | |
| 	})
 | |
| 
 | |
| 	// Done with lock.
 | |
| 	c.mutex.Unlock()
 | |
| 
 | |
| 	if invalid != nil {
 | |
| 		// Pass all invalidated values
 | |
| 		// to given user hook (if set).
 | |
| 		for _, value := range values {
 | |
| 			invalid(value)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Trim will truncate the cache to ensure it
 | |
| // stays within given percentage of MaxSize.
 | |
| func (c *Cache[T]) Trim(perc float64) {
 | |
| 	// Acquire lock.
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Calculate number of cache items to drop.
 | |
| 	max := (perc / 100) * float64(c.maxSize)
 | |
| 	diff := c.lruList.len - int(max)
 | |
| 
 | |
| 	if diff <= 0 {
 | |
| 		// Trim not needed.
 | |
| 		c.mutex.Unlock()
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Iterate over 'diff' results
 | |
| 	// from back (oldest) of cache.
 | |
| 	for i := 0; i < diff; i++ {
 | |
| 
 | |
| 		// Get oldest LRU element.
 | |
| 		oldest := c.lruList.tail
 | |
| 
 | |
| 		if oldest == nil {
 | |
| 			// reached end.
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		// Drop oldest from cache.
 | |
| 		res := (*result)(oldest.data)
 | |
| 		c.delete(res)
 | |
| 	}
 | |
| 
 | |
| 	// Done with lock.
 | |
| 	c.mutex.Unlock()
 | |
| }
 | |
| 
 | |
| // Clear empties the cache by calling .Trim(0).
 | |
| func (c *Cache[T]) Clear() { c.Trim(0) }
 | |
| 
 | |
| // Len returns the current length of cache.
 | |
| func (c *Cache[T]) Len() int {
 | |
| 	c.mutex.Lock()
 | |
| 	l := c.lruList.len
 | |
| 	c.mutex.Unlock()
 | |
| 	return l
 | |
| }
 | |
| 
 | |
| // Cap returns the maximum capacity (size) of cache.
 | |
| func (c *Cache[T]) Cap() int {
 | |
| 	c.mutex.Lock()
 | |
| 	m := c.maxSize
 | |
| 	c.mutex.Unlock()
 | |
| 	return m
 | |
| }
 | |
| 
 | |
| func (c *Cache[T]) store_value(index *Index[T], hash Hash, key []any, value T) {
 | |
| 	// Acquire new result.
 | |
| 	res := result_acquire(c)
 | |
| 
 | |
| 	if index != nil {
 | |
| 		// Append result to the provided index
 | |
| 		// with precalculated key / its hash.
 | |
| 		index_append(c, index, hash, key, res)
 | |
| 	}
 | |
| 
 | |
| 	// Create COPY of value.
 | |
| 	value = c.copy(value)
 | |
| 	res.data = value
 | |
| 
 | |
| 	// Acquire hasher.
 | |
| 	h := get_hasher()
 | |
| 
 | |
| 	for i := range c.indices {
 | |
| 		// Get current index ptr.
 | |
| 		idx := &(c.indices[i])
 | |
| 
 | |
| 		if idx == index {
 | |
| 			// Already stored under
 | |
| 			// this index, ignore.
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Get key and hash sum for this index.
 | |
| 		key, sum, ok := index_key(idx, h, value)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Append result to index at key.
 | |
| 		index_append(c, idx, sum, key, res)
 | |
| 	}
 | |
| 
 | |
| 	// Done with h.
 | |
| 	hash_pool.Put(h)
 | |
| 
 | |
| 	if c.lruList.len > c.maxSize {
 | |
| 		// Cache has hit max size!
 | |
| 		// Drop the oldest element.
 | |
| 		ptr := c.lruList.tail.data
 | |
| 		res := (*result)(ptr)
 | |
| 		c.delete(res)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Cache[T]) store_error(index *Index[T], hash Hash, key []any, err error) {
 | |
| 	if index == nil {
 | |
| 		// nothing we
 | |
| 		// can do here.
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Acquire new result.
 | |
| 	res := result_acquire(c)
 | |
| 	res.data = err
 | |
| 
 | |
| 	// Append result to the provided index
 | |
| 	// with precalculated key / its hash.
 | |
| 	index_append(c, index, hash, key, res)
 | |
| 
 | |
| 	if c.lruList.len > c.maxSize {
 | |
| 		// Cache has hit max size!
 | |
| 		// Drop the oldest element.
 | |
| 		ptr := c.lruList.tail.data
 | |
| 		res := (*result)(ptr)
 | |
| 		c.delete(res)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // delete will delete the given result from the cache, deleting
 | |
| // it from all indices it is stored under, and main LRU list.
 | |
| func (c *Cache[T]) delete(res *result) {
 | |
| 	for len(res.indexed) != 0 {
 | |
| 
 | |
| 		// Pop last indexed entry from list.
 | |
| 		entry := res.indexed[len(res.indexed)-1]
 | |
| 		res.indexed = res.indexed[:len(res.indexed)-1]
 | |
| 
 | |
| 		// Drop entry from index.
 | |
| 		index_delete_entry(c, entry)
 | |
| 
 | |
| 		// Release to memory pool.
 | |
| 		index_entry_release(entry)
 | |
| 	}
 | |
| 
 | |
| 	// Release res to pool.
 | |
| 	result_release(c, res)
 | |
| }
 |