// Copyright (C) 2019 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. #pragma once #include "base/Lookup.h" #include "base/Optional.h" #include #include #include #include #include #define ENTITY_MANAGER_DEBUG 0 #if ENTITY_MANAGER_DEBUG #define EM_DBG(fmt,...) fprintf(stderr, "%s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__); #else #define EM_DBG(...) #endif #define INVALID_ENTITY_HANDLE 0 #define INVALID_COMPONENT_HANDLE 0 namespace android { namespace base { // EntityManager: A way to represent an abstrat space of objects with handles. // Each handle is associated with data of type Item for quick access from handles to data. // Otherwise, entity data is spread through ComponentManagers. template class EntityManager { public: static_assert(64 == (indexBits + generationBits + typeBits), "bits of index, generation, and type must add to 64"); using EntityHandle = uint64_t; using IteratorFunc = std::function; using ConstIteratorFunc = std::function; static size_t getHandleIndex(EntityHandle h) { return static_cast(h & ((1ULL << indexBits) - 1ULL)); } static size_t getHandleGeneration(EntityHandle h) { return static_cast( (h >> indexBits) & ((1ULL << generationBits) - 1ULL)); } static size_t getHandleType(EntityHandle h) { return static_cast( (h >> (indexBits + generationBits)) & ((1ULL << typeBits) - 1ULL)); return h & ((1ULL << indexBits) - 1ULL); } static EntityHandle makeHandle( size_t index, size_t generation, size_t type) { EntityHandle res = index; res |= generation << indexBits; res |= type << (indexBits + generationBits); return res; } static EntityHandle withIndex(EntityHandle h, size_t i) { return makeHandle(i, getHandleGeneration(h), getHandleType(h)); } static EntityHandle withGeneration(EntityHandle h, size_t nextGen) { return makeHandle(getHandleIndex(h), nextGen, getHandleType(h)); } static EntityHandle withType(EntityHandle h, size_t newType) { return makeHandle(getHandleIndex(h), getHandleGeneration(h), newType); } EntityManager() : EntityManager(0) { } EntityManager(size_t initialItems) : mEntries(initialItems), mFirstFreeIndex(0), mLiveEntries(0) { reserve(initialItems); } ~EntityManager() { clear(); } struct EntityEntry { EntityHandle handle = 0; size_t nextFreeIndex = 0; // 0 is a special generation for brand new entries // that are not used yet size_t liveGeneration = 1; Item item; }; void clear() { reserve(mEntries.size()); mFirstFreeIndex = 0; mLiveEntries = 0; } size_t nextFreeIndex() const { return mFirstFreeIndex; } EntityHandle add(const Item& item, size_t type) { if (!type) return INVALID_ENTITY_HANDLE; size_t maxElements = (1ULL << indexBits); if (mLiveEntries == maxElements) return INVALID_ENTITY_HANDLE; size_t newIndex = mFirstFreeIndex; EM_DBG("newIndex/firstFree: %zu type: %zu", newIndex, type); size_t neededCapacity = newIndex + 1; if (maxElements < neededCapacity) return INVALID_ENTITY_HANDLE; size_t currentCapacity = mEntries.size(); size_t nextCapacity = neededCapacity << 1; if (nextCapacity > maxElements) nextCapacity = maxElements; EM_DBG("needed/current/next capacity: %zu %zu %zu", neededCapacity, currentCapacity, nextCapacity); if (neededCapacity > mEntries.size()) { mEntries.resize(nextCapacity); for (size_t i = currentCapacity; i < nextCapacity; ++i) { mEntries[i].handle = makeHandle(i, 0, type); mEntries[i].nextFreeIndex = i + 1; EM_DBG("new un-init entry: index %zu nextFree %zu", i, i + 1); } } mEntries[newIndex].handle = makeHandle(newIndex, mEntries[newIndex].liveGeneration, type); mEntries[newIndex].item = item; mFirstFreeIndex = mEntries[newIndex].nextFreeIndex; EM_DBG("created. new first free: %zu", mFirstFreeIndex); ++mLiveEntries; EM_DBG("result handle: 0x%llx", (unsigned long long)mEntries[newIndex].handle); return mEntries[newIndex].handle; } EntityHandle addFixed(EntityHandle fixedHandle, const Item& item, size_t type) { // 3 cases: // 1. handle is not allocated and doesn't correspond to mFirstFreeIndex bool isFreeListNonHead = false; // 2. handle already exists (replace) bool isAlloced = false; // 3. index(handle) == mFirstFreeIndex bool isFreeListHead = false; if (!type) return INVALID_ENTITY_HANDLE; size_t maxElements = (1ULL << indexBits); if (mLiveEntries == maxElements) return INVALID_ENTITY_HANDLE; size_t newIndex = getHandleIndex(fixedHandle); EM_DBG("newIndex/firstFree: %zu type: %zu", newIndex, type); size_t neededCapacity = newIndex + 1; if (maxElements < neededCapacity) return INVALID_ENTITY_HANDLE; size_t currentCapacity = mEntries.size(); size_t nextCapacity = neededCapacity << 1; if (nextCapacity > maxElements) nextCapacity = maxElements; EM_DBG("needed/current/next capacity: %zu %zu %zu", neededCapacity, currentCapacity, nextCapacity); if (neededCapacity > mEntries.size()) { mEntries.resize(nextCapacity); for (size_t i = currentCapacity; i < nextCapacity; ++i) { mEntries[i].handle = makeHandle(i, 0, type); mEntries[i].nextFreeIndex = i + 1; EM_DBG("new un-init entry: index %zu nextFree %zu", i, i + 1); } } // Now we ensured that there is enough space to talk about the entry of // this |fixedHandle|. if (mFirstFreeIndex == newIndex) { isFreeListHead = true; } else { auto& entry = mEntries[newIndex]; if (entry.liveGeneration == getHandleGeneration(entry.handle)) { isAlloced = true; } else { isFreeListNonHead = true; } } mEntries[newIndex].handle = fixedHandle; mEntries[newIndex].liveGeneration = getHandleGeneration(fixedHandle); mEntries[newIndex].item = item; EM_DBG("new index: %zu", newIndex); if (isFreeListHead) { EM_DBG("first free index reset from %zu to %zu", mFirstFreeIndex, mEntries[newIndex].nextFreeIndex); mFirstFreeIndex = mEntries[newIndex].nextFreeIndex; ++mLiveEntries; } else if (isAlloced) { // Already replaced whatever is there, and since it's already allocated, // no need to update freelist. EM_DBG("entry at %zu already alloced. replacing.", newIndex); } else if (isFreeListNonHead) { // Go through the freelist and skip over the entry we just added. size_t prevEntryIndex = mFirstFreeIndex; EM_DBG("in free list but not head. reorganizing freelist. " "start at %zu -> %zu", mFirstFreeIndex, mEntries[prevEntryIndex].nextFreeIndex); while (mEntries[prevEntryIndex].nextFreeIndex != newIndex) { EM_DBG("next: %zu -> %zu", prevEntryIndex, mEntries[prevEntryIndex].nextFreeIndex); prevEntryIndex = mEntries[prevEntryIndex].nextFreeIndex; } EM_DBG("finished. set prev entry %zu to new entry's next, %zu", prevEntryIndex, mEntries[newIndex].nextFreeIndex); mEntries[prevEntryIndex].nextFreeIndex = mEntries[newIndex].nextFreeIndex; ++mLiveEntries; } return fixedHandle; } void remove(EntityHandle h) { if (get(h) == nullptr) return; size_t index = getHandleIndex(h); EM_DBG("remove handle: 0x%llx -> index %zu", (unsigned long long)h, index); auto& entry = mEntries[index]; EM_DBG("handle gen: %zu entry gen: %zu", getHandleGeneration(h), entry.liveGeneration); ++entry.liveGeneration; if ((entry.liveGeneration == 0) || (entry.liveGeneration == (1ULL << generationBits))) { entry.liveGeneration = 1; } entry.nextFreeIndex = mFirstFreeIndex; mFirstFreeIndex = index; EM_DBG("new first free: %zu next free: %zu", mFirstFreeIndex, entry.nextFreeIndex); --mLiveEntries; } Item* get(EntityHandle h) { EM_DBG("get 0x%llx", (unsigned long long)h); size_t index = getHandleIndex(h); if (index >= mEntries.size()) { return nullptr; } auto& entry = mEntries[index]; if (entry.liveGeneration != getHandleGeneration(h)) { return nullptr; } return &entry.item; } const Item* get_const(EntityHandle h) const { size_t index = getHandleIndex(h); if (index >= mEntries.size()) return nullptr; const auto& entry = mEntries.data() + index; if (entry->liveGeneration != getHandleGeneration(h)) return nullptr; return &entry->item; } bool isLive(EntityHandle h) const { size_t index = getHandleIndex(h); if (index >= mEntries.size()) return false; const auto& entry = mEntries[index]; return (entry.liveGeneration == getHandleGeneration(h)); } void forEachEntry(IteratorFunc func) { for (auto& entry: mEntries) { auto handle = entry.handle; bool live = isLive(handle); auto& item = entry.item; func(live, handle, item); } } void forEachLiveEntry(IteratorFunc func) { for (auto& entry: mEntries) { auto handle = entry.handle; bool live = isLive(handle); if (!live) continue; auto& item = entry.item; func(live, handle, item); } } void forEachLiveEntry_const(ConstIteratorFunc func) const { for (auto& entry: mEntries) { auto handle = entry.handle; bool live = isLive(handle); if (!live) continue; const auto& item = entry.item; func(live, handle, item); } } private: void reserve(size_t count) { if (mEntries.size() < count) { mEntries.resize(count); } for (size_t i = 0; i < count; ++i) { mEntries[i].handle = makeHandle(i, 0, 1); mEntries[i].nextFreeIndex = i + 1; ++mEntries[i].liveGeneration; if ((mEntries[i].liveGeneration == 0) || (mEntries[i].liveGeneration == (1ULL << generationBits))) { mEntries[i].liveGeneration = 1; } EM_DBG("new un-init entry: index %zu nextFree %zu", i, i + 1); } } std::vector mEntries; size_t mFirstFreeIndex; size_t mLiveEntries; }; // Tracks components over a given space of entities. // Looking up by entity index is slower, but takes less space overall versus // a flat array that parallels the entities. template class ComponentManager { public: static_assert(64 == (indexBits + generationBits + typeBits), "bits of index, generation, and type must add to 64"); using ComponentHandle = uint64_t; using EntityHandle = uint64_t; using ComponentIteratorFunc = std::function; using ConstComponentIteratorFunc = std::function; // Adds the given |data| and associates it with EntityHandle. // We can also opt-in to immediately tracking the handle in the reverse mapping, // which has an upfront cost in runtime. // Many uses of ComponentManager don't really need to track the associated entity handle, // so it is opt-in. ComponentHandle add( EntityHandle h, const Data& data, size_t type, bool tracked = false) { InternalItem item = { h, data, tracked }; auto res = static_cast(mData.add(item, type)); if (tracked) { mEntityToComponentMap[h] = res; } return res; } void clear() { mData.clear(); mEntityToComponentMap.clear(); } // If we didn't explicitly track, just fail. ComponentHandle getComponentHandle(EntityHandle h) const { auto componentHandlePtr = android::base::find(mEntityToComponentMap, h); if (!componentHandlePtr) return INVALID_COMPONENT_HANDLE; return *componentHandlePtr; } EntityHandle getEntityHandle(ComponentHandle h) const { return mData.get(h)->entityHandle; } void removeByEntity(EntityHandle h) { auto componentHandle = getComponentHandle(h); removeByComponent(componentHandle); } void removeByComponent(ComponentHandle h) { auto item = mData.get(h); if (!item) return; if (item->tracked) { mEntityToComponentMap.erase(item->entityHandle); } mData.remove(h); } Data* getByEntity(EntityHandle h) { return getByComponent(getComponentHandle(h)); } Data* getByComponent(ComponentHandle h) { auto item = mData.get(h); if (!item) return nullptr; return &(item->data); } void forEachComponent(ComponentIteratorFunc func) { mData.forEachEntry( [func](bool live, typename InternalEntityManager::EntityHandle componentHandle, InternalItem& item) { func(live, componentHandle, item.entityHandle, item.data); }); } void forEachLiveComponent(ComponentIteratorFunc func) { mData.forEachLiveEntry( [func](bool live, typename InternalEntityManager::EntityHandle componentHandle, InternalItem& item) { func(live, componentHandle, item.entityHandle, item.data); }); } void forEachLiveComponent_const(ConstComponentIteratorFunc func) const { mData.forEachLiveEntry_const( [func](bool live, typename InternalEntityManager::EntityHandle componentHandle, const InternalItem& item) { func(live, componentHandle, item.entityHandle, item.data); }); } private: struct InternalItem { EntityHandle entityHandle; Data data; bool tracked; }; using InternalEntityManager = EntityManager; using EntityToComponentMap = std::unordered_map; mutable InternalEntityManager mData; EntityToComponentMap mEntityToComponentMap; }; // ComponentManager, but unpacked; uses the same index space as the associated // entities. Takes more space by default, but not more if all entities have this component. template class UnpackedComponentManager { public: using ComponentHandle = uint64_t; using EntityHandle = uint64_t; using ComponentIteratorFunc = std::function; using ConstComponentIteratorFunc = std::function; EntityHandle add(EntityHandle h, const Data& data) { size_t index = indexOfEntity(h); if (index + 1 > mItems.size()) { mItems.resize((index + 1) * 2); } mItems[index].live = true; mItems[index].handle = h; mItems[index].data = data; return h; } void clear() { mItems.clear(); } void remove(EntityHandle h) { size_t index = indexOfEntity(h); if (index >= mItems.size()) return; mItems[index].live = false; } Data* get(EntityHandle h) { size_t index = indexOfEntity(h); if (index + 1 > mItems.size()) { mItems.resize((index + 1) * 2); } auto item = mItems.data() + index; if (!item->live) return nullptr; return &item->data; } const Data* get_const(EntityHandle h) const { size_t index = indexOfEntity(h); if (index + 1 > mItems.size()) { return nullptr; } auto item = mItems.data() + index; if (!item->live) return nullptr; return &item->data; } void forEachComponent(ComponentIteratorFunc func) { for (auto& item : mItems) { func(item.live, item.handle, item.handle, item.data); } } void forEachLiveComponent(ComponentIteratorFunc func) { for (auto& item : mItems) { if (item.live) func(item.live, item.handle, item.handle, item.data); } } void forEachLiveComponent_const(ConstComponentIteratorFunc func) const { for (auto& item : mItems) { if (item.live) func(item.live, item.handle, item.handle, item.data); } } private: static size_t indexOfEntity(EntityHandle h) { return EntityManager::getHandleIndex(h); } struct InternalItem { bool live = false; EntityHandle handle = 0; Data data; }; std::vector mItems; }; } // namespace android } // namespace base