mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 11:32:25 -05:00 
			
		
		
		
	
		
			
	
	
		
			1009 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			1009 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package structr | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"cmp" | ||
|  | 	"reflect" | ||
|  | 	"slices" | ||
|  | 	"sync" | ||
|  | 	"unsafe" | ||
|  | ) | ||
|  | 
 | ||
|  | // 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 string | ||
|  | 
 | ||
|  | 	// 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 | ||
|  | 
 | ||
|  | 	// 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]) { | ||
|  | 	rt := reflect.TypeOf((*T)(nil)).Elem() | ||
|  | 
 | ||
|  | 	if len(config.Indices) == 0 { | ||
|  | 		panic("no indices provided") | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if config.Copy == nil { | ||
|  | 		panic("copy function must be provided") | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Safely copy over | ||
|  | 	// provided config. | ||
|  | 	t.mutex.Lock() | ||
|  | 	defer t.mutex.Unlock() | ||
|  | 
 | ||
|  | 	// The first index is created from PKey. | ||
|  | 	t.indices = make([]Index, len(config.Indices)+1) | ||
|  | 	t.indices[0].ptr = unsafe.Pointer(t) | ||
|  | 	t.indices[0].init(rt, IndexConfig{ | ||
|  | 		Fields:    config.PKey, | ||
|  | 		AllowZero: true, | ||
|  | 		Multiple:  true, | ||
|  | 	}, 0) | ||
|  | 	if len(t.indices[0].fields) > 1 { | ||
|  | 		panic("primary key must contain only 1 field") | ||
|  | 	} | ||
|  | 	for i, cfg := range config.Indices { | ||
|  | 		t.indices[i+1].ptr = unsafe.Pointer(t) | ||
|  | 		t.indices[i+1].init(rt, cfg, 0) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Before extracting | ||
|  | 	// first index for pkey. | ||
|  | 	field := t.indices[0].fields[0] | ||
|  | 	t.pkey = pkey_field{ | ||
|  | 		rtype:   field.rtype, | ||
|  | 		offsets: field.offsets, | ||
|  | 		likeptr: field.likeptr, | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// 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) | ||
|  | } | ||
|  | 
 | ||
|  | // 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. | ||
|  | func (t *Timeline[T, PK]) Insert(values ...T) { | ||
|  | 
 | ||
|  | 	// 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("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) | ||
|  | 
 | ||
|  | 		var pkey PK | ||
|  | 		if kptr != nil { | ||
|  | 			// Cast as PK type. | ||
|  | 			pkey = *(*PK)(kptr) | ||
|  | 		} else { | ||
|  | 			// Use zero value pointer. | ||
|  | 			kptr = unsafe.Pointer(&pkey) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// 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, | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var last *list_elem | ||
|  | 
 | ||
|  | 	// BEFORE inserting the prepared slice of value copies w/ primary | ||
|  | 	// keys, sort them by their primary key, ascending. 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 | ||
|  | 		} | ||
|  | 	}) | ||
|  | 
 | ||
|  | 	// 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 | ||
|  | 
 | ||
|  | 	// 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) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // 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(T) bool) { | ||
|  | 	return func(yield func(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() | ||
|  | 
 | ||
|  | 		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(value) { | ||
|  | 					break | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 		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(value) { | ||
|  | 					break | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // 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 | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // 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 { | ||
|  | 
 | ||
|  | 				// 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 { | ||
|  | 
 | ||
|  | 				// 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 | ||
|  | 
 | ||
|  | 	// Acquire key buf. | ||
|  | 	buf := new_buffer() | ||
|  | 
 | ||
|  | 	// Convert to indexed_item ptr. | ||
|  | 	i_item := from_timeline_item(t_item) | ||
|  | 
 | ||
|  | 	// Append already-extracted | ||
|  | 	// primary key to 0th index. | ||
|  | 	idx := (&t.indices[0]) | ||
|  | 	partptrs := []unsafe.Pointer{value.kptr} | ||
|  | 	key := idx.key(buf, partptrs) | ||
|  | 	evicted := idx.append(key, i_item) | ||
|  | 	if evicted != nil { | ||
|  | 
 | ||
|  | 		// This item is no longer | ||
|  | 		// indexed, remove from list. | ||
|  | 		t.list.remove(&evicted.elem) | ||
|  | 
 | ||
|  | 		// Now convert from index_item ptr | ||
|  | 		// and release it to global mem pool. | ||
|  | 		evicted := to_timeline_item(evicted) | ||
|  | 		free_timeline_item(evicted) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	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. | ||
|  | 		key := idx.key(buf, parts) | ||
|  | 		if key == "" { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Append this item to index. | ||
|  | 		evicted := idx.append(key, i_item) | ||
|  | 		if evicted != nil { | ||
|  | 
 | ||
|  | 			// This item is no longer | ||
|  | 			// indexed, remove from list. | ||
|  | 			t.list.remove(&evicted.elem) | ||
|  | 
 | ||
|  | 			// Now convert from index_item ptr | ||
|  | 			// and release it to global mem pool. | ||
|  | 			evicted := to_timeline_item(evicted) | ||
|  | 			free_timeline_item(evicted) | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Done with buf. | ||
|  | 	free_buffer(buf) | ||
|  | 
 | ||
|  | 	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) | ||
|  | 			return t.list.head | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// 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) | ||
|  | 			return t.list.head | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Set last=head | ||
|  | 		// to work from. | ||
|  | 		last = t.list.head | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Iterate through linked list | ||
|  | 	// from head to find location. | ||
|  | 	for next := last.next; // | ||
|  | 	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) | ||
|  | 		return next | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// We reached the end of the | ||
|  | 	// list, insert at tail pos. | ||
|  | 	t.list.push_back(&t_item.elem) | ||
|  | 	return t.list.tail | ||
|  | } | ||
|  | 
 | ||
|  | 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("invalid timeline_item{}.indexed_item offset") | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // 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 are | ||
|  | 		// set indicating it was a | ||
|  | 		// timeline_item originally. | ||
|  | 		should_not_reach(true) | ||
|  | 	} | ||
|  | 	return to | ||
|  | } | ||
|  | 
 | ||
|  | var timeline_item_pool sync.Pool | ||
|  | 
 | ||
|  | // new_timeline_item returns a new prepared timeline_item. | ||
|  | func new_timeline_item() *timeline_item { | ||
|  | 	v := timeline_item_pool.Get() | ||
|  | 	if v == nil { | ||
|  | 		i := new(timeline_item) | ||
|  | 		i.elem.data = unsafe.Pointer(i) | ||
|  | 		i.ck = ^uint(0) | ||
|  | 		v = i | ||
|  | 	} | ||
|  | 	item := v.(*timeline_item) | ||
|  | 	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 { | ||
|  | 		should_not_reach(false) | ||
|  | 		return | ||
|  | 	} | ||
|  | 	item.data = nil | ||
|  | 	item.pk = nil | ||
|  | 	timeline_item_pool.Put(item) | ||
|  | } |