7#include <unordered_map>
8#include <unordered_set>
13int appendDetachedNode(SceneHost::Tree& tree, NodeDesc&&
desc,
int parentIndex) {
14 const int index = int(tree.nodes.size());
17 node.persistentId =
desc.persistentId;
21 if (
node.space.empty())
node.space =
"3d";
41 node.localDirty =
true;
42 node.world = glm::mat4(1.f);
44 node.nextSibling = -1;
45 node.parent = parentIndex;
47 tree.nodes.push_back(std::move(
node));
52 int childIndex = appendDetachedNode(tree, std::move(child),
index);
53 if (firstChild < 0) firstChild = childIndex;
54 if (prevChild >= 0) tree.nodes[size_t(prevChild)].nextSibling = childIndex;
55 prevChild = childIndex;
57 tree.nodes[size_t(
index)].firstChild = firstChild;
61bool structureMatches(
const SceneHost::Tree &tree,
int nodeIndex,
const NodeDesc &
desc) {
62 if (nodeIndex < 0 || nodeIndex >=
int(tree.nodes.size()))
return false;
63 const SceneNode &
n = tree.nodes[size_t(
nodeIndex)];
64 const std::string &dk =
desc.reconcileKey();
65 if (!dk.empty() && !
n.key.empty() &&
n.key != dk)
return false;
67 std::vector<std::string> oldKeys;
68 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
69 oldKeys.push_back(tree.nodes[
size_t(
c)].key);
71 if (oldKeys.size() !=
desc.children.size())
return false;
72 for (
size_t i = 0; i <
desc.children.size(); ++i) {
73 const std::string &ck =
desc.children[i].reconcileKey();
74 if (ck.empty() || oldKeys[i].empty()) {
75 if (!(ck.empty() && oldKeys[i].empty()))
return false;
76 }
else if (ck != oldKeys[i]) {
83 if (!structureMatches(tree, child, ch))
return false;
84 child = tree.nodes[size_t(child)].nextSibling;
93bool structureMatchesSet(
const SceneHost::Tree &tree,
int nodeIndex,
94 const NodeDesc &
desc) {
95 if (nodeIndex < 0 || nodeIndex >=
int(tree.nodes.size()))
return false;
96 const SceneNode &
n = tree.nodes[size_t(
nodeIndex)];
98 if (!dk.empty() && !
n.key.empty() &&
n.key != dk)
return false;
100 std::vector<std::string> oldKeys;
101 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
102 oldKeys.push_back(tree.nodes[
size_t(
c)].key);
104 if (oldKeys.size() !=
desc.children.size())
return false;
106 std::unordered_multiset<std::string> want;
107 for (
const auto &ch :
desc.
children) want.insert(ch.reconcileKey());
108 for (
const auto &k : oldKeys) {
109 auto it = want.find(k);
110 if (it == want.end())
return false;
115 std::vector<bool> used(
desc.children.size(),
false);
116 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
118 const std::string &ck = tree.nodes[size_t(
c)].key;
119 for (
size_t i = 0; i <
desc.children.size(); ++i) {
120 if (used[i])
continue;
121 const std::string &dk2 =
desc.children[i].reconcileKey();
122 if (ck == dk2 || (ck.empty() && dk2.empty())) {
123 if (structureMatchesSet(tree,
c,
desc.children[i])) {
130 if (!
found)
return false;
136bool reorderChildrenRecursive(SceneHost::Tree &tree,
int nodeIndex,
137 const NodeDesc &
desc) {
139 std::unordered_map<std::string, std::vector<int>> byKey;
140 for (
int c =
n.firstChild;
c >= 0;
c = tree.nodes[size_t(
c)].nextSibling) {
141 byKey[tree.nodes[size_t(
c)].key].push_back(
c);
143 std::vector<int>
order;
146 auto it = byKey.find(ch.reconcileKey());
147 if (it == byKey.end() || it->second.empty())
return false;
148 order.push_back(it->second.back());
149 it->second.pop_back();
155 for (
size_t i = 0; i <
order.size(); ++i) {
156 tree.nodes[size_t(
order[i])].nextSibling =
160 for (
size_t i = 0; i <
order.size(); ++i) {
161 if (!reorderChildrenRecursive(tree,
order[i],
desc.children[i]))
return false;
167 SceneHost::Tree &tree = *
host->tree();
169 n.visible =
desc.visible;
171 n.layer =
desc.layer;
172 n.bminX =
desc.bminX;
173 n.bminY =
desc.bminY;
174 n.bminZ =
desc.bminZ;
175 n.bmaxX =
desc.bmaxX;
176 n.bmaxY =
desc.bmaxY;
177 n.bmaxZ =
desc.bmaxZ;
178 n.hasBounds =
desc.hasBounds;
183 n.pitch =
desc.pitch;
188 if (!
desc.persistentId.isNil())
n.persistentId =
desc.persistentId;
189 if (!
desc.space.empty())
n.space =
desc.space;
191 if (!
desc.name.empty())
n.name =
desc.name;
193 host->fireEvent(
"node_changed",
n.id,
194 n.parent >= 0 ? tree.nodes[
size_t(
n.parent)].id :
"");
198 patchProps(
host, child, std::move(ch));
199 child = tree.nodes[size_t(child)].nextSibling;
206 std::unordered_set<std::string> seen;
208 if (!
d.id.empty() && !seen.insert(
d.id).second) {
209 throw std::runtime_error(
"scene: duplicate node id '" +
d.id +
"'");
211 for (
const auto &
c :
d.children) walk(
c);
218 d.
id = std::move(
id);
220 d.name =
name.empty() ?
d.id : std::move(
name);
227 d.
id = std::move(
id);
229 d.name =
d.id.empty() ?
"group" :
d.id;
235 if (!cond)
return group({},
"__when_empty");
240 return cond ? std::move(ifTrue) : std::move(ifFalse);
246 auto t =
host->tree();
248 std::unordered_set<std::string> oldIds;
249 for (
const auto &
n :
t->nodes) {
250 if (!
n.id.empty()) oldIds.insert(
n.id);
253 std::unordered_map<std::string, std::vector<SceneLink>> saved;
254 for (
const auto &
n :
t->nodes) {
255 if (!
n.links.empty() && !
n.id.empty()) saved[
n.id] =
n.links;
260 std::unordered_map<std::string, uint32_t> savedObjects;
261 for (
const auto &
n :
t->nodes) {
262 if (
n.objectId != 0 && !
n.id.empty()) savedObjects[
n.id] =
n.objectId;
267 t->root = appendDetachedNode(*
t, std::move(
root), -1);
268 for (
auto &
n :
t->nodes) {
269 auto it = saved.find(
n.id);
270 if (it != saved.end()) {
271 n.links = it->second;
273 auto oit = savedObjects.find(
n.id);
274 if (oit != savedObjects.end())
n.objectId = oit->second;
276 host->invalidateIndex();
278 t->transformDirty =
true;
281 std::unordered_set<std::string> newIds;
282 for (
const auto &
n :
t->nodes) {
283 if (!
n.id.empty()) newIds.insert(
n.id);
285 for (
const auto &
id : oldIds) {
286 if (!newIds.count(
id))
host->fireEvent(
"node_removed",
id);
288 for (
const auto &
n :
t->nodes) {
289 if (!oldIds.count(
n.id)) {
290 host->fireEvent(
"node_added",
n.id,
291 n.parent >= 0 ?
t->nodes[
size_t(
n.parent)].id :
"");
302 std::unordered_map<std::string, const SceneNode*> preserved;
303 preserved.reserve(
previous->nodes.size());
305 if (!
node.id.empty()) preserved.emplace(
node.id, &
node);
307 const auto found = preserved.find(
node.id);
308 if (
found == preserved.end())
continue;
314 target.transformDirty =
true;
315 target.indexValid =
false;
317 }
catch (
const std::exception& exception) {
324 if (!
host)
return true;
325 auto t =
host->tree();
326 if (
t->root < 0 ||
t->nodes.empty() ||
327 !structureMatchesSet(*
t,
t->root,
root)) {
334 if (!reorderChildrenRecursive(*
t,
t->root,
root)) {
338 patchProps(
host,
t->root, std::move(
root));
340 t->transformDirty =
true;
building::EdgeCurveGroup group
graphics::Canvas * previous
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
ECS mount point for one scene graph (full scene or nested subtree root). Isomorphic to eve::ui::UIHos...
static eve::Result< Tree > buildDetachedTree(const Tree *previous, NodeDesc root)
Builds a detached replacement tree without mutating a host or firing callbacks.
bool applyTreeReconcile(SceneHost *host, NodeDesc root)
Key-aware patch when structure matches; else full replace. Returns true if full rebuild.
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)
Applies tree.
NodeDesc whenElse(bool cond, NodeDesc ifTrue, NodeDesc ifFalse)
When else.
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)....