From d5330606978d695eb8259ba07f90f2065418e3d8 Mon Sep 17 00:00:00 2001 From: Marie Giacomel Date: Thu, 30 Oct 2025 19:12:51 +0100 Subject: [PATCH 1/3] feat(add-component): add popup to add component to an entity in the UI --- editor/CMakeLists.txt | 1 + .../src/DocumentWindows/EditorScene/EditorScene.hpp | 2 +- .../EntityProperties/MaterialProperty.cpp | 2 +- editor/src/DocumentWindows/InspectorWindow/Init.cpp | 1 + .../InspectorWindow/InspectorWindow.hpp | 5 +++++ editor/src/DocumentWindows/InspectorWindow/Show.cpp | 1 + editor/src/DocumentWindows/PopupManager.hpp | 2 +- engine/src/Application.cpp | 10 +++++----- engine/src/ecs/Coordinator.hpp | 13 ++++++++++--- .../TypeErasedComponent/ComponentDescription.hpp | 1 + 10 files changed, 27 insertions(+), 11 deletions(-) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index aee759ca8..84b2cfb80 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -76,6 +76,7 @@ set(SRCS editor/src/DocumentWindows/InspectorWindow/Show.cpp editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp editor/src/DocumentWindows/InspectorWindow/Update.cpp + editor/src/DocumentWindows/InspectorWindow/AddComponent.cpp editor/src/DocumentWindows/MaterialInspector/Init.cpp editor/src/DocumentWindows/MaterialInspector/Show.cpp editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index 13143dd82..0954eac9f 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -61,7 +61,7 @@ namespace nexo::editor { */ void show() override; - bool showToolbar = false; + bool showToolbar = true; bool isPhysicsRunning = false; /** diff --git a/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp b/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp index ac43537bf..0c8fe8a12 100644 --- a/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp @@ -180,8 +180,8 @@ namespace nexo::editor { ImGui::EndGroup(); const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::TreePop(); } - ImGui::TreePop(); if (m_popupManager.showPopupModal("Create new material")) { createMaterialPopup(entity); diff --git a/editor/src/DocumentWindows/InspectorWindow/Init.cpp b/editor/src/DocumentWindows/InspectorWindow/Init.cpp index 253a11bf8..e3a39f54d 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Init.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Init.cpp @@ -59,6 +59,7 @@ namespace nexo::editor { const auto& componentDescriptions = coordinator->getComponentDescriptions(); for (const auto& [componentType, description] : componentDescriptions) { + if (description->internalComponent) continue; registerProperty(componentType, std::make_shared(*this, componentType, description)); } } diff --git a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp index 542b12aea..a3873b811 100644 --- a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp @@ -21,6 +21,7 @@ #include "DocumentWindows/EntityProperties/AEntityProperty.hpp" #include "core/scene/SceneManager.hpp" +#include #include namespace nexo::editor { @@ -217,5 +218,9 @@ namespace nexo::editor { } m_entityProperties[type] = std::move(property); } + + void showAddComponentButton(ecs::Entity entity); + + PopupManager m_popupManager; }; }; // namespace nexo::editor diff --git a/editor/src/DocumentWindows/InspectorWindow/Show.cpp b/editor/src/DocumentWindows/InspectorWindow/Show.cpp index 20636aee8..fba8e8690 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Show.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Show.cpp @@ -67,6 +67,7 @@ namespace nexo::editor { m_entityProperties[type]->show(entity); } } + showAddComponentButton(entity); } void InspectorWindow::show() diff --git a/editor/src/DocumentWindows/PopupManager.hpp b/editor/src/DocumentWindows/PopupManager.hpp index 184a9c2ac..a2979fa79 100644 --- a/editor/src/DocumentWindows/PopupManager.hpp +++ b/editor/src/DocumentWindows/PopupManager.hpp @@ -125,7 +125,7 @@ namespace nexo::editor { */ struct TransparentHasher { using is_transparent = void; // Required for heterogeneous lookup - std::size_t operator()(std::string_view key) const noexcept + std::size_t operator()(const std::string_view key) const noexcept { return std::hash{}(key); } diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index cecd130b8..f597e926d 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -96,9 +96,9 @@ namespace nexo { m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); m_coordinator->setRestoreComponent(); - m_coordinator->registerComponent(); + m_coordinator->registerComponent("Perspective Camera Controller"); m_coordinator->setRestoreComponent(); - m_coordinator->registerComponent(); + m_coordinator->registerComponent("Perspective Camera Target"); m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); m_coordinator->setRestoreComponent(); @@ -107,11 +107,11 @@ namespace nexo { m_coordinator->registerComponent(); m_coordinator->registerComponent(); m_coordinator->registerComponent(); - m_coordinator->registerComponent(); - m_coordinator->registerComponent(); + m_coordinator->registerComponent("Video"); + m_coordinator->registerComponent("Material"); m_coordinator->registerComponent(); m_coordinator->registerSingletonComponent(); - m_coordinator->registerComponent(); + m_coordinator->registerComponent("Physic Body"); } void Application::registerWindowCallbacks() const diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 436dd3553..5916d8646 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -137,9 +137,10 @@ namespace nexo::ecs { * @brief Registers a new component type within the ComponentManager. */ template - void registerComponent() + void registerComponent(const std::string& displayName = "") { m_componentManager->registerComponent(); + addComponentDescription(getComponentType(), ComponentDescription{displayName, {}, true}); m_getComponentFunctions[typeid(T)] = [this](const Entity entity) -> std::any { return this->getComponent(entity); @@ -240,6 +241,12 @@ namespace nexo::ecs { m_systemManager->entitySignatureChanged(entity, oldSignature, signature); } + template + void addComponent(const Entity entity) const + { + addComponent(entity, T{}); + } + /** * @brief Adds a component to an entity, updates its signature, and notifies systems. * @@ -528,7 +535,7 @@ namespace nexo::ecs { * @brief Retrieves all registered component descriptions. * * @return const std::unordered_map>& - * A const reference to the map of component types to their descriptions. + * A const reference to the map of component types to their descriptions. */ [[nodiscard]] const std::unordered_map>& getComponentDescriptions() const @@ -548,7 +555,7 @@ namespace nexo::ecs { { auto newQuerySystem = m_systemManager->registerQuerySystem(std::forward(args)...); const std::span livingEntities = m_entityManager->getLivingEntities(); - const Signature querySystemSignature = newQuerySystem->getSignature(); + const Signature querySystemSignature = newQuerySystem->getSignature(); for (Entity entity : livingEntities) { const Signature entitySignature = m_entityManager->getSignature(entity); if ((entitySignature & querySystemSignature) == querySystemSignature) { diff --git a/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp index 806ceeb47..3836cfca6 100644 --- a/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp +++ b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp @@ -30,6 +30,7 @@ namespace nexo::ecs { struct ComponentDescription { std::string name; // Name of the component std::vector fields; // List of fields in the component + bool internalComponent = false; // Indicates if the component is internal }; } // namespace nexo::ecs From 4583dcaa68655f6b9fa60e66e77202d0b275a865 Mon Sep 17 00:00:00 2001 From: Marie Giacomel Date: Thu, 6 Nov 2025 18:40:53 +0100 Subject: [PATCH 2/3] feat(add-component): add component to an entity in the UI --- engine/src/ecs/ComponentArray.cpp | 26 ++++++++++ engine/src/ecs/ComponentArray.hpp | 52 +++++++++++++++++++ engine/src/ecs/Components.hpp | 30 +++++++++++ engine/src/ecs/Coordinator.hpp | 27 +++++++++- .../ComponentDescription.hpp | 7 +-- 5 files changed, 138 insertions(+), 4 deletions(-) diff --git a/engine/src/ecs/ComponentArray.cpp b/engine/src/ecs/ComponentArray.cpp index 03b412fb5..ac4006a52 100644 --- a/engine/src/ecs/ComponentArray.cpp +++ b/engine/src/ecs/ComponentArray.cpp @@ -64,6 +64,32 @@ namespace nexo::ecs { ++m_size; } + void TypeErasedComponentArray::insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst)) + { + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); + + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component", entity); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + + // Resize component data vector if needed + const size_t requiredSize = (m_size + 1) * m_componentSize; + if (m_componentData.size() < requiredSize) { + m_componentData.resize(requiredSize); + } + + // Call the constructor to initialize the component in place + constructor(m_componentData.data()); + ++m_size; + } + void TypeErasedComponentArray::remove(const Entity entity) { if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index ba34a1aac..5e4754f04 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -107,6 +107,17 @@ namespace nexo::ecs { */ virtual void insertRaw(Entity entity, const void* componentData) = 0; + /** + * @brief Inserts a new component for the given entity using a constructor. + * + * @param entity The entity to add the component to + * @param constructor Pointer to the constructor function or functor + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @pre The entity must be a valid entity ID + */ + virtual void insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst)) = 0; + /** * @brief Removes the component for the given entity. * @@ -262,6 +273,36 @@ namespace nexo::ecs { } } + /** + * @brief Inserts a new component for the given entity using a constructor. + * + * @param entity The entity to add the component to + * @param constructor Pointer to the constructor function or functor + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @pre The entity must be a valid entity ID + */ + void insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst)) + { + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); + + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component: {}", entity, typeid(T).name()); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + + // allocate new component in the array + m_componentArray.emplace_back(); + constructor(&m_componentArray[newIndex]); + ++m_size; + } + /** * @brief Removes the component for the given entity. * @@ -656,6 +697,17 @@ namespace nexo::ecs { */ void insertRaw(Entity entity, const void* componentData) override; + /** + * @brief Inserts a new component for the given entity using a constructor. + * + * @param entity The entity to add the component to + * @param constructor Pointer to the constructor function or functor + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @pre The entity must be a valid entity ID + */ + void insertRawWithConstructor(Entity entity, void (*constructor)(void* memoryDst)) override; + /** * @brief Removes the component for the given entity * @param entity The entity to remove the component from diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 9e2d3f785..b73dfc715 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -327,6 +327,36 @@ namespace nexo::ecs { } } + /** + * @brief Adds a component to an entity using type ID and constructor + * + * Adds the component using the component type ID and a constructor function, + * useful for runtime component type handling. Updates any groups that + * match the entity's new signature. + * + * @param entity The entity to add the component to + * @param componentType The type ID of the component to add + * @param constructor Pointer to the constructor function + * @param oldSignature The entity's signature before adding the component + * @param newSignature The entity's signature after adding the component + * + * @pre componentType must be a valid-registered component type + */ + void addComponentWithConstructor(const Entity entity, const ComponentType componentType, + void (*constructor)(void* memoryDst), const Signature oldSignature, + const Signature newSignature) + { + getComponentArray(componentType)->insertRawWithConstructor(entity, constructor); + + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + // Check if entity qualifies now but did not qualify before. + if (((oldSignature & group->allSignature()) != group->allSignature()) && + ((newSignature & group->allSignature()) == group->allSignature())) { + group->addToGroup(entity); + } + } + } + /** * @brief Removes a component from an entity using type ID * diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 5916d8646..c5606d809 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -140,7 +140,8 @@ namespace nexo::ecs { void registerComponent(const std::string& displayName = "") { m_componentManager->registerComponent(); - addComponentDescription(getComponentType(), ComponentDescription{displayName, {}, true}); + addComponentDescription(getComponentType(), + ComponentDescription{displayName, {}, true, [](void* memoryDst) { new (memoryDst) T{}; }}); m_getComponentFunctions[typeid(T)] = [this](const Entity entity) -> std::any { return this->getComponent(entity); @@ -271,6 +272,30 @@ namespace nexo::ecs { m_systemManager->entitySignatureChanged(entity, oldSignature, signature); } + /** + * @brief Adds a component to an entity using ComponentType, default-constructs it, + * updates its signature, and notifies systems. + * + * @param entity - The ID of the entity. + * @param componentType - The ID of the component type to add. + */ + void addComponentWithDefault(const Entity entity, const ComponentType componentType) const + { + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + signature.set(componentType, true); + const auto& componentDescription = m_componentDescriptions.at(componentType); + if (componentDescription == nullptr) { + THROW_EXCEPTION(ComponentNotRegistered); + } + m_componentManager->addComponentWithConstructor(entity, componentType, componentDescription->constructor, oldSignature, + signature); + + m_entityManager->setSignature(entity, signature); + + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); + } + /** * @brief Removes a component from an entity using ComponentType, updates its signature, and notifies systems. * diff --git a/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp index 3836cfca6..923d78d47 100644 --- a/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp +++ b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp @@ -28,9 +28,10 @@ namespace nexo::ecs { struct ComponentDescription { - std::string name; // Name of the component - std::vector fields; // List of fields in the component - bool internalComponent = false; // Indicates if the component is internal + std::string name; // Name of the component + std::vector fields; // List of fields in the component + bool internalComponent = false; // Indicates if the component is internal + void (*constructor)(void* memoryDst); // Pointer to the constructor function }; } // namespace nexo::ecs From 5ac9d9db00f696439b57f961b51b71648abf6e26 Mon Sep 17 00:00:00 2001 From: Marie Giacomel Date: Fri, 7 Nov 2025 11:35:32 +0100 Subject: [PATCH 3/3] feat(add-component): add component to an entity in the UI --- .../InspectorWindow/AddComponent.cpp | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 editor/src/DocumentWindows/InspectorWindow/AddComponent.cpp diff --git a/editor/src/DocumentWindows/InspectorWindow/AddComponent.cpp b/editor/src/DocumentWindows/InspectorWindow/AddComponent.cpp new file mode 100644 index 000000000..400a842ae --- /dev/null +++ b/editor/src/DocumentWindows/InspectorWindow/AddComponent.cpp @@ -0,0 +1,63 @@ +//// AddComponent.cpp /////////////////////////////////////////////////////////////// +// +// ⢀⢀⢀⣤⣤⣤⡀⢀⢀⢀⢀⢀⢀⢠⣤⡄⢀⢀⢀⢀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⢀⢀⢀⢠⣤⣄⢀⢀⢀⢀⢀⢀⢀⣤⣤⢀⢀⢀⢀⢀⢀⢀⢀⣀⣄⢀⢀⢠⣄⣀⢀⢀⢀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⣿⣷⡀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡟⡛⡛⡛⡛⡛⡛⡛⢁⢀⢀⢀⢀⢻⣿⣦⢀⢀⢀⢀⢠⣾⡿⢃⢀⢀⢀⢀⢀⣠⣾⣿⢿⡟⢀⢀⡙⢿⢿⣿⣦⡀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⡛⣿⣷⡀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡙⣿⡷⢀⢀⣰⣿⡟⢁⢀⢀⢀⢀⢀⣾⣿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⣿⡆⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⡈⢿⣷⡄⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⢀⢀⢀⢀⢀⢀⢀⡈⢀⢀⣼⣿⢏⢀⢀⢀⢀⢀⢀⣼⣿⡏⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡘⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⡈⢿⣿⡄⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣿⢿⢿⢿⢿⢿⢿⢿⢇⢀⢀⢀⢀⢀⢀⢀⢠⣾⣿⣧⡀⢀⢀⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⡈⢿⣿⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣰⣿⡟⡛⣿⣷⡄⢀⢀⢀⢀⢀⢿⣿⣇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⡈⢿⢀⢀⢸⣿⡇⢀⢀⢀⢀⡛⡟⢁⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⡟⢀⢀⡈⢿⣿⣄⢀⢀⢀⢀⡘⣿⣿⣄⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⢏⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⢀⢀⢀⣠⣾⡿⢃⢀⢀⢀⢀⢀⢻⣿⣧⡀⢀⢀⢀⡈⢻⣿⣷⣦⣄⢀⢀⣠⣤⣶⣿⡿⢋⢀⢀⢀⢀ +// ⢀⢀⢀⢿⢿⢀⢀⢀⢀⢀⢀⢀⢀⢸⢿⢃⢀⢀⢀⢀⢻⢿⢿⢿⢿⢿⢿⢿⢿⢿⢃⢀⢀⢀⢿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⡗⢀⢀⢀⢀⢀⡈⡉⡛⡛⢀⢀⢹⡛⢋⢁⢀⢀⢀⢀⢀⢀ +// +// Author: Marie GIACOMEL +// Date: 2025-10-30 +// Description: Source file for the AddComponent functions +// +///////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "InspectorWindow.hpp" + +namespace nexo::editor { + + void searchComponentBar() + { + // TODO: Implement search bar for components + } + + void addComponentPopup(const ecs::Entity entity) + { + const auto& coord = nexo::Application::m_coordinator; + const auto signature = coord->getSignature(entity); + ImGui::Text("Add component to %u", entity); + ImGui::Separator(); + for (const auto& [componentType, description] : coord->getComponentDescriptions()) { + // Skip empty components or if the entity already has it + if (description->name.empty() || signature.test(componentType)) continue; + if (ImGui::Selectable(description->name.c_str())) { + LOG(NEXO_INFO, "Adding component {} to entity {}", description->name, entity); + coord->addComponentWithDefault(entity, componentType); + } + } + ImGui::Separator(); + if (ImNexo::Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { + PopupManager::closePopup(); + } + PopupManager::endPopup(); + } + + void InspectorWindow::showAddComponentButton(const ecs::Entity entity) + { + if (ImGui::Button("Add Component")) { + ImGui::Text("add component to entity %u", entity); + m_popupManager.openPopup("Add Component Popup"); + } + + if (m_popupManager.showPopup("Add Component Popup")) { + addComponentPopup(entity); + } + } + +} // namespace nexo::editor