Skip to content

feat(tetragrama): migrate InspectorViewUIComponent to generic reflection loop #651

Description

@JeanPhilippeKernel

Dependency position

#647 (GetComponentRaw)  ──┐
#649 (Registry)           ├──► THIS ISSUE
#650 (Registrations)      ──┘

Wait for all three — #647, #649, and #650 — to be merged before starting.
This is the integration point where all Track A and Track B work comes together.


Why

InspectorViewUIComponent.cpp today has a per-component block for every known type:

// current code
auto* tc = actor->GetComponent<TransformComponent>();
if (tc) { ImGui::DragFloat3("Position", &tc->Position.x); ... }
auto* mc = actor->GetComponent<MeshComponent>();
if (mc) { ... }
// ...one block per component — plugin components can never appear

After this issue, all of that is replaced by a single generic loop that works for any registered component, including future game and plugin types.


Work

File: Tetragrama/Components/InspectorViewUIComponent.cpp

Replace the per-component rendering code with:

void InspectorViewUIComponent::DrawSelectedEntity(ZEngine::ECS::EntityID entity_id,
                                                   ZEngine::ECS::Scene& scene)
{
    if (!scene.IsAlive(entity_id))
        return;

    const ZEngine::ECS::ArchetypeMask mask = scene.GetMask(entity_id);

    ZEngine::ECS::ComponentReflectionRegistry::Get().ForEach(
        [&](const ZEngine::ECS::ComponentMeta& meta)
        {
            if (!ZEngine::ECS::MaskHas(mask, meta.TypeID))
                return;

            void* raw = scene.GetComponentRaw(entity_id, meta.TypeID);
            if (!raw)
                return;

            if (ImGui::CollapsingHeader(meta.TypeName, ImGuiTreeNodeFlags_DefaultOpen))
                DrawComponentFields(meta, raw);
        });
}

void InspectorViewUIComponent::DrawComponentFields(const ZEngine::ECS::ComponentMeta& meta,
                                                    void* data)
{
    ImGui::PushID(static_cast<int>(meta.TypeID));
    for (uint32_t i = 0; i < meta.FieldCount; ++i)
    {
        const auto& field = meta.Fields[i];
        if (field.Hidden)
            continue;
        void* ptr = static_cast<uint8_t*>(data) + field.Offset;
        ImGui::BeginDisabled(field.ReadOnly);
        DrawField(field, ptr);
        if (field.Tooltip && ImGui::IsItemHovered())
            ImGui::SetTooltip("%s", field.Tooltip);
        ImGui::EndDisabled();
    }
    ImGui::PopID();
}

void InspectorViewUIComponent::DrawField(const ZEngine::ECS::FieldDescriptor& field,
                                          void* ptr)
{
    using FT = ZEngine::ECS::FieldType;
    switch (field.Type)
    {
        case FT::Float:   ImGui::DragFloat(field.Name, static_cast<float*>(ptr), 0.05f, field.Min, field.Max); break;
        case FT::Vec3f:   ImGui::DragFloat3(field.Name, static_cast<float*>(ptr), 0.05f); break;
        case FT::Bool:    ImGui::Checkbox(field.Name, static_cast<bool*>(ptr)); break;
        case FT::Int32:   ImGui::DragInt(field.Name, static_cast<int*>(ptr)); break;
        case FT::UInt32:  { auto v = *static_cast<uint32_t*>(ptr); ImGui::Text("%s: %u", field.Name, v); break; }
        case FT::String:  ImGui::InputText(field.Name, static_cast<char*>(ptr), field.StringCap); break;
        case FT::AssetUUID: {
            auto* uuid = static_cast<uuids::uuid*>(ptr);
            auto  name = ZEngine::Managers::AssetManager::Instance() ?
                         /* lookup by uuid → asset name if found, else hex string */
                         uuids::to_string(*uuid) : uuids::to_string(*uuid);
            ImGui::Text("%s: %s", field.Name, name.c_str());
            break;
        }
        case FT::Enum: {
            auto* val = static_cast<int32_t*>(ptr);
            const char* current = "unknown";
            for (uint32_t i = 0; i < field.EnumCount; ++i)
                if (field.EnumValues[i].Value == *val) { current = field.EnumValues[i].Name; break; }
            if (ImGui::BeginCombo(field.Name, current)) {
                for (uint32_t i = 0; i < field.EnumCount; ++i) {
                    bool sel = (field.EnumValues[i].Value == *val);
                    if (ImGui::Selectable(field.EnumValues[i].Name, sel))
                        *val = static_cast<int32_t>(field.EnumValues[i].Value);
                }
                ImGui::EndCombo();
            }
            break;
        }
        default: ImGui::Text("%s: (type not rendered)", field.Name); break;
    }
}

Connecting to the Actor tier

The inspector currently receives an Actor*. Extract the EntityID and pass the engine's Scene:

if (actor)
{
    auto* ctx   = ZEngine::Engine::GetContext();
    auto* scene = ctx ? ctx->Scene : nullptr;
    if (scene)
        DrawSelectedEntity(actor->GetEntityID(), *scene);
}

Acceptance criteria

  • Inspector shows all fields of all 8 components for any selected actor
  • Fields are editable where ReadOnly = false
  • NameComponent.Value renders as an editable text input
  • LightComponent.LightType renders as a dropdown with Directional/Point/Spot
  • UUIDComponent.Value is read-only text
  • No per-component #include or per-component if block remains in the inspector
  • Plugin components (not yet built) would appear automatically once registered

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