Skip to content

feat(ecs): AddComponentRaw + Add Component button in inspector (Phase 2) #652

Description

@JeanPhilippeKernel

Dependency position

#651 (Inspector migration) — recommended but not strictly required
(standalone — can be started any time after #649 and #650)

This is Phase 2. Do #647#648#649#650#651 first. Start this issue once the generic inspector loop from #651 is working. It adds the "Add Component" button.


Why

The generic inspector loop (#651) can display and edit existing components but cannot add new ones. The "Add Component" button requires type-erased component creation — AddComponentRaw — since the inspector does not know component types at compile time.

The challenge

Scene::AddComponent<T> requires T at compile time. AddComponentRaw needs to:

  1. Zero-initialize sizeof(T) bytes
  2. Call ComponentStorage<T>::Add(entity_id, zeroed_component)

Without a per-type factory, this is not possible generically. The solution is a factory function stored alongside the ComponentMeta.

Work

1. Add factory to ComponentMeta (#648 file — update it)

struct ComponentMeta {
    // ... existing fields ...

    // Factory: zero-initializes the component and adds it to the scene.
    // Stored as a function pointer — no captures, no heap.
    using AddFn = void (*)(ZEngine::ECS::Scene&, ZEngine::ECS::EntityID);
    AddFn Add = nullptr;
};

2. Register the factory in each component .cpp file (#650 files — update each)

// In TransformComponent.cpp registration:
.Add = [](ZEngine::ECS::Scene& scene, ZEngine::ECS::EntityID id) {
    scene.AddComponent<ZEngine::ECS::Components::TransformComponent>(id, {});
},

3. Scene::AddComponentRaw (#647 file — update it)

void AddComponentRaw(EntityID id, ComponentTypeID type_id)
{
    const auto* meta = ComponentReflectionRegistry::Get().Lookup(type_id);
    if (meta && meta->Add)
        meta->Add(*this, id);
}

4. "Add Component" popup in InspectorViewUIComponent (#651 file — update it)

if (ImGui::Button("+ Add Component"))
    ImGui::OpenPopup("add_component_popup");

if (ImGui::BeginPopup("add_component_popup"))
{
    ComponentReflectionRegistry::Get().ForEach([&](const ComponentMeta& meta) {
        if (MaskHas(mask, meta.TypeID)) return;  // already on entity
        if (!meta.Add) return;                   // no factory registered
        if (ImGui::Selectable(meta.TypeName))
            scene.AddComponentRaw(entity_id, meta.TypeID);
    });
    ImGui::EndPopup();
}

Acceptance criteria

  • "Add Component" button appears at the bottom of the inspector
  • Dropdown lists only components the entity does not already have
  • Selecting a component adds it with zero-initialized fields
  • Component appears in the inspector immediately after adding
  • HasComponent<T> on the entity returns true after adding via the button

Metadata

Metadata

Assignees

Labels

P2High priority — next sprintenhancementNew feature or request

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions