diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 1fea7f7a..6577e877 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -33,6 +33,7 @@ add_executable(${PROJECT_NAME} WIN32 src/Application.cpp src/Theme.cpp src/Core/UndoManager.cpp + src/ComponentEditors/ComponentEditorRegistry.cpp src/Managers/SceneManager.cpp src/Managers/ProjectManager.cpp src/Core/EditorConsoleSink.cpp diff --git a/editor/src/ComponentEditors/CameraComponentEditor.h b/editor/src/ComponentEditors/CameraComponentEditor.h index 4f14c027..88585941 100644 --- a/editor/src/ComponentEditors/CameraComponentEditor.h +++ b/editor/src/ComponentEditors/CameraComponentEditor.h @@ -1,6 +1,7 @@ #pragma once #include "IComponentEditor.h" +#include "Core/IUndoManager.h" #include "UI/UI.h" #include @@ -10,12 +11,12 @@ namespace Editor { class CameraComponentEditor : public IComponentEditor { public: - const char* GetDisplayName() const override { + const char* GetComponentTypeName() const override { return "Camera"; } - bool CanEdit(::XCEngine::Components::Component* component) const override { - return dynamic_cast<::XCEngine::Components::CameraComponent*>(component) != nullptr; + const char* GetDisplayName() const override { + return "Camera"; } bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) override { @@ -119,10 +120,6 @@ public: return gameObject->GetComponent<::XCEngine::Components::CameraComponent>() ? "Already Added" : nullptr; } - ::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const override { - return gameObject ? gameObject->AddComponent<::XCEngine::Components::CameraComponent>() : nullptr; - } - bool CanRemove(::XCEngine::Components::Component* component) const override { return CanEdit(component); } diff --git a/editor/src/ComponentEditors/ComponentEditorRegistry.cpp b/editor/src/ComponentEditors/ComponentEditorRegistry.cpp new file mode 100644 index 00000000..f0321274 --- /dev/null +++ b/editor/src/ComponentEditors/ComponentEditorRegistry.cpp @@ -0,0 +1,45 @@ +#include "ComponentEditors/ComponentEditorRegistry.h" + +#include "ComponentEditors/CameraComponentEditor.h" +#include "ComponentEditors/LightComponentEditor.h" +#include "ComponentEditors/TransformComponentEditor.h" + +namespace XCEngine { +namespace Editor { + +ComponentEditorRegistry& ComponentEditorRegistry::Get() { + static ComponentEditorRegistry registry; + return registry; +} + +ComponentEditorRegistry::ComponentEditorRegistry() { + RegisterEditor(std::make_unique()); + RegisterEditor(std::make_unique()); + RegisterEditor(std::make_unique()); +} + +void ComponentEditorRegistry::RegisterEditor(std::unique_ptr editor) { + if (!editor) { + return; + } + + IComponentEditor* editorPtr = editor.get(); + m_editorsByType[editor->GetComponentTypeName()] = editorPtr; + m_editors.push_back(std::move(editor)); +} + +IComponentEditor* ComponentEditorRegistry::FindEditor(::XCEngine::Components::Component* component) const { + return component ? FindEditorByTypeName(component->GetName()) : nullptr; +} + +IComponentEditor* ComponentEditorRegistry::FindEditorByTypeName(const std::string& componentTypeName) const { + const auto it = m_editorsByType.find(componentTypeName); + return it != m_editorsByType.end() ? it->second : nullptr; +} + +const std::vector>& ComponentEditorRegistry::GetEditors() const { + return m_editors; +} + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/ComponentEditors/ComponentEditorRegistry.h b/editor/src/ComponentEditors/ComponentEditorRegistry.h new file mode 100644 index 00000000..e72a9968 --- /dev/null +++ b/editor/src/ComponentEditors/ComponentEditorRegistry.h @@ -0,0 +1,30 @@ +#pragma once + +#include "IComponentEditor.h" + +#include +#include +#include +#include + +namespace XCEngine { +namespace Editor { + +class ComponentEditorRegistry { +public: + static ComponentEditorRegistry& Get(); + + void RegisterEditor(std::unique_ptr editor); + IComponentEditor* FindEditor(::XCEngine::Components::Component* component) const; + IComponentEditor* FindEditorByTypeName(const std::string& componentTypeName) const; + const std::vector>& GetEditors() const; + +private: + ComponentEditorRegistry(); + + std::vector> m_editors; + std::unordered_map m_editorsByType; +}; + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/ComponentEditors/IComponentEditor.h b/editor/src/ComponentEditors/IComponentEditor.h index b79b8b60..3ba1a546 100644 --- a/editor/src/ComponentEditors/IComponentEditor.h +++ b/editor/src/ComponentEditors/IComponentEditor.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -12,8 +13,11 @@ class IComponentEditor { public: virtual ~IComponentEditor() = default; + virtual const char* GetComponentTypeName() const = 0; virtual const char* GetDisplayName() const = 0; - virtual bool CanEdit(::XCEngine::Components::Component* component) const = 0; + virtual bool CanEdit(::XCEngine::Components::Component* component) const { + return component && component->GetName() == GetComponentTypeName(); + } virtual bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) = 0; virtual bool ShowInAddComponentMenu() const { return true; } @@ -23,8 +27,9 @@ public: return nullptr; } virtual ::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const { - (void)gameObject; - return nullptr; + return gameObject + ? ::XCEngine::Components::ComponentFactoryRegistry::Get().CreateComponent(gameObject, GetComponentTypeName()) + : nullptr; } virtual bool CanRemove(::XCEngine::Components::Component* component) const { diff --git a/editor/src/ComponentEditors/LightComponentEditor.h b/editor/src/ComponentEditors/LightComponentEditor.h index 7aab1dbf..01bd8d68 100644 --- a/editor/src/ComponentEditors/LightComponentEditor.h +++ b/editor/src/ComponentEditors/LightComponentEditor.h @@ -1,6 +1,7 @@ #pragma once #include "IComponentEditor.h" +#include "Core/IUndoManager.h" #include "UI/UI.h" #include @@ -10,12 +11,12 @@ namespace Editor { class LightComponentEditor : public IComponentEditor { public: - const char* GetDisplayName() const override { + const char* GetComponentTypeName() const override { return "Light"; } - bool CanEdit(::XCEngine::Components::Component* component) const override { - return dynamic_cast<::XCEngine::Components::LightComponent*>(component) != nullptr; + const char* GetDisplayName() const override { + return "Light"; } bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) override { @@ -103,10 +104,6 @@ public: return gameObject->GetComponent<::XCEngine::Components::LightComponent>() ? "Already Added" : nullptr; } - ::XCEngine::Components::Component* AddTo(::XCEngine::Components::GameObject* gameObject) const override { - return gameObject ? gameObject->AddComponent<::XCEngine::Components::LightComponent>() : nullptr; - } - bool CanRemove(::XCEngine::Components::Component* component) const override { return CanEdit(component); } diff --git a/editor/src/ComponentEditors/TransformComponentEditor.h b/editor/src/ComponentEditors/TransformComponentEditor.h index c8c38439..56854b0d 100644 --- a/editor/src/ComponentEditors/TransformComponentEditor.h +++ b/editor/src/ComponentEditors/TransformComponentEditor.h @@ -1,6 +1,7 @@ #pragma once #include "IComponentEditor.h" +#include "Core/IUndoManager.h" #include "UI/UI.h" #include @@ -10,12 +11,12 @@ namespace Editor { class TransformComponentEditor : public IComponentEditor { public: - const char* GetDisplayName() const override { + const char* GetComponentTypeName() const override { return "Transform"; } - bool CanEdit(::XCEngine::Components::Component* component) const override { - return dynamic_cast<::XCEngine::Components::TransformComponent*>(component) != nullptr; + const char* GetDisplayName() const override { + return "Transform"; } bool Render(::XCEngine::Components::Component* component, IUndoManager* undoManager) override { diff --git a/editor/src/Managers/SceneManager.cpp b/editor/src/Managers/SceneManager.cpp index 29c1f8a3..e6c70ca5 100644 --- a/editor/src/Managers/SceneManager.cpp +++ b/editor/src/Managers/SceneManager.cpp @@ -1,10 +1,9 @@ #include "SceneManager.h" #include "Core/EventBus.h" #include "Core/EditorEvents.h" +#include #include #include -#include -#include #include #include #include @@ -21,29 +20,6 @@ std::pair SerializeComponent(const ::XCEngine::Compone return { component->GetName(), payload.str() }; } -::XCEngine::Components::Component* CreateComponentByName( - ::XCEngine::Components::GameObject* gameObject, - const std::string& componentName) { - if (!gameObject) { - return nullptr; - } - - if (componentName == "Camera") { - return gameObject->AddComponent<::XCEngine::Components::CameraComponent>(); - } - if (componentName == "Light") { - return gameObject->AddComponent<::XCEngine::Components::LightComponent>(); - } - if (componentName == "AudioSource") { - return gameObject->AddComponent<::XCEngine::Components::AudioSourceComponent>(); - } - if (componentName == "AudioListener") { - return gameObject->AddComponent<::XCEngine::Components::AudioListenerComponent>(); - } - - return nullptr; -} - } // namespace SceneManager::SceneManager(EventBus* eventBus) @@ -152,7 +128,7 @@ void SceneManager::CopyEntity(::XCEngine::Components::GameObject::ID id) { } for (const auto& componentData : data.components) { - if (auto* component = CreateComponentByName(newEntity, componentData.first)) { + if (auto* component = ::XCEngine::Components::ComponentFactoryRegistry::Get().CreateComponent(newEntity, componentData.first)) { if (!componentData.second.empty()) { std::istringstream payloadStream(componentData.second); component->Deserialize(payloadStream); diff --git a/editor/src/panels/InspectorPanel.cpp b/editor/src/panels/InspectorPanel.cpp index 95becb03..cd7ee5a1 100644 --- a/editor/src/panels/InspectorPanel.cpp +++ b/editor/src/panels/InspectorPanel.cpp @@ -5,10 +5,8 @@ #include "Core/IUndoManager.h" #include "Core/EventBus.h" #include "Core/EditorEvents.h" -#include "ComponentEditors/CameraComponentEditor.h" +#include "ComponentEditors/ComponentEditorRegistry.h" #include "ComponentEditors/IComponentEditor.h" -#include "ComponentEditors/LightComponentEditor.h" -#include "ComponentEditors/TransformComponentEditor.h" #include "Utils/UndoUtils.h" #include #include @@ -16,9 +14,7 @@ namespace XCEngine { namespace Editor { -InspectorPanel::InspectorPanel() : Panel("Inspector") { - RegisterDefaultComponentEditors(); -} +InspectorPanel::InspectorPanel() : Panel("Inspector") {} InspectorPanel::~InspectorPanel() { if (m_context) { @@ -33,34 +29,6 @@ void InspectorPanel::OnSelectionChanged(const SelectionChangedEvent& event) { m_selectedEntityId = event.primarySelection; } -void InspectorPanel::RegisterDefaultComponentEditors() { - RegisterComponentEditor(std::make_unique()); - RegisterComponentEditor(std::make_unique()); - RegisterComponentEditor(std::make_unique()); -} - -void InspectorPanel::RegisterComponentEditor(std::unique_ptr editor) { - if (!editor) { - return; - } - - m_componentEditors.push_back(std::move(editor)); -} - -IComponentEditor* InspectorPanel::GetEditorFor(::XCEngine::Components::Component* component) const { - if (!component) { - return nullptr; - } - - for (const auto& editor : m_componentEditors) { - if (editor && editor->CanEdit(component)) { - return editor.get(); - } - } - - return nullptr; -} - void InspectorPanel::Render() { ImGui::Begin(m_name.c_str(), nullptr, ImGuiWindowFlags_None); @@ -126,7 +94,7 @@ void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* ImGui::Separator(); bool drewAnyEntry = false; - for (const auto& editor : m_componentEditors) { + for (const auto& editor : ComponentEditorRegistry::Get().GetEditors()) { if (!editor || !editor->ShowInAddComponentMenu()) { continue; } @@ -167,7 +135,7 @@ void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) { if (!component) return; - IComponentEditor* editor = GetEditorFor(component); + IComponentEditor* editor = ComponentEditorRegistry::Get().FindEditor(component); const char* name = component->GetName().c_str(); diff --git a/editor/src/panels/InspectorPanel.h b/editor/src/panels/InspectorPanel.h index 86e6736f..f0268623 100644 --- a/editor/src/panels/InspectorPanel.h +++ b/editor/src/panels/InspectorPanel.h @@ -3,8 +3,6 @@ #include "Panel.h" #include -#include -#include namespace XCEngine { namespace Components { @@ -14,8 +12,6 @@ class GameObject; namespace Editor { -class IComponentEditor; - class InspectorPanel : public Panel { public: InspectorPanel(); @@ -24,9 +20,6 @@ public: void Render() override; private: - void RegisterDefaultComponentEditors(); - void RegisterComponentEditor(std::unique_ptr editor); - IComponentEditor* GetEditorFor(::XCEngine::Components::Component* component) const; void RenderGameObject(::XCEngine::Components::GameObject* gameObject); void RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject); void RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject); @@ -35,7 +28,6 @@ private: uint64_t m_selectionHandlerId = 0; uint64_t m_selectedEntityId = 0; - std::vector> m_componentEditors; }; } diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 755d4bfe..52af1e07 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -247,6 +247,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/LightComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/AudioSourceComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/AudioListenerComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Components/ComponentFactoryRegistry.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/Component.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/TransformComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/GameObject.cpp @@ -254,6 +255,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/LightComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/AudioSourceComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/AudioListenerComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Components/ComponentFactoryRegistry.cpp # Scene ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Scene/Scene.h diff --git a/engine/include/XCEngine/Components/ComponentFactoryRegistry.h b/engine/include/XCEngine/Components/ComponentFactoryRegistry.h new file mode 100644 index 00000000..cf0923ff --- /dev/null +++ b/engine/include/XCEngine/Components/ComponentFactoryRegistry.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +namespace XCEngine { +namespace Components { + +class Component; +class GameObject; + +class ComponentFactoryRegistry { +public: + using CreateComponentFn = Component* (*)(GameObject* gameObject); + + static ComponentFactoryRegistry& Get(); + + void RegisterFactory(const std::string& typeName, CreateComponentFn createFn); + Component* CreateComponent(GameObject* gameObject, const std::string& typeName) const; + bool IsRegistered(const std::string& typeName) const; + const std::vector& GetRegisteredTypes() const; + +private: + ComponentFactoryRegistry(); + + std::unordered_map m_factories; + std::vector m_registrationOrder; +}; + +} // namespace Components +} // namespace XCEngine diff --git a/engine/src/Components/ComponentFactoryRegistry.cpp b/engine/src/Components/ComponentFactoryRegistry.cpp new file mode 100644 index 00000000..56d460cd --- /dev/null +++ b/engine/src/Components/ComponentFactoryRegistry.cpp @@ -0,0 +1,67 @@ +#include "Components/ComponentFactoryRegistry.h" + +#include "Components/AudioListenerComponent.h" +#include "Components/AudioSourceComponent.h" +#include "Components/CameraComponent.h" +#include "Components/GameObject.h" +#include "Components/LightComponent.h" + +namespace XCEngine { +namespace Components { + +namespace { + +template +Component* CreateBuiltInComponent(GameObject* gameObject) { + return gameObject ? gameObject->AddComponent() : nullptr; +} + +} // namespace + +ComponentFactoryRegistry& ComponentFactoryRegistry::Get() { + static ComponentFactoryRegistry registry; + return registry; +} + +ComponentFactoryRegistry::ComponentFactoryRegistry() { + RegisterFactory("Camera", &CreateBuiltInComponent); + RegisterFactory("Light", &CreateBuiltInComponent); + RegisterFactory("AudioSource", &CreateBuiltInComponent); + RegisterFactory("AudioListener", &CreateBuiltInComponent); +} + +void ComponentFactoryRegistry::RegisterFactory(const std::string& typeName, CreateComponentFn createFn) { + if (typeName.empty() || !createFn) { + return; + } + + const auto [it, inserted] = m_factories.insert_or_assign(typeName, createFn); + (void)it; + if (inserted) { + m_registrationOrder.push_back(typeName); + } +} + +Component* ComponentFactoryRegistry::CreateComponent(GameObject* gameObject, const std::string& typeName) const { + if (!gameObject) { + return nullptr; + } + + const auto it = m_factories.find(typeName); + if (it == m_factories.end() || !it->second) { + return nullptr; + } + + return it->second(gameObject); +} + +bool ComponentFactoryRegistry::IsRegistered(const std::string& typeName) const { + return m_factories.find(typeName) != m_factories.end(); +} + +const std::vector& ComponentFactoryRegistry::GetRegisteredTypes() const { + return m_registrationOrder; +} + +} // namespace Components +} // namespace XCEngine diff --git a/engine/src/Scene/Scene.cpp b/engine/src/Scene/Scene.cpp index bf9ac01a..4f2abfca 100644 --- a/engine/src/Scene/Scene.cpp +++ b/engine/src/Scene/Scene.cpp @@ -1,10 +1,7 @@ #include "Scene/Scene.h" +#include "Components/ComponentFactoryRegistry.h" #include "Components/GameObject.h" #include "Components/TransformComponent.h" -#include "Components/CameraComponent.h" -#include "Components/LightComponent.h" -#include "Components/AudioSourceComponent.h" -#include "Components/AudioListenerComponent.h" #include #include #include @@ -66,27 +63,6 @@ std::string UnescapeString(const std::string& value) { return unescaped; } -Component* CreateComponentByType(GameObject* gameObject, const std::string& type) { - if (!gameObject) { - return nullptr; - } - - if (type == "Camera") { - return gameObject->AddComponent(); - } - if (type == "Light") { - return gameObject->AddComponent(); - } - if (type == "AudioSource") { - return gameObject->AddComponent(); - } - if (type == "AudioListener") { - return gameObject->AddComponent(); - } - - return nullptr; -} - void SerializeGameObjectRecursive(std::ostream& os, GameObject* gameObject) { if (!gameObject) { return; @@ -345,7 +321,7 @@ void Scene::DeserializeFromString(const std::string& data) { } for (const PendingComponentData& componentData : pending.components) { - if (Component* component = CreateComponentByType(go.get(), componentData.type)) { + if (Component* component = ComponentFactoryRegistry::Get().CreateComponent(go.get(), componentData.type)) { if (!componentData.payload.empty()) { std::istringstream componentStream(componentData.payload); component->Deserialize(componentStream); diff --git a/tests/Components/CMakeLists.txt b/tests/Components/CMakeLists.txt index 2c130f54..b3c7ba5e 100644 --- a/tests/Components/CMakeLists.txt +++ b/tests/Components/CMakeLists.txt @@ -4,6 +4,7 @@ project(XCEngine_ComponentsTests) set(COMPONENTS_TEST_SOURCES test_component.cpp + test_component_factory_registry.cpp test_transform_component.cpp test_game_object.cpp test_camera_light_component.cpp diff --git a/tests/Components/test_component_factory_registry.cpp b/tests/Components/test_component_factory_registry.cpp new file mode 100644 index 00000000..4936c893 --- /dev/null +++ b/tests/Components/test_component_factory_registry.cpp @@ -0,0 +1,36 @@ +#include + +#include +#include +#include +#include +#include +#include + +using namespace XCEngine::Components; + +namespace { + +TEST(ComponentFactoryRegistry_Test, BuiltInTypesAreRegistered) { + auto& registry = ComponentFactoryRegistry::Get(); + + EXPECT_TRUE(registry.IsRegistered("Camera")); + EXPECT_TRUE(registry.IsRegistered("Light")); + EXPECT_TRUE(registry.IsRegistered("AudioSource")); + EXPECT_TRUE(registry.IsRegistered("AudioListener")); + EXPECT_FALSE(registry.IsRegistered("Transform")); + EXPECT_FALSE(registry.IsRegistered("MissingComponent")); +} + +TEST(ComponentFactoryRegistry_Test, CreateBuiltInComponentsByTypeName) { + GameObject gameObject("FactoryTarget"); + auto& registry = ComponentFactoryRegistry::Get(); + + EXPECT_NE(dynamic_cast(registry.CreateComponent(&gameObject, "Camera")), nullptr); + EXPECT_NE(dynamic_cast(registry.CreateComponent(&gameObject, "Light")), nullptr); + EXPECT_NE(dynamic_cast(registry.CreateComponent(&gameObject, "AudioSource")), nullptr); + EXPECT_NE(dynamic_cast(registry.CreateComponent(&gameObject, "AudioListener")), nullptr); + EXPECT_EQ(registry.CreateComponent(&gameObject, "MissingComponent"), nullptr); +} + +} // namespace diff --git a/tests/Scene/test_scene.cpp b/tests/Scene/test_scene.cpp index ecd719e2..22689fca 100644 --- a/tests/Scene/test_scene.cpp +++ b/tests/Scene/test_scene.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -262,6 +264,30 @@ TEST_F(SceneTest, Save_ContainsGameObjectData) { std::filesystem::remove(scenePath); } +TEST_F(SceneTest, Save_And_Load_PreservesAudioComponents) { + GameObject* listenerObject = testScene->CreateGameObject("Listener"); + GameObject* sourceObject = testScene->CreateGameObject("Source"); + + listenerObject->AddComponent(); + sourceObject->AddComponent(); + + const std::filesystem::path scenePath = GetTempScenePath("test_scene_audio_components.xc"); + testScene->Save(scenePath.string()); + + Scene loadedScene; + loadedScene.Load(scenePath.string()); + + GameObject* loadedListenerObject = loadedScene.Find("Listener"); + GameObject* loadedSourceObject = loadedScene.Find("Source"); + ASSERT_NE(loadedListenerObject, nullptr); + ASSERT_NE(loadedSourceObject, nullptr); + + EXPECT_NE(loadedListenerObject->GetComponent(), nullptr); + EXPECT_NE(loadedSourceObject->GetComponent(), nullptr); + + std::filesystem::remove(scenePath); +} + TEST_F(SceneTest, Save_And_Load_PreservesHierarchyAndComponents) { testScene->SetName("Serialized Scene"); testScene->SetActive(false);