9Result<std::map<ObjectId, SceneObjectSnapshot>> SceneTargetBase::parseSnapshot(
const EditorValue&
value) {
10 using Objects = std::map<ObjectId, SceneObjectSnapshot>;
11 const auto reject = [](
const char*
message) {
12 return editing::failed<Objects>(EditorStatus::Rejected,
RuleId(
"scene.snapshot.invalid"),
message);
15 if (!
root || !
root->contains(
"schemaVersion") || !
root->contains(
"objects") ||
16 root->size() != (
root->contains(
"schemaId") ? 3u : 2u))
17 return reject(
"Scene snapshot requires schemaId, schemaVersion and objects");
18 if (
root->contains(
"schemaId")) {
19 const auto* schema =
root->at(
"schemaId").getIf<std::string>();
20 if (!schema || *schema !=
"eve.scene.hierarchy")
return reject(
"Unknown scene schema id");
22 const auto* version =
root->at(
"schemaVersion").getIf<std::int64_t>();
24 if (!version || *version != 1 || !
entries)
return reject(
"Unsupported scene snapshot schema");
26 for (
const auto& entry : *
entries) {
29 return reject(
"Scene object requires id, parent, name and transform");
31 if (!transform || transform->size() != 9)
return reject(
"Scene transform requires all nine TRS fields");
34 auto& normalizedTransform =
36 for (
const auto*
key : {
"x",
"y",
"z",
"rotationX",
"rotationY",
"rotationZ",
"scaleX",
"scaleY",
"scaleZ"}) {
37 const auto found = transform->find(
key);
38 if (
found == transform->end())
return reject(
"Unknown or missing transform field");
40 if (
const auto* real =
found->second.getIf<
double>())
42 else if (
const auto* integer =
found->second.getIf<std::int64_t>())
43 number =
static_cast<double>(*integer);
45 return reject(
"Transform fields must be numbers");
46 if (!std::isfinite(
number) || std::abs(
number) > std::numeric_limits<float>::max())
47 return reject(
"Transform must be finite and representable by the scene runtime");
48 if (std::string_view(
key).starts_with(
"scale") &&
static_cast<float>(
number) == 0.f)
49 return reject(
"Scene scale must be nonzero");
52 auto parsed = parseObject(normalized);
54 auto object = std::move(parsed.value());
55 if (
object.
id.empty() ||
object.
name.empty() || objects.contains(
object.id))
56 return reject(
"Object ids must be unique and names nonempty");
57 objects.emplace(
object.
id, std::move(
object));
59 for (
const auto& [
id,
object] : objects) {
60 std::set<ObjectId> visited{
id};
61 auto parent =
object.parent;
63 if (!objects.contains(
parent))
return reject(
"Scene parent does not exist");
64 if (!visited.insert(
parent).second)
return reject(
"Scene hierarchy contains a cycle");
68 return editing::applied<Objects>(std::move(objects));
72 auto validated = parseSnapshot(snapshot);
80 for (
const auto& [
id,
object] : objects_)
operation.affectedObjects.push_back({
targetId(),
id.value(), 0});
81 for (
const auto& [
id,
object] : validated.
value())
82 if (!objects_.contains(
id))
operation.affectedObjects.push_back({
targetId(),
id.value(), 0});
83 return editing::applied<DomainOperation>(std::move(
operation));
ActionParameterOperation operation
std::unordered_map< std::string, QuestRuntime > entries
std::vector< std::string > fields
Move-only operation result carrying either a value or Status.
static Result failure(Status status)
Construct a failed result from a structured status.
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< DomainOperation > makeRestore(const EditorValue &snapshot) const
Plan atomic replacement of hierarchy/TRS from a versioned snapshot.
TargetId targetId() const override
Target id.
EditorValue snapshotValue() const override
Capture deterministic scene content for document persistence.
editing::Value EditorValue
DomainOperation public API.