diff options
| author | 2025-03-09 17:47:56 +0100 | |
|---|---|---|
| committer | 2025-12-01 22:08:04 +0100 | |
| commit | b1af8fd87760b34e3ff2fd3bda38f211815a0473 (patch) | |
| tree | 9317fad1a7ec298d7a8d2678e4e422953bbc6f33 /vendor/codeberg.org/gruf/go-structr/timeline.go | |
| parent | [chore] update URLs to forked source (diff) | |
| download | gotosocial-b1af8fd87760b34e3ff2fd3bda38f211815a0473.tar.xz | |
[chore] remove vendor
Diffstat (limited to 'vendor/codeberg.org/gruf/go-structr/timeline.go')
| -rw-r--r-- | vendor/codeberg.org/gruf/go-structr/timeline.go | 1197 |
1 files changed, 0 insertions, 1197 deletions
diff --git a/vendor/codeberg.org/gruf/go-structr/timeline.go b/vendor/codeberg.org/gruf/go-structr/timeline.go deleted file mode 100644 index e738a8228..000000000 --- a/vendor/codeberg.org/gruf/go-structr/timeline.go +++ /dev/null @@ -1,1197 +0,0 @@ -package structr - -import ( - "cmp" - "os" - "reflect" - "slices" - "strings" - "sync" - "sync/atomic" - "unsafe" - - "codeberg.org/gruf/go-mempool" -) - -// Direction defines a direction -// to iterate entries in a Timeline. -type Direction bool - -const ( - // Asc = ascending, i.e. bottom-up. - Asc = Direction(true) - - // Desc = descending, i.e. top-down. - Desc = Direction(false) -) - -// TimelineConfig defines config vars for initializing a Timeline{}. -type TimelineConfig[StructType any, PK cmp.Ordered] struct { - - // Copy provides a means of copying - // timelined values, to ensure returned values - // do not share memory with those in timeline. - Copy func(StructType) StructType - - // Invalidate is called when timelined - // values are invalidated, either as passed - // to Insert(), or by calls to Invalidate(). - Invalidate func(StructType) - - // PKey defines the generic parameter StructType's - // field to use as the primary key for this cache. - // It must be ordered so that the timeline can - // maintain correct sorting of inserted values. - // - // Field selection logic follows the same path as - // with IndexConfig{}.Fields. Noting that in this - // case only a single field is permitted, though - // it may be nested, and as described above the - // type must conform to cmp.Ordered. - PKey IndexConfig - - // Indices defines indices to create - // in the Timeline for the receiving - // generic struct type parameter. - Indices []IndexConfig -} - -// Timeline provides an ordered-list like cache of structures, -// with automated indexing and invalidation by any initialization -// defined combination of fields. The list order is maintained -// according to the configured struct primary key. -type Timeline[StructType any, PK cmp.Ordered] struct { - - // hook functions. - invalid func(StructType) - copy func(StructType) StructType - - // main underlying - // timeline list. - // - // where: - // - head = top = largest - // - tail = btm = smallest - list list - - // contains struct field information of - // the field used as the primary key for - // this timeline. it can also be found - // under indices[0] - pkey pkey_field - - // indices used in storing passed struct - // types by user defined sets of fields. - indices []Index - - // atomically updated head - // / tail primary key values. - headPK unsafe.Pointer - tailPK unsafe.Pointer - - // protective mutex, guards: - // - Timeline{}.* - // - Index{}.data - mutex sync.Mutex -} - -// Init initializes the timeline with given configuration -// including struct fields to index, and necessary fns. -func (t *Timeline[T, PK]) Init(config TimelineConfig[T, PK]) { - ti := get_type_iter[T]() - - if len(config.Indices) == 0 { - panic("no indices provided") - } - - if config.Copy == nil { - panic("copy function must be provided") - } - - if strings.Contains(config.PKey.Fields, ",") { - panic("primary key must contain only 1 field") - } - - // Verify primary key parameter type is correct. - names := strings.Split(config.PKey.Fields, ".") - if _, ftype := find_field(ti, names); // - ftype != reflect.TypeFor[PK]() { - panic("primary key field path and generic parameter type do not match") - } - - // Safely copy over - // provided config. - t.mutex.Lock() - defer t.mutex.Unlock() - - // The first index is created from PKey, - // other indices are created as expected. - t.indices = make([]Index, len(config.Indices)+1) - t.indices[0].ptr = unsafe.Pointer(t) - t.indices[0].init(ti, config.PKey, 0) - for i, cfg := range config.Indices { - t.indices[i+1].ptr = unsafe.Pointer(t) - t.indices[i+1].init(ti, cfg, 0) - } - - // Extract pkey details from index. - field := t.indices[0].fields[0] - t.pkey = pkey_field{ - zero: field.zero, - offsets: field.offsets, - } - - // Copy over remaining. - t.copy = config.Copy - t.invalid = config.Invalidate -} - -// Index selects index with given name from timeline, else panics. -func (t *Timeline[T, PK]) Index(name string) *Index { - for i, idx := range t.indices { - if idx.name == name { - return &(t.indices[i]) - } - } - panic("unknown index: " + name) -} - -// Head returns the current head primary key. -func (t *Timeline[T, PK]) Head() *PK { - return (*PK)(atomic.LoadPointer(&t.headPK)) -} - -// Tail returns the current tail primary key. -func (t *Timeline[T, PK]) Tail() *PK { - return (*PK)(atomic.LoadPointer(&t.tailPK)) -} - -// Select allows you to retrieve a slice of values, in order, from the timeline. -// This slice is defined by the minimum and maximum primary key parameters, up to -// a given length in size. The direction in which you select will determine which -// of the min / max primary key values is used as the *cursor* to begin the start -// of the selection, and which is used as the *boundary* to mark the end, if set. -// In either case, the length parameter is always optional. -// -// dir = Asc : cursors up from 'max' (required), with boundary 'min' (optional). -// dir = Desc : cursors down from 'min' (required), with boundary 'max' (optional). -func (t *Timeline[T, PK]) Select(min, max *PK, length *int, dir Direction) (values []T) { - - // Acquire lock. - t.mutex.Lock() - - // Check init'd. - if t.copy == nil { - t.mutex.Unlock() - panic("not initialized") - } - - switch dir { - case Asc: - // Verify args. - if min == nil { - t.mutex.Unlock() - panic("min must be provided when selecting asc") - } - - // Select determined values ASCENDING. - values = t.select_asc(*min, max, length) - - case Desc: - // Verify args. - if max == nil { - t.mutex.Unlock() - panic("max must be provided when selecting asc") - } - - // Select determined values DESCENDING. - values = t.select_desc(min, *max, length) - } - - // Done with lock. - t.mutex.Unlock() - - return values -} - -// Insert will insert the given values into the timeline, -// calling any set invalidate hook on each inserted value. -// Returns current list length after performing inserts. -func (t *Timeline[T, PK]) Insert(values ...T) int { - - // Acquire lock. - t.mutex.Lock() - - // Check init'd. - if t.copy == nil { - t.mutex.Unlock() - panic("not initialized") - } - - // Allocate a slice of our value wrapping struct type. - with_keys := make([]value_with_pk[T, PK], len(values)) - if len(with_keys) != len(values) { - panic(assert("BCE")) - } - - // Range the provided values. - for i, value := range values { - - // Create our own copy - // of value to work with. - value = t.copy(value) - - // Take ptr to the value copy. - vptr := unsafe.Pointer(&value) - - // Extract primary key from vptr. - kptr := extract_pkey(vptr, t.pkey) - pkey := *(*PK)(kptr) - - // Append wrapped value to slice with - // the acquire pointers and primary key. - with_keys[i] = value_with_pk[T, PK]{ - k: pkey, - v: value, - - kptr: kptr, - vptr: vptr, - } - } - - // BEFORE inserting the prepared slice of value copies w/ primary - // keys, sort them by their primary key, descending. This permits - // us to re-use the 'last' timeline position as next insert cursor. - // Otherwise we would have to iterate from 'head' every single time. - slices.SortFunc(with_keys, func(a, b value_with_pk[T, PK]) int { - const k = +1 - switch { - case a.k < b.k: - return +k - case b.k < a.k: - return -k - default: - return 0 - } - }) - - var last *list_elem - - // Store each value in the timeline, - // updating the last used list element - // each time so we don't have to iter - // down from head on every single store. - for _, value := range with_keys { - last = t.store_one(last, value) - } - - // Get func ptrs. - invalid := t.invalid - - // Get length AFTER - // insert to return. - len := t.list.len - - // Done with lock. - t.mutex.Unlock() - - if invalid != nil { - // Pass all invalidated values - // to given user hook (if set). - for _, value := range values { - invalid(value) - } - } - - return len -} - -// Invalidate invalidates all entries stored in index under given keys. -// Note that if set, this will call the invalidate hook on each value. -func (t *Timeline[T, PK]) Invalidate(index *Index, keys ...Key) { - if index == nil { - panic("no index given") - } else if index.ptr != unsafe.Pointer(t) { - panic("invalid index for timeline") - } - - // Acquire lock. - t.mutex.Lock() - - // Preallocate expected ret slice. - values := make([]T, 0, len(keys)) - - for i := range keys { - // Delete all items under key from index, collecting - // value items and dropping them from all their indices. - index.delete(keys[i].key, func(item *indexed_item) { - - // Cast to *actual* timeline item. - t_item := to_timeline_item(item) - - if value, ok := item.data.(T); ok { - // No need to copy, as item - // being deleted from cache. - values = append(values, value) - } - - // Delete item. - t.delete(t_item) - }) - } - - // Get func ptrs. - invalid := t.invalid - - // Done with lock. - t.mutex.Unlock() - - if invalid != nil { - // Pass all invalidated values - // to given user hook (if set). - for _, value := range values { - invalid(value) - } - } -} - -// Range will range over all values in the timeline in given direction. -// dir = Asc : ranges from the bottom-up. -// dir = Desc : ranges from the top-down. -// -// Please note that the entire Timeline{} will be locked for the duration of the range -// operation, i.e. from the beginning of the first yield call until the end of the last. -func (t *Timeline[T, PK]) Range(dir Direction) func(yield func(index int, value T) bool) { - return func(yield func(int, T) bool) { - if t.copy == nil { - panic("not initialized") - } else if yield == nil { - panic("nil func") - } - - // Acquire lock. - t.mutex.Lock() - defer t.mutex.Unlock() - - var i int - switch dir { - - case Asc: - // Iterate through linked list from bottom (i.e. tail). - for prev := t.list.tail; prev != nil; prev = prev.prev { - - // Extract item from list element. - item := (*timeline_item)(prev.data) - - // Create copy of item value. - value := t.copy(item.data.(T)) - - // Pass to given function. - if !yield(i, value) { - break - } - - // Iter - i++ - } - - case Desc: - // Iterate through linked list from top (i.e. head). - for next := t.list.head; next != nil; next = next.next { - - // Extract item from list element. - item := (*timeline_item)(next.data) - - // Create copy of item value. - value := t.copy(item.data.(T)) - - // Pass to given function. - if !yield(i, value) { - break - } - - // Iter - i++ - } - } - } -} - -// RangeUnsafe is functionally similar to Range(), except it does not pass *copies* of -// data. It allows you to operate on the data directly and modify it. As such it can also -// be more performant to use this function, even for read-write operations. -// -// Please note that the entire Timeline{} will be locked for the duration of the range -// operation, i.e. from the beginning of the first yield call until the end of the last. -func (t *Timeline[T, PK]) RangeUnsafe(dir Direction) func(yield func(index int, value T) bool) { - return func(yield func(int, T) bool) { - if t.copy == nil { - panic("not initialized") - } else if yield == nil { - panic("nil func") - } - - // Acquire lock. - t.mutex.Lock() - defer t.mutex.Unlock() - - var i int - switch dir { - - case Asc: - // Iterate through linked list from bottom (i.e. tail). - for prev := t.list.tail; prev != nil; prev = prev.prev { - - // Extract item from list element. - item := (*timeline_item)(prev.data) - - // Pass to given function. - if !yield(i, item.data.(T)) { - break - } - - // Iter - i++ - } - - case Desc: - // Iterate through linked list from top (i.e. head). - for next := t.list.head; next != nil; next = next.next { - - // Extract item from list element. - item := (*timeline_item)(next.data) - - // Pass to given function. - if !yield(i, item.data.(T)) { - break - } - - // Iter - i++ - } - } - } -} - -// RangeKeys will iterate over all values for given keys in the given index. -// -// Please note that the entire Timeline{} will be locked for the duration of the range -// operation, i.e. from the beginning of the first yield call until the end of the last. -func (t *Timeline[T, PK]) RangeKeys(index *Index, keys ...Key) func(yield func(T) bool) { - return func(yield func(T) bool) { - if t.copy == nil { - panic("not initialized") - } else if index == nil { - panic("no index given") - } else if index.ptr != unsafe.Pointer(t) { - panic("invalid index for timeline") - } else if yield == nil { - panic("nil func") - } - - // Acquire lock. - t.mutex.Lock() - defer t.mutex.Unlock() - - for _, key := range keys { - var done bool - - // Iterate over values in index under key. - index.get(key.key, func(i *indexed_item) { - - // Cast to timeline_item type. - item := to_timeline_item(i) - - // Create copy of item value. - value := t.copy(item.data.(T)) - - // Pass val to yield function. - done = done || !yield(value) - }) - - if done { - break - } - } - } -} - -// RangeKeysUnsafe is functionally similar to RangeKeys(), except it does not pass *copies* -// of data. It allows you to operate on the data directly and modify it. As such it can also -// be more performant to use this function, even for read-write operations. -// -// Please note that the entire Timeline{} will be locked for the duration of the range -// operation, i.e. from the beginning of the first yield call until the end of the last. -func (t *Timeline[T, PK]) RangeKeysUnsafe(index *Index, keys ...Key) func(yield func(T) bool) { - return func(yield func(T) bool) { - if t.copy == nil { - panic("not initialized") - } else if index == nil { - panic("no index given") - } else if index.ptr != unsafe.Pointer(t) { - panic("invalid index for timeline") - } else if yield == nil { - panic("nil func") - } - - // Acquire lock. - t.mutex.Lock() - defer t.mutex.Unlock() - - for _, key := range keys { - var done bool - - // Iterate over values in index under key. - index.get(key.key, func(i *indexed_item) { - - // Cast to timeline_item type. - item := to_timeline_item(i) - - // Pass value data to yield function. - done = done || !yield(item.data.(T)) - }) - - if done { - break - } - } - } -} - -// Trim will remove entries from the timeline in given -// direction, ensuring timeline is no larger than 'max'. -// If 'max' >= t.Len(), this function is a no-op. -// dir = Asc : trims from the bottom-up. -// dir = Desc : trims from the top-down. -func (t *Timeline[T, PK]) Trim(max int, dir Direction) { - // Acquire lock. - t.mutex.Lock() - - // Calculate number to drop. - diff := t.list.len - int(max) - if diff <= 0 { - - // Trim not needed. - t.mutex.Unlock() - return - } - - switch dir { - case Asc: - // Iterate over 'diff' items - // from bottom of timeline list. - for range diff { - - // Get bottom list elem. - bottom := t.list.tail - if bottom == nil { - - // Zero head + tail primary keys. - atomic.StorePointer(&t.headPK, nil) - atomic.StorePointer(&t.tailPK, nil) - - // reached - // end. - break - } - - // Drop bottom-most item from timeline. - item := (*timeline_item)(bottom.data) - t.delete(item) - } - - case Desc: - // Iterate over 'diff' items - // from top of timeline list. - for range diff { - - // Get top list elem. - top := t.list.head - if top == nil { - - // Zero head + tail primary keys. - atomic.StorePointer(&t.headPK, nil) - atomic.StorePointer(&t.tailPK, nil) - - // reached - // end. - break - } - - // Drop top-most item from timeline. - item := (*timeline_item)(top.data) - t.delete(item) - } - } - - // Compact index data stores. - for _, idx := range t.indices { - (&idx).data.Compact() - } - - // Done with lock. - t.mutex.Unlock() -} - -// Clear empties the timeline by calling .TrimBottom(0, Down). -func (t *Timeline[T, PK]) Clear() { t.Trim(0, Desc) } - -// Len returns the current length of cache. -func (t *Timeline[T, PK]) Len() int { - t.mutex.Lock() - l := t.list.len - t.mutex.Unlock() - return l -} - -// Debug returns debug stats about cache. -func (t *Timeline[T, PK]) Debug() map[string]any { - m := make(map[string]any, 2) - t.mutex.Lock() - m["list"] = t.list.len - indices := make(map[string]any, len(t.indices)) - m["indices"] = indices - for _, idx := range t.indices { - var n uint64 - for _, l := range idx.data.m { - n += uint64(l.len) - } - indices[idx.name] = n - } - t.mutex.Unlock() - return m -} - -func (t *Timeline[T, PK]) select_asc(min PK, max *PK, length *int) (values []T) { - // Iterate through linked list - // from bottom (i.e. tail), asc. - prev := t.list.tail - - // Iterate from 'prev' up, skipping all - // entries with pkey below cursor 'min'. - for ; prev != nil; prev = prev.prev { - item := (*timeline_item)(prev.data) - pkey := *(*PK)(item.pk) - - // Check below min. - if pkey <= min { - continue - } - - // Reached - // cursor. - break - } - - if prev == nil { - // No values - // remaining. - return - } - - // Optimized switch case to handle - // each set of argument combinations - // separately, in order to minimize - // number of checks during loops. - switch { - - case length != nil && max != nil: - // Deref arguments. - length := *length - max := *max - - // Optimistic preallocate slice. - values = make([]T, 0, length) - - // Both a length and maximum were given, - // select from cursor until either reached. - for ; prev != nil; prev = prev.prev { - item := (*timeline_item)(prev.data) - pkey := *(*PK)(item.pk) - - // Check above max. - if pkey >= max { - break - } - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - - // Check if length reached. - if len(values) >= length { - break - } - } - - case length != nil: - // Deref length. - length := *length - - // Optimistic preallocate slice. - values = make([]T, 0, length) - - // Only a length was given, select - // from cursor until length reached. - for ; prev != nil; prev = prev.prev { - item := (*timeline_item)(prev.data) - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - - // Check if length reached. - if len(values) >= length { - break - } - } - - case max != nil: - // Deref min. - max := *max - - // Only a maximum was given, select - // from cursor until max is reached. - for ; prev != nil; prev = prev.prev { - item := (*timeline_item)(prev.data) - pkey := *(*PK)(item.pk) - - // Check above max. - if pkey >= max { - break - } - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - } - - default: - // No maximum or length were given, - // ALL from cursor need selecting. - for ; prev != nil; prev = prev.prev { - item := (*timeline_item)(prev.data) - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - } - } - - return -} - -func (t *Timeline[T, PK]) select_desc(min *PK, max PK, length *int) (values []T) { - // Iterate through linked list - // from top (i.e. head), desc. - next := t.list.head - - // Iterate from 'next' down, skipping - // all entries with pkey above cursor 'max'. - for ; next != nil; next = next.next { - item := (*timeline_item)(next.data) - pkey := *(*PK)(item.pk) - - // Check above max. - if pkey >= max { - continue - } - - // Reached - // cursor. - break - } - - if next == nil { - // No values - // remaining. - return - } - - // Optimized switch case to handle - // each set of argument combinations - // separately, in order to minimize - // number of checks during loops. - switch { - - case length != nil && min != nil: - // Deref arguments. - length := *length - min := *min - - // Optimistic preallocate slice. - values = make([]T, 0, length) - - // Both a length and minimum were given, - // select from cursor until either reached. - for ; next != nil; next = next.next { - item := (*timeline_item)(next.data) - pkey := *(*PK)(item.pk) - - // Check below min. - if pkey <= min { - break - } - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - - // Check if length reached. - if len(values) >= length { - break - } - } - - case length != nil: - // Deref length. - length := *length - - // Optimistic preallocate slice. - values = make([]T, 0, length) - - // Only a length was given, select - // from cursor until length reached. - for ; next != nil; next = next.next { - item := (*timeline_item)(next.data) - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - - // Check if length reached. - if len(values) >= length { - break - } - } - - case min != nil: - // Deref min. - min := *min - - // Only a minimum was given, select - // from cursor until minimum reached. - for ; next != nil; next = next.next { - item := (*timeline_item)(next.data) - pkey := *(*PK)(item.pk) - - // Check below min. - if pkey <= min { - break - } - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - } - - default: - // No minimum or length were given, - // ALL from cursor need selecting. - for ; next != nil; next = next.next { - item := (*timeline_item)(next.data) - - // Append value copy. - value := item.data.(T) - value = t.copy(value) - values = append(values, value) - } - } - - return -} - -// value_with_pk wraps an incoming value type, with -// its extracted primary key, and pointers to both. -// this encompasses all arguments related to a value -// required by store_one(), simplifying some logic. -// -// with all the primary keys extracted, it also -// makes it much easier to sort input before insert. -type value_with_pk[T any, PK comparable] struct { - k PK // primary key value - v T // value copy - - kptr unsafe.Pointer // primary key ptr - vptr unsafe.Pointer // value copy ptr -} - -func (t *Timeline[T, PK]) store_one(last *list_elem, value value_with_pk[T, PK]) *list_elem { - // NOTE: the value passed here should - // already be a copy of the original. - - // Alloc new index item. - t_item := new_timeline_item() - if cap(t_item.indexed) < len(t.indices) { - - // Preallocate item indices slice to prevent Go auto - // allocating overlying large slices we don't need. - t_item.indexed = make([]*index_entry, 0, len(t.indices)) - } - - // Set item value data. - t_item.data = value.v - t_item.pk = value.kptr - - // Get zero'th index, i.e. - // the primary key index. - idx0 := (&t.indices[0]) - - // Acquire key buf. - buf := new_buffer() - - // Calculate index key from already extracted - // primary key, checking for zero return value. - partptrs := []unsafe.Pointer{value.kptr} - key := idx0.key(buf, partptrs) - if key == "" { // i.e. (!allow_zero && pkey == zero) - free_timeline_item(t_item) - free_buffer(buf) - return last - } - - // Convert to indexed_item pointer. - i_item := from_timeline_item(t_item) - - if last == nil { - // No previous element was provided, this is - // first insert, we need to work from head. - - // Check for emtpy head. - if t.list.head == nil { - - // The easiest case, this will - // be the first item in list. - t.list.push_front(&t_item.elem) - last = t.list.head // return value - goto indexing - } - - // Extract head item and its primary key. - headItem := (*timeline_item)(t.list.head.data) - headPK := *(*PK)(headItem.pk) - if value.k > headPK { - - // Another easier case, this also - // will be the first item in list. - t.list.push_front(&t_item.elem) - last = t.list.head // return value - goto indexing - } - - // Check (and drop) if pkey is a collision! - if value.k == headPK && is_unique(idx0.flags) { - free_timeline_item(t_item) - free_buffer(buf) - return t.list.head - } - - // Set last = head.next - // as next to work from. - last = t.list.head.next - } - - // Iterate through list from head - // to find location. Optimized into two - // cases to minimize loop CPU cycles. - if is_unique(idx0.flags) { - for next := last; // - next != nil; next = next.next { - - // Extract item and it's primary key. - nextItem := (*timeline_item)(next.data) - nextPK := *(*PK)(nextItem.pk) - - // If pkey smaller than - // cursor's, keep going. - if value.k < nextPK { - continue - } - - // Check (and drop) if - // pkey is a collision! - if value.k == nextPK { - free_timeline_item(t_item) - free_buffer(buf) - return next - } - - // New pkey is larger than cursor, - // insert into list just before it. - t.list.insert(&t_item.elem, next.prev) - last = next // return value - goto indexing - } - } else { - for next := last; // - next != nil; next = next.next { - - // Extract item and it's primary key. - nextItem := (*timeline_item)(next.data) - nextPK := *(*PK)(nextItem.pk) - - // If pkey smaller than - // cursor's, keep going. - if value.k < nextPK { - continue - } - - // New pkey is larger than cursor, - // insert into list just before it. - t.list.insert(&t_item.elem, next.prev) - last = next // return value - goto indexing - } - } - - // We reached the end of the - // list, insert at tail pos. - t.list.push_back(&t_item.elem) - last = t.list.tail // return value - goto indexing - -indexing: - // Set new head / tail - // primary key values. - switch last { - case t.list.head: - atomic.StorePointer(&t.headPK, value.kptr) - case t.list.tail: - atomic.StorePointer(&t.tailPK, value.kptr) - } - - // Append already-extracted - // primary key to 0th index. - _ = idx0.add(key, i_item) - - // Insert item into each of indices. - for i := 1; i < len(t.indices); i++ { - - // Get current index ptr. - idx := (&t.indices[i]) - - // Extract fields comprising index key from value. - parts := extract_fields(value.vptr, idx.fields) - - // Calculate this index key, - // checking for zero values. - key := idx.key(buf, parts) - if key == "" { - continue - } - - // Add this item to index, - // checking for collisions. - if !idx.add(key, i_item) { - - // This key already appears - // in this unique index. So - // drop new timeline item. - t.delete(t_item) - free_buffer(buf) - return last - } - } - - // Done with bufs. - free_buffer(buf) - return last -} - -func (t *Timeline[T, PK]) delete(i *timeline_item) { - for len(i.indexed) > 0 { - // Pop last indexed entry from list. - entry := i.indexed[len(i.indexed)-1] - i.indexed[len(i.indexed)-1] = nil - i.indexed = i.indexed[:len(i.indexed)-1] - - // Get entry's index. - index := entry.index - - // Drop this index_entry. - index.delete_entry(entry) - } - - // Drop from main list. - t.list.remove(&i.elem) - - // Free unused item. - free_timeline_item(i) -} - -type timeline_item struct { - indexed_item - - // retains fast ptr access - // to primary key value of - // above indexed_item{}.data - pk unsafe.Pointer - - // check bits always all set - // to 1. used to ensure cast - // from indexed_item to this - // type was originally a - // timeline_item to begin with. - ck uint -} - -func init() { - // ensure the embedded indexed_item struct is ALWAYS at zero offset. - // we rely on this to allow a ptr to one to be a ptr to either of them. - const off = unsafe.Offsetof(timeline_item{}.indexed_item) - if off != 0 { - panic(assert("offset_of(timeline_item{}.indexed_item) = 0")) - } -} - -// from_timeline_item converts a timeline_item ptr to indexed_item, given the above init() guarantee. -func from_timeline_item(item *timeline_item) *indexed_item { - return (*indexed_item)(unsafe.Pointer(item)) -} - -// to_timeline_item converts an indexed_item ptr to timeline_item, given the above init() guarantee. -// NOTE THIS MUST BE AN indexed_item THAT WAS INITIALLY CONVERTED WITH from_timeline_item(). -func to_timeline_item(item *indexed_item) *timeline_item { - to := (*timeline_item)(unsafe.Pointer(item)) - if to.ck != ^uint(0) { - // ensure check bits set, indicating - // it was a timeline_item originally. - panic(assert("t.ck = ^uint(0)")) - } - return to -} - -var timeline_item_pool mempool.UnsafePool - -// new_timeline_item returns a new prepared timeline_item. -func new_timeline_item() *timeline_item { - if ptr := timeline_item_pool.Get(); ptr != nil { - return (*timeline_item)(ptr) - } - item := new(timeline_item) - item.elem.data = unsafe.Pointer(item) - item.ck = ^uint(0) - return item -} - -// free_timeline_item releases the timeline_item. -func free_timeline_item(item *timeline_item) { - if len(item.indexed) > 0 || - item.elem.next != nil || - item.elem.prev != nil { - msg := assert("item not in use") - os.Stderr.WriteString(msg + "\n") - return - } - item.data = nil - item.pk = nil - ptr := unsafe.Pointer(item) - timeline_item_pool.Put(ptr) -} |
