Skip to content

Commit

Permalink
Use generation ids as the IdAllocator default ID. (#211)
Browse files Browse the repository at this point in the history
* Use generation ids as the IdAllocator default ID.

* Fix compile issue.

* Compile fix
  • Loading branch information
Duttenheim authored Mar 18, 2024
1 parent c444a18 commit b205c09
Show file tree
Hide file tree
Showing 52 changed files with 759 additions and 823 deletions.
2 changes: 1 addition & 1 deletion code/application/game/filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Filter
FilterBuilder::CreateFilter(FilterCreateInfo info)
{
n_assert(info.numInclusive > 0);
uint32_t filter = filterAllocator.Alloc();
Ids::Id32 filter = filterAllocator.Alloc();

ComponentArray inclusiveArray;
inclusiveArray.Resize(info.numInclusive);
Expand Down
78 changes: 53 additions & 25 deletions code/foundation/ids/id.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,23 @@
}; \
static constexpr x Invalid##x = Ids::InvalidId16;

#define ID_32_24_8_NAMED_TYPE(x, id32_name, id24_name, id8_name) struct x { \
#define ID_32_24_8_NAMED_TYPE(x, id32_name, id24_name, id8_name, combined_name) struct x { \
Ids::Id32 id32_name : 32;\
Ids::Id32 id24_name : 24;\
Ids::Id32 id8_name: 8;\
union\
{\
struct\
{\
Ids::Id32 id24_name : 24;\
Ids::Id32 id8_name: 8;\
};\
Ids::Id32 combined_name;\
};\
constexpr x() : id32_name(Ids::InvalidId32), id24_name(Ids::InvalidId24), id8_name(Ids::InvalidId8) {};\
constexpr x(const Ids::Id32 id32, const Ids::Id24 id24, const Ids::Id8 id8) : id32_name(id32), id24_name(id24), id8_name(id8){} \
constexpr x(const Ids::Id64 id) : id32_name(Ids::Id::GetHigh(id)), id24_name(Ids::Id::GetBig(Ids::Id::GetLow(id))), id8_name(Ids::Id::GetTiny(Ids::Id::GetLow(id))) {};\
constexpr x(const Ids::Id64 id) : id32_name(Ids::Id::GetHigh(id)), id24_name(Ids::Index(Ids::Id::GetLow(id))), id8_name(Ids::Generation(Ids::Id::GetLow(id))) {};\
explicit constexpr operator Ids::Id64() const { return Ids::Id::MakeId32_24_8(id32_name, id24_name, id8_name); }\
static constexpr x Invalid() { return Ids::Id::MakeId32_24_8(Ids::InvalidId32, Ids::InvalidId24, Ids::InvalidId8); }\
constexpr uint32_t HashCode() const { return (uint32_t)Ids::Id::MakeId24_8(id24_name, id8_name); }\
constexpr uint32_t HashCode() const { return (uint32_t)combined_name; }\
constexpr Ids::Id64 HashCode64() const { return Ids::Id::MakeId32_24_8(id32_name, id24_name, id8_name); }\
const bool operator==(const x& rhs) const { return id32_name == rhs.id32_name && id24_name == rhs.id24_name && id8_name == rhs.id8_name; }\
const bool operator!=(const x& rhs) const { return id32_name != rhs.id32_name || id24_name != rhs.id24_name || id8_name != rhs.id8_name; }\
Expand All @@ -61,20 +68,34 @@
template <typename T> constexpr T As() const { static_assert(sizeof(T) == sizeof(x), "Can only convert between ID types of equal size"); T ret; memcpy((void*)&ret, this, sizeof(T)); return ret; }; \
}; \
static constexpr x Invalid##x = Ids::Id::MakeId32_24_8(Ids::InvalidId32, Ids::InvalidId24, Ids::InvalidId8);
#define ID_32_24_8_TYPE(x) ID_32_24_8_NAMED_TYPE(x, id32, id24, id8)
#define ID_32_24_8_TYPE(x) ID_32_24_8_NAMED_TYPE(x, parent, index, generation, id)

#define ID_24_8_24_8_NAMED_TYPE(x, id24_0_name, id8_0_name, id24_1_name, id8_1_name) struct x { \
Ids::Id32 id24_0_name : 24;\
Ids::Id8 id8_0_name : 8;\
Ids::Id32 id24_1_name : 24;\
Ids::Id8 id8_1_name : 8;\
constexpr x() : id24_0_name(Ids::InvalidId24), id8_0_name(Ids::InvalidId8), id24_1_name(Ids::InvalidId24), id8_1_name(Ids::InvalidId8) {};\
#define ID_24_8_24_8_NAMED_TYPE(x, id24_0_name, id8_0_name, id24_1_name, id8_1_name, combined0_name, combined1_name) struct x { \
union\
{\
struct\
{\
Ids::Id32 id24_0_name : 24;\
Ids::Id8 id8_0_name : 8;\
};\
Ids::Id32 combined0_name;\
};\
union\
{\
struct\
{\
Ids::Id32 id24_1_name : 24;\
Ids::Id8 id8_1_name : 8;\
};\
Ids::Id32 combined1_name;\
};\
constexpr x() : combined0_name(Ids::InvalidId32), combined1_name(Ids::InvalidId32) {};\
constexpr x(const Ids::Id24 id24_0, const Ids::Id8 id8_0, const Ids::Id24 id24_1, const Ids::Id8 id8_1) : id24_0_name(id24_0), id8_0_name(id8_0), id24_1_name(id24_1), id8_1_name(id8_1) {} \
constexpr x(const Ids::Id64 id) : id24_0_name(Ids::Id::GetBig(Ids::Id::GetHigh(id))), id8_0_name(Ids::Id::GetTiny(Ids::Id::GetHigh(id))), id24_1_name(Ids::Id::GetBig(Ids::Id::GetLow(id))), id8_1_name(Ids::Id::GetTiny(Ids::Id::GetLow(id))) {};\
constexpr x(const Ids::Id64 id) : combined0_name(Ids::Id::GetLow(id)), combined1_name(Ids::Id::GetHigh(id)) {};\
explicit constexpr operator Ids::Id64() const { return Ids::Id::MakeId24_8_24_8(id24_0_name, id8_0_name, id24_1_name, id8_1_name); }\
static constexpr x Invalid() { return Ids::Id::MakeId24_8_24_8(Ids::InvalidId24, Ids::InvalidId8, Ids::InvalidId24, Ids::InvalidId8); }\
constexpr Ids::Id32 AllocId() const { return Ids::Id::MakeId24_8(id24_1_name, id8_1_name); }\
constexpr uint32_t HashCode() const { return (uint32_t)Ids::Id::MakeId24_8(id24_0_name, id8_0_name); }\
static constexpr x Invalid() { return Ids::InvalidId64; }\
constexpr Ids::Id32 AllocId() const { return combined1_name; }\
constexpr uint32_t HashCode() const { return (uint32_t)combined0_name; }\
constexpr Ids::Id64 HashCode64() const { return Ids::Id::MakeId24_8_24_8(id24_0_name, id8_0_name, id24_1_name, id8_1_name); }\
const bool operator==(const x& rhs) const { return id24_0_name == rhs.id24_0_name && id8_0_name == rhs.id8_0_name && id24_1_name == rhs.id24_1_name && id8_1_name == rhs.id8_1_name; }\
const bool operator!=(const x& rhs) const { return id24_0_name != rhs.id24_0_name || id8_0_name != rhs.id8_0_name || id24_1_name != rhs.id24_1_name || id8_1_name != rhs.id8_1_name; }\
Expand All @@ -83,25 +104,32 @@
template <typename T> constexpr T As() const { static_assert(sizeof(T) == sizeof(x), "Can only convert between ID types of equal size"); T ret; memcpy((void*)&ret, this, sizeof(T)); return ret; }; \
}; \
static constexpr x Invalid##x = Ids::Id::MakeId24_8_24_8(Ids::InvalidId24, Ids::InvalidId8, Ids::InvalidId24, Ids::InvalidId8);
#define ID_24_8_24_8_TYPE(x) ID_24_8_24_8_NAMED_TYPE(x, id24_0, id8_0, id24_1, id8_1)
#define ID_24_8_24_8_TYPE(x) ID_24_8_24_8_NAMED_TYPE(x, index0, generation0, index1, generation1, id0, id1)

#define ID_24_8_NAMED_TYPE(x, id24_name, id8_name) struct x { \
Ids::Id32 id24_name : 24; \
Ids::Id32 id8_name : 8; \
#define ID_24_8_NAMED_TYPE(x, id24_name, id8_name, combined_name) struct x { \
union \
{\
struct\
{\
Ids::Id32 id24_name : 24; \
Ids::Id32 id8_name : 8; \
};\
Ids::Id32 combined_name;\
}; \
constexpr x() : id24_name(Ids::InvalidId24), id8_name(Ids::InvalidId8) {} \
constexpr x(const Ids::Id24 id0, const Ids::Id8 id1) : id24_name(id0), id8_name(id1) {} \
constexpr x(const Ids::Id32 id) : id24_name(Ids::Id::GetBig(id)), id8_name(Ids::Id::GetTiny(id)) {};\
explicit constexpr operator Ids::Id32() const { return Ids::Id::MakeId24_8(id24_name, id8_name); }\
static constexpr x Invalid() { return Ids::Id::MakeId24_8(Ids::InvalidId24, Ids::InvalidId8); }\
constexpr uint32_t HashCode() const { return (uint32_t)Ids::Id::MakeId24_8(id24_name, id8_name); }\
constexpr x(const Ids::Id32 id) : combined_name(id) {};\
explicit constexpr operator Ids::Id32() const { return combined_name; }\
static constexpr x Invalid() { return Ids::InvalidId32; }\
constexpr uint32_t HashCode() const { return (uint32_t)combined_name; }\
const bool operator==(const x& rhs) const { return id24_name == rhs.id24_name && id8_name == rhs.id8_name; }\
const bool operator!=(const x& rhs) const { return id24_name != rhs.id24_name || id8_name != rhs.id8_name; }\
const bool operator<(const x& rhs) const { return HashCode() < rhs.HashCode(); }\
const bool operator>(const x& rhs) const { return HashCode() > rhs.HashCode(); }\
template <typename T> constexpr T As() const { static_assert(sizeof(T) == sizeof(x), "Can only convert between ID types of equal size"); T ret; memcpy((void*)&ret, this, sizeof(T)); return ret; }; \
}; \
static constexpr x Invalid##x = Ids::Id::MakeId24_8(Ids::InvalidId24, Ids::InvalidId8);
#define ID_24_8_TYPE(x) ID_24_8_NAMED_TYPE(x, id24, id8)
#define ID_24_8_TYPE(x) ID_24_8_NAMED_TYPE(x, index, generation, id)

namespace Ids
{
Expand Down
140 changes: 102 additions & 38 deletions code/foundation/ids/idallocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "util/tupleutility.h"
#include "util/arrayallocator.h"
#include "util/arrayallocatorsafe.h"
#include "idgenerationpool.h"

namespace Ids
{
Expand All @@ -42,36 +43,57 @@ class IdAllocator : public Util::ArrayAllocator<TYPES...>
IdAllocator(uint32_t maxid = 0xFFFFFFFF) : maxId(maxid) {};

/// Allocate an object.
uint32_t Alloc()
Ids::Id32 Alloc()
{
/// @note This purposefully hides the default allocation method and should definetly not be virtual!

uint32_t index;
if (this->freeIds.Size() > 0)
Ids::Id32 id;
if (this->pool.Allocate(id))
{
index = this->freeIds.Back();
this->freeIds.EraseBack();
}
else
{
index = Util::ArrayAllocator<TYPES...>::Alloc();
n_assert2(this->maxId > index, "max amount of allocations exceeded!\n");
Util::ArrayAllocator<TYPES...>::Alloc();
n_assert2(this->maxId > Ids::Index(id), "max amount of allocations exceeded!\n");
}

return index;
return id;
}

/// Deallocate an object. Just places it in freeids array for recycling
void Dealloc(uint32_t index)
void Dealloc(Ids::Id32 id)
{
// TODO: We could possibly get better performance when defragging if we insert it in reverse order (high to low)
this->freeIds.Append(index);
this->pool.Deallocate(id);
}

/// Set element
template<int MEMBER>
inline void Set(const Ids::Id32 id, const Util::tuple_array_t<MEMBER, TYPES...>& type)
{
Util::ArrayAllocator<TYPES...>::template Set<MEMBER>(Ids::Index(id), type);
}

/// Returns the list of free ids.
Util::Array<uint32_t>& FreeIds()
/// Set elements
inline void Set(const Ids::Id32 id, TYPES... values)
{
return this->freeIds;
Util::ArrayAllocator<TYPES...>::Set(Ids::Index(id), values...);
}

/// Get element
template<int MEMBER>
inline Util::tuple_array_t<MEMBER, TYPES...>& Get(const Ids::Id32 id)
{
return Util::ArrayAllocator<TYPES...>::template Get<MEMBER>(Ids::Index(id));
}

/// Const get element
template<int MEMBER>
inline const Util::tuple_array_t<MEMBER, TYPES...>& ConstGet(const Ids::Id32 id) const
{
return Util::ArrayAllocator<TYPES...>::template ConstGet<MEMBER>(Ids::Index(id));
}

/// Get the free ids list from the pool
inline Util::Queue<Id32>& FreeIds()
{
return this->pool.FreeIds();
}

/// return number of allocated ids
Expand All @@ -82,7 +104,7 @@ class IdAllocator : public Util::ArrayAllocator<TYPES...>

private:
uint32_t maxId = 0xFFFFFFFF;
Util::Array<uint32_t> freeIds;
Ids::IdGenerationPool pool;
};

#define _DECL_ACQUIRE_RELEASE(ty) \
Expand All @@ -98,12 +120,8 @@ class IdAllocator : public Util::ArrayAllocator<TYPES...>
};

#define _IMPL_ACQUIRE_RELEASE(ty, allocator) \
bool ty##Acquire(const ty id) { return allocator.Acquire(id.id24); } \
void ty##Release(const ty id) { allocator.Release(id.id24); }

#define _IMPL_ACQUIRE_RELEASE_RESOURCE(ty, allocator) \
bool ty##Acquire(const ty id) { return allocator.Acquire(id.resourceId); } \
void ty##Release(const ty id) { allocator.Release(id.resourceId); }
bool ty##Acquire(const ty id) { return allocator.Acquire(id.id); } \
void ty##Release(const ty id) { allocator.Release(id.id); }

template<int MAX_ALLOCS, class... TYPES>
class IdAllocatorSafe : public Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>
Expand All @@ -115,40 +133,86 @@ class IdAllocatorSafe : public Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>
};

/// Allocate an object.
uint32_t Alloc()
Ids::Id32 Alloc()
{
/// @note This purposefully hides the default allocation method and should definitely not be virtual!
this->allocationLock.Lock();
uint32_t index;
if (this->freeIds.Size() > 0)
Ids::Id32 id;
if (this->pool.Allocate(id))
{
index = this->freeIds.Back();
this->owners[index] = Threading::Thread::GetMyThreadId();
this->freeIds.EraseBack();
alloc_for_each_in_tuple(this->objects);
this->owners.Append(Threading::Thread::GetMyThreadId());
n_assert2(MAX_ALLOCS > Ids::Index(id), "max amount of allocations exceeded!\n");
}
else
{
alloc_for_each_in_tuple(this->objects);
index = this->size++;
this->owners.Append(Threading::Thread::GetMyThreadId());
n_assert2(MAX_ALLOCS > index, "max amount of allocations exceeded!\n");
this->owners[Ids::Index(id)] = Threading::Thread::GetMyThreadId();
}
this->allocationLock.Unlock();

return index;
return id;
}

/// Set element
template<int MEMBER>
inline void Set(const Ids::Id32 id, const Util::tuple_array_t<MEMBER, TYPES...>& type)
{
Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>::template Set<MEMBER>(Ids::Index(id), type);
}

/// Set elements
inline void Set(const Ids::Id32 id, TYPES... values)
{
Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>::Set(Ids::Index(id), values...);
}

/// Get element
template<int MEMBER>
inline Util::tuple_array_t<MEMBER, TYPES...>& Get(const Ids::Id32 id)
{
return Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>::template Get<MEMBER>(Ids::Index(id));
}

/// Const get element
template<int MEMBER>
inline const Util::tuple_array_t<MEMBER, TYPES...>& ConstGet(const Ids::Id32 id) const
{
return Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>::template ConstGet<MEMBER>(Ids::Index(id));
}

/// Get the free ids list from the pool
inline Util::Queue<Id32>& FreeIds()
{
return this->pool.FreeIds();
}

/// Spinlock to acquire
void TryAcquire(const Ids::Id32 id)
{
Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>::TryAcquire(Ids::Index(id));
}
/// Acquire element, asserts if false and returns true if this call acquired
bool Acquire(const Ids::Id32 id)
{
return Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>::Acquire(Ids::Index(id));
}
/// Release an object, the next thread that acquires may use this instance as it fits
void Release(const Ids::Id32 id)
{
Util::ArrayAllocatorSafe<MAX_ALLOCS, TYPES...>::Release(Ids::Index(id));
}

/// Deallocate an object. Just places it in freeids array for recycling
void Dealloc(uint32_t index)
void Dealloc(Ids::Id32 id)
{
// TODO: We could possibly get better performance when defragging if we insert it in reverse order (high to low)
this->allocationLock.Lock();
this->freeIds.Append(index);
this->pool.Deallocate(id);
this->allocationLock.Unlock();
}

private:
Util::Array<uint32_t> freeIds;
Ids::IdGenerationPool pool;
};

} // namespace Ids
15 changes: 13 additions & 2 deletions code/foundation/ids/idgenerationpool.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class IdGenerationPool
void Deallocate(Id32 id);
/// check if valid
bool IsValid(Id32 id) const;
/// Get free ids
Util::Queue<Id32>& FreeIds();

private:
/// array containing generation value for every index
Expand All @@ -66,7 +68,7 @@ class IdGenerationPool
//------------------------------------------------------------------------------
/**
*/
static Id24
constexpr static Id24
Index(const Id32 id)
{
return id & ID_MASK;
Expand All @@ -75,7 +77,7 @@ Index(const Id32 id)
//------------------------------------------------------------------------------
/**
*/
static generation_t
constexpr static generation_t
Generation(const Id32 id)
{
return (id >> ID_BITS) & GENERATION_MASK;
Expand All @@ -93,5 +95,14 @@ CreateId(const Id24 index, generation_t generation)
return id;
}

//------------------------------------------------------------------------------
/**
*/
inline Util::Queue<Id32>&
IdGenerationPool::FreeIds()
{
return this->freeIds;
}

} // namespace Ids
//------------------------------------------------------------------------------
4 changes: 2 additions & 2 deletions code/foundation/util/arrayallocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class ArrayAllocator

/// same as 32 bit get, but const
template <int MEMBER>
const tuple_array_t<MEMBER, TYPES...>& Get(const uint32_t index) const;
const tuple_array_t<MEMBER, TYPES...>& ConstGet(const uint32_t index) const;

/// set single item
template <int MEMBER>
Expand Down Expand Up @@ -268,7 +268,7 @@ ArrayAllocator<TYPES...>::Get(const uint32_t index)
template<class ...TYPES>
template<int MEMBER>
inline const tuple_array_t<MEMBER, TYPES...>&
ArrayAllocator<TYPES...>::Get(const uint32_t index) const
ArrayAllocator<TYPES...>::ConstGet(const uint32_t index) const
{
return std::get<MEMBER>(this->objects)[index];
}
Expand Down
Loading

0 comments on commit b205c09

Please sign in to comment.