diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index a0517e54f..6a41ccb59 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -49,6 +49,7 @@ set(COMMON_SOURCES engine/src/core/scene/SceneManager.cpp engine/src/ecs/Entity.cpp engine/src/ecs/Components.cpp + engine/src/ecs/ComponentArray.cpp engine/src/ecs/Coordinator.cpp engine/src/ecs/System.cpp engine/src/systems/CameraSystem.cpp diff --git a/engine/src/ecs/ComponentArray.cpp b/engine/src/ecs/ComponentArray.cpp new file mode 100644 index 000000000..491325f3e --- /dev/null +++ b/engine/src/ecs/ComponentArray.cpp @@ -0,0 +1,254 @@ +//// ComponentArray.cpp /////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Guillaume HEIN +// Date: 24/06/2025 +// Description: Source file for the component array class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ComponentArray.hpp" + +namespace nexo::ecs { + + TypeErasedComponentArray::TypeErasedComponentArray(const size_t componentSize, const size_t initialCapacity): m_componentSize(componentSize), m_capacity(initialCapacity) + { + if (componentSize == 0) { + throw std::invalid_argument("Component size cannot be zero"); + } + + m_sparse.resize(m_capacity, INVALID_ENTITY); + m_dense.reserve(m_capacity); + m_componentData.reserve(m_capacity * m_componentSize); + } + + void TypeErasedComponentArray::insert(Entity entity, const void* componentData) + { + insertRaw(entity, componentData); + } + + void TypeErasedComponentArray::insertRaw(Entity entity, const void* componentData) + { + 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 + size_t requiredSize = (m_size + 1) * m_componentSize; + if (m_componentData.size() < requiredSize) { + m_componentData.resize(requiredSize); + } + + // Copy component data + std::memcpy(m_componentData.data() + newIndex * m_componentSize, + componentData, m_componentSize); + + ++m_size; + } + + void TypeErasedComponentArray::remove(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t indexToRemove = m_sparse[entity]; + + // Handle grouped components + if (indexToRemove < m_groupSize) { + size_t groupLastIndex = m_groupSize - 1; + if (indexToRemove != groupLastIndex) { + swapComponents(indexToRemove, groupLastIndex); + std::swap(m_dense[indexToRemove], m_dense[groupLastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + m_sparse[m_dense[groupLastIndex]] = groupLastIndex; + } + --m_groupSize; + indexToRemove = groupLastIndex; + } + + // Standard removal + const size_t lastIndex = m_size - 1; + if (indexToRemove != lastIndex) { + swapComponents(indexToRemove, lastIndex); + std::swap(m_dense[indexToRemove], m_dense[lastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + } + + m_sparse[entity] = INVALID_ENTITY; + m_dense.pop_back(); + --m_size; + + shrinkIfNeeded(); + } + + bool TypeErasedComponentArray::hasComponent(Entity entity) const + { + return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); + } + + void TypeErasedComponentArray::entityDestroyed(Entity entity) + { + if (hasComponent(entity)) + remove(entity); + } + + void TypeErasedComponentArray::duplicateComponent(Entity sourceEntity, Entity destEntity) + { + if (!hasComponent(sourceEntity)) + THROW_EXCEPTION(ComponentNotFound, sourceEntity); + + const void* sourceData = getRawComponent(sourceEntity); + insert(destEntity, sourceData); + } + + size_t TypeErasedComponentArray::getComponentSize() const + { + return m_componentSize; + } + + size_t TypeErasedComponentArray::size() const + { + return m_size; + } + + void* TypeErasedComponentArray::getRawComponent(Entity entity) + { + if (!hasComponent(entity)) + return nullptr; + return m_componentData.data() + m_sparse[entity] * m_componentSize; + } + + const void* TypeErasedComponentArray::getRawComponent(Entity entity) const + { + if (!hasComponent(entity)) + return nullptr; + return m_componentData.data() + m_sparse[entity] * m_componentSize; + } + + void* TypeErasedComponentArray::getRawData() + { + return m_componentData.data(); + } + + const void* TypeErasedComponentArray::getRawData() const + { + return m_componentData.data(); + } + + std::span TypeErasedComponentArray::entities() const + { + return {m_dense.data(), m_size}; + } + + Entity TypeErasedComponentArray::getEntityAtIndex(size_t index) const + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + return m_dense[index]; + } + + void TypeErasedComponentArray::addToGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index < m_groupSize) + return; + + if (index != m_groupSize) { + swapComponents(index, m_groupSize); + std::swap(m_dense[index], m_dense[m_groupSize]); + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + ++m_groupSize; + } + + void TypeErasedComponentArray::removeFromGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index >= m_groupSize) + return; + + --m_groupSize; + if (index != m_groupSize) { + swapComponents(index, m_groupSize); + std::swap(m_dense[index], m_dense[m_groupSize]); + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + } + + constexpr size_t TypeErasedComponentArray::groupSize() const + { + return m_groupSize; + } + + size_t TypeErasedComponentArray::memoryUsage() const + { + return m_componentData.capacity() + + sizeof(size_t) * m_sparse.capacity() + + sizeof(Entity) * m_dense.capacity(); + } + + void TypeErasedComponentArray::ensureSparseCapacity(Entity entity) + { + if (entity >= m_sparse.size()) { + size_t newSize = m_sparse.size(); + if (newSize == 0) + newSize = m_capacity; + while (entity >= newSize) + newSize *= 2; + m_sparse.resize(newSize, INVALID_ENTITY); + } + } + + void TypeErasedComponentArray::swapComponents(const size_t index1, const size_t index2) + { + if (index1 == index2) return; + + std::byte* data1 = m_componentData.data() + index1 * m_componentSize; + std::byte* data2 = m_componentData.data() + index2 * m_componentSize; + + // Use a temporary buffer for swapping + std::vector temp(m_componentSize); + std::memcpy(temp.data(), data1, m_componentSize); + std::memcpy(data1, data2, m_componentSize); + std::memcpy(data2, temp.data(), m_componentSize); + } + + void TypeErasedComponentArray::shrinkIfNeeded() + { + if (m_size < m_componentData.capacity() / 4 && m_componentData.capacity() > m_capacity * m_componentSize * 2) { + size_t newCapacity = std::max(m_size * 2, static_cast(m_capacity)) * m_componentSize; + if (newCapacity < m_capacity * m_componentSize) + newCapacity = m_capacity * m_componentSize; + + m_componentData.shrink_to_fit(); + m_dense.shrink_to_fit(); + + m_componentData.reserve(newCapacity); + m_dense.reserve(newCapacity / m_componentSize); + } + } + +} // namespace nexo::ecs diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 52559659e..9dbde9bc1 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace nexo::ecs { /** @@ -51,6 +52,62 @@ namespace nexo::ecs { virtual void entityDestroyed(Entity entity) = 0; virtual void duplicateComponent(Entity sourceEntity, Entity destEntity) = 0; + + /** + * @brief Gets the size of each component in bytes + * @return Size of individual component in bytes + */ + [[nodiscard]] virtual size_t getComponentSize() const = 0; + + /** + * @brief Gets the total number of components in the array + * @return The number of active components + */ + [[nodiscard]] virtual size_t size() const = 0; + + /** + * @brief Gets raw pointer to component data for an entity + * @param entity The entity to get the component from + * @return Raw pointer to component data, or nullptr if not found + */ + [[nodiscard]] virtual void* getRawComponent(Entity entity) = 0; + + /** + * @brief Gets const raw pointer to component data for an entity + * @param entity The entity to get the component from + * @return Const raw pointer to component data, or nullptr if not found + */ + [[nodiscard]] virtual const void* getRawComponent(Entity entity) const = 0; + + /** + * @brief Gets raw pointer to all component data + * @return Raw pointer to contiguous component data + */ + [[nodiscard]] virtual void* getRawData() = 0; + + /** + * @brief Gets const raw pointer to all component data + * @return Const raw pointer to contiguous component data + */ + [[nodiscard]] virtual const void* getRawData() const = 0; + + /** + * @brief Inserts a raw new component for the given entity. + * + * @param entity The entity to add the component to + * @param componentData Pointer to the raw component data + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @pre The entity must be a valid entity ID + * @pre componentData must point to valid memory of component's size + */ + virtual void insertRaw(Entity entity, const void *componentData) = 0; + + /** + * @brief Gets a span of all entities with this component + * @return Span of entity IDs + */ + [[nodiscard]] virtual std::span entities() const = 0; }; /** @@ -67,7 +124,7 @@ namespace nexo::ecs { * @note This class is not thread-safe. Access should be synchronized externally when * used in multi-threaded contexts. */ - template + template requires (capacity >= 1) class alignas(64) ComponentArray final : public IComponentArray { public: @@ -89,6 +146,35 @@ namespace nexo::ecs { m_componentArray.reserve(capacity); } + [[nodiscard]] size_t getComponentSize() const override + { + return sizeof(T); + } + + [[nodiscard]] void* getRawComponent(Entity entity) override + { + if (!hasComponent(entity)) + return nullptr; + return &m_componentArray[m_sparse[entity]]; + } + + [[nodiscard]] const void* getRawComponent(Entity entity) const override + { + if (!hasComponent(entity)) + return nullptr; + return &m_componentArray[m_sparse[entity]]; + } + + [[nodiscard]] void* getRawData() override + { + return m_componentArray.data(); + } + + [[nodiscard]] const void* getRawData() const override + { + return m_componentArray.data(); + } + /** * @brief Inserts a new component for the given entity. * @@ -119,6 +205,39 @@ namespace nexo::ecs { ++m_size; } + /** + * @brief Inserts a raw new component for the given entity. + * + * @param entity The entity to add the component to + * @param componentData Pointer to the raw component data + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @pre The entity must be a valid entity ID + * @pre componentData must point to valid memory of component's size + */ + void insertRaw(Entity entity, const void *componentData) override + { + 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(); + // copy the raw data into the new component + std::memcpy(&m_componentArray[newIndex], componentData, sizeof(T)); + + ++m_size; + } + /** * @brief Removes the component for the given entity. * @@ -233,7 +352,7 @@ namespace nexo::ecs { * * @return The number of active components */ - [[nodiscard]] constexpr size_t size() const + [[nodiscard]] constexpr size_t size() const override { return m_size; } @@ -279,7 +398,7 @@ namespace nexo::ecs { * * @return Const span of entity IDs */ - [[nodiscard]] std::span entities() const + [[nodiscard]] std::span entities() const override { return {m_dense.data(), m_size}; } @@ -484,4 +603,121 @@ namespace nexo::ecs { } } }; + + /** + * @class TypeErasedComponentArray + * @brief A type-erased component array that can store components of any size. + * + * This class allows you to create component arrays at runtime without knowing + * the component type at compile time. You only need to specify the size of + * each component. + */ + class alignas(64) TypeErasedComponentArray final : public IComponentArray { + public: + /** + * @brief Constructs a new type-erased component array + * @param componentSize Size of each component in bytes + * @param initialCapacity Initial capacity for the array + */ + explicit TypeErasedComponentArray(size_t componentSize, size_t initialCapacity = 1024); + + /** + * @brief Inserts a new component for the given entity + * @param entity The entity to add the component to + * @param componentData Raw pointer to the component data to copy + */ + void insert(Entity entity, const void* componentData); + + /** + * @brief Inserts a raw new component for the given entity. + * + * @param entity The entity to add the component to + * @param componentData Pointer to the raw component data + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @pre The entity must be a valid entity ID + * @pre componentData must point to valid memory of component's size + */ + void insertRaw(Entity entity, const void* componentData) override; + + /** + * @brief Removes the component for the given entity + * @param entity The entity to remove the component from + */ + void remove(Entity entity); + + [[nodiscard]] bool hasComponent(Entity entity) const override; + + void entityDestroyed(Entity entity) override; + + void duplicateComponent(Entity sourceEntity, Entity destEntity) override; + + [[nodiscard]] size_t getComponentSize() const override; + + [[nodiscard]] size_t size() const override; + + [[nodiscard]] void* getRawComponent(Entity entity) override; + + [[nodiscard]] const void* getRawComponent(Entity entity) const override; + + [[nodiscard]] void* getRawData() override; + + [[nodiscard]] const void* getRawData() const override; + + [[nodiscard]] std::span entities() const override; + + /** + * @brief Gets the entity at the given index in the dense array + * @param index The index to look up + * @return The entity at that index + */ + [[nodiscard]] Entity getEntityAtIndex(size_t index) const; + + /** + * @brief Adds an entity to the group region + * @param entity The entity to add to the group + */ + void addToGroup(Entity entity); + + /** + * @brief Removes an entity from the group region + * @param entity The entity to remove from the group + */ + void removeFromGroup(Entity entity); + + /** + * @brief Gets the number of entities in the group region + * @return Number of grouped entities + */ + [[nodiscard]] constexpr size_t groupSize() const; + + /** + * @brief Get the estimated memory usage of this component array + * @return Size in bytes of memory used by this component array + */ + [[nodiscard]] size_t memoryUsage() const; + + private: + // Component data storage + std::vector m_componentData; + // Sparse mapping: maps entity ID to index in the dense arrays + std::vector m_sparse; + // Dense storage for entity IDs + std::vector m_dense; + // Size of each component in bytes + size_t m_componentSize; + // Initial capacity + size_t m_capacity; + // Current number of active components + size_t m_size = 0; + // Group size for component grouping + size_t m_groupSize = 0; + + void ensureSparseCapacity(Entity entity); + + void swapComponents(size_t index1, size_t index2); + + void shrinkIfNeeded(); + }; + } diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 2ca96ab0a..9edb48770 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -227,6 +227,7 @@ namespace nexo::ecs { { const ComponentType typeID = getComponentTypeID(); + assert(typeID < m_componentArrays.size() && "Component type ID exceeds component array size"); if (m_componentArrays[typeID] != nullptr) { LOG(NEXO_WARN, "Component already registered"); return; @@ -235,6 +236,16 @@ namespace nexo::ecs { m_componentArrays[typeID] = std::make_shared>(); } + ComponentType registerComponent(const size_t componentSize, const size_t initialCapacity = 1024) + { + const ComponentType typeID = generateComponentTypeID(); + assert(typeID < m_componentArrays.size() && "Component type ID exceeds component array size"); + + assert(m_componentArrays[typeID] == nullptr && "TypeErasedComponent already registered, should really not happen"); + m_componentArrays[typeID] = std::make_shared(componentSize, initialCapacity); + return typeID; + } + /** * @brief Gets the unique identifier for a component type * @@ -279,6 +290,35 @@ namespace nexo::ecs { } } + /** + * @brief Adds a component to an entity using type ID and raw data + * + * Adds the component using the component type ID and raw data pointer, + * 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 componentData Pointer to the raw component data + * @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 + * @pre componentData must point to valid memory of the component's size + */ + void addComponent(const Entity entity, const ComponentType componentType, const void *componentData, const Signature oldSignature, const Signature newSignature) + { + getComponentArray(componentType)->insertRaw(entity, componentData); + + 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 * @@ -380,6 +420,22 @@ namespace nexo::ecs { } } + /** + * @brief Gets the component array for a specific component type with ComponentType (const version) + * + * @param typeID The component type ID + * @return Const shared pointer to the component array + * @throws ComponentNotRegistered if the component type is not registered + */ + [[nodiscard]] std::shared_ptr getComponentArray(const ComponentType typeID) const + { + const auto& componentArray = m_componentArrays[typeID]; + if (componentArray == nullptr) + THROW_EXCEPTION(ComponentNotRegistered); + + return componentArray; + } + /** * @brief Gets the component array for a specific component type * @@ -391,12 +447,7 @@ namespace nexo::ecs { [[nodiscard]] std::shared_ptr> getComponentArray() { const ComponentType typeID = getComponentTypeID(); - - const auto& componentArray = m_componentArrays[typeID]; - if (componentArray == nullptr) - THROW_EXCEPTION(ComponentNotRegistered); - - return std::static_pointer_cast>(componentArray); + return std::static_pointer_cast>(getComponentArray(typeID)); } /** @@ -410,12 +461,7 @@ namespace nexo::ecs { [[nodiscard]] std::shared_ptr> getComponentArray() const { const ComponentType typeID = getComponentTypeID(); - - const auto& componentArray = m_componentArrays[typeID]; - if (componentArray == nullptr) - THROW_EXCEPTION(ComponentNotRegistered); - - return std::static_pointer_cast>(componentArray); + return std::static_pointer_cast>(getComponentArray(typeID)); } /** @@ -435,6 +481,22 @@ namespace nexo::ecs { return componentArray->get(entity); } + /** + * @brief Safely attempts to get a component from an entity + * + * @param entity The entity to get the component from + * @param typeID The component type ID + * @return Pointer to the component if it exists, or nullptr if not found + */ + [[nodiscard]] void *tryGetComponent(const Entity entity, const ComponentType typeID) const + { + const auto componentArray = getComponentArray(typeID); + if (!componentArray->hasComponent(entity)) + return nullptr; + + return componentArray->getRawComponent(entity); + } + /** * @brief Notifies all component arrays that an entity has been destroyed * diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index f8c5c3864..fde661ecd 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -176,6 +176,12 @@ namespace nexo::ecs { } } + ComponentType registerComponent(const size_t componentSize, const size_t initialCapacity = 1024) + { + auto typeID = m_componentManager->registerComponent(componentSize, initialCapacity); + return typeID; + } + /** * @brief Registers a new singleton component * @@ -207,6 +213,30 @@ namespace nexo::ecs { m_systemManager->entitySignatureChanged(entity, oldSignature, signature); } + /** + * @brief Adds a component to an entity, updates its signature, and notifies systems. + * + * This function allows adding a component by its type ID and raw data pointer. + * + * @param entity The ID of the entity to which the component will be added. + * @param componentType The type ID of the component to be added. + * @param componentData Pointer to the raw component data. + * + * @pre componentType must be a valid registered component type. + * @pre componentData must point to valid memory of the component's size. + */ + void addComponent(const Entity entity, const ComponentType componentType, const void *componentData) const + { + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + signature.set(componentType, true); + m_componentManager->addComponent(entity, componentType, componentData, oldSignature, signature); + + m_entityManager->setSignature(entity, signature); + + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); + } + /** * @brief Removes a component from an entity, updates its signature, and notifies systems. * @@ -296,29 +326,9 @@ namespace nexo::ecs { return m_componentManager->tryGetComponent(entity); } - void* tryGetComponentById(ComponentType componentType, Entity entity) + void *tryGetComponentById(const ComponentType componentType, const Entity entity)const { - auto itType = m_typeIDtoTypeIndex.find(componentType); - if (itType == m_typeIDtoTypeIndex.end()) { - return nullptr; - } - - const std::type_index& typeIndex = itType->second; - auto itGetter = m_getComponentPointers.find(typeIndex); - if (itGetter == m_getComponentPointers.end()) { - return nullptr; - } - - std::any componentAny = itGetter->second(entity); - if (!componentAny.has_value()) { - return nullptr; - } - - if (componentAny.type() != typeid(void*)) { - return nullptr; - } - - return std::any_cast(componentAny); + return m_componentManager->tryGetComponent(entity, componentType); } const std::unordered_map& getTypeIdToTypeIndex() const { @@ -501,10 +511,23 @@ namespace nexo::ecs { * @return false Otherwise. */ template - bool entityHasComponent(const Entity entity) const + [[nodiscard]] bool entityHasComponent(const Entity entity) const { - const Signature signature = m_entityManager->getSignature(entity); const ComponentType componentType = m_componentManager->getComponentType(); + return entityHasComponent(entity, componentType); + } + + /** + * @brief Checks whether an entity has a specific component by its type ID. + * + * @param entity The target entity. + * @param componentType The type ID of the component. + * @return true If the entity has the component. + * @return false Otherwise. + */ + [[nodiscard]] bool entityHasComponent(const Entity entity, const ComponentType componentType) const + { + const Signature signature = m_entityManager->getSignature(entity); return signature.test(componentType); } diff --git a/engine/src/ecs/Definitions.hpp b/engine/src/ecs/Definitions.hpp index 99e8a1f63..0d1fe0a7b 100644 --- a/engine/src/ecs/Definitions.hpp +++ b/engine/src/ecs/Definitions.hpp @@ -58,6 +58,12 @@ namespace nexo::ecs { */ inline ComponentType globalComponentCounter = 0; + inline ComponentType generateComponentTypeID() + { + assert(globalComponentCounter < MAX_COMPONENT_TYPE && "Maximum number of component types exceeded"); + return globalComponentCounter++; + } + /** * @brief Gets a unique ID for a component type * @@ -73,10 +79,8 @@ namespace nexo::ecs { { // This static variable is instantiated once per type T, // but it will be assigned a unique value from the shared global counter. - static const ComponentType id = []() { - assert(globalComponentCounter < MAX_COMPONENT_TYPE && "Maximum number of component types exceeded"); - return globalComponentCounter++; - }(); + // TODO: Warning! This is not thread-safe. (and it's a crappy implementation, but it works for now) + static const ComponentType id = generateComponentTypeID(); return id; } diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 611d7b427..e894be38b 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -33,6 +33,7 @@ set(SOURCES Components/Scene.cs Components/Transform.cs Components/Uuid.cs + Components/ComponentBase.cs Systems/SystemBase.cs Systems/WorldState.cs Scripts/CubeSystem.cs diff --git a/engine/src/scripting/managed/Components/ComponentBase.cs b/engine/src/scripting/managed/Components/ComponentBase.cs new file mode 100644 index 000000000..794ed6071 --- /dev/null +++ b/engine/src/scripting/managed/Components/ComponentBase.cs @@ -0,0 +1,63 @@ +//// ComponentBase.cs ///////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Guillaume HEIN +// Date: 22/06/2025 +// Description: Interface for the user's components in NEXO's ECS framework +// +/////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using Nexo; + +namespace Nexo.Components; + +public interface IComponentBase +{ + [UnmanagedCallersOnly] + public static Int32 InitializeComponents() + { + try + { + // Find all types that derive from IComponentBase + var componentTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(IComponentBase).IsAssignableFrom(type) && + !type.IsAbstract && + !type.IsInterface && + type != typeof(IComponentBase)) // Exclude the interface itself + .ToList(); + + Logger.Log(LogLevel.Info, $"Found {componentTypes.Count} component types to register."); + foreach (var componentType in componentTypes) + { + Logger.Log(LogLevel.Info, $"Component: {componentType.Name}"); + } + + // Register each component type + foreach (var componentType in componentTypes) + { + if (NativeInterop.RegisterComponent(componentType) < 0) + { + Logger.Log(LogLevel.Error, $"Failed to register component {componentType.Name}"); + return 1; + } + } + + return 0; + } + catch (Exception ex) + { + Logger.Log(LogLevel.Fatal, $"Error initializing components: {ex.Message}"); + return 1; + } + } + +} diff --git a/engine/src/scripting/managed/Logger.cs b/engine/src/scripting/managed/Logger.cs index c47d1c408..2a233f757 100644 --- a/engine/src/scripting/managed/Logger.cs +++ b/engine/src/scripting/managed/Logger.cs @@ -34,6 +34,6 @@ public static class Logger /// /// Specifies the log level (e.g., Fatal, Error, Warn, Info, Debug, Dev, User). /// The message to be logged. - public static void Log(LogLevel level, String message) => NativeInterop.NxLog((UInt32)level, message); + public static void Log(LogLevel level, String message) => NativeInterop.Log((UInt32)level, message); } diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 7246c524a..97310aacc 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -47,7 +47,7 @@ public static class NativeInterop /// Native API struct that matches the C++ struct /// [StructLayout(LayoutKind.Sequential)] - private struct NativeApiCallbacks + private unsafe struct NativeApiCallbacks { [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate void HelloFromNativeDelegate(); @@ -68,28 +68,32 @@ private struct NativeApiCallbacks public delegate ref Transform GetTransformDelegate(UInt32 entityId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate IntPtr NxGetComponentDelegate(UInt32 typeId, UInt32 entityId); + public delegate IntPtr NxGetComponentDelegate(UInt32 entityId, UInt32 typeId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); + public delegate void NxAddComponentDelegate(UInt32 entityId, UInt32 typeId, void *componentData); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate bool NxHasComponentDelegate(UInt32 entityId, UInt32 typeId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate void NxAddComponentDelegate(UInt32 typeId, UInt32 entityId); - + public delegate Int64 NxRegisterComponentDelegate(String name, UInt64 size); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate bool NxHasComponentDelegate(UInt32 typeId, UInt32 entityId); + public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); // Function pointers - public HelloFromNativeDelegate HelloFromNative; - public AddNumbersDelegate AddNumbers; - public GetNativeMessageDelegate GetNativeMessage; + public HelloFromNativeDelegate NxHelloFromNative; + public AddNumbersDelegate NxAddNumbers; + public GetNativeMessageDelegate NxGetNativeMessage; public NxLogDelegate NxLog; - public CreateCubeDelegate CreateCube; - public GetTransformDelegate GetTransform; + public CreateCubeDelegate NxCreateCube; + public GetTransformDelegate NxGetTransform; public NxGetComponentDelegate NxGetComponent; - public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; public NxAddComponentDelegate NxAddComponent; public NxHasComponentDelegate NxHasComponent; + public NxRegisterComponentDelegate NxRegisterComponent; + public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; } private static NativeApiCallbacks s_callbacks; @@ -158,7 +162,7 @@ public static void HelloFromNative() { try { - s_callbacks.HelloFromNative.Invoke(); + s_callbacks.NxHelloFromNative.Invoke(); } catch (Exception ex) { @@ -173,7 +177,7 @@ public static Int32 AddNumbers(Int32 a, Int32 b) { try { - return s_callbacks.AddNumbers.Invoke(a, b); + return s_callbacks.NxAddNumbers.Invoke(a, b); } catch (Exception ex) { @@ -189,7 +193,7 @@ public static String GetNativeMessage() { try { - IntPtr messagePtr = s_callbacks.GetNativeMessage.Invoke(); + IntPtr messagePtr = s_callbacks.NxGetNativeMessage.Invoke(); return messagePtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(messagePtr) ?? string.Empty : string.Empty; } catch (Exception ex) @@ -204,7 +208,7 @@ public static String GetNativeMessage() /// /// The level of the log message /// The message to log - public static void NxLog(UInt32 level, String message) + public static void Log(UInt32 level, String message) { try { @@ -221,7 +225,7 @@ public static UInt32 CreateCube(in Vector3 position, in Vector3 size, in Vector3 { try { - return s_callbacks.CreateCube.Invoke(position, size, rotation, color); + return s_callbacks.NxCreateCube.Invoke(position, size, rotation, color); } catch (Exception ex) { @@ -234,7 +238,7 @@ public static ref Transform GetTransform(UInt32 entityId) { try { - return ref s_callbacks.GetTransform.Invoke(entityId); + return ref s_callbacks.NxGetTransform.Invoke(entityId); } catch (Exception ex) { @@ -248,21 +252,21 @@ public static unsafe ref T GetComponent(UInt32 entityId) where T : unmanaged if (!_typeToNativeIdMap.TryGetValue(typeof(T), out var typeId)) throw new InvalidOperationException($"Unsupported component type: {typeof(T)}"); - IntPtr ptr = s_callbacks.NxGetComponent(typeId, entityId); + IntPtr ptr = s_callbacks.NxGetComponent(entityId, typeId); if (ptr == IntPtr.Zero) throw new InvalidOperationException($"Component {typeof(T)} not found on entity {entityId}"); return ref Unsafe.AsRef((void*)ptr); } - public static void AddComponent(UInt32 entityId) + public static unsafe void AddComponent(UInt32 entityId, ref T componentData) where T : unmanaged { if (!_typeToNativeIdMap.TryGetValue(typeof(T), out var typeId)) throw new InvalidOperationException($"Unsupported component type: {typeof(T)}"); try { - s_callbacks.NxAddComponent.Invoke(typeId, entityId); + s_callbacks.NxAddComponent.Invoke(entityId, typeId, Unsafe.AsPointer(ref componentData)); } catch (Exception ex) { @@ -277,7 +281,7 @@ public static bool HasComponent(UInt32 entityId) try { - return s_callbacks.NxHasComponent.Invoke(typeId, entityId); + return s_callbacks.NxHasComponent.Invoke(entityId, typeId); } catch (Exception ex) { @@ -287,6 +291,32 @@ public static bool HasComponent(UInt32 entityId) } + public static Int64 RegisterComponent(Type componentType) + { + var name = componentType.Name; + try + { + var size = (UInt64)Marshal.SizeOf(componentType); + + Logger.Log(LogLevel.Info, $"Registering component {name}"); + + var typeId = s_callbacks.NxRegisterComponent.Invoke(name, size); + if (typeId < 0) + { + Logger.Log(LogLevel.Error, $"Failed to register component {name}, returned: {typeId}"); + return typeId; + } + _typeToNativeIdMap[componentType] = (UInt32)typeId; + Logger.Log(LogLevel.Info, $"Registered component {name} with type ID {typeId}"); + return typeId; + } + catch (Exception ex) + { + Logger.Log(LogLevel.Error, $"Error calling NxRegisterComponent for {name}: {ex.Message}"); + return -1; + } + } + private static UInt32 _cubeId = 0; /// @@ -318,18 +348,7 @@ public static void DemonstrateNativeCalls() UInt32 cubeId = CreateCube(new Vector3(1, 4.2f, 3), new Vector3(1, 1, 1), new Vector3(7, 8, 9), new Vector4(1, 0, 0, 1)); _cubeId = cubeId; Console.WriteLine($"Created cube with ID: {cubeId}"); - - // AddComponent test - AddComponent(cubeId); - - try { - ref UuidComponent uuid = ref GetComponent(cubeId); - Console.WriteLine($"Successfully got UuidComponent for cube"); - } - catch (Exception e) { - Console.WriteLine($"Failed to get UuidComponent: {e.Message}"); - } - + // HasComponent test if (HasComponent(cubeId)) Console.WriteLine("Entity has a camera!"); diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs index 4614aedff..01c68016f 100644 --- a/engine/src/scripting/managed/Scripts/CubeSystem.cs +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -20,13 +20,13 @@ namespace Nexo.Scripts; public class CubeSystem : SystemBase { - private struct CubeAnimationState + private struct CubeAnimationState : IComponentBase { public Single Angle; public Single BreathingPhase; } - private readonly Dictionary _cubeStates = []; + private readonly List _cubes = []; protected override void OnInitialize(WorldState worldState) { @@ -36,17 +36,8 @@ protected override void OnInitialize(WorldState worldState) private void MoveCube(UInt32 cubeId, Single deltaTime) { ref Transform transform = ref NativeInterop.GetComponent(cubeId); - - // Get or create animation state for this cube - if (!_cubeStates.TryGetValue(cubeId, out var state)) - { - state = new CubeAnimationState - { - Angle = 0.0f, - BreathingPhase = 0.0f - }; - } - + ref CubeAnimationState state = ref NativeInterop.GetComponent(cubeId); + // Rotating cube effect float rotationSpeed = 1.0f; transform.quat = Quaternion.CreateFromAxisAngle(Vector3.UnitY, deltaTime * rotationSpeed) * transform.quat; @@ -80,19 +71,18 @@ private void MoveCube(UInt32 cubeId, Single deltaTime) } transform.size.Z = startScale + ((MathF.Sin(state.BreathingPhase) * 0.5f + 0.5f) * (endScale - startScale)); - - // Update the stored state - _cubeStates[cubeId] = state; } private void SpawnCube(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color) { var cubeId = NativeInterop.CreateCube(position, size, rotation, color); - _cubeStates[cubeId] = new CubeAnimationState + var state = new CubeAnimationState { - Angle = Random.Shared.NextSingle() * MathF.PI * 2.0f, + Angle = Random.Shared.NextSingle() * MathF.PI * 2.0f, BreathingPhase = Random.Shared.NextSingle() * MathF.PI * 2.0f }; + NativeInterop.AddComponent(cubeId, ref state); + _cubes.Add(cubeId); } protected override void OnUpdate(WorldState worldState) @@ -114,7 +104,7 @@ protected override void OnUpdate(WorldState worldState) SpawnCube(position, size, rotation, color); } - foreach (var cubeId in _cubeStates.Keys) + foreach (var cubeId in _cubes) { MoveCube(cubeId, deltaTime); } @@ -122,13 +112,8 @@ protected override void OnUpdate(WorldState worldState) protected override void OnShutdown(WorldState worldState) { - _cubeStates.Clear(); + _cubes.Clear(); Logger.Log(LogLevel.Info, $"Shutting down {Name} system"); } - // Helper method to clean up destroyed cubes - public void RemoveCubeState(uint cubeId) - { - _cubeStates.Remove(cubeId); - } } \ No newline at end of file diff --git a/engine/src/scripting/managed/Systems/SystemBase.cs b/engine/src/scripting/managed/Systems/SystemBase.cs index 07822ce60..0177fa4d0 100644 --- a/engine/src/scripting/managed/Systems/SystemBase.cs +++ b/engine/src/scripting/managed/Systems/SystemBase.cs @@ -124,16 +124,6 @@ public static unsafe Int32 ShutdownSystems(WorldState.NativeWorldState *nativeWo return 0; } - public static T? GetSystem() where T : SystemBase - { - return AllSystems.OfType().FirstOrDefault(); - } - - public static IEnumerable GetAllSystems() - { - return AllSystems.AsReadOnly(); - } - protected virtual void OnInitialize(WorldState worldState) { } diff --git a/engine/src/scripting/managed/Systems/WorldState.cs b/engine/src/scripting/managed/Systems/WorldState.cs index 8dd7cddd9..cc18ab32d 100644 --- a/engine/src/scripting/managed/Systems/WorldState.cs +++ b/engine/src/scripting/managed/Systems/WorldState.cs @@ -16,30 +16,6 @@ namespace Nexo.Systems; -// public class WorldState -// { -// [StructLayout(LayoutKind.Sequential)] -// public struct NativeWorldState -// { -// [StructLayout(LayoutKind.Sequential)] -// public struct WorldTime { -// public Double DeltaTime; // Time since last update -// public Double TotalTime; // Total time since the start of the world -// } -// -// [StructLayout(LayoutKind.Sequential)] -// public struct WorldStats -// { -// public UInt64 frameCount; // Number of frames rendered -// } -// -// public WorldTime Time; -// public WorldStats Stats; -// } -// -// public NativeWorldState State; -// } - public unsafe class WorldState { [StructLayout(LayoutKind.Sequential)] diff --git a/engine/src/scripting/native/ManagedApi.hpp b/engine/src/scripting/native/ManagedApi.hpp index ec4fe88fd..6ae8d14fb 100644 --- a/engine/src/scripting/native/ManagedApi.hpp +++ b/engine/src/scripting/native/ManagedApi.hpp @@ -105,6 +105,7 @@ namespace nexo::scripting { struct SystemBaseApi { ManagedApiFn InitializeSystems; + ManagedApiFn InitializeComponents; ManagedApiFn ShutdownSystems; diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index b348b889a..e97379acb 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -28,16 +28,16 @@ namespace nexo::scripting { // Implementation of the native functions extern "C" { - void HelloFromNative() { + void NxHelloFromNative() { std::cout << "Hello World from C++ native code!" << std::endl; } - Int32 AddNumbers(const Int32 a, const Int32 b) { + Int32 NxAddNumbers(const Int32 a, const Int32 b) { std::cout << "Native AddNumbers called with " << a << " and " << b << std::endl; return a + b; } - const char* GetNativeMessage() { + const char* NxGetNativeMessage() { std::cout << "GetNativeMessage called from C#" << std::endl; return nativeMessage; } @@ -46,18 +46,17 @@ namespace nexo::scripting { LOG(static_cast(level), "[Scripting] {}", message); } - ecs::Entity CreateCube(const Vector3 pos, const Vector3 size, const Vector3 rotation, const Vector4 color) + ecs::Entity NxCreateCube(const Vector3 pos, const Vector3 size, const Vector3 rotation, const Vector4 color) { auto &app = getApp(); - const ecs::Entity basicCube = EntityFactory3D::createCube(std::move(pos), std::move(size), std::move(rotation), std::move(color)); + const ecs::Entity basicCube = EntityFactory3D::createCube(pos, size, rotation, color); app.getSceneManager().getScene(0).addEntity(basicCube); return basicCube; } - components::TransformComponent *GetTransformComponent(ecs::Entity entity) + components::TransformComponent *NxGetTransformComponent(ecs::Entity entity) { - const auto &app = getApp(); - const auto opt = app.m_coordinator->tryGetComponent(entity); + const auto opt = Application::m_coordinator->tryGetComponent(entity); if (!opt.has_value()) { LOG(NEXO_WARN, "GetTransformComponent: Entity {} does not have a TransformComponent", entity); return nullptr; @@ -65,65 +64,41 @@ namespace nexo::scripting { return &opt.value().get(); } - void* GetComponent(UInt32 componentTypeId, ecs::Entity entity) + void* NxGetComponent(const ecs::Entity entity, const UInt32 componentTypeId) { - auto& coordinator = *getApp().m_coordinator; + auto& coordinator = *Application::m_coordinator; const auto opt = coordinator.tryGetComponentById(componentTypeId, entity); return opt; } - void AddComponent(UInt32 typeId, ecs::Entity entity) + void NxAddComponent(const ecs::Entity entity, const UInt32 typeId, const void *componentData) { - auto& coordinator = *getApp().m_coordinator; - - const auto& map = coordinator.getTypeIdToTypeIndex(); - auto it = map.find(typeId); - - if (it == map.end()) { - LOG(NEXO_ERROR, "AddComponent: Unknown typeId {}", typeId); + if (componentData == nullptr) { + LOG(NEXO_ERROR, "NxAddComponent: componentData is null for entity {}", entity); return; } + const auto& coordinator = *Application::m_coordinator; - const std::type_index& typeIndex = it->second; - const auto& addFn = coordinator.getAddComponentFunctions().find(typeIndex); - if (addFn == coordinator.getAddComponentFunctions().end()) { - LOG(NEXO_ERROR, "AddComponent: No add function registered for component {}", typeIndex.name()); - return; - } - - try { - std::any defaultConstructed = coordinator.restoreComponent(std::any{}, typeIndex); - addFn->second(entity, defaultConstructed); - } catch (const std::bad_any_cast& e) { - LOG(NEXO_ERROR, "AddComponent: bad_any_cast for component {}: {}", typeIndex.name(), e.what()); - } + coordinator.addComponent(entity, typeId, componentData); } - bool HasComponent(UInt32 typeId, ecs::Entity entity) + bool NxHasComponent(const ecs::Entity entity, const UInt32 typeId) { - auto& coordinator = *getApp().m_coordinator; - - const auto& typeMap = coordinator.getTypeIdToTypeIndex(); - auto it = typeMap.find(typeId); - if (it == typeMap.end()) - { - LOG(NEXO_WARN, "HasComponent: Unknown typeId {}", typeId); - return false; - } + const auto& coordinator = *Application::m_coordinator; - ecs::ComponentType bitIndex = typeId; - - ecs::Signature signature = coordinator.getSignature(entity); - - // LOG(NEXO_WARN, "HasComponent: entity = {}, typeId = {}, bitIndex = {}, signature = {}", - // entity, typeId, bitIndex, signature.to_string()); + return coordinator.entityHasComponent(entity, typeId); + } - return signature.test(bitIndex); + Int64 NxRegisterComponent(const char* name, const UInt64 size) + { + (void)name; // TODO: unused for now + auto& coordinator = *Application::m_coordinator; + return coordinator.registerComponent(size); } - ComponentTypeIds GetComponentTypeIds() + ComponentTypeIds NxGetComponentTypeIds() { - auto& coordinator = *getApp().m_coordinator; + auto& coordinator = *Application::m_coordinator; return ComponentTypeIds { .Transform = coordinator.getComponentType(), @@ -139,9 +114,6 @@ namespace nexo::scripting { .PerspectiveCameraTarget = coordinator.getComponentType(), }; } - - } - } // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index b7430962b..9a720df66 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -80,33 +80,35 @@ namespace nexo::scripting { UInt32 PerspectiveCameraTarget; }; - NEXO_RET(void) HelloFromNative(void); - NEXO_RET(Int32) AddNumbers(Int32 a, Int32 b); - NEXO_RET(const char*) GetNativeMessage(void); + NEXO_RET(void) NxHelloFromNative(void); + NEXO_RET(Int32) NxAddNumbers(Int32 a, Int32 b); + NEXO_RET(const char*) NxGetNativeMessage(void); NEXO_RET(void) NxLog(UInt32 level, const char *message); - NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); - NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); - NEXO_RET(void *) GetComponent(UInt32 typeId, ecs::Entity entity); - NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); - NEXO_RET(void) AddComponent(UInt32 typeId, ecs::Entity entity); - NEXO_RET(bool) HasComponent(UInt32 typeId, ecs::Entity entity); + NEXO_RET(ecs::Entity) NxCreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); + NEXO_RET(components::TransformComponent *) NxGetTransformComponent(ecs::Entity entity); + NEXO_RET(void *) NxGetComponent(ecs::Entity entity, UInt32 componentTypeId); + NEXO_RET(void) NxAddComponent(ecs::Entity entity, UInt32 typeId, const void *componentData); + NEXO_RET(bool) NxHasComponent(ecs::Entity entity, UInt32 typeId); + NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 size); + NEXO_RET(ComponentTypeIds) NxGetComponentTypeIds(); + } struct NativeApiCallbacks { - ApiCallback HelloFromNative{&scripting::HelloFromNative}; - ApiCallback AddNumbers{&scripting::AddNumbers}; - ApiCallback GetNativeMessage{&scripting::GetNativeMessage}; + ApiCallback NxHelloFromNative{&scripting::NxHelloFromNative}; + ApiCallback NxAddNumbers{&scripting::NxAddNumbers}; + ApiCallback NxGetNativeMessage{&scripting::NxGetNativeMessage}; ApiCallback NxLog{&scripting::NxLog}; - ApiCallback CreateCube{&scripting::CreateCube}; - ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; - ApiCallback GetComponent{&scripting::GetComponent}; - ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; - ApiCallback AddComponent{&scripting::AddComponent}; - ApiCallback HasComponent{&scripting::HasComponent}; - + ApiCallback NxCreateCube{&scripting::NxCreateCube}; + ApiCallback NxGetTransformComponent{&scripting::NxGetTransformComponent}; + ApiCallback NxGetComponent{&scripting::NxGetComponent}; + ApiCallback NxAddComponent{&scripting::NxAddComponent}; + ApiCallback NxHasComponent{&scripting::NxHasComponent}; + ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; + ApiCallback NxGetComponentTypeIds{&scripting::NxGetComponentTypeIds}; }; inline NativeApiCallbacks nativeApiCallbacks; diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 76f72fe17..abc75c470 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -270,6 +270,11 @@ namespace nexo::scripting { "InitializeSystems", UNMANAGEDCALLERSONLY ), + .InitializeComponents = getManagedFptr( + "Nexo.Components.IComponentBase, Nexo", + "InitializeComponents", + UNMANAGEDCALLERSONLY + ), .ShutdownSystems = getManagedFptr( "Nexo.Systems.SystemBase, Nexo", "ShutdownSystems", diff --git a/engine/src/systems/ScriptingSystem.cpp b/engine/src/systems/ScriptingSystem.cpp index 043cac8de..43b5e30d2 100644 --- a/engine/src/systems/ScriptingSystem.cpp +++ b/engine/src/systems/ScriptingSystem.cpp @@ -48,8 +48,13 @@ namespace nexo::system { LOG(NEXO_INFO, "Successfully ran runScriptExample"); updateWorldState(); + if (auto ret = scriptHost.getManagedApi().SystemBase.InitializeComponents(); ret != 0) { + LOG(NEXO_ERROR, "Failed to initialize scripting components, returned: {}", ret); + return ret; + } + LOG(NEXO_INFO, "Scripting components initialized successfully"); if (auto ret = scriptHost.getManagedApi().SystemBase.InitializeSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { - LOG(NEXO_ERROR, "Failed to initialize scripting systems: {}", ret); + LOG(NEXO_ERROR, "Failed to initialize scripting systems, returned: {}", ret); return ret; } LOG(NEXO_INFO, "Scripting systems initialized successfully");