载入中...
搜索中...
未找到
SceneRuntime.cpp
浏览该文件的文档.
2
3#include "scene/SceneHost.h"
4
5#include <cmath>
6#include <map>
7#include <set>
8#include <stdexcept>
9#include <utility>
10
11namespace eve::scene_editing {
12namespace {
13
14std::map<ObjectId, SceneObjectSnapshot> collect(const SceneTargetBase& target) {
15 std::map<ObjectId, SceneObjectSnapshot> result;
16 const EditorValue snapshot = target.snapshotValue();
17 const auto* root = snapshot.getIf<EditorValue::Object>();
18 if (!root) return result;
19 const auto found = root->find("objects");
20 if (found == root->end()) return result;
21 const auto* objects = found->second.getIf<EditorValue::Array>();
22 if (!objects) return result;
23 for (const EditorValue& value : *objects) {
24 const auto* object = value.getIf<EditorValue::Object>();
25 if (!object) continue;
26 const auto* id = object->at("id").getIf<std::string>();
27 auto snapshot = target.sceneObject(ObjectId(*id));
28 if (snapshot.ok()) result.emplace(snapshot.value().id, snapshot.value());
29 }
30 return result;
31}
32
33scene::SceneNode runtimeNode(const SceneObjectSnapshot& object) {
34 scene::SceneNode node;
35 node.id = object.id.value();
36 node.name = object.name;
37 node.x = static_cast<float>(object.transform.x);
38 node.y = static_cast<float>(object.transform.y);
39 node.z = static_cast<float>(object.transform.z);
40 node.pitch = static_cast<float>(object.transform.rotationX);
41 node.yaw = static_cast<float>(object.transform.rotationY);
42 node.roll = static_cast<float>(object.transform.rotationZ);
43 node.sx = static_cast<float>(object.transform.scaleX);
44 node.sy = static_cast<float>(object.transform.scaleY);
45 node.sz = static_cast<float>(object.transform.scaleZ);
46 return node;
47}
48
49bool hostMatches(scene::SceneHost* host, const SceneObjectSnapshot& object) {
50 const int index = host->findIndexById(object.id.value());
51 scene::SceneNode* node = host->getNode(index);
52 if (!node) return false;
53 const std::string parent = node->parent >= 0 ? host->getNode(node->parent)->id : std::string{};
54 const auto close = [](double left, double right) { return std::abs(left - right) <= 1e-5; };
55 return parent == object.parent.value() && node->name == object.name &&
56 close(node->x, object.transform.x) && close(node->y, object.transform.y) &&
57 close(node->z, object.transform.z) && close(node->pitch, object.transform.rotationX) &&
58 close(node->yaw, object.transform.rotationY) && close(node->roll, object.transform.rotationZ) &&
59 close(node->sx, object.transform.scaleX) && close(node->sy, object.transform.scaleY) &&
60 close(node->sz, object.transform.scaleZ);
61}
62
63} // namespace
64
66 : SceneTargetBase(std::move(id), "runtime-scene-host"), hostHandle_(ecs::handle_of(host)), staging_(!host) {
67 if (!host) return;
68 const auto tree = host->tree();
69 std::set<std::string> imported;
70 while (imported.size() < tree->nodes.size()) {
71 const auto before = imported.size();
72 for (const scene::SceneNode& node : tree->nodes) {
73 if (imported.contains(node.id)) continue;
74 if (node.parent >= 0 && !imported.contains(tree->nodes.at(size_t(node.parent)).id)) continue;
76 request.id = ObjectId(node.id);
77 request.parent = node.parent >= 0 ? ObjectId(tree->nodes[size_t(node.parent)].id) : ObjectId{};
78 request.name = node.name;
79 request.transform = {node.x, node.y, node.z, node.pitch, node.yaw, node.roll, node.sx, node.sy, node.sz};
81 if (!operation.ok()) throw std::invalid_argument("Scene host contains invalid editing data");
83 if (!applied.ok()) throw std::invalid_argument("Scene host could not be imported");
84 imported.insert(node.id);
85 }
86 if (before == imported.size()) throw std::invalid_argument("Scene host contains invalid hierarchy");
87 }
89}
90
96
98 const ObjectId& object) const {
99 auto* live = host();
100 if (!live)
101 return eve::editing::failed<std::vector<SceneComponentLinkSnapshot>>(
102 EditorStatus::Unsupported, RuleId("editor.scene.live-host-required"),
103 "Component links are unavailable on a staging target");
104 auto nodeResult = live->findById(object.value());
105 if (!nodeResult.ok() || !nodeResult.value())
106 return eve::editing::failed<std::vector<SceneComponentLinkSnapshot>>(
107 EditorStatus::NotFound, RuleId("editor.scene.component-object-not-found"),
108 "Live scene object does not exist: " + object.value());
109 std::vector<SceneComponentLinkSnapshot> result;
110 for (const scene::SceneLink& link : nodeResult.value()->links) {
111 const scene::LinkOps* operations = scene::linkOps(link.kind);
112 const bool alive = link.target && (!operations || !operations->alive || operations->alive(link.target));
113 result.push_back({scene::linkKindName(link.kind), link.syncMode, alive});
114 }
115 std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) { return a.kind < b.kind; });
116 return eve::editing::applied<std::vector<SceneComponentLinkSnapshot>>(std::move(result));
117}
118
121 auto candidate = cloneDomainState();
122 auto* staged = dynamic_cast<SceneHostEditorTarget*>(candidate.get());
123 if (!staged)
124 return eve::editing::failed<void>(EditorStatus::Failed, RuleId("editor.scene.live-stage-failed"),
125 "Could not stage live scene operation");
126 Result<void> applied = staged->SceneTargetBase::applyDomainOperation(operation);
127 if (!applied.ok()) return applied;
128 return commitDomainState(std::move(candidate));
129}
130
131std::unique_ptr<IDomainOperationTarget> SceneHostEditorTarget::cloneDomainState() const {
132 auto result = std::make_unique<SceneHostEditorTarget>(*this);
133 result->hostHandle_ = {};
134 result->staging_ = true;
135 return result;
136}
137
139 return dynamic_cast<scene::SceneHost*>(ecs::try_get(hostHandle_));
140}
141
142Result<void> SceneHostEditorTarget::synchronizeHost(const SceneTargetBase& desiredTarget) {
143 auto* live = host();
144 if (!live)
145 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.scene.live-host-required"),
146 "Live scene host has been destroyed");
147 const auto current = collect(*this);
148 const auto desired = collect(desiredTarget);
149 const auto tree = live->tree();
150 if (tree->nodes.size() != current.size())
151 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.scene.live-host-diverged"),
152 "Live scene structure changed; start a new editing session");
153 for (const auto& [id, object] : current) {
154 if (!hostMatches(live, object))
155 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.scene.live-host-diverged"),
156 "Live scene changed outside this target: " + id.value());
157 const auto* node = live->getNode(live->findIndexById(id.value()));
158 if (!desired.contains(id) && (!node->links.empty() || node->objectId != 0))
159 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.scene.live-delete-attached"),
160 "Detach domain links and behaviors before deleting this node");
161 }
162 // Validate the whole candidate before publishing any host mutation.
163 auto valid = desiredTarget.makeRestore(desiredTarget.snapshotValue());
164 if (!valid.ok()) return Result<void>::failure(valid.status());
165 scene::SceneHost::Tree candidate;
166 std::map<ObjectId, int> indices;
167 for (const auto& [id, object] : desired) {
168 scene::SceneNode node = runtimeNode(object);
169 const int old = live->findIndexById(id.value());
170 if (old >= 0) {
171 node = *live->getNode(old);
172 const auto transform = runtimeNode(object);
173 node.name = object.name;
174 node.x = transform.x;
175 node.y = transform.y;
176 node.z = transform.z;
177 node.pitch = transform.pitch;
178 node.yaw = transform.yaw;
179 node.roll = transform.roll;
180 node.sx = transform.sx;
181 node.sy = transform.sy;
182 node.sz = transform.sz;
183 }
184 node.parent = node.firstChild = node.nextSibling = -1;
185 node.localDirty = node.subtreeDirty = true;
186 indices.emplace(id, static_cast<int>(candidate.nodes.size()));
187 candidate.nodes.push_back(std::move(node));
188 }
189 std::map<int, int> tails;
190 for (const auto& [id, object] : desired) {
191 const int index = indices.at(id);
192 const int parent = object.parent.empty() ? -1 : indices.at(object.parent);
193 candidate.nodes[index].parent = parent;
194 if (tails.contains(parent))
195 candidate.nodes[tails.at(parent)].nextSibling = index;
196 else if (parent >= 0)
197 candidate.nodes[parent].firstChild = index;
198 else
199 candidate.root = index;
200 tails[parent] = index;
201 }
202 *live->tree() = std::move(candidate);
203 return editing::applied<void>();
204}
205
207 std::unique_ptr<IDomainOperationTarget> candidate) {
208 auto* staged = dynamic_cast<SceneHostEditorTarget*>(candidate.get());
209 if (!staged || staged->targetId() != targetId())
210 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.scene.live-candidate-mismatch"),
211 "Live scene candidate has an incompatible type");
212 Result<void> synchronized = synchronizeHost(*staged);
213 if (!synchronized.ok()) return synchronized;
214 auto committed = SceneTargetBase::commitDomainState(std::move(candidate));
215 if (!committed.ok()) return committed;
216 // Observers run only after both published host and transaction baseline agree.
217 if (auto* live = host()) live->fireEvent("tree_changed", "");
218 return editing::applied<void>();
219}
220
221} // namespace eve::scene_editing
LogicalId target
ActionParameterOperation operation
double value
int root
Definition AnimSmr.cpp:119
const GltfImportRequest & request
std::uint32_t alive
std::vector< std::uint32_t > indices
HexVec3 left
HexVec3 right
std::string imported
std::int32_t parent
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string id
Definition PlayHost.cpp:108
const RoadNode * node
bool found
double current
UIHostHandle host
uint32_t index
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
virtual void clearDirtyRegion()=0
Clears dirty region.
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
ECS mount point for one scene graph (full scene or nested subtree root). Isomorphic to eve::ui::UIHos...
Definition SceneHost.h:89
Read-only live component/link inspection capability for scene inspectors.
Definition SceneTarget.h:63
static CapabilityId editorCapabilityId()
Stable capability id for component/link metadata inspection.
Definition SceneTarget.h:68
Optional live adapter mirroring editor operations into one SceneHost.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
scene::SceneHost * host() const
Compatibility-only pointer projection of the generation-checked host.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
SceneHostEditorTarget(std::string id, scene::SceneHost *host)
Import a host using a generation handle.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
Result< std::vector< SceneComponentLinkSnapshot > > componentLinks(const ObjectId &object) const override
Enumerate safe link metadata without exposing runtime pointers.
Shared in-memory scene target implementation for document/runtime adapters.
Definition SceneTarget.h:86
void * queryCapability(const CapabilityId &capability) override
Queries capability.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
Result< DomainOperation > makeCreate(const CreateSceneObjectRequest &request) const override
Make create.
Result< DomainOperation > makeRestore(const EditorValue &snapshot) const
Plan atomic replacement of hierarchy/TRS from a versioned snapshot.
TargetId targetId() const override
Target id.
Definition SceneTarget.h:94
EditorValue snapshotValue() const override
Capture deterministic scene content for document persistence.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
editing::ObjectId ObjectId
Result< T > failed(Status status, eve::DiagnosticCode code, RuleId rule, std::string message)
Construct a failed editing result with explicit status and diagnostic categories.
editing::Value EditorValue
const char * linkKindName(int kindId)
Link kind name.
Definition SceneLink.cpp:53
const LinkOps * linkOps(int kindId)
Link ops.
Definition SceneLink.cpp:47
DomainOperation public API.
LinkOps public API.
Definition SceneLink.h:28
bool(* alive)(const void *target)
Bool.
Definition SceneLink.h:48
std::vector< SceneNode > nodes
Definition SceneHost.h:111
Retained scene node (arena). Conceptual GameObject; isomorphic to eve::ui::UINode.
Definition SceneHost.h:46
Request used to create a scene object in either backend.
Definition SceneTarget.h:27