diff --git a/ICE/Components/include/CameraComponent.h b/ICE/Components/include/CameraComponent.h deleted file mode 100644 index 3a567aa3..00000000 --- a/ICE/Components/include/CameraComponent.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by Thomas Ibanez on 25.11.20. -// - -#ifndef ICE_CAMERACOMPONENT_H -#define ICE_CAMERACOMPONENT_H - -#include - -namespace ICE { - struct CameraComponent { - Camera* camera; - bool active; - }; -} - - -#endif //ICE_CAMERACOMPONENT_H diff --git a/ICE/Components/include/Component.h b/ICE/Components/include/Component.h index 36b6bc5e..2e4b8a00 100644 --- a/ICE/Components/include/Component.h +++ b/ICE/Components/include/Component.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include namespace ICE { @@ -34,9 +36,9 @@ class ComponentArray : public IComponentArray { entityToIndexMap[entity] = newIndex; indexToEntityMap[newIndex] = entity; if (size < componentArray.size()) { - componentArray[newIndex] = component; + componentArray[newIndex] = std::move(component); } else { - componentArray.push_back(component); + componentArray.push_back(std::move(component)); } size++; } @@ -106,6 +108,9 @@ class ComponentManager { auto const& type = typeid(T); assert(componentTypes.find(type) == componentTypes.end() && "Registering component type more than once."); + // The signature is a bitset<64>; a component type index >= 64 would throw from + // signature.set() at runtime. + assert(nextComponentType < 64 && "Too many component types registered (max 64)."); // Add this component type to the component type map componentTypes.insert({type, nextComponentType}); @@ -126,19 +131,20 @@ class ComponentManager { template void addComponent(Entity entity, T component) { - // Add a component to the array for an entity - getComponentArray()->insertData(entity, component); + // Add a component to the array for an entity; move to avoid an extra copy of + // potentially heavy components. + getComponentArray().insertData(entity, std::move(component)); } template void removeComponent(Entity entity) { - getComponentArray()->removeData(entity); + getComponentArray().removeData(entity); } template T* getComponent(Entity entity) { // Get a reference to a component from the array for an entity - return getComponentArray()->getData(entity); + return getComponentArray().getData(entity); } // Returns nullptr if the type is not registered or the entity has no such component, @@ -172,12 +178,12 @@ class ComponentManager { // The component type to be assigned to the next registered component - starting at 0 ComponentType nextComponentType{}; - // Convenience function to get the statically casted pointer to the ComponentArray of type T. + // Reference to the ComponentArray of type T. Returns a reference (not a shared_ptr + // copy) to avoid an atomic refcount round-trip on every component access. .at() throws + // std::out_of_range for an unregistered type instead of operator[] inserting a null. template - std::shared_ptr> getComponentArray() { - // .at() throws std::out_of_range for an unregistered component type instead of - // operator[] inserting a null shared_ptr that later null-derefs. - return std::static_pointer_cast>(componentArrays.at(typeid(T))); + ComponentArray& getComponentArray() { + return static_cast&>(*componentArrays.at(typeid(T))); } }; } // namespace ICE diff --git a/ICE/Components/include/SkeletonPoseComponent.h b/ICE/Components/include/SkeletonPoseComponent.h index 2185f326..468f1ee1 100644 --- a/ICE/Components/include/SkeletonPoseComponent.h +++ b/ICE/Components/include/SkeletonPoseComponent.h @@ -1,6 +1,12 @@ #pragma once -#include +#include +#include + +#include +#include +#include +#include namespace ICE { struct SkeletonPoseComponent : public Component { diff --git a/ICE/Entity/include/Entity.h b/ICE/Entity/include/Entity.h index b2bd605a..07a409c4 100644 --- a/ICE/Entity/include/Entity.h +++ b/ICE/Entity/include/Entity.h @@ -14,7 +14,12 @@ namespace ICE { using Entity = std::uint32_t; -using Signature = std::bitset<32>; +// 64 bits allows up to 64 component types (8 built-in + custom). See registerComponent's +// bound check. +using Signature = std::bitset<64>; + +// Reserved "no entity" / scene-graph-root sentinel. +constexpr Entity NULL_ENTITY = 0; class EntityManager { public: @@ -39,11 +44,21 @@ class EntityManager { } void releaseEntity(Entity e) { - signatures[e].reset(); + // Guard against releasing a non-alive/already-released entity: that used to enqueue + // the same id twice (later handed out as two live entities) and underflow the count. + // Presence of a signature entry is the aliveness proxy. + if (!signatures.contains(e)) { + return; + } + signatures.erase(e); // erase, not reset: the map was growing monotonically releasedEntities.push(e); - entityCount--; + if (entityCount > 0) { + entityCount--; + } } + bool isAlive(Entity e) const { return e != NULL_ENTITY && signatures.contains(e); } + void setSignature(Entity e, Signature s) { signatures[e] = s; } Signature getSignature(Entity e) const { diff --git a/ICE/Scene/include/Scene.h b/ICE/Scene/include/Scene.h index ef2b6735..6325f23f 100644 --- a/ICE/Scene/include/Scene.h +++ b/ICE/Scene/include/Scene.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -16,23 +17,23 @@ class Renderer; class Scene { public: - Scene(const std::string& name); + Scene(const std::string &name); - bool setAlias(Entity entity, const std::string& newName); - std::string getAlias(Entity e); + bool setAlias(Entity entity, const std::string &newName); + std::string getAlias(Entity e) const; std::shared_ptr getGraph() const; std::string getName() const; - void setName(const std::string& name); + void setName(const std::string &name); std::shared_ptr getRegistry() const; Entity createEntity(); - Entity spawnTree(AssetUID model_id, const std::shared_ptr& bank); + Entity spawnTree(AssetUID model_id, const std::shared_ptr &bank); - void addEntity(Entity e, const std::string& alias, Entity parent); + void addEntity(Entity e, const std::string &alias, Entity parent); void removeEntity(Entity e); - bool hasEntity(Entity e); + bool hasEntity(Entity e) const; private: std::string name; diff --git a/ICE/Scene/src/Scene.cpp b/ICE/Scene/src/Scene.cpp index 63172fde..58c3c33e 100644 --- a/ICE/Scene/src/Scene.cpp +++ b/ICE/Scene/src/Scene.cpp @@ -30,8 +30,11 @@ bool Scene::setAlias(Entity entity, const std::string &newName) { return true; } -std::string Scene::getAlias(Entity e) { - return aliases[e]; +std::string Scene::getAlias(Entity e) const { + // find, not operator[]: the latter inserted an empty alias for unknown entities, which + // (combined with the old alias-based hasEntity) made a nonexistent entity "exist". + auto it = aliases.find(e); + return it == aliases.end() ? std::string() : it->second; } std::shared_ptr Scene::getRegistry() const { @@ -125,8 +128,10 @@ void Scene::removeEntity(Entity e) { m_graph->removeEntity(e); } -bool Scene::hasEntity(Entity e) { - return aliases.contains(e); +bool Scene::hasEntity(Entity e) const { + // Aliveness is owned by the registry, not the alias map (an entity can be alive without + // an alias, and getAlias no longer fabricates entries). + return registry->isAlive(e); } } // namespace ICE \ No newline at end of file diff --git a/ICE/Storage/CMakeLists.txt b/ICE/Storage/CMakeLists.txt index 46fb5335..cb74d8be 100644 --- a/ICE/Storage/CMakeLists.txt +++ b/ICE/Storage/CMakeLists.txt @@ -3,17 +3,14 @@ project(storage) message(STATUS "Building ${PROJECT_NAME} module") -add_library(${PROJECT_NAME} STATIC) +# Header-only after removing the empty Filesystem stub (JsonParser is the only content). +add_library(${PROJECT_NAME} INTERFACE) -target_sources(${PROJECT_NAME} PRIVATE - src/Filesystem.cpp -) - -target_link_libraries(${PROJECT_NAME} PUBLIC +target_link_libraries(${PROJECT_NAME} INTERFACE nlohmann_json ) -target_include_directories(${PROJECT_NAME} PUBLIC +target_include_directories(${PROJECT_NAME} INTERFACE $ $) diff --git a/ICE/Storage/include/Filesystem.h b/ICE/Storage/include/Filesystem.h deleted file mode 100644 index 93865c80..00000000 --- a/ICE/Storage/include/Filesystem.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Created by Thomas Ibanez on 29.07.21. -// - -#ifndef ICE_FILESYSTEM_H -#define ICE_FILESYSTEM_H - -#include -#include - -namespace ICE { - class Filesystem { - - }; -} - -#endif //ICE_FILESYSTEM_H diff --git a/ICE/Storage/include/JsonParser.h b/ICE/Storage/include/JsonParser.h index 115ba91f..5fedfcfb 100644 --- a/ICE/Storage/include/JsonParser.h +++ b/ICE/Storage/include/JsonParser.h @@ -29,7 +29,7 @@ namespace ICE { return j; } - inline Eigen::Matrix4f readMat4(const json& j) { + static Eigen::Matrix4f readMat4(const json& j) { Eigen::Matrix4f mat; if (!j.is_array() || j.size() != 16) throw std::runtime_error("Invalid JSON for Eigen::Matrix4f"); diff --git a/ICE/Storage/src/Filesystem.cpp b/ICE/Storage/src/Filesystem.cpp deleted file mode 100644 index 1eb1a9e1..00000000 --- a/ICE/Storage/src/Filesystem.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by Thomas Ibanez on 29.07.21. -// - -#include "Filesystem.h" -#include - -namespace ICE { - -} diff --git a/ICE/System/include/AnimationSystem.h b/ICE/System/include/AnimationSystem.h index 29962890..f9f8d82b 100644 --- a/ICE/System/include/AnimationSystem.h +++ b/ICE/System/include/AnimationSystem.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "Animation.h" @@ -16,12 +17,12 @@ struct BonePose { class AnimationSystem : public System { public: - AnimationSystem(const std::shared_ptr& reg, const std::shared_ptr& bank); + AnimationSystem(const std::shared_ptr ®, const std::shared_ptr &bank); void update(double delta) override; int updateOrder() const override { return AnimationSystemOrder; } - std::vector getSignatures(const ComponentManager& comp_manager) const override { + std::vector getSignatures(const ComponentManager &comp_manager) const override { Signature signature; signature.set(comp_manager.getComponentType()); signature.set(comp_manager.getComponentType()); @@ -30,7 +31,7 @@ class AnimationSystem : public System { private: template - size_t findKeyIndex(double animationTime, const std::vector& keys) { + size_t findKeyIndex(double animationTime, const std::vector &keys) { for (size_t i = 0; i < keys.size() - 1; ++i) { if (animationTime < keys[i + 1].timeStamp) { return i; @@ -39,20 +40,21 @@ class AnimationSystem : public System { return keys.size() - 1; } - void updateSkeleton(const std::shared_ptr& model, double time, SkeletonPoseComponent* pose, const Animation& anim); - void finalizePose(); + void updateSkeleton(const std::shared_ptr &model, double time, SkeletonPoseComponent *pose, const Animation &anim); + void finalizePose(Entity e); - BonePose sampleBonePose(const std::string& boneName, const Animation& anim, double time, const std::shared_ptr& model); - static BonePose blendPoses(const BonePose& a, const BonePose& b, float factor); + BonePose sampleBonePose(const std::string &boneName, const Animation &anim, double time, const std::shared_ptr &model); + static BonePose blendPoses(const BonePose &a, const BonePose &b, float factor); - Eigen::Vector3f interpolatePosition(double timeInTicks, const BoneAnimation& track); - Eigen::Vector3f interpolateScale(double timeInTicks, const BoneAnimation& track); - Eigen::Quaternionf interpolateRotation(double time, const BoneAnimation& track); + Eigen::Vector3f interpolatePosition(double timeInTicks, const BoneAnimation &track); + Eigen::Vector3f interpolateScale(double timeInTicks, const BoneAnimation &track); + Eigen::Quaternionf interpolateRotation(double time, const BoneAnimation &track); - void applyTransforms(const Model::Node* node, const Eigen::Matrix4f& parentTransform, const Model::Skeleton& skeleton, double time, - SkeletonPoseComponent* pose, const Animation& anim, const std::vector& allModelNodes); + void applyTransforms(const Model::Node *node, const Eigen::Matrix4f &parentTransform, const Model::Skeleton &skeleton, double time, + SkeletonPoseComponent *pose, const Animation &anim, const std::vector &allModelNodes); - std::shared_ptr m_registry; + // Non-owning back-reference (the Registry owns this system) to avoid an ownership cycle. + Registry* m_registry = nullptr; std::shared_ptr m_asset_bank; }; } // namespace ICE diff --git a/ICE/System/include/Registry.h b/ICE/System/include/Registry.h index 00f361a9..72fb1617 100644 --- a/ICE/System/include/Registry.h +++ b/ICE/System/include/Registry.h @@ -6,12 +6,10 @@ #define ICE_REGISTRY_H #include -#include #include #include #include #include -#include #include #include #include @@ -27,7 +25,6 @@ class Registry { componentManager.registerComponent(); componentManager.registerComponent(); componentManager.registerComponent(); - componentManager.registerComponent(); componentManager.registerComponent(); componentManager.registerComponent(); componentManager.registerComponent(); @@ -63,19 +60,26 @@ class Registry { systemManager.entityDestroyed(e); } - std::vector getEntities() const { return entities; } + const std::vector& getEntities() const { return entities; } + + bool isAlive(Entity e) const { return entityManager.isAlive(e); } template - bool entityHasComponent(Entity e) { + bool entityHasComponent(Entity e) const { return entityManager.getSignature(e).test(componentManager.getComponentType()); } + // WARNING: the returned pointer is only valid until the next structural change to the + // T component storage. addComponent/removeComponent on ANY entity can reallocate + // or swap-move the backing vector, invalidating outstanding T* handles. Do not cache + // component pointers across such changes -- re-fetch instead (see the editor Inspector). template T *getComponent(Entity e) { return componentManager.getComponent(e); } - // Returns nullptr instead of asserting when the entity has no component of type T. + // Returns nullptr instead of asserting when the entity has no component of type T. Same + // pointer-invalidation caveat as getComponent applies. template T *tryGetComponent(Entity e) { return componentManager.tryGetComponent(e); @@ -83,7 +87,7 @@ class Registry { template void addComponent(Entity e, T component) { - componentManager.addComponent(e, component); + componentManager.addComponent(e, std::move(component)); auto signature = entityManager.getSignature(e); signature.set(componentManager.getComponentType(), true); entityManager.setSignature(e, signature); diff --git a/ICE/System/include/RenderSystem.h b/ICE/System/include/RenderSystem.h index d9d076e8..67cdfbc1 100644 --- a/ICE/System/include/RenderSystem.h +++ b/ICE/System/include/RenderSystem.h @@ -69,7 +69,9 @@ class RenderSystem : public System { std::shared_ptr m_api; std::shared_ptr m_factory; - std::shared_ptr m_registry; + // Non-owning back-reference: the Registry owns this system, so a shared_ptr here formed + // a Registry -> SystemManager -> this -> Registry cycle. The Registry outlives its systems. + Registry* m_registry = nullptr; std::shared_ptr m_gpu_bank; std::shared_ptr m_quad_vao; diff --git a/ICE/System/include/SceneGraphSystem.h b/ICE/System/include/SceneGraphSystem.h index c0a59ab9..161c6464 100644 --- a/ICE/System/include/SceneGraphSystem.h +++ b/ICE/System/include/SceneGraphSystem.h @@ -23,7 +23,10 @@ class SceneGraphSystem : public System { } private: - std::shared_ptr m_scene; + // Non-owning: the Scene owns this system's Registry (which owns this system), so holding + // a shared_ptr here formed a Scene -> Registry -> SystemManager -> this -> Scene cycle + // that leaked the whole scene. The Scene always outlives its systems. + Scene* m_scene = nullptr; std::unordered_map m_transformVersions; }; } // namespace ICE diff --git a/ICE/System/include/System.h b/ICE/System/include/System.h index 7eee4407..efbe1dc1 100644 --- a/ICE/System/include/System.h +++ b/ICE/System/include/System.h @@ -62,7 +62,12 @@ class SystemManager { for (auto const& system : orderedSystems) { auto const& systemSignature = system->getSignatures(comp_manager); - // Entity signature matches system signature - insert into set + // Entity signature matches system signature - insert into set. onEntityAdded is + // fired on every matching signature change (not just the first insert): a system + // like RenderSystem derives several sub-lists (renderables/lights/skybox) from + // different component types and must resync when any relevant component is added + // or removed while the entity is already a member. Such onEntityAdded handlers + // must therefore be idempotent (resync, e.g. remove-then-add their sub-lists). bool match = false; for (const auto& s : systemSignature) { if ((entitySignature & s) == s) { @@ -72,7 +77,7 @@ class SystemManager { break; } } - // Entity signature does not match system signature - erase from set + // Entity signature no longer matches - erase from set and notify (both idempotent). if (!match) { system->entities.erase(entity); system->onEntityRemoved(entity); diff --git a/ICE/System/src/AnimationSystem.cpp b/ICE/System/src/AnimationSystem.cpp index 79c0b442..af386d5b 100644 --- a/ICE/System/src/AnimationSystem.cpp +++ b/ICE/System/src/AnimationSystem.cpp @@ -3,7 +3,7 @@ #include namespace ICE { -AnimationSystem::AnimationSystem(const std::shared_ptr& reg, const std::shared_ptr& bank) : m_registry(reg), m_asset_bank(bank) { +AnimationSystem::AnimationSystem(const std::shared_ptr& reg, const std::shared_ptr& bank) : m_registry(reg.get()), m_asset_bank(bank) { } void AnimationSystem::update(double dt) { @@ -80,7 +80,7 @@ void AnimationSystem::update(double dt) { updateSkeleton(model, anim->currentTime, pose, currentAnim); } - finalizePose(); + finalizePose(e); } } @@ -124,21 +124,22 @@ void AnimationSystem::updateSkeleton(const std::shared_ptr& model, double } } -void AnimationSystem::finalizePose() { - for (auto e : entities) { - auto pose = m_registry->getComponent(e); - auto model = m_asset_bank->getAsset(pose->skeletonModel); - auto& skeleton = model->getSkeleton(); +void AnimationSystem::finalizePose(Entity e) { + // Finalize only the entity being processed. This used to loop over every animated + // entity on each call, and it is called once per entity in update(), so the work was + // O(N^2) (with a matrix inverse per skeleton) while producing identical results. + auto pose = m_registry->getComponent(e); + auto model = m_asset_bank->getAsset(pose->skeletonModel); + auto& skeleton = model->getSkeleton(); - auto rootTransform = m_registry->getComponent(e); - Eigen::Matrix4f modelWorldInv = rootTransform->getWorldMatrix().inverse(); + auto rootTransform = m_registry->getComponent(e); + Eigen::Matrix4f modelWorldInv = rootTransform->getWorldMatrix().inverse(); - for (const auto& [name, id] : skeleton.boneMapping) { - Entity boneEntity = pose->bone_entity.at(name); + for (const auto& [name, id] : skeleton.boneMapping) { + Entity boneEntity = pose->bone_entity.at(name); - Eigen::Matrix4f boneWorld = m_registry->getComponent(boneEntity)->getWorldMatrix(); - pose->bone_transform[id] = modelWorldInv * boneWorld; - } + Eigen::Matrix4f boneWorld = m_registry->getComponent(boneEntity)->getWorldMatrix(); + pose->bone_transform[id] = modelWorldInv * boneWorld; } } diff --git a/ICE/System/src/RenderSystem.cpp b/ICE/System/src/RenderSystem.cpp index dd21a281..48563613 100644 --- a/ICE/System/src/RenderSystem.cpp +++ b/ICE/System/src/RenderSystem.cpp @@ -12,7 +12,7 @@ RenderSystem::RenderSystem(const std::shared_ptr &api, const std::s const std::shared_ptr ®, const std::shared_ptr &gpu_bank) : m_api(api), m_factory(factory), - m_registry(reg), + m_registry(reg.get()), m_gpu_bank(gpu_bank) { m_quad_vao = factory->createVertexArray(); auto quad_vertex_vbo = factory->createVertexBuffer(); @@ -154,6 +154,10 @@ void RenderSystem::update(double delta) { } void RenderSystem::onEntityAdded(Entity e) { + // Resync from scratch: onEntityAdded fires on every signature change while the entity + // matches, so clear this entity from all sub-lists first, then re-add based on its + // current components. This keeps the renderables/lights/skybox lists correct when an + // entity gains a second relevant component (e.g. a light added to a renderable). onEntityRemoved(e); if (m_registry->entityHasComponent(e)) { m_render_queue.emplace_back(e); @@ -178,6 +182,8 @@ void RenderSystem::onEntityRemoved(Entity e) { if (e == m_skybox) { m_skybox = NO_ASSET_ID; } + // Evict cached culling data so a recycled entity id can't inherit a stale AABB. + m_culling_cache.erase(e); } std::shared_ptr RenderSystem::getRenderer() const { diff --git a/ICE/System/src/SceneGraphSystem.cpp b/ICE/System/src/SceneGraphSystem.cpp index b8201a6c..6b9d45e7 100644 --- a/ICE/System/src/SceneGraphSystem.cpp +++ b/ICE/System/src/SceneGraphSystem.cpp @@ -1,12 +1,15 @@ #include "SceneGraphSystem.h" namespace ICE { -SceneGraphSystem::SceneGraphSystem(const std::shared_ptr &scene) : m_scene(scene) { +SceneGraphSystem::SceneGraphSystem(const std::shared_ptr &scene) : m_scene(scene.get()) { } void SceneGraphSystem::onEntityAdded(Entity e) { } void SceneGraphSystem::onEntityRemoved(Entity e) { + // Evict the cached transform version so a recycled entity id isn't skipped because its + // new version happens to match the stale cached one. + m_transformVersions.erase(e); } void SceneGraphSystem::update(double delta) { auto root = m_scene->getGraph()->getRoot(); diff --git a/ICE/Util/include/KeyboardHandler.h b/ICE/Util/include/KeyboardHandler.h index eef88084..8f94d4a5 100644 --- a/ICE/Util/include/KeyboardHandler.h +++ b/ICE/Util/include/KeyboardHandler.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace ICE { enum class Key : int { diff --git a/ICE/Util/include/MouseHandler.h b/ICE/Util/include/MouseHandler.h index 26b8bfef..2c310394 100644 --- a/ICE/Util/include/MouseHandler.h +++ b/ICE/Util/include/MouseHandler.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace ICE {