10#include <glm/gtx/matrix_decompose.hpp>
11#include <glm/gtx/quaternion.hpp>
15#include <unordered_map>
16#include <unordered_set>
25 if (!result.
ok())
return nullptr;
26 return std::move(result).takeValue();
29SceneHost *hostByName(
const std::string &
name) {
30 if (
name.empty())
return nullptr;
31 if (ecs::current()->getManager<SceneHost>() ==
nullptr)
return nullptr;
32 auto view = ecs::View<SceneHost, SceneHost::Meta>();
33 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
35 if (meta->entity && meta->name ==
name)
return meta->entity;
40SceneHost *hostAt(
int index) {
41 if (
index < 0 || ecs::current()->getManager<SceneHost>() ==
nullptr)
return nullptr;
42 auto view = ecs::View<SceneHost, SceneHost::Meta>();
44 for (
auto it =
view.begin(); it !=
view.end(); ++it, ++i) {
46 if (i ==
index)
return meta->entity;
54 std::string_view
bone)
const override {
55 auto*
object =
dynamic_cast<SceneObject*
>(ecs::try_get(
handle));
60 "Scene root provider cannot resolve a bone or socket",
"bone");
61 auto*
host = hostByName(
object->meta()->hostName);
68 glm::vec3 translation;
70 glm::vec4 perspective;
71 if (!glm::decompose(
node.value()->world,
scale,
rotation, translation, skew, perspective))
73 const glm::vec3 euler = glm::degrees(glm::eulerAngles(
rotation));
75 euler.x, euler.y, euler.z,
87SceneNodeInfo toInfo(SceneHost *
host,
const SceneNode &
n) {
92 info.visible =
n.visible;
102 if (
auto *
p =
host->getParentById(
n.id))
info.parent =
p->id;
103 for (
int i = 0; i <
host->getChildCountById(
n.id); ++i) {
104 if (
auto *
c =
host->getChildAtById(
n.id, i))
info.children.push_back(
c->id);
111 SceneHost *
host()
const {
112 auto *
s = eve::ModuleManager::getInstance<Scene>(
"Scene");
113 return s ?
s->current() :
nullptr;
116 std::string activeHost()
const override {
118 return h ?
h->getName() : std::string();
121 int hostCount()
const override {
122 if (ecs::current()->getManager<SceneHost>() ==
nullptr)
return 0;
123 auto view = ecs::View<SceneHost, SceneHost::Meta>();
125 for (
auto it =
view.begin(); it !=
view.end(); ++it) ++
n;
129 std::string hostNameAt(
int index)
const override {
131 return h ?
h->getName() : std::string();
134 int nodeCount()
const override {
136 return h ?
h->getNodeCount() : 0;
139 std::string rootId()
const override {
141 auto *
r =
h ?
h->getRoot() :
nullptr;
142 return r ?
r->id : std::string();
145 std::vector<SceneNodeInfo>
nodes(
int limit)
const override {
149 std::vector<SceneNodeInfo> nodesOf(
const std::string &hostName,
int limit)
const override {
150 std::vector<SceneNodeInfo> out;
151 SceneHost *
h = hostName.empty() ?
host() : hostByName(hostName);
153 h->walkDepthFirst([&](SceneHost *,
int, SceneNode &
n) {
154 if (
static_cast<int>(out.size()) >=
limit)
return;
155 out.push_back(toInfo(
h,
n));
160 bool getNode(
const std::string &
id, SceneNodeInfo *out)
const override {
162 return h ? getNodeIn(
h->getName(),
id, out) : false;
165 bool getNodeIn(
const std::string &hostName,
const std::string &
id,
166 SceneNodeInfo *out)
const override {
167 SceneHost *
h = hostName.empty() ?
host() : hostByName(hostName);
168 SceneNode *
n =
h ? borrowSceneResult(
h->findById(
id)) : nullptr;
169 if (!
n || !out)
return false;
170 *out = toInfo(
h, *
n);
174 bool setNodeTransform(
const std::string &
id,
float x,
float y,
float z)
override {
176 SceneNode *
n =
h ? borrowSceneResult(
h->findById(
id)) : nullptr;
177 if (!
n)
return false;
181 h->markTransformDirty();
185 bool setNodeVisible(
const std::string &
id,
bool visible)
override {
187 SceneNode *
n =
h ? borrowSceneResult(
h->findById(
id)) : nullptr;
188 if (!
n)
return false;
198 bool applyBatch(
const std::string& batchId,
199 const std::vector<eve::ProcgenInstanceDesc>&
instances)
override {
200 if (batchId.empty())
return false;
201 SceneHost*
host = hostByName(hostName(batchId));
203 if (!
host)
return false;
206 root.id =
"pcg-root";
207 root.key =
"pcg-root";
209 root.tags = {
"pcg",
"pcg.batch"};
215 child.name = instance.asset;
216 child.x = instance.x;
217 child.y = instance.y;
218 child.z = instance.z;
219 child.yaw = instance.yaw;
220 child.sx = instance.scaleX;
221 child.sy = instance.scaleY;
222 child.sz = instance.scaleZ;
223 child.tags = {
"pcg",
"pcg.instance"};
224 if (!instance.asset.empty())
child.tags.push_back(
"pcg.asset:" + instance.asset);
227 std::unordered_set<std::string> nextIds;
228 std::unordered_map<uint64_t, std::string> nextPointIds;
232 if (!nextIds.insert(instance.id).second)
return false;
233 if (instance.sourcePointId != 0 && !nextPointIds.emplace(instance.sourcePointId, instance.id).second)
236 const auto previous = ids_.find(batchId);
237 const auto& previousIds =
previous == ids_.end() ? emptyIds_ :
previous->second;
239 for (
const auto&
id : nextIds) {
240 if (previousIds.find(
id) == previousIds.end()) ++
stats.created;
243 for (
const auto&
id : previousIds) {
244 if (nextIds.find(
id) == nextIds.end()) ++
stats.removed;
247 host->setVisible(
true);
248 host->setTreeReconcile(std::move(
root));
250 counts_[batchId] = int(
instances.size());
251 ids_[batchId] = std::move(nextIds);
253 pointIds_[batchId] = std::move(nextPointIds);
254 revisions_[batchId] = revisions_[batchId] + 1;
255 stats_[batchId] =
stats;
260 const std::vector<eve::ProcgenInstanceDesc>&
instances)
override {
261 const uint64_t currentRevision = batchRevision(batchId);
262 if (batchId.empty() || targetRevision == 0)
265 "procedural scene snapshot requires a batch id and non-zero target revision",
"targetRevision"));
266 if (currentRevision >= targetRevision)
269 std::unordered_set<std::string> instanceIds;
270 std::unordered_set<uint64_t> pointIds;
274 if (instance.id.empty() || instance.sourcePointId == 0 || !instanceIds.insert(instance.id).second ||
275 !pointIds.insert(instance.sourcePointId).second)
278 "procedural scene snapshot requires unique instance ids and source PointIds",
"instances"));
283 revisions_[batchId] = targetRevision;
287 eve::Result<uint64_t> replaceBatches(
const std::vector<eve::ProcgenBatchSnapshot>& snapshots)
override {
288 if (snapshots.empty())
291 "procedural scene transaction requires at least one snapshot",
"snapshots"));
293 struct PreparedBatch {
295 SceneHost*
host =
nullptr;
296 SceneHost::Tree tree;
297 std::unordered_set<std::string>
ids;
298 std::unordered_map<uint64_t, std::string> pointIds;
299 std::vector<std::string> addedIds;
300 std::vector<std::string> reusedIds;
301 std::vector<std::string> removedIds;
304 std::vector<PreparedBatch> prepared;
305 prepared.reserve(snapshots.size());
306 std::unordered_set<std::string> batchIds;
307 batchIds.reserve(snapshots.size());
309 for (
const auto& snapshot : snapshots) {
310 if (snapshot.batchId.empty() || snapshot.targetRevision == 0)
313 "procedural scene transaction requires batch ids and non-zero revisions",
"snapshots"));
314 if (!batchIds.insert(snapshot.batchId).second)
317 if (batchRevision(snapshot.batchId) >= snapshot.targetRevision)
320 "procedural scene transaction contains a stale revision", snapshot.batchId));
323 batch.snapshot = &snapshot;
324 batch.ids.reserve(snapshot.instances.size());
325 batch.pointIds.reserve(snapshot.instances.size());
326 for (
const auto& instance : snapshot.
instances) {
327 if (instance.id.empty() || instance.sourcePointId == 0 || !batch.ids.insert(instance.id).second ||
328 !batch.pointIds.emplace(instance.sourcePointId, instance.id).second)
331 "procedural scene transaction requires unique instance ids and source PointIds",
334 const auto previous = ids_.find(snapshot.batchId);
335 const auto& previousIds =
previous == ids_.end() ? emptyIds_ :
previous->second;
336 for (
const auto&
id : batch.
ids) {
337 if (previousIds.find(
id) == previousIds.end()) {
338 ++batch.stats.created;
339 batch.addedIds.push_back(
id);
341 ++batch.stats.reused;
342 batch.reusedIds.push_back(
id);
345 for (
const auto&
id : previousIds) {
346 if (batch.ids.find(
id) != batch.ids.end())
continue;
347 ++batch.stats.removed;
348 batch.removedIds.push_back(
id);
351 batch.host = hostByName(hostName(snapshot.batchId));
352 NodeDesc
root = makeRoot(snapshot.batchId, snapshot.instances);
355 batch.tree = std::move(tree).takeValue();
356 prepared.push_back(std::move(batch));
359 auto nextCounts = counts_;
361 auto nextInstances = instances_;
362 auto nextPointIds = pointIds_;
363 auto nextRevisions = revisions_;
364 auto nextStats = stats_;
365 for (
const auto& batch : prepared) {
366 const auto& snapshot = *batch.snapshot;
367 nextCounts[snapshot.batchId] = int(snapshot.instances.size());
368 nextIds[snapshot.batchId] = batch.ids;
369 nextInstances[snapshot.batchId] = snapshot.instances;
370 nextPointIds[snapshot.batchId] = batch.pointIds;
371 nextRevisions[snapshot.batchId] = snapshot.targetRevision;
372 nextStats[snapshot.batchId] = batch.stats;
375 for (
auto& batch : prepared) {
376 if (batch.host)
continue;
381 batch.snapshot->batchId));
382 batch.host->setVisible(
false);
385 for (
auto& batch : prepared) {
386 *batch.host->tree() = std::move(batch.tree);
387 batch.host->setVisible(
true);
389 counts_.swap(nextCounts);
391 instances_.swap(nextInstances);
392 pointIds_.swap(nextPointIds);
393 revisions_.swap(nextRevisions);
394 stats_.swap(nextStats);
395 for (
const auto& batch : prepared) {
396 for (
const auto&
id : batch.removedIds) batch.
host->fireEvent(
"node_removed",
id);
397 for (
const auto&
id : batch.addedIds) batch.
host->fireEvent(
"node_added",
id,
"pcg-root");
398 for (
const auto&
id : batch.reusedIds) batch.
host->fireEvent(
"node_changed",
id,
"pcg-root");
400 for (
const auto& batch : prepared) TransformSystem::updateHost(batch.
host);
406 const auto current = revisions_.find(batchId);
407 if (batchId.empty() ||
current == revisions_.end())
415 std::unordered_map<std::string, eve::ProcgenInstanceDesc> staged;
416 for (
const auto& instance : instances_.
at(batchId)) staged.emplace(instance.
id, instance);
417 std::unordered_map<uint64_t, std::string> stagedPointIds = pointIds_.at(batchId);
418 std::unordered_set<std::string> removedIds;
420 for (
const auto pointId : delta.removedPointIds) {
421 const auto found = stagedPointIds.find(pointId);
422 if (pointId == 0 ||
found == stagedPointIds.end() || !removedIds.insert(
found->second).second)
427 for (
const auto&
id : delta.
removed)
428 if (
id.empty() || !removedIds.insert(
id).
second)
431 for (
const auto&
id : removedIds) {
432 const auto found = staged.find(
id);
433 if (
found == staged.end())
436 if (
found->second.sourcePointId != 0) stagedPointIds.erase(
found->second.sourcePointId);
439 for (
const auto& instance : delta.
updated) {
440 if (instance.id.empty() || instance.sourcePointId == 0)
443 "procedural scene delta updates require an id and source PointId",
"updated"));
444 const auto identity = stagedPointIds.find(instance.sourcePointId);
445 if (identity == stagedPointIds.end())
448 if (identity->second != instance.id && staged.find(instance.id) != staged.end())
451 "procedural scene delta update renames to a duplicate identity",
"updated"));
452 staged.erase(identity->second);
453 staged.emplace(instance.id, instance);
454 identity->second = instance.id;
456 for (
const auto& instance : delta.added) {
457 if (instance.id.empty() || instance.sourcePointId == 0 ||
458 stagedPointIds.find(instance.sourcePointId) != stagedPointIds.end() ||
459 !staged.emplace(instance.id, instance).second)
462 stagedPointIds.emplace(instance.sourcePointId, instance.id);
467 "procedural scene delta cannot mix PointId and compatibility target orders",
"targetPointOrder"));
468 std::vector<std::string> resolvedOrder;
471 std::unordered_set<uint64_t> orderedPointIds;
472 for (
const auto pointId : delta.targetPointOrder) {
473 const auto found = stagedPointIds.find(pointId);
474 if (pointId == 0 ||
found == stagedPointIds.end() || !orderedPointIds.insert(pointId).second)
477 "procedural scene delta target order has an unknown or duplicate source PointId",
478 "targetPointOrder"));
479 resolvedOrder.push_back(
found->second);
484 if (staged.size() != resolvedOrder.size())
487 std::vector<eve::ProcgenInstanceDesc>
target;
488 target.reserve(resolvedOrder.size());
489 for (
const auto&
id : resolvedOrder) {
490 auto found = staged.find(
id);
491 if (
found == staged.end())
494 "procedural scene delta target order has unknown identity",
"targetOrder"));
501 "procedural scene delta target order repeats an identity",
"targetOrder"));
505 uint64_t batchRevision(
const std::string& batchId)
const override {
506 const auto found = revisions_.find(batchId);
507 return found == revisions_.end() ? 0 :
found->second;
510 bool removeBatch(
const std::string& batchId)
override {
511 SceneHost*
host = hostByName(hostName(batchId));
512 if (!
host)
return false;
514 root.id =
"pcg-root";
515 root.key =
"pcg-root";
517 root.tags = {
"pcg",
"pcg.batch"};
519 host->setVisible(
false);
521 const auto ids = ids_.find(batchId);
522 if (
ids != ids_.end())
stats.removed = int(
ids->second.size());
523 stats_[batchId] =
stats;
524 counts_.erase(batchId);
526 instances_.erase(batchId);
527 pointIds_.erase(batchId);
528 revisions_.erase(batchId);
537 const std::vector<std::string>& batchIds,
539 if (batchIds.empty())
542 "procedural scene removal transaction requires at least one batch id",
"batchIds"));
543 struct PreparedRemoval {
545 SceneHost*
host =
nullptr;
546 SceneHost::Tree tree;
547 std::vector<std::string> removedIds;
550 std::vector<PreparedRemoval> prepared;
551 prepared.reserve(batchIds.size());
552 std::unordered_set<std::string> uniqueBatchIds;
553 uniqueBatchIds.reserve(batchIds.size());
554 for (
const auto& batchId : batchIds) {
555 const auto ids = ids_.find(batchId);
556 if (batchId.empty() || !uniqueBatchIds.insert(batchId).second)
559 "procedural scene removal transaction requires unique non-empty batch ids",
"batchIds"));
560 SceneHost*
host = hostByName(hostName(batchId));
561 if (
ids == ids_.end() || !
host)
565 PreparedRemoval removal;
566 removal.batchId = batchId;
568 removal.removedIds.assign(
ids->second.begin(),
ids->second.end());
569 std::sort(removal.removedIds.begin(), removal.removedIds.end());
570 removal.stats.removed = int(
ids->second.size());
573 removal.tree = std::move(tree).takeValue();
574 prepared.push_back(std::move(removal));
577 auto nextCounts = counts_;
579 auto nextInstances = instances_;
580 auto nextPointIds = pointIds_;
581 auto nextRevisions = revisions_;
582 auto nextStats = stats_;
583 for (
const auto& removal : prepared) {
584 nextCounts.erase(removal.batchId);
585 nextIds.erase(removal.batchId);
586 nextInstances.erase(removal.batchId);
587 nextPointIds.erase(removal.batchId);
588 nextRevisions.erase(removal.batchId);
589 nextStats[removal.batchId] = removal.stats;
591 if (!commitPreparedOwner)
594 "commitPreparedOwner"));
595 auto ownerCommit = commitPreparedOwner();
597 for (
auto& removal : prepared) {
598 *removal.host->tree() = std::move(removal.tree);
599 removal.host->setVisible(
false);
601 counts_.swap(nextCounts);
603 instances_.swap(nextInstances);
604 pointIds_.swap(nextPointIds);
605 revisions_.swap(nextRevisions);
606 stats_.swap(nextStats);
607 for (
const auto& removal : prepared)
608 for (const auto&
id : removal.removedIds) removal.
host->fireEvent(
"node_removed",
id);
612 int instanceCount(
const std::string& batchId)
const override {
613 const auto found = counts_.find(batchId);
614 return found == counts_.end() ? 0 :
found->second;
616 int lastCreatedCount(
const std::string& batchId)
const override {
617 const auto found = stats_.find(batchId);
618 return found == stats_.end() ? 0 :
found->second.created;
620 int lastReusedCount(
const std::string& batchId)
const override {
621 const auto found = stats_.find(batchId);
622 return found == stats_.end() ? 0 :
found->second.reused;
624 int lastRemovedCount(
const std::string& batchId)
const override {
625 const auto found = stats_.find(batchId);
626 return found == stats_.end() ? 0 :
found->second.removed;
635 static NodeDesc makeRoot(
const std::string& batchId,
const std::vector<eve::ProcgenInstanceDesc>&
instances) {
637 root.id =
"pcg-root";
638 root.key =
"pcg-root";
640 root.tags = {
"pcg",
"pcg.batch"};
646 child.name = instance.asset;
647 child.x = instance.x;
648 child.y = instance.y;
649 child.z = instance.z;
650 child.yaw = instance.yaw;
651 child.sx = instance.scaleX;
652 child.sy = instance.scaleY;
653 child.sz = instance.scaleZ;
654 child.tags = {
"pcg",
"pcg.instance"};
655 if (!instance.asset.empty())
child.tags.push_back(
"pcg.asset:" + instance.asset);
660 static std::string hostName(
const std::string& batchId) {
return "__pcg/" + batchId; }
661 const std::unordered_set<std::string> emptyIds_;
662 std::unordered_map<std::string, int> counts_;
663 std::unordered_map<std::string, std::unordered_set<std::string>> ids_;
664 std::unordered_map<std::string, std::vector<eve::ProcgenInstanceDesc>> instances_;
665 std::unordered_map<std::string, std::unordered_map<uint64_t, std::string>> pointIds_;
666 std::unordered_map<std::string, uint64_t> revisions_;
667 std::unordered_map<std::string, Stats> stats_;
673 static SceneQueryImpl
impl;
674 static ProcgenSceneSinkImpl procgenSink;
675 static SceneEntitySpatialProvider spatialProvider;
676 eve::cap::provide<eve::ISceneQuery>(&
impl);
677 eve::cap::provide<eve::IProcgenSceneSink>(&procgenSink);
678 eve::cap::addListener<eve::IEntitySpatialProvider>(&spatialProvider);
std::vector< BuildingInstanceSnapshot > instances
Optional ECS-entity to world-pose capability.
std::uint32_t instanceCount
std::array< float, 4 > rotation
std::array< float, 3 > scale
graphics::Canvas * previous
std::array< std::uint64_t, kPixelChunkSize *kPixelChunkSize > updated
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.
One optional domain provider capable of resolving selected ECS entities.
Optional scene consumer for procedural instance batches.
Scene graph query/mutation surface (provided by the scene module).
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
bool ok() const noexcept
Whether this result represents a non-failure outcome.
static Result failure(Status status)
Construct a failed result from a structured status.
static eve::Result< Tree > buildDetachedTree(const Tree *previous, NodeDesc root)
Builds a detached replacement tree without mutating a host or firing callbacks.
static eve::Result< SceneHost * > createHost(const std::string &name="")
Creates an ECS-owned scene host with an explicit Result contract.
eve::Diagnostic Diagnostic
void registerSceneCapabilities()
Registers scene capabilities.
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Build metadata (engine git commit, build time, third-party version).
DiagnosticCode
Stable machine-readable diagnostic codes.
One complete cell snapshot participating in an atomic multi-batch publication.
Identity-based, revision-checked mutation of one procedural scene batch.
std::vector< uint64_t > removedPointIds
Stable source PointIds removed from the target batch.
std::vector< std::string > targetOrder
Compatibility target order addressed directly by Scene instance id.
std::vector< std::string > removed
Compatibility removals addressed directly by Scene instance id.
std::vector< uint64_t > targetPointOrder
Exact target order addressed by stable source PointId.