7#include <unordered_map>
8#include <unordered_set>
13int appendNode(SceneHost::Tree &tree, NodeDesc &&desc,
int parentIndex) {
14 const int index = int(tree.nodes.size());
16 node.
id = std::move(desc.id);
40 node.localDirty =
true;
41 node.world = glm::mat4(1.f);
43 node.nextSibling = -1;
44 node.parent = parentIndex;
46 tree.nodes.push_back(std::move(
node));
51 int childIndex = appendNode(tree, std::move(child), index);
52 if (firstChild < 0) firstChild = childIndex;
53 if (prevChild >= 0) tree.nodes[size_t(prevChild)].nextSibling = childIndex;
54 prevChild = childIndex;
56 tree.nodes[size_t(index)].firstChild = firstChild;
60bool structureMatches(
const SceneHost::Tree &tree,
int nodeIndex,
const NodeDesc &desc) {
61 if (nodeIndex < 0 || nodeIndex >=
int(tree.nodes.size()))
return false;
62 const SceneNode &
n = tree.nodes[size_t(nodeIndex)];
63 const std::string &dk = desc.reconcileKey();
64 if (!dk.empty() && !
n.key.empty() &&
n.key != dk)
return false;
66 std::vector<std::string> oldKeys;
67 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
68 oldKeys.push_back(tree.nodes[
size_t(
c)].key);
70 if (oldKeys.size() != desc.children.size())
return false;
71 for (
size_t i = 0; i < desc.children.size(); ++i) {
72 const std::string &ck = desc.children[i].reconcileKey();
73 if (ck.empty() || oldKeys[i].empty()) {
74 if (!(ck.empty() && oldKeys[i].empty()))
return false;
75 }
else if (ck != oldKeys[i]) {
81 for (
const auto &ch : desc.
children) {
82 if (!structureMatches(tree, child, ch))
return false;
83 child = tree.nodes[size_t(child)].nextSibling;
92bool structureMatchesSet(
const SceneHost::Tree &tree,
int nodeIndex,
93 const NodeDesc &desc) {
94 if (nodeIndex < 0 || nodeIndex >=
int(tree.nodes.size()))
return false;
95 const SceneNode &
n = tree.nodes[size_t(nodeIndex)];
97 if (!dk.empty() && !
n.key.empty() &&
n.key != dk)
return false;
99 std::vector<std::string> oldKeys;
100 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
101 oldKeys.push_back(tree.nodes[
size_t(
c)].key);
103 if (oldKeys.size() != desc.children.size())
return false;
105 std::unordered_multiset<std::string> want;
106 for (
const auto &ch : desc.
children) want.insert(ch.reconcileKey());
107 for (
const auto &k : oldKeys) {
108 auto it = want.find(k);
109 if (it == want.end())
return false;
114 std::vector<bool> used(desc.children.size(),
false);
115 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
117 const std::string &ck = tree.nodes[size_t(
c)].key;
118 for (
size_t i = 0; i < desc.children.size(); ++i) {
119 if (used[i])
continue;
120 const std::string &dk2 = desc.children[i].reconcileKey();
121 if (ck == dk2 || (ck.empty() && dk2.empty())) {
122 if (structureMatchesSet(tree,
c, desc.children[i])) {
129 if (!found)
return false;
135bool reorderChildrenRecursive(SceneHost::Tree &tree,
int nodeIndex,
136 const NodeDesc &desc) {
137 SceneNode &
n = tree.nodes[size_t(nodeIndex)];
138 std::unordered_map<std::string, std::vector<int>> byKey;
139 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
140 byKey[tree.nodes[size_t(
c)].key].push_back(
c);
142 std::vector<int> order;
143 order.reserve(desc.children.size());
144 for (
const auto &ch : desc.
children) {
145 auto it = byKey.find(ch.reconcileKey());
146 if (it == byKey.end() || it->second.empty())
return false;
147 order.push_back(it->second.back());
148 it->second.pop_back();
153 n.firstChild = order[0];
154 for (
size_t i = 0; i < order.size(); ++i) {
155 tree.nodes[size_t(order[i])].nextSibling =
156 (i + 1 < order.size()) ? order[i + 1] : -1;
159 for (
size_t i = 0; i < order.size(); ++i) {
160 if (!reorderChildrenRecursive(tree, order[i], desc.children[i]))
return false;
165void patchProps(SceneHost *host,
int nodeIndex, NodeDesc &&desc) {
166 SceneHost::Tree &tree = *host->tree();
167 SceneNode &
n = tree.nodes[size_t(nodeIndex)];
168 n.visible = desc.visible;
170 n.layer = desc.layer;
171 n.bminX = desc.bminX;
172 n.bminY = desc.bminY;
173 n.bminZ = desc.bminZ;
174 n.bmaxX = desc.bmaxX;
175 n.bmaxY = desc.bmaxY;
176 n.bmaxZ = desc.bmaxZ;
177 n.hasBounds = desc.hasBounds;
182 n.pitch = desc.pitch;
187 if (!desc.space.empty())
n.space = desc.space;
188 if (!desc.id.empty())
n.id = desc.id;
189 if (!desc.name.empty())
n.name = desc.name;
190 host->markSubtreeDirty(nodeIndex);
191 host->fireEvent(
"node_changed",
n.id,
192 n.parent >= 0 ? tree.nodes[
size_t(
n.parent)].id :
"");
196 patchProps(host, child, std::move(ch));
197 child = tree.nodes[size_t(child)].nextSibling;
204 std::unordered_set<std::string> seen;
206 if (!
d.id.empty() && !seen.insert(
d.id).second) {
207 throw std::runtime_error(
"scene: duplicate node id '" +
d.id +
"'");
209 for (
const auto &
c :
d.children) walk(
c);
216 d.
id = std::move(
id);
218 d.name =
name.empty() ?
d.id : std::move(
name);
225 d.
id = std::move(
id);
227 d.name =
d.id.empty() ?
"group" :
d.id;
233 if (!cond)
return group({},
"__when_empty");
238 return cond ? std::move(ifTrue) : std::move(ifFalse);
244 auto t = host->tree();
246 std::unordered_set<std::string> oldIds;
247 for (
const auto &
n : t->nodes) {
248 if (!
n.id.empty()) oldIds.insert(
n.id);
251 std::unordered_map<std::string, std::vector<SceneLink>> saved;
252 for (
const auto &
n : t->nodes) {
253 if (!
n.links.empty() && !
n.id.empty()) saved[
n.id] =
n.links;
258 std::unordered_map<std::string, uint32_t> savedObjects;
259 for (
const auto &
n : t->nodes) {
260 if (
n.objectId != 0 && !
n.id.empty()) savedObjects[
n.id] =
n.objectId;
265 t->root = appendNode(*t, std::move(root), -1);
266 for (
auto &
n : t->nodes) {
267 auto it = saved.find(
n.id);
268 if (it != saved.end()) {
269 n.links = it->second;
271 auto oit = savedObjects.find(
n.id);
272 if (oit != savedObjects.end())
n.objectId = oit->second;
276 t->transformDirty =
true;
279 std::unordered_set<std::string> newIds;
280 for (
const auto &
n : t->nodes) {
281 if (!
n.id.empty()) newIds.insert(
n.id);
283 for (
const auto &
id : oldIds) {
284 if (!newIds.count(
id)) host->
fireEvent(
"node_removed",
id);
286 for (
const auto &
n : t->nodes) {
287 if (!oldIds.count(
n.id)) {
289 n.parent >= 0 ? t->nodes[
size_t(
n.parent)].id :
"");
295 if (!host)
return true;
296 auto t = host->tree();
297 if (t->root < 0 || t->nodes.empty() ||
298 !structureMatchesSet(*t, t->root, root)) {
305 if (!reorderChildrenRecursive(*t, t->root, root)) {
309 patchProps(host, t->root, std::move(root));
311 t->transformDirty =
true;
ECS mount point for one scene graph (full scene or nested subtree root). Isomorphic to eve::ui::UIHos...
void fireEvent(const std::string &action, const std::string &nodeId, const std::string &parentId={})
Fire "node_added" / "node_removed" / "node_moved" / "node_changed".
bool applyTreeReconcile(SceneHost *host, NodeDesc root)
Key-aware patch when structure matches; else full replace. Returns true if full rebuild.
NodeDesc whenElse(bool cond, NodeDesc ifTrue, NodeDesc ifFalse)
NodeDesc node(std::string id, std::vector< NodeDesc > children, std::string name)
void validateUniqueIds(const NodeDesc &root)
NodeDesc when(bool cond, NodeDesc child)
Conditional: include child only when cond is true (empty group otherwise).
void applyTree(SceneHost *host, NodeDesc root)
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Declarative scene-node description (build once / on dirty → flatten into SceneHost::Tree)....
std::string key
Reconciliation key; defaults to id when empty.
std::string space
"2d" or "3d" (string enum per module convention).
std::vector< std::string > tags