Skip to content

feat(ecs): implement ComponentReflectionRegistry #649

Description

@JeanPhilippeKernel

Dependency position

#648 (FieldType / ComponentMeta types)
  └──► THIS ISSUE ──► #650 (Register built-in components)
                   └──► #651 (Inspector migration)

Start after #648 is merged. Can be worked immediately after #648 since that PR is just headers.


Why

The registry is the central store all component metadata flows through. The inspector calls ForEach on it. Registration code in component .cpp files calls Register on it. Without it, neither #650 nor #651 can proceed.

Work

ZEngine/ZEngine/ECS/Reflection/ComponentReflectionRegistry.h

#pragma once
#include <ZEngine/ECS/Reflection/ComponentMeta.h>
#include <ZEngine/ECS/ComponentTypeID.h>
#include <ZEngine/Core/Containers/Array.h>
#include <ZEngine/Core/Memory/Allocator.h>

namespace ZEngine::ECS {

    class ComponentReflectionRegistry {
    public:
        static ComponentReflectionRegistry& Get();

        void Initialize(Core::Memory::ArenaAllocator* arena);

        // Register metadata. meta.Fields must outlive this registry
        // (use static arrays at the call site).
        // Second registration with the same TypeID is a no-op (debug warning logged).
        void Register(const ComponentMeta& meta);

        [[nodiscard]] const ComponentMeta* Lookup(ComponentTypeID id) const;
        [[nodiscard]] const ComponentMeta* LookupByName(const char* type_name) const;

        template<typename Fn>
        void ForEach(Fn&& fn) const {
            for (uint32_t i = 0; i < m_metas.size(); ++i)
                fn(m_metas[i]);
        }

        [[nodiscard]] uint32_t Count() const;

    private:
        Core::Containers::Array<ComponentMeta> m_metas;
        Core::Memory::ArenaAllocator*          m_arena = nullptr;
    };

}

ZEngine/ZEngine/ECS/Reflection/ComponentReflectionRegistry.cpp

  • Get() returns a static instance (initialized once).
  • Initialize(arena) stores the arena and calls m_metas.init(arena, 64).
  • Register checks for duplicate TypeID (debug assert/warn), then m_metas.push(meta).
  • Lookup iterates m_metas linearly (N ≤ 64, cache-friendly, no hash needed).
  • LookupByName uses secure_strcmp on meta.TypeName.
  • Call ComponentReflectionRegistry::Get().Initialize(arena) from Engine::Initialize after the ECS arena is set up.

Acceptance criteria

  • Register then Lookup(TypeID) returns the same metadata
  • LookupByName("TransformComponent") returns the right entry
  • ForEach visits all registered types in insertion order
  • Double registration with the same TypeID logs a debug warning and skips
  • Thread safety: registration only happens at startup (single-threaded); no locks needed

Metadata

Metadata

Assignees

Labels

P1Critical path — blocks other workenhancementNew feature or request

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions