package registry import ( "math" "github.com/bloeys/nmage/assert" ) type freeListitem struct { ItemIndex uint64 nextFree *freeListitem } // Registry is a storage data structure that can efficiently create/get/free items using generational indices. // Each item stored in the registry is associated with a 'handle' object that is used to get and free objects // // The registry 'owns' all items it stores and returns pointers to items in its array. All items are allocated upfront. // // It is NOT safe to concurrently create or free items. However, it is SAFE to concurrently get items type Registry[T any] struct { ItemCount uint Handles []Handle Items []T FreeList *freeListitem FreeListSize uint32 // The number of slots required to be in the free list before the free list // is used for creating new entries FreeListUsageThreshold uint32 } func (r *Registry[T]) New() (*T, Handle) { assert.T(r.ItemCount < uint(len(r.Handles)), "Can not add more entities to registry because it is full") var index uint64 = math.MaxUint64 // Find index to use for the new item if r.FreeList != nil && r.FreeListSize > r.FreeListUsageThreshold { index = r.FreeList.ItemIndex r.FreeList = r.FreeList.nextFree r.FreeListSize-- } else { for i := 0; i < len(r.Handles); i++ { handle := r.Handles[i] if handle.HasFlag(HandleFlag_Alive) { continue } index = uint64(i) break } } if index == math.MaxUint64 { panic("failed to create new entity because we did not find a free spot in the registry. Why did the item count assert not go off?") } var newItem T newHandle := NewHandle(r.Handles[index].Generation()+1, HandleFlag_Alive, index) assert.T(newHandle != 0, "Entity handle must not be zero") r.ItemCount++ r.Handles[index] = newHandle r.Items[index] = newItem // It is very important we return directly from the items array, because if we return // a pointer to newItem, and T is a value not a pointer, then newItem and what's stored in items will be different return &r.Items[index], newHandle } func (r *Registry[T]) Get(id Handle) *T { if id.IsZero() { return nil } index := id.Index() assert.T(index < uint64(len(r.Handles)), "Failed to get entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id) handle := r.Handles[index] if handle.Generation() != id.Generation() || !handle.HasFlag(HandleFlag_Alive) { return nil } item := &r.Items[index] return item } // Free resets the entity flags then adds this entity to the free list func (r *Registry[T]) Free(id Handle) { index := id.Index() assert.T(index < uint64(len(r.Handles)), "Failed to free entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id) // Nothing to do if already free handle := r.Handles[index] if handle.Generation() != id.Generation() || !handle.HasFlag(HandleFlag_Alive) { return } // Generation is incremented on aquire, so here we just reset flags r.ItemCount-- r.Handles[index] = NewHandle(id.Generation(), HandleFlag_None, index) // Add to free list r.FreeList = &freeListitem{ ItemIndex: index, nextFree: r.FreeList, } r.FreeListSize++ } func (r *Registry[T]) NewIterator() Iterator[T] { return Iterator[T]{ registry: r, remainingItems: r.ItemCount, currIndex: 0, } } func NewRegistry[T any](size uint32) *Registry[T] { assert.T(size > 0, "Registry size must be more than zero") return &Registry[T]{ Handles: make([]Handle, size), Items: make([]T, size), FreeListUsageThreshold: 30, } }