Files
filament/libs/utils/include/utils/SingleInstanceComponentManager.h
Mathias Agopian 7e21db1de1 Fix StructureOfArray alignments
StructureOfArray always aligned each array to the same alignment as
malloc (usually 8 bytes), but that was not enough if one of its type 
has stricter alignment requirements. 

StructureOfArray now always honors at least the alignment requirement
of each array.

Also removed dependency on EntityInstance.h

Fixes #5727
2022-08-03 14:40:27 -07:00

316 lines
10 KiB
C++

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H
#define TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H
#include <utils/compiler.h>
#include <utils/Entity.h>
#include <utils/EntityInstance.h>
#include <utils/EntityManager.h>
#include <utils/StructureOfArrays.h>
#include <tsl/robin_map.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
class EntityManager;
/*
* Helper class to create single instance component managers.
*
* This handles the component's storage as a structure-of-arrays, as well
* as the garbage collection.
*
* This is intended to be used as base class for a real component manager. When doing so,
* and the real component manager is a public API, make sure to forward the public methods
* to the implementation.
*
*/
template <typename ... Elements>
class UTILS_PUBLIC SingleInstanceComponentManager {
private:
// this is just to avoid using std::default_random_engine, since we're in a public header.
class default_random_engine {
uint32_t mState = 1u; // must be 0 < seed < 0x7fffffff
public:
inline uint32_t operator()() noexcept {
return mState = uint32_t((uint64_t(mState) * 48271u) % 0x7fffffffu);
}
};
protected:
static constexpr size_t ENTITY_INDEX = sizeof ... (Elements);
public:
using SoA = StructureOfArrays<Elements ..., Entity>;
using Instance = EntityInstanceBase::Type;
SingleInstanceComponentManager() noexcept {
// We always start with a dummy entry because index=0 is reserved. The component
// at index = 0, is guaranteed to be default-initialized.
// Sub-classes can use this to their advantage.
mData.push_back();
}
SingleInstanceComponentManager(SingleInstanceComponentManager&&) noexcept {/* = default */}
SingleInstanceComponentManager& operator=(SingleInstanceComponentManager&&) noexcept {/* = default */}
~SingleInstanceComponentManager() noexcept = default;
// not copyable
SingleInstanceComponentManager(SingleInstanceComponentManager const& rhs) = delete;
SingleInstanceComponentManager& operator=(SingleInstanceComponentManager const& rhs) = delete;
// returns true if the given Entity has a component of this Manager
bool hasComponent(Entity e) const noexcept {
return getInstance(e) != 0;
}
// Get instance of this Entity to be used to retrieve components
UTILS_NOINLINE
Instance getInstance(Entity e) const noexcept {
auto const& map = mInstanceMap;
// find() generates quite a bit of code
auto pos = map.find(e);
return pos != map.end() ? pos->second : 0;
}
// returns the number of components (i.e. size of each arrays)
size_t getComponentCount() const noexcept {
// The array as an extra dummy component at index 0, so the visible count is 1 less.
return mData.size() - 1;
}
bool empty() const noexcept {
return getComponentCount() == 0;
}
// returns a pointer to the Entity array. This is basically the list
// of entities this component manager handles.
// The pointer becomes invalid when adding or removing a component.
Entity const* getEntities() const noexcept {
return begin<ENTITY_INDEX>();
}
Entity getEntity(Instance i) const noexcept {
return elementAt<ENTITY_INDEX>(i);
}
// Add a component to the given Entity. If the entity already has a component from this
// manager, this function is a no-op.
// This invalidates all pointers components.
inline Instance addComponent(Entity e);
// Removes a component from the given entity.
// This invalidates all pointers components.
inline Instance removeComponent(Entity e);
// trigger one round of garbage collection. this is intended to be called on a regular
// basis. This gc gives up after it cannot randomly free 'ratio' component in a row.
void gc(const EntityManager& em, size_t ratio = 4) noexcept {
gc(em, ratio, [this](Entity e) {
removeComponent(e);
});
}
// return the first instance
Instance begin() const noexcept { return 1u; }
// return the past-the-last instance
Instance end() const noexcept { return Instance(begin() + getComponentCount()); }
// return a pointer to the first element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* begin() noexcept {
return mData.template data<ElementIndex>() + 1;
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* begin() const noexcept {
return mData.template data<ElementIndex>() + 1;
}
// return a pointer to the past-the-end element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* end() noexcept {
return begin<ElementIndex>() + getComponentCount();
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* end() const noexcept {
return begin<ElementIndex>() + getComponentCount();
}
// return a Slice<>
template<size_t ElementIndex>
Slice<typename SoA::template TypeAt<ElementIndex>> slice() noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
template<size_t ElementIndex>
Slice<const typename SoA::template TypeAt<ElementIndex>> slice() const noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
// return a reference to the index'th element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>& elementAt(Instance index) noexcept {
assert(index);
return data<ElementIndex>()[index];
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const& elementAt(Instance index) const noexcept {
assert(index);
return data<ElementIndex>()[index];
}
// returns a pointer to the RAW ARRAY of components including the first dummy component
// Use with caution.
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* raw_array() const noexcept {
return data<ElementIndex>();
}
// We need our own version of Field because mData is private
template<size_t E>
struct Field : public SoA::template Field<E> {
Field(SingleInstanceComponentManager& soa, EntityInstanceBase::Type i) noexcept
: SoA::template Field<E>{ soa.mData, i } {
}
using SoA::template Field<E>::operator =;
};
protected:
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* data() noexcept {
return mData.template data<ElementIndex>();
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* data() const noexcept {
return mData.template data<ElementIndex>();
}
// swap only internals
void swap(Instance i, Instance j) noexcept {
assert(i);
assert(j);
if (i && j) {
// update the index map
auto& map = mInstanceMap;
Entity& ei = elementAt<ENTITY_INDEX>(i);
Entity& ej = elementAt<ENTITY_INDEX>(j);
std::swap(ei, ej);
if (ei) {
map[ei] = i;
}
if (ej) {
map[ej] = j;
}
}
}
template<typename REMOVE>
void gc(const EntityManager& em, size_t ratio,
REMOVE removeComponent) noexcept {
Entity const* entities = getEntities();
size_t count = getComponentCount();
size_t aliveInARow = 0;
default_random_engine& rng = mRng;
UTILS_NOUNROLL
while (count && aliveInARow < ratio) {
// note: using the modulo favorizes lower number
size_t i = rng() % count;
if (UTILS_LIKELY(em.isAlive(entities[i]))) {
++aliveInARow;
continue;
}
aliveInARow = 0;
count--;
removeComponent(entities[i]);
}
}
protected:
SoA mData;
private:
// maps an entity to an instance index
tsl::robin_map<Entity, Instance, Entity::Hasher> mInstanceMap;
default_random_engine mRng;
};
// Keep these outside of the class because CLion has trouble parsing them
template<typename ... Elements>
typename SingleInstanceComponentManager<Elements ...>::Instance
SingleInstanceComponentManager<Elements ...>::addComponent(Entity e) {
Instance ci = 0;
if (!e.isNull()) {
if (!hasComponent(e)) {
// this is like a push_back(e);
mData.push_back().template back<ENTITY_INDEX>() = e;
// index 0 is used when the component doesn't exist
ci = Instance(mData.size() - 1);
mInstanceMap[e] = ci;
} else {
// if the entity already has this component, just return its instance
ci = mInstanceMap[e];
}
}
assert(ci != 0);
return ci;
}
// Keep these outside of the class because CLion has trouble parsing them
template <typename ... Elements>
typename SingleInstanceComponentManager<Elements ...>::Instance
SingleInstanceComponentManager<Elements ... >::removeComponent(Entity e) {
auto& map = mInstanceMap;
auto pos = map.find(e);
if (UTILS_LIKELY(pos != map.end())) {
size_t index = pos->second;
assert(index != 0);
size_t last = mData.size() - 1;
if (last != index) {
// move the last item to where we removed this component, as to keep
// the array tightly packed.
mData.forEach([index, last](auto* p) {
p[index] = std::move(p[last]);
});
Entity lastEntity = mData.template elementAt<ENTITY_INDEX>(index);
map[lastEntity] = index;
}
mData.pop_back();
map.erase(pos);
return last;
}
return 0;
}
} // namespace filament
#endif // TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H