17 if (!
object)
return nullptr;
18 const auto found =
object->find(
key);
19 return found ==
object->end() ? nullptr : &
found->second;
24 {
"rotationX",
value.rotationX}, {
"rotationY",
value.rotationY},
25 {
"rotationZ",
value.rotationZ}, {
"scaleX",
value.scaleX},
26 {
"scaleY",
value.scaleY}, {
"scaleZ",
value.scaleZ}};
30 const auto number = [&](
const char*
key) ->
const double* {
32 return entry ? entry->getIf<
double>() : nullptr;
37 const double* rx =
number(
"rotationX");
38 const double* ry =
number(
"rotationY");
39 const double* rz =
number(
"rotationZ");
43 if (!
x || !
y || !
z || !rx || !ry || !rz || !
sx || !
sy || !
sz)
44 return eve::editing::failed<SceneTransformValue>(EditorStatus::Rejected,
45 RuleId(
"editor.prefab.invalid-transform"),
46 "Prefab transform requires complete numeric TRS fields");
47 return eve::editing::applied<SceneTransformValue>({*
x, *
y, *
z, *rx, *ry, *rz, *
sx, *
sy, *
sz});
50Result<void> validatePrefab(
const PrefabAssetSnapshot&
prefab) {
52 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.prefab.invalid-identity"),
53 "Prefab requires asset, revision, root and objects");
54 std::set<ObjectId>
ids;
55 for (
const PrefabObjectRecord&
object :
prefab.objects)
58 "Prefab object ids must be unique and non-empty");
59 for (
const PrefabObjectRecord&
object :
prefab.objects)
60 if (
object.nestedPrefab.empty() != (
object.nestedRevision == 0))
62 "Nested prefab references require both asset and non-zero revision");
64 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.prefab.root-not-found"),
65 "Prefab root does not exist");
66 for (
const PrefabObjectRecord&
object :
prefab.objects) {
67 if (
object.
sourceId ==
prefab.rootSourceId && !
object.parentSourceId.empty())
68 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.prefab.root-has-parent"),
69 "Prefab root must not have an internal parent");
70 if (
object.
sourceId !=
prefab.rootSourceId && !
ids.contains(
object.parentSourceId))
71 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.prefab.parent-not-found"),
72 "Prefab object references a missing parent");
73 std::set<ObjectId> ancestry;
75 while (!
cursor->parentSourceId.empty()) {
76 if (!ancestry.insert(
cursor->sourceId).second)
77 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.prefab.hierarchy-cycle"),
78 "Prefab hierarchy contains a cycle");
80 [&](
const PrefabObjectRecord& candidate) {
81 return candidate.sourceId == cursor->parentSourceId;
87 return eve::editing::failed<void>(EditorStatus::Rejected,
RuleId(
"editor.prefab.disconnected-object"),
88 "Every prefab object must descend from its root");
90 return eve::editing::applied<void>();
99 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Rejected,
RuleId(
"editor.prefab.asset-required"),
100 "Prefab asset identity is required");
101 auto rootObject =
scene.sceneObject(
root);
102 if (!rootObject.ok())
103 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::NotFound,
RuleId(
"editor.prefab.root-not-found"),
104 "Prefab capture root does not exist");
111 auto object =
scene.sceneObject(
id);
113 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Conflict,
114 RuleId(
"editor.prefab.capture-diverged"),
115 "Scene hierarchy changed during prefab capture");
121 return eve::editing::applied<PrefabAssetSnapshot>(std::move(
prefab));
125 const std::string& instanceId,
128 if (!validatePrefab(
prefab).
ok() || instanceId.empty())
129 return eve::editing::failed<PrefabInstancePlan>(EditorStatus::Rejected,
130 RuleId(
"editor.prefab.invalid-instance"),
131 "Prefab and non-empty instance id are required");
133 return eve::editing::failed<PrefabInstancePlan>(EditorStatus::NotFound,
134 RuleId(
"editor.prefab.scene-parent-not-found"),
135 "Prefab instance parent does not exist");
142 if (
scene.sceneObject(
id).ok())
143 return eve::editing::failed<PrefabInstancePlan>(EditorStatus::Conflict,
144 RuleId(
"editor.prefab.instance-id-conflict"),
145 "Prefab instance object already exists: " +
id.
value());
150 : instanceObject(instanceId,
object.parentSourceId);
152 request.transform =
object.transform;
158 create.
type =
"scene.object.create.v1";
162 {
"name",
request.name}, {
"transform", transformValue(
request.transform)}};
172 return eve::editing::applied<PrefabInstancePlan>(std::move(plan));
178 if (!validatePrefab(
prefab).
ok() || instanceId.empty())
179 return eve::editing::failed<std::vector<DomainOperation>>(
180 EditorStatus::Rejected,
RuleId(
"editor.prefab.invalid-instance"),
"Prefab and instance id are required");
181 std::vector<DomainOperation> operations;
184 auto object =
scene.sceneObject(
id);
186 return eve::editing::failed<std::vector<DomainOperation>>(
187 EditorStatus::Conflict,
RuleId(
"editor.prefab.instance-structure-changed"),
188 "Prefab instance object is missing: " +
id.
value());
191 : instanceObject(instanceId,
source.parentSourceId);
195 return eve::editing::failed<std::vector<DomainOperation>>(EditorStatus::Conflict,
196 RuleId(
"editor.prefab.revert-reparent"),
197 "Could not plan prefab reparent revert");
198 operations.push_back(std::move(
operation).takeValue());
204 RuleId(
"editor.prefab.revert-rename"),
205 "Could not plan prefab rename revert");
206 operations.push_back(std::move(
operation).takeValue());
208 if (
object.
value().transform !=
source.transform) {
212 RuleId(
"editor.prefab.revert-transform"),
213 "Could not plan prefab transform revert");
214 operations.push_back(std::move(
operation).takeValue());
217 return eve::editing::applied<std::vector<DomainOperation>>(std::move(operations));
221 const std::string& instanceId,
224 if (!validatePrefab(
prefab).
ok() || instanceId.empty())
225 return eve::editing::failed<PrefabAssetSnapshot>(
226 EditorStatus::Rejected,
RuleId(
"editor.prefab.invalid-instance"),
"Prefab and instance id are required");
229 std::map<ObjectId, ObjectId> instanceToSource;
231 instanceToSource.emplace(instanceObject(instanceId,
source.sourceId),
source.sourceId);
233 const ObjectId instance = instanceObject(instanceId, destination.sourceId);
234 auto object =
scene.sceneObject(instance);
236 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Conflict,
237 RuleId(
"editor.prefab.instance-structure-changed"),
238 "Prefab instance object is missing: " + instance.
value());
239 destination.name =
object.value().name;
240 destination.transform =
object.value().transform;
241 if (destination.sourceId ==
prefab.rootSourceId) {
243 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Conflict,
244 RuleId(
"editor.prefab.root-reparented"),
245 "Prefab root moved outside its instance parent");
246 destination.parentSourceId =
ObjectId{};
248 const auto sourceParent = instanceToSource.find(
object.
value().
parent);
249 if (sourceParent == instanceToSource.end())
250 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Conflict,
251 RuleId(
"editor.prefab.child-reparented-outside"),
252 "Prefab child moved outside its instance");
253 destination.parentSourceId = sourceParent->second;
256 return eve::editing::applied<PrefabAssetSnapshot>(std::move(result));
262 if (!validatePrefab(
prefab).
ok() || instanceId.empty())
263 return eve::editing::failed<std::vector<PrefabOverrideRecord>>(
264 EditorStatus::Rejected,
RuleId(
"editor.prefab.invalid-instance"),
"Prefab and instance id are required");
265 std::vector<PrefabOverrideRecord> result;
267 const ObjectId instance = instanceObject(instanceId,
source.sourceId);
268 auto object =
scene.sceneObject(instance);
270 result.push_back({instance,
source.sourceId,
"object",
"missing"});
274 ?
parent : instanceObject(instanceId,
source.parentSourceId);
276 result.push_back({instance,
source.sourceId,
"hierarchy.parent",
"modified"});
278 result.push_back({instance,
source.sourceId,
"object.name",
"modified"});
280 result.push_back({instance,
source.sourceId,
"transform",
"modified"});
282 std::sort(result.begin(), result.end(), [](
const auto&
a,
const auto&
b) {
283 return a.instanceObject == b.instanceObject ? a.property < b.property
284 : a.instanceObject < b.instanceObject;
286 return eve::editing::applied<std::vector<PrefabOverrideRecord>>(std::move(result));
298 if (!resolver || !validatePrefab(
prefab).ok()) {
299 report.
status = EditorStatus::Rejected;
301 DiagnosticSeverity::Error,
302 "Dependency inspection requires a valid prefab and resolver");
305 std::set<AssetGuid>
visiting, visited;
309 DiagnosticSeverity::Error,
310 "Nested prefab dependency cycle reaches: " +
current.asset.value());
313 if (visited.contains(
current.asset))
return true;
317 if (
object.nestedPrefab.empty())
continue;
318 auto resolved = resolver(
object.nestedPrefab);
319 if (!resolved.ok()) {
321 DiagnosticSeverity::Error,
322 "Nested prefab cannot be resolved: " +
object.nestedPrefab.value());
323 valid =
false;
continue;
325 if (!validatePrefab(resolved.value()).ok()) {
327 DiagnosticSeverity::Error,
328 "Nested prefab is invalid: " +
object.nestedPrefab.value());
329 valid =
false;
continue;
331 if (resolved.value().revision !=
object.nestedRevision)
333 DiagnosticSeverity::Warning,
334 "Nested prefab revision pin is stale: " +
object.nestedPrefab.value());
335 if (!visit(resolved.value()))
valid =
false;
342 report.
status =
valid ? EditorStatus::Applied : EditorStatus::Rejected;
349 if (dependencies.status != EditorStatus::Applied)
350 return eve::editing::failed<PrefabAssetSnapshot>(dependencies.status,
351 RuleId(
"editor.prefab.invalid-dependencies"),
352 "Nested prefab dependencies are missing, invalid or cyclic");
354 bool changed =
false;
356 if (
object.nestedPrefab.empty())
continue;
357 auto resolved = resolver(
object.nestedPrefab);
359 return eve::editing::failed<PrefabAssetSnapshot>(
360 resolved.code(),
RuleId(
"editor.prefab.dependency-missing"),
"Nested prefab cannot be resolved");
361 if (
object.nestedRevision != resolved.value().revision) {
362 object.nestedRevision = resolved.value().revision;
367 return eve::editing::applied<PrefabAssetSnapshot>(std::move(result));
374 {
"parentSourceId",
object.parentSourceId.value()},
375 {
"name",
object.name},
376 {
"transform", transformValue(
object.transform)},
377 {
"nestedPrefab",
object.nestedPrefab.value()},
378 {
"nestedRevision",
static_cast<int64_t
>(
object.nestedRevision)}});
379 return EditorValue::Object{{
"schemaVersion", int64_t{2}}, {
"asset",
prefab.asset.value()},
380 {
"revision",
static_cast<int64_t
>(
prefab.revision)},
381 {
"rootSourceId",
prefab.rootSourceId.value()}, {
"objects", std::move(objects)}};
385 const EditorValue* schemaValue = field(snapshot,
"schemaVersion");
386 const EditorValue* assetValue = field(snapshot,
"asset");
387 const EditorValue* revisionValue = field(snapshot,
"revision");
388 const EditorValue* rootValue = field(snapshot,
"rootSourceId");
389 const EditorValue* objectsValue = field(snapshot,
"objects");
390 const auto* schema = schemaValue ? schemaValue->
getIf<int64_t>() :
nullptr;
391 const auto* asset = assetValue ? assetValue->
getIf<std::string>() :
nullptr;
392 const auto*
revision = revisionValue ? revisionValue->
getIf<int64_t>() :
nullptr;
393 const auto*
root = rootValue ? rootValue->
getIf<std::string>() :
nullptr;
395 if (!schema || (*schema != 1 && *schema != 2) || !asset || !
revision || *
revision <= 0 || !
root || !objects)
396 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Unsupported,
397 RuleId(
"editor.prefab.invalid-snapshot"),
398 "Prefab snapshot schema is invalid or unsupported");
407 const auto*
id = idValue ? idValue->
getIf<std::string>() :
nullptr;
408 const auto*
parentId = parentValue ? parentValue->
getIf<std::string>() :
nullptr;
409 const auto*
name = nameValue ? nameValue->
getIf<std::string>() :
nullptr;
411 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Rejected,
412 RuleId(
"editor.prefab.invalid-object"),
413 "Prefab snapshot contains an invalid object");
414 auto parsed = parseTransform(*transform);
416 return eve::editing::failed<PrefabAssetSnapshot>(EditorStatus::Rejected,
417 RuleId(
"editor.prefab.invalid-transform"),
418 "Prefab snapshot contains an invalid transform");
422 const auto* nested = nestedAssetValue ? nestedAssetValue->
getIf<std::string>() :
nullptr;
423 const auto*
pinned = nestedRevisionValue ? nestedRevisionValue->
getIf<int64_t>() :
nullptr;
425 return eve::editing::failed<PrefabAssetSnapshot>(
426 EditorStatus::Rejected,
RuleId(
"editor.prefab.invalid-nested-pin"),
427 "Prefab snapshot contains an invalid nested revision pin");
428 nestedAsset =
AssetGuid(*nested); nestedRevision =
static_cast<Revision>(*pinned);
431 nestedAsset, nestedRevision});
437 return eve::editing::applied<PrefabAssetSnapshot>(std::move(
prefab));
ActionParameterOperation operation
std::vector< QuestEvent > pending
const GltfImportRequest & request
std::weak_ptr< PrimitiveScene > scene
std::set< std::string > visiting
const UnitySourceAsset & source
Move-only operation result carrying either a value or Status.
static Result failure(Status status)
Construct a failed result from a structured status.
UUID-backed identifier adapter for legacy textual boundaries.
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
bool empty() const noexcept
True when no compatibility spelling has been assigned.
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
Result< PrefabAssetSnapshot > applyOverrides(const PrefabAssetSnapshot &prefab, const std::string &instanceId, const ObjectId &parent, const SceneTargetBase &scene) const
Produce a new prefab revision from one structurally compatible instance.
Result< PrefabAssetSnapshot > refreshNestedRevisions(const PrefabAssetSnapshot &prefab, const PrefabResolver &resolver) const
Refresh direct nested revision pins and bump the parent only when they changed.
PrefabDependencyReport inspectDependencies(const PrefabAssetSnapshot &prefab, const PrefabResolver &resolver) const
Resolve the complete nested dependency graph and diagnose cycles/stale pins.
Result< std::vector< PrefabOverrideRecord > > inspectOverrides(const PrefabAssetSnapshot &prefab, const std::string &instanceId, const ObjectId &parent, const SceneTargetBase &scene) const
Compare one instance with its source and return stable override badges.
Result< PrefabAssetSnapshot > capture(const AssetGuid &asset, const SceneTargetBase &scene, const ObjectId &root) const
Capture one scene subtree as a deterministic prefab asset.
Result< std::vector< DomainOperation > > revertOverrides(const PrefabAssetSnapshot &prefab, const std::string &instanceId, const ObjectId &parent, const SceneTargetBase &scene) const
Plan operations reverting one instance to its source prefab revision.
std::function< Result< PrefabAssetSnapshot >(const AssetGuid &)> PrefabResolver
Result< PrefabInstancePlan > instantiate(const PrefabAssetSnapshot &prefab, const std::string &instanceId, const ObjectId &parent, const SceneTargetBase &scene) const
Plan collision-safe creation of a prefab below one scene parent.
EditorValue snapshotValue(const PrefabAssetSnapshot &prefab) const
Convert a prefab snapshot to deterministic persisted data.
Shared in-memory scene target implementation for document/runtime adapters.
Result< T > failed(Status status, eve::DiagnosticCode code, RuleId rule, std::string message)
Construct a failed editing result with explicit status and diagnostic categories.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
eve::editing::Value EditorValue
Compatibility alias for the UI-independent authoring value tree.
eve::editing::TargetId TargetId
eve::editing::RuleId RuleId
scene_editing::SceneTransformValue SceneTransformValue
eve::StatusCode EditorStatus
eve::editing::Revision Revision
eve::editing::ObjectId ObjectId
const EditorValue * field(const EditorValue &value, const char *name)
Build metadata (engine git commit, build time, third-party version).
DiagnosticCode
Stable machine-readable diagnostic codes.
StrongUuid< detail::AssetGuidTag > AssetGuid
Stable asset identity used by sidecars, package manifests and caches.
Severity
Stable severity of a diagnostic, independent of its display text.
DomainOperation public API.
std::vector< ObjectRefValue > affectedObjects
Runtime-neutral prefab authoring snapshot.
std::vector< PrefabObjectRecord > objects
Nested-prefab dependency diagnostics tied to the inspected root revision.
std::vector< AssetGuid > dependencyOrder
std::vector< EditorDiagnostic > diagnostics
Stable identity mapping returned with an instantiated prefab plan.
std::vector< DomainOperation > operations
One stable scene object stored relative to a prefab root.
Request used to create a scene object in either backend.