Component workflow
This commit is contained in:
@@ -196,15 +196,23 @@ restored to the spawner name so the character respawns correctly.
|
||||
## Adding a New Component
|
||||
|
||||
1. Define the component struct in `components/MyComponent.hpp`.
|
||||
2. Register it for UI editing in `components/MyComponentModule.cpp` using
|
||||
`REGISTER_COMPONENT_GROUP(...)` and `registry.registerComponent<T>(...)`.
|
||||
2. Create `components/MyComponentModule.cpp` with a
|
||||
`REGISTER_COMPONENT_GROUP(...)` block that calls
|
||||
`registry.registerComponent<T>(name, group, editor, adder, remover,
|
||||
onModified, order)`. The registry builds a type-erased renderer from this,
|
||||
so the property panel and Add/Remove menus pick it up automatically — no
|
||||
changes to `EditorUISystem.cpp` are required.
|
||||
3. Implement an editor in `ui/MyComponentEditor.hpp` / `.cpp` deriving from
|
||||
`ComponentEditor`.
|
||||
4. If the component should be saved/loaded, add serialization support in
|
||||
`systems/SceneSerializer.cpp`.
|
||||
`systems/SceneSerializer.cpp` (see `ComponentArchitectureImprovement.md`,
|
||||
step C, for the planned registry-driven serializer).
|
||||
5. Add a Lua binding in `lua/LuaComponentApi.cpp` if needed.
|
||||
6. Add a component-module test case in `tests/component_lua_test.cpp`.
|
||||
|
||||
See `ComponentArchitectureImprovement.md` for the long-term plan to remove the
|
||||
remaining per-component hardcoding in serialization, lifecycle hooks and Lua.
|
||||
|
||||
## Adding a New System
|
||||
|
||||
1. Create `systems/MySystem.hpp` and `systems/MySystem.cpp`.
|
||||
|
||||
@@ -168,6 +168,12 @@ set(EDITSCENE_SOURCES
|
||||
components/TerrainModule.cpp
|
||||
components/SunModule.cpp
|
||||
components/SkyboxModule.cpp
|
||||
components/TransformModule.cpp
|
||||
components/RenderableModule.cpp
|
||||
components/RigidBodyModule.cpp
|
||||
components/PhysicsColliderModule.cpp
|
||||
components/PrefabInstanceModule.cpp
|
||||
components/CharacterIdentityModule.cpp
|
||||
camera/EditorCamera.cpp
|
||||
gizmo/Gizmo.cpp
|
||||
gizmo/Cursor3D.cpp
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
# Component Architecture Improvement — Implementation Specification
|
||||
|
||||
This document is the working specification for removing the remaining
|
||||
per-component hardcoding in `src/features/editScene`. It records what has
|
||||
already been implemented and, step by step, what remains.
|
||||
|
||||
## Goal
|
||||
|
||||
Adding a new component to the editor should require **one** place to describe
|
||||
the component (name, group, editor, add/remove, render, serialize, Lua) instead
|
||||
of editing six or seven files with scattered special-case code.
|
||||
|
||||
## Status
|
||||
|
||||
| Step | Description | Status |
|
||||
|------|-------------|--------|
|
||||
| A | Type-erased editor renderer (`ComponentRenderer`) | Done |
|
||||
| B | Unified registration (extract hardcoded components into `*Module.cpp`) | Done |
|
||||
| C | Serialization hooks (drive `SceneSerializer` from the registry) | To do |
|
||||
| D | Lifecycle hooks (hierarchy badges, delete/duplicate cleanup) | To do |
|
||||
| E | Unified Lua component table (single source of truth) | To do |
|
||||
| F | Flecs meta reflection for auto-generated editors/serde (optional) | To do |
|
||||
|
||||
---
|
||||
|
||||
## What is already done (A + B)
|
||||
|
||||
### A — Type-erased renderer
|
||||
|
||||
`ui/ComponentRegistry.hpp` now stores a `ComponentRenderer` closure per
|
||||
component in `ComponentInfo`:
|
||||
|
||||
```cpp
|
||||
struct ComponentInfo {
|
||||
const char *name;
|
||||
const char *group;
|
||||
int order = 0; // lower renders first; Transform = -1
|
||||
std::unique_ptr<IComponentEditor> editor;
|
||||
ComponentRenderer renderer; // type-erased render
|
||||
ComponentAdder adder;
|
||||
ComponentRemover remover;
|
||||
ComponentChecker checker;
|
||||
ComponentModified onModified; // optional post-render side effect
|
||||
};
|
||||
```
|
||||
|
||||
`registerComponent<T>()` builds `renderer` from the (templated) type, handling
|
||||
empty/tag components via `std::is_empty_v<T>` so `get_mut<T>()` is never called
|
||||
on a zero-sized type.
|
||||
|
||||
`EditorUISystem::renderComponentList()` is now a generic loop: it collects the
|
||||
registered components present on the entity, sorts them by `(order, name)`, and
|
||||
invokes each `renderer`; if the renderer returns `true` it also runs the
|
||||
optional `onModified` hook.
|
||||
|
||||
### B — Unified registration
|
||||
|
||||
The six previously hardcoded components were extracted into module files that
|
||||
use the same `REGISTER_COMPONENT_GROUP` mechanism as every other component:
|
||||
|
||||
- `components/TransformModule.cpp`
|
||||
- `components/RenderableModule.cpp`
|
||||
- `components/RigidBodyModule.cpp`
|
||||
- `components/PhysicsColliderModule.cpp`
|
||||
- `components/PrefabInstanceModule.cpp`
|
||||
- `components/CharacterIdentityModule.cpp`
|
||||
|
||||
`EditorUISystem::registerComponentEditors()` was removed; the constructor now
|
||||
calls `registerModularComponents()` directly, which invokes
|
||||
`ComponentRegistration::registerAll()`.
|
||||
|
||||
Cross-component post-render side effects were moved into `onModified` hooks
|
||||
co-located with each component's registration:
|
||||
|
||||
- Transform → marks `StaticGeometryMemberComponent` dirty.
|
||||
- Sun / Skybox / WaterPlane / Room / ClearArea → `markDirty()`.
|
||||
- NavMesh → syncs `debugDraw` with `NavMeshSystem`.
|
||||
|
||||
---
|
||||
|
||||
## Suggestion C — Serialization hooks
|
||||
|
||||
**Problem.** `systems/SceneSerializer.cpp` (~4366 lines) hardcodes a
|
||||
`serializeXxx`/`deserializeXxx` method pair per component plus two giant
|
||||
`if (entity.has<T>())` / `if (json.contains("key"))` dispatch chains in
|
||||
`serializeEntity()` and `deserializeEntityComponents()`. Adding a component
|
||||
today still requires edits to the serializer.
|
||||
|
||||
**Design.** Extend `ComponentInfo` (or a parallel serde table) with two closures
|
||||
and drive the serializer from the registry:
|
||||
|
||||
```cpp
|
||||
using ComponentSerialize = std::function<nlohmann::json(flecs::entity)>;
|
||||
using ComponentDeserialize = std::function<void(flecs::entity, const nlohmann::json&)>;
|
||||
|
||||
struct ComponentInfo {
|
||||
...
|
||||
ComponentSerialize serialize;
|
||||
ComponentDeserialize deserialize;
|
||||
ComponentModified postLoad; // second-pass reference resolution
|
||||
};
|
||||
```
|
||||
|
||||
The serializer then:
|
||||
|
||||
1. In `serializeEntity`, iterate registered components, call `serialize(e)` for
|
||||
those that have it, and write the result under a per-component JSON key.
|
||||
2. In `deserializeEntityComponents`, for each key in the entity JSON that
|
||||
matches a registered component, call `deserialize(e, value)`.
|
||||
3. Keep the two-pass reference resolution (material references,
|
||||
procedural-texture references, character spawner) as `postLoad` hooks that
|
||||
run after all entities are loaded.
|
||||
|
||||
**Steps.**
|
||||
|
||||
1. Add the serde type aliases + fields to `ComponentRegistry.hpp` (keep the
|
||||
`nlohmann::json` dependency out of the header if possible; prefer a
|
||||
`void*`/`std::function` boundary or move the serde table into
|
||||
`SceneSerializer` so the registry stays UI-only).
|
||||
2. Migrate simple components first (Transform, Renderable, RigidBody,
|
||||
Collider, Light, Camera, Lod, etc.) — move their `serialize/deserialize`
|
||||
bodies from `SceneSerializer.cpp` into their `*Module.cpp` registration.
|
||||
3. Leave complex components (Town/District/Lot, ProceduralMaterial,
|
||||
CharacterSpawner, NavMesh) as explicit overrides initially; the registry is
|
||||
a fallback. Then migrate them one at a time, adding `postLoad` where the
|
||||
current two-pass logic lives.
|
||||
4. Remove the now-dead `serializeXxx`/`deserializeXxx` declarations from
|
||||
`SceneSerializer.hpp` and their dispatch blocks from `.cpp`.
|
||||
|
||||
**Acceptance criteria.**
|
||||
|
||||
- `serializeEntity` / `deserializeEntityComponents` contain no
|
||||
`if (entity.has<T>())` component dispatch (or only a tiny, documented set of
|
||||
explicit overrides for two-pass components).
|
||||
- Round-trip save/load tests still pass (see `tests/` and
|
||||
`docs/SaveLoadSystem.md`).
|
||||
- Adding a new serializable component touches only its `*Module.cpp`.
|
||||
|
||||
---
|
||||
|
||||
## Suggestion D — Lifecycle hooks
|
||||
|
||||
**Problem.** Three UI hot-spots still enumerate components by hand:
|
||||
|
||||
1. `EditorUISystem::renderEntityNode()` — ~26 hardcoded hierarchy badge lines
|
||||
(`indicators += " [T]"`).
|
||||
2. `EditorUISystem::deleteEntity()` — hardcoded cleanup for RigidBody
|
||||
(physics body), Transform (scene node), Renderable (Ogre entity).
|
||||
3. `EditorUISystem::duplicateEntity()` — hardcoded copy logic for name,
|
||||
transform, renderable.
|
||||
|
||||
**Design.** Add nullable hooks to `ComponentInfo` (following the `onModified`
|
||||
pattern already established):
|
||||
|
||||
```cpp
|
||||
struct ComponentInfo {
|
||||
...
|
||||
std::function<std::string(flecs::entity)> badge; // hierarchy indicator
|
||||
std::function<void(flecs::entity)> onDelete; // cleanup on delete
|
||||
std::function<void(flecs::entity, flecs::entity)> onDuplicate; // copy
|
||||
};
|
||||
```
|
||||
|
||||
**Steps.**
|
||||
|
||||
1. Add the fields + optional parameters to `registerComponent<T>()` (or a
|
||||
post-registration setter to avoid growing the signature further).
|
||||
2. Move the badge strings into each `*Module.cpp` (Transform `[T]`, Renderable
|
||||
`[R]`, Light `[L]`, …). `renderEntityNode` then appends
|
||||
`info.badge(entity)` for each component the entity has.
|
||||
3. Move `deleteEntity` cleanup into `onDelete` hooks (RigidBody removes its
|
||||
physics body, Transform destroys its scene node, Renderable destroys its
|
||||
Ogre entity). `deleteEntity` becomes a generic "invoke onDelete for every
|
||||
registered component present, then destruct".
|
||||
4. Move `duplicateEntity` copy logic into `onDuplicate` hooks, or — for most
|
||||
components — a generic `entity.set<T>(other.get<T>())` copy that falls back
|
||||
to a hook for components with non-trivial ownership.
|
||||
|
||||
**Acceptance criteria.**
|
||||
|
||||
- `renderEntityNode`, `deleteEntity`, `duplicateEntity` contain no
|
||||
`if (entity.has<T>())` component dispatch.
|
||||
- Deleting/duplicating an entity with a renderable + rigid body + transform
|
||||
behaves identically to before.
|
||||
|
||||
---
|
||||
|
||||
## Suggestion E — Unified Lua component table
|
||||
|
||||
**Problem.** `lua/LuaComponentApi.cpp` maintains its own `s_components` map and
|
||||
its own `REGISTER_COMPONENT` macro with hand-written push/read field lambdas —
|
||||
a third independent list of components.
|
||||
|
||||
**Design.** Make one shared descriptor the single source of truth. The editor
|
||||
registry, the serializer (C) and the Lua binder (E) all read name/group/fields
|
||||
from it.
|
||||
|
||||
**Steps.**
|
||||
|
||||
1. Introduce a `ComponentTraits<T>` (or `ComponentDescriptor`) struct in a
|
||||
neutral header (not UI-, not Lua-specific) holding at least `name`, `group`,
|
||||
and optional `luaPush` / `luaRead` closures (or field-table reflection).
|
||||
2. Have the `REGISTER_COMPONENT_GROUP` macro (or a new unified macro) populate
|
||||
both the `ComponentRegistry` entry and the Lua `s_components` entry from the
|
||||
same traits.
|
||||
3. Migrate the existing hand-written `REGISTER_COMPONENT(...)` entries in
|
||||
`LuaComponentApi.cpp` to the shared traits one component at a time, keeping
|
||||
`lua-examples/` and `tests/*_lua_test.cpp` green.
|
||||
|
||||
**Acceptance criteria.**
|
||||
|
||||
- There is exactly one place that lists a component's name/group/serde/Lua
|
||||
behaviour.
|
||||
- `component_lua_test` and the other `*_lua_test` executables still pass.
|
||||
|
||||
---
|
||||
|
||||
## Suggestion F — Flecs meta reflection (optional, long term)
|
||||
|
||||
**Problem.** Even after C/D/E, "dumb data" components still need a hand-written
|
||||
editor + serializer + Lua binding.
|
||||
|
||||
**Design.** Register plain-data components with Flecs meta (`flecs::meta`) and
|
||||
generate the ImGui editor, JSON serde and Lua accessor from the reflected
|
||||
fields. Keep custom editors only for behaviour-heavy components (Camera
|
||||
preview, NavMesh, CharacterSlots, etc.).
|
||||
|
||||
**Steps.**
|
||||
|
||||
1. Add `flecs::meta` to the project and register a pilot component with it.
|
||||
2. Write a generic `MetaComponentEditor<T>` that walks `flecs::meta` cursor
|
||||
fields and renders an ImGui widget per field.
|
||||
3. Write a generic meta-based serializer (or use the built-in `flecs::json`
|
||||
exporter) and a meta-based Lua binding.
|
||||
4. Migrate the simplest components; validate against existing round-trip tests.
|
||||
|
||||
**Acceptance criteria.**
|
||||
|
||||
- A new plain-data component can be added without writing an editor, a
|
||||
serializer, or a Lua binding.
|
||||
- OGRE math types (`Ogre::Vector3`, `Quaternion`, `ColourValue`) have registered
|
||||
meta type handlers so they render and serialize correctly.
|
||||
|
||||
---
|
||||
|
||||
## Conventions and caveats
|
||||
|
||||
- Keep `order` on `ComponentInfo` for deterministic render order; only Transform
|
||||
needs a non-zero value today. Do not rely on `unordered_map` iteration order.
|
||||
- Empty/tag components must render via a stack-local dummy (handled by
|
||||
`std::is_empty_v` in the renderer). Do not call `get_mut<T>()` on them.
|
||||
- Two-pass scene load (material/character/procedural-texture references) must
|
||||
stay explicit; fold it into a `postLoad` hook, never into a single pass.
|
||||
- The source code is the source of truth. Update `AGENTS.md` ("Adding a New
|
||||
Component"), `docs/`, and `lua-examples/` whenever the registration API
|
||||
changes.
|
||||
@@ -120,6 +120,10 @@ REGISTER_COMPONENT_GROUP("Room", "Room Layout", RoomComponent, RoomEditor)
|
||||
if (e.has<RoomComponent>()) {
|
||||
e.remove<RoomComponent>();
|
||||
}
|
||||
},
|
||||
// On-modified: mark dirty so RoomLayoutSystem rebuilds the room
|
||||
[](flecs::entity e) {
|
||||
e.get_mut<RoomComponent>().markDirty();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -158,6 +162,10 @@ REGISTER_COMPONENT_GROUP("Clear Area", "Room Layout", ClearAreaComponent, ClearA
|
||||
if (e.has<ClearAreaComponent>()) {
|
||||
e.remove<ClearAreaComponent>();
|
||||
}
|
||||
},
|
||||
// On-modified: mark dirty so RoomLayoutSystem rebuilds the clear area
|
||||
[](flecs::entity e) {
|
||||
e.get_mut<ClearAreaComponent>().markDirty();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "CharacterIdentity.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/CharacterIdentityEditor.hpp"
|
||||
|
||||
// Register CharacterIdentity component
|
||||
REGISTER_COMPONENT_GROUP("Character Identity", "Character",
|
||||
CharacterIdentityComponent, CharacterIdentityEditor)
|
||||
{
|
||||
registry.registerComponent<CharacterIdentityComponent>(
|
||||
"Character Identity", "Character",
|
||||
std::make_unique<CharacterIdentityEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<CharacterIdentityComponent>())
|
||||
e.set<CharacterIdentityComponent>(
|
||||
CharacterIdentityComponent{});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<CharacterIdentityComponent>())
|
||||
e.remove<CharacterIdentityComponent>();
|
||||
});
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/NavMeshEditor.hpp"
|
||||
#include "../ui/NavMeshGeometrySourceEditor.hpp"
|
||||
#include "../systems/NavMeshSystem.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("NavMesh", "Navigation", NavMeshComponent,
|
||||
NavMeshEditor)
|
||||
@@ -18,6 +19,13 @@ REGISTER_COMPONENT_GROUP("NavMesh", "Navigation", NavMeshComponent,
|
||||
[](flecs::entity e) {
|
||||
if (e.has<NavMeshComponent>())
|
||||
e.remove<NavMeshComponent>();
|
||||
},
|
||||
// On-modified: sync the debug-draw toggle with the NavMesh system
|
||||
[](flecs::entity e) {
|
||||
auto &nav = e.get_mut<NavMeshComponent>();
|
||||
if (nav.debugDraw && NavMeshSystem::getInstance())
|
||||
NavMeshSystem::getInstance()->setDebugDraw(e,
|
||||
nav.debugDraw);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "PhysicsCollider.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/PhysicsColliderEditor.hpp"
|
||||
|
||||
// Register PhysicsCollider component
|
||||
REGISTER_COMPONENT_GROUP("Physics Collider", "Physics",
|
||||
PhysicsColliderComponent, PhysicsColliderEditor)
|
||||
{
|
||||
registry.registerComponent<PhysicsColliderComponent>(
|
||||
"Physics Collider", "Physics",
|
||||
std::make_unique<PhysicsColliderEditor>(sceneMgr),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<PhysicsColliderComponent>())
|
||||
e.set<PhysicsColliderComponent>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<PhysicsColliderComponent>())
|
||||
e.remove<PhysicsColliderComponent>();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "PrefabInstance.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/PrefabInstanceEditor.hpp"
|
||||
|
||||
// Register PrefabInstance component
|
||||
REGISTER_COMPONENT_GROUP("Prefab Instance", "Scene", PrefabInstanceComponent,
|
||||
PrefabInstanceEditor)
|
||||
{
|
||||
registry.registerComponent<PrefabInstanceComponent>(
|
||||
"Prefab Instance", "Scene", std::make_unique<PrefabInstanceEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<PrefabInstanceComponent>())
|
||||
e.set<PrefabInstanceComponent>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<PrefabInstanceComponent>())
|
||||
e.remove<PrefabInstanceComponent>();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "Renderable.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/RenderableEditor.hpp"
|
||||
|
||||
// Register Renderable component
|
||||
REGISTER_COMPONENT_GROUP("Renderable", "Rendering", RenderableComponent,
|
||||
RenderableEditor)
|
||||
{
|
||||
registry.registerComponent<RenderableComponent>(
|
||||
"Renderable", "Rendering",
|
||||
std::make_unique<RenderableEditor>(sceneMgr),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<RenderableComponent>())
|
||||
e.set<RenderableComponent>({});
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<RenderableComponent>()) {
|
||||
auto &renderable = e.get_mut<RenderableComponent>();
|
||||
if (renderable.entity) {
|
||||
sceneMgr->destroyEntity(renderable.entity);
|
||||
renderable.entity = nullptr;
|
||||
}
|
||||
e.remove<RenderableComponent>();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "RigidBody.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/RigidBodyEditor.hpp"
|
||||
|
||||
// Register RigidBody component
|
||||
REGISTER_COMPONENT_GROUP("Rigid Body", "Physics", RigidBodyComponent,
|
||||
RigidBodyEditor)
|
||||
{
|
||||
registry.registerComponent<RigidBodyComponent>(
|
||||
"Rigid Body", "Physics", std::make_unique<RigidBodyEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<RigidBodyComponent>())
|
||||
e.set<RigidBodyComponent>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<RigidBodyComponent>())
|
||||
e.remove<RigidBodyComponent>();
|
||||
});
|
||||
}
|
||||
@@ -20,5 +20,9 @@ REGISTER_COMPONENT_GROUP("Skybox", "Environment", SkyboxComponent, SkyboxEditor)
|
||||
if (e.has<SkyboxComponent>()) {
|
||||
e.remove<SkyboxComponent>();
|
||||
}
|
||||
},
|
||||
// On-modified: mark dirty so EditorSkyboxSystem rebuilds the skybox
|
||||
[](flecs::entity e) {
|
||||
e.get_mut<SkyboxComponent>().markDirty();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,5 +20,9 @@ REGISTER_COMPONENT_GROUP("Sun", "Environment", SunComponent, SunEditor)
|
||||
if (e.has<SunComponent>()) {
|
||||
e.remove<SunComponent>();
|
||||
}
|
||||
},
|
||||
// On-modified: mark dirty so EditorSunSystem rebuilds light/material
|
||||
[](flecs::entity e) {
|
||||
e.get_mut<SunComponent>().markDirty();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "Transform.hpp"
|
||||
#include "StaticGeometryMember.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/TransformEditor.hpp"
|
||||
|
||||
// Register Transform component
|
||||
REGISTER_COMPONENT_GROUP("Transform", "Transform", TransformComponent,
|
||||
TransformEditor)
|
||||
{
|
||||
registry.registerComponent<TransformComponent>(
|
||||
"Transform", "Transform", std::make_unique<TransformEditor>(),
|
||||
// Adder
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (!e.has<TransformComponent>()) {
|
||||
TransformComponent transform;
|
||||
transform.node = sceneMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
transform.position = Ogre::Vector3::ZERO;
|
||||
transform.rotation = Ogre::Quaternion::IDENTITY;
|
||||
transform.scale = Ogre::Vector3::UNIT_SCALE;
|
||||
e.set<TransformComponent>(transform);
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[sceneMgr](flecs::entity e) {
|
||||
if (e.has<TransformComponent>()) {
|
||||
auto &transform = e.get_mut<TransformComponent>();
|
||||
if (transform.node) {
|
||||
sceneMgr->destroySceneNode(transform.node);
|
||||
transform.node = nullptr;
|
||||
}
|
||||
e.remove<TransformComponent>();
|
||||
}
|
||||
},
|
||||
// On-modified: keep a StaticGeometryMember in sync with its transform
|
||||
[](flecs::entity e) {
|
||||
if (e.has<StaticGeometryMemberComponent>())
|
||||
e.get_mut<StaticGeometryMemberComponent>().markDirty();
|
||||
},
|
||||
// Render order: Transform always renders first
|
||||
-1);
|
||||
}
|
||||
@@ -21,5 +21,9 @@ REGISTER_COMPONENT_GROUP("Water Plane", "Water", WaterPlane, WaterPlaneEditor)
|
||||
if (e.has<WaterPlane>()) {
|
||||
e.remove<WaterPlane>();
|
||||
}
|
||||
},
|
||||
// On-modified: mark dirty so EditorWaterPlaneSystem rebuilds the plane
|
||||
[](flecs::entity e) {
|
||||
e.get_mut<WaterPlane>().markDirty();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,13 +52,7 @@
|
||||
#include "../components/Inventory.hpp"
|
||||
#include "CharacterClassSystem.hpp"
|
||||
|
||||
#include "../ui/TransformEditor.hpp"
|
||||
#include "../ui/RenderableEditor.hpp"
|
||||
#include "../ui/PhysicsColliderEditor.hpp"
|
||||
#include "../ui/RigidBodyEditor.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/CharacterIdentityEditor.hpp"
|
||||
#include "../ui/PrefabInstanceEditor.hpp"
|
||||
#include "PhysicsSystem.hpp"
|
||||
#include "BuoyancySystem.hpp"
|
||||
#include "NavMeshSystem.hpp"
|
||||
@@ -79,7 +73,7 @@ EditorUISystem::EditorUISystem(flecs::world &world,
|
||||
, m_selectedEntity(flecs::entity::null())
|
||||
, m_nameQuery(world.query<EntityNameComponent>())
|
||||
{
|
||||
registerComponentEditors();
|
||||
registerModularComponents();
|
||||
m_gizmo = std::make_unique<Gizmo>(m_sceneMgr);
|
||||
m_roadGizmo = std::make_unique<RoadGizmo>(m_sceneMgr);
|
||||
m_cursor3D = std::make_unique<Cursor3D>(m_sceneMgr);
|
||||
@@ -249,135 +243,6 @@ bool EditorUISystem::onMouseReleased()
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorUISystem::registerComponentEditors()
|
||||
{
|
||||
// Register Transform component
|
||||
auto transformEditor = std::make_unique<TransformEditor>();
|
||||
m_componentRegistry.registerComponent<TransformComponent>(
|
||||
"Transform", "Transform", std::move(transformEditor),
|
||||
// Adder
|
||||
[this](flecs::entity e) {
|
||||
if (!e.has<TransformComponent>()) {
|
||||
TransformComponent transform;
|
||||
transform.node =
|
||||
m_sceneMgr->getRootSceneNode()
|
||||
->createChildSceneNode();
|
||||
transform.position = Ogre::Vector3::ZERO;
|
||||
transform.rotation = Ogre::Quaternion::IDENTITY;
|
||||
transform.scale = Ogre::Vector3::UNIT_SCALE;
|
||||
e.set<TransformComponent>(transform);
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[this](flecs::entity e) {
|
||||
if (e.has<TransformComponent>()) {
|
||||
auto &transform =
|
||||
e.get_mut<TransformComponent>();
|
||||
if (transform.node) {
|
||||
m_sceneMgr->destroySceneNode(
|
||||
transform.node);
|
||||
transform.node = nullptr;
|
||||
}
|
||||
e.remove<TransformComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
// Register Renderable component
|
||||
auto renderableEditor = std::make_unique<RenderableEditor>(m_sceneMgr);
|
||||
m_componentRegistry.registerComponent<RenderableComponent>(
|
||||
"Renderable", "Rendering", std::move(renderableEditor),
|
||||
// Adder
|
||||
[this](flecs::entity e) {
|
||||
if (!e.has<RenderableComponent>()) {
|
||||
e.set<RenderableComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[this](flecs::entity e) {
|
||||
if (e.has<RenderableComponent>()) {
|
||||
auto &renderable =
|
||||
e.get_mut<RenderableComponent>();
|
||||
if (renderable.entity) {
|
||||
m_sceneMgr->destroyEntity(
|
||||
renderable.entity);
|
||||
renderable.entity = nullptr;
|
||||
}
|
||||
e.remove<RenderableComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
// Register RigidBody component
|
||||
auto rigidBodyEditor = std::make_unique<RigidBodyEditor>();
|
||||
m_componentRegistry.registerComponent<RigidBodyComponent>(
|
||||
"Rigid Body", "Physics", std::move(rigidBodyEditor),
|
||||
// Adder
|
||||
[this](flecs::entity e) {
|
||||
if (!e.has<RigidBodyComponent>()) {
|
||||
e.set<RigidBodyComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[this](flecs::entity e) {
|
||||
if (e.has<RigidBodyComponent>()) {
|
||||
e.remove<RigidBodyComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
// Register PhysicsCollider component
|
||||
auto colliderEditor =
|
||||
std::make_unique<PhysicsColliderEditor>(m_sceneMgr);
|
||||
m_componentRegistry.registerComponent<PhysicsColliderComponent>(
|
||||
"Physics Collider", "Physics", std::move(colliderEditor),
|
||||
// Adder
|
||||
[this](flecs::entity e) {
|
||||
if (!e.has<PhysicsColliderComponent>()) {
|
||||
e.set<PhysicsColliderComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[this](flecs::entity e) {
|
||||
if (e.has<PhysicsColliderComponent>()) {
|
||||
e.remove<PhysicsColliderComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
// Register PrefabInstance component
|
||||
auto prefabEditor = std::make_unique<PrefabInstanceEditor>();
|
||||
m_componentRegistry.registerComponent<PrefabInstanceComponent>(
|
||||
"Prefab Instance", "Scene", std::move(prefabEditor),
|
||||
// Adder
|
||||
[this](flecs::entity e) {
|
||||
if (!e.has<PrefabInstanceComponent>()) {
|
||||
e.set<PrefabInstanceComponent>({});
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[this](flecs::entity e) {
|
||||
if (e.has<PrefabInstanceComponent>()) {
|
||||
e.remove<PrefabInstanceComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
// Register CharacterIdentity component
|
||||
auto characterIdentityEditor =
|
||||
std::make_unique<CharacterIdentityEditor>();
|
||||
m_componentRegistry.registerComponent<CharacterIdentityComponent>(
|
||||
"Character Identity", "Character",
|
||||
std::move(characterIdentityEditor),
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<CharacterIdentityComponent>())
|
||||
e.set<CharacterIdentityComponent>(
|
||||
CharacterIdentityComponent{});
|
||||
},
|
||||
[](flecs::entity e) {
|
||||
if (e.has<CharacterIdentityComponent>())
|
||||
e.remove<CharacterIdentityComponent>();
|
||||
});
|
||||
|
||||
// Register modular components (Light, Camera, etc.)
|
||||
registerModularComponents();
|
||||
}
|
||||
|
||||
void EditorUISystem::registerModularComponents()
|
||||
{
|
||||
// This calls all component modules registered via REGISTER_COMPONENT macro
|
||||
@@ -1056,387 +921,33 @@ void EditorUISystem::renderComponentList(flecs::entity entity)
|
||||
// Render component editors
|
||||
ImGui::BeginChild("Components", ImVec2(0, 0), true);
|
||||
|
||||
// Dynamically render all components the entity has
|
||||
// Collect the components present on the entity and render them through
|
||||
// their registered type-erased renderers. Sort by (order, name) so
|
||||
// Transform (order -1) always renders first.
|
||||
std::vector<const ComponentRegistry::ComponentInfo *> infos;
|
||||
m_componentRegistry.forEach(
|
||||
[&](const std::type_index &,
|
||||
const ComponentRegistry::ComponentInfo &info) {
|
||||
if (info.renderer && info.checker(entity))
|
||||
infos.push_back(&info);
|
||||
});
|
||||
std::sort(infos.begin(), infos.end(),
|
||||
[](const ComponentRegistry::ComponentInfo *a,
|
||||
const ComponentRegistry::ComponentInfo *b) {
|
||||
if (a->order != b->order)
|
||||
return a->order < b->order;
|
||||
return std::string(a->name) < std::string(b->name);
|
||||
});
|
||||
|
||||
int componentCount = 0;
|
||||
|
||||
// Render Transform first if present (it's the base component)
|
||||
if (entity.has<TransformComponent>()) {
|
||||
auto &transform = entity.get_mut<TransformComponent>();
|
||||
if (m_componentRegistry.render<TransformComponent>(entity,
|
||||
transform)) {
|
||||
// Transform changed - mark StaticGeometryMember dirty if present
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
entity.get_mut<StaticGeometryMemberComponent>()
|
||||
.markDirty();
|
||||
}
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Renderable
|
||||
if (entity.has<RenderableComponent>()) {
|
||||
auto &renderable = entity.get_mut<RenderableComponent>();
|
||||
m_componentRegistry.render<RenderableComponent>(entity,
|
||||
renderable);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Light if present
|
||||
if (entity.has<LightComponent>()) {
|
||||
auto &light = entity.get_mut<LightComponent>();
|
||||
m_componentRegistry.render<LightComponent>(entity, light);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Camera if present
|
||||
if (entity.has<CameraComponent>()) {
|
||||
auto &camera = entity.get_mut<CameraComponent>();
|
||||
m_componentRegistry.render<CameraComponent>(entity, camera);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render RigidBody
|
||||
if (entity.has<RigidBodyComponent>()) {
|
||||
auto &rigidBody = entity.get_mut<RigidBodyComponent>();
|
||||
m_componentRegistry.render<RigidBodyComponent>(entity,
|
||||
rigidBody);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render PhysicsCollider
|
||||
if (entity.has<PhysicsColliderComponent>()) {
|
||||
auto &collider = entity.get_mut<PhysicsColliderComponent>();
|
||||
m_componentRegistry.render<PhysicsColliderComponent>(entity,
|
||||
collider);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render BuoyancyInfo if present
|
||||
if (entity.has<BuoyancyInfo>()) {
|
||||
auto &buoyancy = entity.get_mut<BuoyancyInfo>();
|
||||
m_componentRegistry.render<BuoyancyInfo>(entity, buoyancy);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render WaterPhysics if present
|
||||
if (entity.has<WaterPhysics>()) {
|
||||
auto &wp = entity.get_mut<WaterPhysics>();
|
||||
m_componentRegistry.render<WaterPhysics>(entity, wp);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Sun if present
|
||||
if (entity.has<SunComponent>()) {
|
||||
auto &sun = entity.get_mut<SunComponent>();
|
||||
if (m_componentRegistry.render<SunComponent>(entity, sun)) {
|
||||
sun.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Skybox if present
|
||||
if (entity.has<SkyboxComponent>()) {
|
||||
auto &sky = entity.get_mut<SkyboxComponent>();
|
||||
if (m_componentRegistry.render<SkyboxComponent>(entity, sky)) {
|
||||
sky.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render WaterPlane if present
|
||||
if (entity.has<WaterPlane>()) {
|
||||
auto &wp = entity.get_mut<WaterPlane>();
|
||||
if (m_componentRegistry.render<WaterPlane>(entity, wp)) {
|
||||
wp.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Terrain if present
|
||||
if (entity.has<TerrainComponent>()) {
|
||||
auto &tc = entity.get_mut<TerrainComponent>();
|
||||
m_componentRegistry.render<TerrainComponent>(entity, tc);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render LOD Settings if present
|
||||
if (entity.has<LodSettingsComponent>()) {
|
||||
auto &lodSettings = entity.get_mut<LodSettingsComponent>();
|
||||
m_componentRegistry.render<LodSettingsComponent>(entity,
|
||||
lodSettings);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render LOD if present
|
||||
if (entity.has<LodComponent>()) {
|
||||
auto &lod = entity.get_mut<LodComponent>();
|
||||
m_componentRegistry.render<LodComponent>(entity, lod);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render StaticGeometry Region if present
|
||||
if (entity.has<StaticGeometryComponent>()) {
|
||||
auto ®ion = entity.get_mut<StaticGeometryComponent>();
|
||||
m_componentRegistry.render<StaticGeometryComponent>(entity,
|
||||
region);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render StaticGeometry Member if present
|
||||
if (entity.has<StaticGeometryMemberComponent>()) {
|
||||
auto &member = entity.get_mut<StaticGeometryMemberComponent>();
|
||||
m_componentRegistry.render<StaticGeometryMemberComponent>(
|
||||
entity, member);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render ProceduralTexture if present
|
||||
if (entity.has<ProceduralTextureComponent>()) {
|
||||
auto &texture = entity.get_mut<ProceduralTextureComponent>();
|
||||
m_componentRegistry.render<ProceduralTextureComponent>(entity,
|
||||
texture);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render ProceduralMaterial if present
|
||||
if (entity.has<ProceduralMaterialComponent>()) {
|
||||
auto &material = entity.get_mut<ProceduralMaterialComponent>();
|
||||
m_componentRegistry.render<ProceduralMaterialComponent>(
|
||||
entity, material);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Primitive if present
|
||||
if (entity.has<PrimitiveComponent>()) {
|
||||
auto &primitive = entity.get_mut<PrimitiveComponent>();
|
||||
m_componentRegistry.render<PrimitiveComponent>(entity,
|
||||
primitive);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render TriangleBuffer if present
|
||||
if (entity.has<TriangleBufferComponent>()) {
|
||||
auto &tb = entity.get_mut<TriangleBufferComponent>();
|
||||
m_componentRegistry.render<TriangleBufferComponent>(entity, tb);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Character if present
|
||||
if (entity.has<CharacterComponent>()) {
|
||||
auto &cc = entity.get_mut<CharacterComponent>();
|
||||
m_componentRegistry.render<CharacterComponent>(entity, cc);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render CharacterSlots if present
|
||||
if (entity.has<CharacterSlotsComponent>()) {
|
||||
/* CharacterSlotsComponent is now an empty tag; get_mut is illegal
|
||||
* for zero-sized types in flecs. */
|
||||
CharacterSlotsComponent cs;
|
||||
m_componentRegistry.render<CharacterSlotsComponent>(entity, cs);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render CharacterIdentity if present
|
||||
if (entity.has<CharacterIdentityComponent>()) {
|
||||
auto &ci = entity.get_mut<CharacterIdentityComponent>();
|
||||
m_componentRegistry.render<CharacterIdentityComponent>(entity,
|
||||
ci);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render CharacterSpawner if present
|
||||
if (entity.has<CharacterSpawnerComponent>()) {
|
||||
auto &spawner = entity.get_mut<CharacterSpawnerComponent>();
|
||||
m_componentRegistry.render<CharacterSpawnerComponent>(entity,
|
||||
spawner);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render AnimationTree if present
|
||||
if (entity.has<AnimationTreeComponent>()) {
|
||||
auto &at = entity.get_mut<AnimationTreeComponent>();
|
||||
m_componentRegistry.render<AnimationTreeComponent>(entity, at);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render StartupMenu if present
|
||||
if (entity.has<StartupMenuComponent>()) {
|
||||
auto &sm = entity.get_mut<StartupMenuComponent>();
|
||||
m_componentRegistry.render<StartupMenuComponent>(entity, sm);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render PlayerController if present
|
||||
if (entity.has<PlayerControllerComponent>()) {
|
||||
auto &pc = entity.get_mut<PlayerControllerComponent>();
|
||||
m_componentRegistry.render<PlayerControllerComponent>(entity,
|
||||
pc);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render CellGrid if present
|
||||
if (entity.has<CellGridComponent>()) {
|
||||
auto &grid = entity.get_mut<CellGridComponent>();
|
||||
m_componentRegistry.render<CellGridComponent>(entity, grid);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Lot if present
|
||||
if (entity.has<LotComponent>()) {
|
||||
auto &lot = entity.get_mut<LotComponent>();
|
||||
m_componentRegistry.render<LotComponent>(entity, lot);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render District if present
|
||||
if (entity.has<DistrictComponent>()) {
|
||||
auto &district = entity.get_mut<DistrictComponent>();
|
||||
m_componentRegistry.render<DistrictComponent>(entity, district);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Town if present
|
||||
if (entity.has<TownComponent>()) {
|
||||
auto &town = entity.get_mut<TownComponent>();
|
||||
m_componentRegistry.render<TownComponent>(entity, town);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Roof if present
|
||||
if (entity.has<RoofComponent>()) {
|
||||
auto &roof = entity.get_mut<RoofComponent>();
|
||||
m_componentRegistry.render<RoofComponent>(entity, roof);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Room if present
|
||||
if (entity.has<RoomComponent>()) {
|
||||
auto &room = entity.get_mut<RoomComponent>();
|
||||
if (m_componentRegistry.render<RoomComponent>(entity, room)) {
|
||||
room.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render ClearArea if present
|
||||
if (entity.has<ClearAreaComponent>()) {
|
||||
auto &clearArea = entity.get_mut<ClearAreaComponent>();
|
||||
if (m_componentRegistry.render<ClearAreaComponent>(entity,
|
||||
clearArea)) {
|
||||
clearArea.markDirty();
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render FurnitureTemplate if present
|
||||
if (entity.has<FurnitureTemplateComponent>()) {
|
||||
auto &furniture = entity.get_mut<FurnitureTemplateComponent>();
|
||||
m_componentRegistry.render<FurnitureTemplateComponent>(
|
||||
entity, furniture);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render ActionDatabaseComponent if present
|
||||
if (entity.has<ActionDatabaseComponent>()) {
|
||||
auto &db = entity.get_mut<ActionDatabaseComponent>();
|
||||
m_componentRegistry.render<ActionDatabaseComponent>(entity, db);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render ActionDebug if present
|
||||
|
||||
if (entity.has<ActionDebug>()) {
|
||||
auto &debug = entity.get_mut<ActionDebug>();
|
||||
m_componentRegistry.render<ActionDebug>(entity, debug);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render BehaviorTree if present
|
||||
if (entity.has<BehaviorTreeComponent>()) {
|
||||
auto &bt = entity.get_mut<BehaviorTreeComponent>();
|
||||
m_componentRegistry.render<BehaviorTreeComponent>(entity, bt);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render NavMesh if present
|
||||
if (entity.has<NavMeshComponent>()) {
|
||||
auto &nav = entity.get_mut<NavMeshComponent>();
|
||||
if (m_componentRegistry.render<NavMeshComponent>(entity, nav)) {
|
||||
if (nav.debugDraw && NavMeshSystem::getInstance())
|
||||
NavMeshSystem::getInstance()->setDebugDraw(
|
||||
entity, nav.debugDraw);
|
||||
}
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render NavMeshGeometrySource if present
|
||||
if (entity.has<NavMeshGeometrySource>()) {
|
||||
auto &src = entity.get_mut<NavMeshGeometrySource>();
|
||||
m_componentRegistry.render<NavMeshGeometrySource>(entity, src);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render SmartObject if present
|
||||
if (entity.has<SmartObjectComponent>()) {
|
||||
auto &so = entity.get_mut<SmartObjectComponent>();
|
||||
m_componentRegistry.render<SmartObjectComponent>(entity, so);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render GoapPlanner if present
|
||||
if (entity.has<GoapPlannerComponent>()) {
|
||||
auto &planner = entity.get_mut<GoapPlannerComponent>();
|
||||
m_componentRegistry.render<GoapPlannerComponent>(entity,
|
||||
planner);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render GoapRunner if present
|
||||
if (entity.has<GoapRunnerComponent>()) {
|
||||
auto &runner = entity.get_mut<GoapRunnerComponent>();
|
||||
m_componentRegistry.render<GoapRunnerComponent>(entity, runner);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render PathFollowing if present
|
||||
if (entity.has<PathFollowingComponent>()) {
|
||||
auto &pf = entity.get_mut<PathFollowingComponent>();
|
||||
m_componentRegistry.render<PathFollowingComponent>(entity, pf);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Actuator if present
|
||||
if (entity.has<ActuatorComponent>()) {
|
||||
auto &actuator = entity.get_mut<ActuatorComponent>();
|
||||
m_componentRegistry.render<ActuatorComponent>(entity, actuator);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render EventHandler if present
|
||||
if (entity.has<EventHandlerComponent>()) {
|
||||
auto &handler = entity.get_mut<EventHandlerComponent>();
|
||||
m_componentRegistry.render<EventHandlerComponent>(entity,
|
||||
handler);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Item if present
|
||||
if (entity.has<ItemComponent>()) {
|
||||
auto &item = entity.get_mut<ItemComponent>();
|
||||
m_componentRegistry.render<ItemComponent>(entity, item);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Render Inventory if present
|
||||
if (entity.has<InventoryComponent>()) {
|
||||
auto &inv = entity.get_mut<InventoryComponent>();
|
||||
m_componentRegistry.render<InventoryComponent>(entity, inv);
|
||||
for (const auto *info : infos) {
|
||||
bool modified = info->renderer(entity);
|
||||
if (modified && info->onModified)
|
||||
info->onModified(entity);
|
||||
componentCount++;
|
||||
}
|
||||
|
||||
// Show message if no components
|
||||
|
||||
if (componentCount == 0) {
|
||||
ImGui::TextDisabled("No components");
|
||||
ImGui::Text("Click 'Add Component' to add components");
|
||||
|
||||
@@ -243,7 +243,6 @@ private:
|
||||
void renderRemoveComponentMenu(flecs::entity entity);
|
||||
|
||||
// Helper functions
|
||||
void registerComponentEditors();
|
||||
void registerModularComponents();
|
||||
flecs::entity findEntityParent(flecs::entity entity);
|
||||
std::vector<flecs::entity> getEntityChildren(flecs::entity entity);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define EDITSCENE_COMPONENTREGISTRY_HPP
|
||||
#pragma once
|
||||
#include <typeindex>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
@@ -26,6 +27,24 @@ using ComponentRemover = std::function<void(flecs::entity)>;
|
||||
*/
|
||||
using ComponentChecker = std::function<bool(flecs::entity)>;
|
||||
|
||||
/**
|
||||
* Function type for rendering a component editor from an entity.
|
||||
*
|
||||
* The closure performs the type-erased fetch of the component pointer
|
||||
* (via e.get_mut<T>() for data components, or a stack-local instance for
|
||||
* empty/tag components) and forwards it to the registered editor.
|
||||
*/
|
||||
using ComponentRenderer = std::function<bool(flecs::entity)>;
|
||||
|
||||
/**
|
||||
* Function type invoked after a component editor reports a modification.
|
||||
*
|
||||
* Used for cross-component/system side effects that cannot live inside a
|
||||
* single editor (e.g. marking a StaticGeometryMember dirty when its
|
||||
* Transform changed, or syncing NavMesh debug-draw state).
|
||||
*/
|
||||
using ComponentModified = std::function<void(flecs::entity)>;
|
||||
|
||||
/**
|
||||
* Registry for component editors and their add/remove functions
|
||||
*/
|
||||
@@ -34,10 +53,13 @@ public:
|
||||
struct ComponentInfo {
|
||||
const char *name;
|
||||
const char *group; // Group name for menu organization (nullptr = "Miscellaneous")
|
||||
int order = 0; // Render order (lower renders first; Transform is -1)
|
||||
std::unique_ptr<IComponentEditor> editor;
|
||||
ComponentRenderer renderer; // Type-erased render closure
|
||||
ComponentAdder adder;
|
||||
ComponentRemover remover;
|
||||
ComponentChecker checker; // Checks if entity has this component
|
||||
ComponentModified onModified; // Optional post-render side effect
|
||||
};
|
||||
|
||||
ComponentRegistry() = default;
|
||||
@@ -58,19 +80,38 @@ public:
|
||||
* @param editor The editor for this component
|
||||
* @param adder Function to add the component to an entity
|
||||
* @param remover Function to remove the component from an entity
|
||||
* @param onModified Optional callback invoked after the editor reports a
|
||||
* modification (for cross-component side effects)
|
||||
* @param order Render order; lower values render first (Transform uses -1)
|
||||
*/
|
||||
template<typename T>
|
||||
void registerComponent(const char *name, const char *group,
|
||||
std::unique_ptr<ComponentEditor<T>> editor,
|
||||
ComponentAdder adder, ComponentRemover remover)
|
||||
ComponentAdder adder, ComponentRemover remover,
|
||||
ComponentModified onModified = nullptr,
|
||||
int order = 0)
|
||||
{
|
||||
ComponentInfo info;
|
||||
info.name = name;
|
||||
info.group = group ? group : "Miscellaneous";
|
||||
info.order = order;
|
||||
IComponentEditor *rawEditor = editor.get();
|
||||
info.editor = std::move(editor);
|
||||
info.renderer = [rawEditor](flecs::entity e) -> bool {
|
||||
if (!e.has<T>())
|
||||
return false;
|
||||
if constexpr (std::is_empty_v<T>) {
|
||||
/* Empty/tag components are zero-sized in flecs, so
|
||||
* get_mut<T>() is illegal; render against a dummy. */
|
||||
T dummy{};
|
||||
return rawEditor->render(e, &dummy);
|
||||
}
|
||||
return rawEditor->render(e, &e.get_mut<T>());
|
||||
};
|
||||
info.adder = adder;
|
||||
info.remover = remover;
|
||||
info.checker = [](flecs::entity e) { return e.has<T>(); };
|
||||
info.onModified = onModified;
|
||||
m_components[std::type_index(typeid(T))] = std::move(info);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user