Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch droids to use PagedEntityContainer<DROID> as backing storage #3689

Merged
merged 5 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 91 additions & 24 deletions lib/framework/paged_entity_container.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
/// As noted above, the container allocates memory in fixed-size
/// continuous chunks, or pages, hence the name.
///
/// Currently, each page is set to hold exactly 1024 elements.
/// Each page is set to hold exactly `MaxElementsPerPage` elements,
/// which is 1024 by default.
///
/// Also, each element is equipped with some additional metadata,
/// which allows the container to reuse the same memory (also called "slots")
Expand Down Expand Up @@ -97,14 +98,15 @@
/// * https://github.com/Masstronaut/slot_array/blob/master/slot_map.hpp
/// </summary>
/// <typeparam name="T">Entity type. Should be a complete type.</typeparam>
template <typename T>
/// <typeparam name="MaxElementsPerPage">The fixed number of elements each page may hold.</typeparam>
/// <typeparam name="ReuseSlots">If `false`, slots are one-shot and set to expire after single use.</typeparam>
template <typename T, size_t MaxElementsPerPage = 1024, bool ReuseSlots = true>
class PagedEntityContainer
{
using SlotIndexType = size_t;

static constexpr size_t MAX_ELEMENTS_PER_PAGE = 1024;
// Default initial capacity is exactly 1 page.
static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1 * MAX_ELEMENTS_PER_PAGE;
static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1 * MaxElementsPerPage;

static constexpr SlotIndexType INVALID_SLOT_IDX = std::numeric_limits<SlotIndexType>::max();
static constexpr size_t INVALID_PAGE_IDX = std::numeric_limits<size_t>::max();
Expand Down Expand Up @@ -149,6 +151,11 @@ class PagedEntityContainer
return _isAlive;
}

void invalidate()
{
_generation = INVALID_GENERATION;
}

void set_dead()
{
_isAlive = false;
Expand All @@ -174,7 +181,11 @@ class PagedEntityContainer
/// This class holds the actual contiguous storage and metadata for the elements,
/// as well as the queue for recycled indices.
///
/// Always allocates storage for exactly `MAX_ELEMENTS_PER_PAGE` elements.
/// Always allocates storage for exactly `MaxElementsPerPage` elements.
///
/// If the parent container is instantiated with the `ReuseSlots=false` mode,
/// expired pages will deallocate their storage to keep memory consumption
/// under control.
/// </summary>
class Page
{
Expand All @@ -195,17 +206,17 @@ class PagedEntityContainer

bool is_full() const
{
return _currentSize + _expiredSlotsCount == MAX_ELEMENTS_PER_PAGE;
return _currentSize + _expiredSlotsCount == MaxElementsPerPage;
}

void allocate_storage()
{
assert(_storage == nullptr);
assert(_slotMetadata == nullptr);

// Allocate storage for MAX_ELEMENTS_PER_PAGE elements.
_storage = std::make_unique<AlignedElementStorage[]>(MAX_ELEMENTS_PER_PAGE);
_slotMetadata = std::make_unique<SlotMetadata[]>(MAX_ELEMENTS_PER_PAGE);
// Allocate storage for `MaxElementsPerPage` elements.
_storage = std::make_unique<AlignedElementStorage[]>(MaxElementsPerPage);
_slotMetadata = std::make_unique<SlotMetadata[]>(MaxElementsPerPage);
}

bool has_recycled_indices() const
Expand Down Expand Up @@ -272,9 +283,21 @@ class PagedEntityContainer
++_expiredSlotsCount;
}

// When `ReuseSlots=false`, expired pages will also deallocate
// their storage upon reaching "expired" state.
bool is_expired() const
{
return _expiredSlotsCount == MAX_ELEMENTS_PER_PAGE;
return _expiredSlotsCount == MaxElementsPerPage;
}

// This method renders the page unusable!
//
// The page will then need to call `allocate_storage()` + `reset_metadata()`
// to be usable once again.
void deallocate_storage()
{
_storage.reset();
_slotMetadata.reset();
}

// Reset generations to least possible valid value for all slots,
Expand All @@ -283,7 +306,7 @@ class PagedEntityContainer
{
auto* meta = slotMetadata();
assert(meta != nullptr);
for (size_t i = 0; i < MAX_ELEMENTS_PER_PAGE; ++i)
for (size_t i = 0; i < MaxElementsPerPage; ++i)
{
auto& slot = meta[i];
slot.reset_generation();
Expand Down Expand Up @@ -342,7 +365,7 @@ class PagedEntityContainer
{
return;
}
size_t needed_nr_of_pages = (capacity / MAX_ELEMENTS_PER_PAGE) - _pages.size();
size_t needed_nr_of_pages = (capacity / MaxElementsPerPage) - _pages.size();
while (needed_nr_of_pages-- != 0)
{
allocate_new_page();
Expand All @@ -354,7 +377,7 @@ class PagedEntityContainer
Page newPage;
newPage.allocate_storage();
_pages.emplace_back(std::move(newPage));
_capacity += MAX_ELEMENTS_PER_PAGE;
_capacity += MaxElementsPerPage;
}

template <typename... Args>
Expand Down Expand Up @@ -398,8 +421,11 @@ class PagedEntityContainer
{
return;
}
// Advance slot generation number.
slotMetadata.advance_generation();

// Either advance slot generation number or invalidate the slot right away,
// the behavior depends on whether we reuse the slots or not.
advance_slot_generation<ReuseSlots>(slotMetadata);

// Ensure that the element pointed-to by this slot is dead.
slotMetadata.set_dead();

Expand All @@ -418,6 +444,19 @@ class PagedEntityContainer
// Increase counters for expired slots tracking.
_pages[pageIdx.first].increase_expired_slots_count();
++_expiredSlotsCount;
// Looks like at least GCC is smart enough to optimize the following
// instructions away, even when this is not a `constexpr if`, but a regular `if`,
// See https://godbolt.org/z/bY8oEYsdz.
if (!ReuseSlots)
{
if (_pages[pageIdx.first].is_expired())
{
// Free storage pointers for expired pages when not reusing slots,
// this should alleviate the problem of excessive memory consumption
// in such cases.
_pages[pageIdx.first].deallocate_storage();
}
}
}
else
{
Expand Down Expand Up @@ -579,7 +618,7 @@ class PagedEntityContainer

const_iterator begin() const
{
return const_iterator(const_cast<PagedEntityContainer<T>*>(this)->begin());
return const_iterator(const_cast<PagedEntityContainer*>(this)->begin());
}

iterator begin()
Expand Down Expand Up @@ -656,7 +695,7 @@ class PagedEntityContainer

const_iterator find(const T& x) const
{
return const_iterator(const_cast<PagedEntityContainer<T>*>(this)->find(const_cast<T&>(x)));
return const_iterator(const_cast<PagedEntityContainer*>(this)->find(const_cast<T&>(x)));
}

void erase(const_iterator it)
Expand All @@ -682,9 +721,21 @@ class PagedEntityContainer
}
// Shrink the storage to just a single page.
_pages.resize(1);
_capacity = MAX_ELEMENTS_PER_PAGE;
_capacity = MaxElementsPerPage;
// No valid items in the container now.
_maxIndex = INVALID_SLOT_IDX;
if (!ReuseSlots)
{
// If the first page is in the expired state, then
// its storage is deallocated, too. So, reallocate it.
//
// NOTE: expired pages free their storage only when
// the slot recycling mechanism is disabled!
if (_pages.front().is_expired())
{
_pages.front().allocate_storage();
}
}
_pages.front().reset_metadata();
_expiredSlotsCount = 0;
}
Expand Down Expand Up @@ -742,12 +793,12 @@ class PagedEntityContainer

static PageIndex global_to_page_index(SlotIndexType idx)
{
return {idx / MAX_ELEMENTS_PER_PAGE, idx % MAX_ELEMENTS_PER_PAGE};
return { idx / MaxElementsPerPage, idx % MaxElementsPerPage };
}

static SlotIndexType page_index_to_global(const PageIndex& idx)
{
return idx.first * MAX_ELEMENTS_PER_PAGE + idx.second;
return idx.first * MaxElementsPerPage + idx.second;
}

// No need to call destructors manually for trivially destructible types.
Expand Down Expand Up @@ -786,16 +837,32 @@ class PagedEntityContainer
return (reinterpret_cast<uintptr_t>(addr) % alignof(T)) == 0;
}

template <bool ReuseSlotsAux = ReuseSlots, std::enable_if_t<ReuseSlotsAux, bool> = true>
void advance_slot_generation(SlotMetadata& meta)
{
// Advance slot generation number, when `ReuseSlots=true`.
meta.advance_generation();
}

// Specialization for the case when `ReuseSlots=false`.
template <bool ReuseSlotsAux = ReuseSlots, std::enable_if_t<!ReuseSlotsAux, bool> = true>
void advance_slot_generation(SlotMetadata& meta)
{
// Invalidate slot right away so that it cannot be reused anymore.
meta.invalidate();
}

std::vector<Page> _pages;
SlotIndexType _maxIndex = INVALID_SLOT_IDX;
size_t _size = 0;
size_t _capacity = 0;
size_t _expiredSlotsCount = 0;
};

template <typename T>
constexpr typename PagedEntityContainer<T>::SlotIndexType PagedEntityContainer<T>::INVALID_SLOT_IDX;
template <typename T, size_t MaxElementsPerPage, bool ReuseSlots>
constexpr typename PagedEntityContainer<T, MaxElementsPerPage, ReuseSlots>::SlotIndexType
PagedEntityContainer<T, MaxElementsPerPage, ReuseSlots>::INVALID_SLOT_IDX;

template <typename T>
constexpr size_t PagedEntityContainer<T>::INVALID_PAGE_IDX;
template <typename T, size_t MaxElementsPerPage, bool ReuseSlots>
constexpr size_t PagedEntityContainer<T, MaxElementsPerPage, ReuseSlots>::INVALID_PAGE_IDX;

2 changes: 1 addition & 1 deletion src/component.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ void drawMuzzleFlash(WEAPON sWeap, iIMDShape *weaponImd, iIMDShape *flashImd, PI
#define PART_IMD(STATS,DROID,COMPONENT,PLAYER) (STATS[DROID->asBits[COMPONENT]].pIMD)

/* Get the chassis imd */
#define BODY_IMD(DROID,PLAYER) (DROID->getBodyStats()->pIMD)
#define BODY_IMD(DROID,PLAYER) ((DROID)->getBodyStats()->pIMD)
/* Get the brain imd - NOTE: Unused!*/
#define BRAIN_IMD(DROID,PLAYER) (DROID->getBrainStats()->pIMD)
/* Get the weapon imd */
Expand Down
Loading
Loading