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:
- Zero-initialize
sizeof(T) bytes
- 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
Dependency position
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>requiresTat compile time.AddComponentRawneeds to:sizeof(T)bytesComponentStorage<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(#648file — update it)2. Register the factory in each component
.cppfile (#650files — update each)3.
Scene::AddComponentRaw(#647file — update it)4. "Add Component" popup in
InspectorViewUIComponent(#651file — update it)Acceptance criteria
HasComponent<T>on the entity returnstrueafter adding via the button