载入中...
搜索中...
未找到
SocialDocument.cpp
浏览该文件的文档.
2
3#include <cmath>
4#include <set>
5#include <utility>
6
8namespace {
9const EditorValue* field(const EditorValue& value, const char* key) {
10 const auto* o = value.getIf<EditorValue::Object>();
11 if (!o) return nullptr;
12 const auto f = o->find(key);
13 return f == o->end() ? nullptr : &f->second;
14}
15EditorValue entityValue(const SocialEntityRecord& e) {
16 return EditorValue::Object{{"id", e.id.value()}, {"label", e.label}, {"category", e.category}};
17}
19 const auto* i = field(value, "id");
20 const auto* l = field(value, "label");
21 const auto* c = field(value, "category");
22 const auto* id = i ? i->getIf<std::string>() : nullptr;
23 const auto* label = l ? l->getIf<std::string>() : nullptr;
24 const auto* category = c ? c->getIf<std::string>() : nullptr;
25 if (!id || id->empty() || !label || label->empty() || !category)
26 return eve::editing::failed<SocialEntityRecord>(EditorStatus::Rejected, RuleId("editor.social.invalid-entity"),
27 "Social entity requires identity, label and category");
28 return eve::editing::applied<SocialEntityRecord>({StableId(*id), *label, *category});
29}
30EditorValue edgeValue(const SocialEdgeRecord& e) {
32 {"id", e.id.value()}, {"source", e.source.value()}, {"target", e.target.value()}, {"kind", e.kind},
33 {"type", e.type}, {"weight", e.weight}};
34}
36 auto str = [&](const char* k) -> const std::string* {
37 const auto* v = field(value, k);
38 return v ? v->getIf<std::string>() : nullptr;
39 };
40 const auto* w = field(value, "weight");
41 const auto* weight = w ? w->getIf<double>() : nullptr;
42 const auto *id = str("id"), *source = str("source"), *target = str("target"), *kind = str("kind"),
43 *type = str("type");
44 static const std::set<std::string> kinds{"owner", "controller", "assignment", "relation"};
45 if (!id || id->empty() || !source || source->empty() || !target || target->empty() || !kind ||
46 !kinds.contains(*kind) || !type || !weight || !std::isfinite(*weight) ||
47 ((*kind == "assignment" || *kind == "relation") && type->empty()))
48 return eve::editing::failed<SocialEdgeRecord>(
49 EditorStatus::Rejected, RuleId("editor.social.invalid-edge"),
50 "Social edge requires ids, supported kind/type and finite weight");
51 return eve::editing::applied<SocialEdgeRecord>(
53}
54DomainOperation op(const char* t, const char* inverse, const std::string& target, EditorValue payload, EditorValue undo,
55 const StableId& id) {
57 r.type = t;
58 r.inverseType = inverse;
59 r.target = TargetId(target);
60 r.payload = std::move(payload);
61 r.inverse = std::move(undo);
62 r.hasInverse = true;
63 r.affectedObjects.push_back({TargetId(target), id.value(), 0});
64 return r;
65}
66template <class T>
67std::vector<T> values(const std::map<StableId, T>& m) {
68 std::vector<T> r;
69 for (const auto& [id, v] : m) {
70 (void)id;
71 r.push_back(v);
72 }
73 return r;
74}
75} // namespace
76
77SocialDocumentTarget::SocialDocumentTarget(std::string id) : id_(std::move(id)) {}
79 return {TargetId(id_), "social-document", revisionValue(), false, {editorCapabilityId()}};
80}
82 return capability == editorCapabilityId() ? static_cast<SocialDocumentTarget*>(this) : nullptr;
83}
85 if (o.target != TargetId(id_))
86 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.target"),
87 "Operation targets another social document");
88 if (o.type == "social.entity.set.v1") {
89 auto v = parseEntity(o.payload);
90 if (!v.ok())
91 return eve::editing::failed<void>(v.code(), RuleId("editor.social.entity-payload"),
92 "Invalid entity payload");
93 entities_.insert_or_assign(v.value().id, v.value());
94 } else if (o.type == "social.edge.set.v1") {
95 auto v = parseEdge(o.payload);
96 if (!v.ok())
97 return eve::editing::failed<void>(v.code(), RuleId("editor.social.edge-payload"), "Invalid edge payload");
98 if (!entities_.contains(v.value().source) || !entities_.contains(v.value().target))
99 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.social.missing-endpoint"),
100 "Social edge endpoints must exist");
101 edges_.insert_or_assign(v.value().id, v.value());
102 } else if (o.type == "social.entity.delete.v1" || o.type == "social.edge.delete.v1") {
103 const auto* id = o.payload.getIf<std::string>();
104 if (!id)
105 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.delete-payload"),
106 "Delete requires a stable id");
107 const StableId stable(*id);
108 if (o.type == "social.entity.delete.v1") {
109 for (const auto& [edgeId, e] : edges_) {
110 (void)edgeId;
111 if (e.source == stable || e.target == stable)
112 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.social.entity-in-use"),
113 "Entity is referenced by an edge");
114 }
115 if (!entities_.erase(stable))
116 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.social.entity-not-found"),
117 "Entity was not found");
118 } else if (!edges_.erase(stable))
119 return eve::editing::failed<void>(EditorStatus::NotFound, RuleId("editor.social.edge-not-found"),
120 "Edge was not found");
121 } else
122 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.operation"),
123 "Unsupported social operation");
124 bumpRevision();
125 widenDirty(0, 0);
126 return eve::editing::applied<void>();
127}
128std::unique_ptr<IDomainOperationTarget> SocialDocumentTarget::cloneDomainState() const {
129 return std::make_unique<SocialDocumentTarget>(*this);
130}
131Result<void> SocialDocumentTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> candidate) {
132 auto* t = dynamic_cast<SocialDocumentTarget*>(candidate.get());
133 if (!t || t->id_ != id_)
134 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.staging"),
135 "Invalid staged social state");
136 *this = *t;
137 return eve::editing::applied<void>();
138}
139std::vector<SocialEntityRecord> SocialDocumentTarget::entities() const { return values(entities_); }
140std::vector<SocialEdgeRecord> SocialDocumentTarget::edges() const { return values(edges_); }
142 if (!parseEntity(entityValue(e)).ok())
143 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.social.invalid-entity"),
144 "Cannot plan invalid entity");
145 const auto f = entities_.find(e.id);
146 return eve::editing::applied<DomainOperation>(
147 op("social.entity.set.v1", f == entities_.end() ? "social.entity.delete.v1" : "social.entity.set.v1", id_,
148 entityValue(e), f == entities_.end() ? EditorValue(e.id.value()) : entityValue(f->second), e.id));
149}
151 const auto f = entities_.find(id);
152 if (f == entities_.end())
153 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.social.entity-not-found"),
154 "Entity was not found");
155 for (const auto& [edgeId, e] : edges_) {
156 (void)edgeId;
157 if (e.source == id || e.target == id)
158 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict, RuleId("editor.social.entity-in-use"),
159 "Entity is referenced by an edge");
160 }
161 return eve::editing::applied<DomainOperation>(
162 op("social.entity.delete.v1", "social.entity.set.v1", id_, id.value(), entityValue(f->second), id));
163}
165 if (!parseEdge(edgeValue(e)).ok())
166 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.social.invalid-edge"),
167 "Cannot plan invalid edge");
168 if (!entities_.contains(e.source) || !entities_.contains(e.target))
169 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict, RuleId("editor.social.missing-endpoint"),
170 "Social edge endpoints must exist");
171 for (const auto& [id, current] : edges_)
172 if (id != e.id && current.source == e.source && current.target == e.target && current.kind == e.kind &&
173 current.type == e.type)
174 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict, RuleId("editor.social.duplicate-edge"),
175 "Equivalent social edge already exists");
176 const auto f = edges_.find(e.id);
177 return eve::editing::applied<DomainOperation>(
178 op("social.edge.set.v1", f == edges_.end() ? "social.edge.delete.v1" : "social.edge.set.v1", id_, edgeValue(e),
179 f == edges_.end() ? EditorValue(e.id.value()) : edgeValue(f->second), e.id));
180}
182 const auto f = edges_.find(id);
183 if (f == edges_.end())
184 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.social.edge-not-found"),
185 "Edge was not found");
186 return eve::editing::applied<DomainOperation>(
187 op("social.edge.delete.v1", "social.edge.set.v1", id_, id.value(), edgeValue(f->second), id));
188}
189std::vector<EditorDiagnostic> SocialDocumentTarget::validate() const {
190 std::vector<EditorDiagnostic> r;
191 std::set<std::tuple<StableId, StableId, std::string, std::string>> unique;
192 std::map<StableId, int> owners, controllers;
193 for (const auto& [id, e] : edges_) {
194 if (!entities_.contains(e.source) || !entities_.contains(e.target))
196 eve::DiagnosticCode::NotFound, RuleId("editor.social.missing-endpoint"),
197 DiagnosticSeverity::Error, "Edge " + id.value() + " has a missing endpoint"));
198 if (!unique.emplace(e.source, e.target, e.kind, e.type).second)
200 eve::DiagnosticCode::Conflict, RuleId("editor.social.duplicate-edge"),
201 DiagnosticSeverity::Error, "Duplicate social edge semantics"));
202 if (e.kind == "owner" && ++owners[e.source] > 1)
204 eve::DiagnosticCode::Conflict, RuleId("editor.social.multiple-owners"),
205 DiagnosticSeverity::Error, "Entity has multiple owners"));
206 if (e.kind == "controller" && ++controllers[e.source] > 1)
208 eve::DiagnosticCode::Conflict, RuleId("editor.social.multiple-controllers"),
209 DiagnosticSeverity::Error, "Entity has multiple controllers"));
210 }
211 return r;
212}
215 for (const auto& [id, v] : entities_) {
216 (void)id;
217 entities.push_back(entityValue(v));
218 }
219 for (const auto& [id, v] : edges_) {
220 (void)id;
221 edges.push_back(edgeValue(v));
222 }
223 return EditorValue::Object{
224 {"schemaVersion", int64_t{1}}, {"entities", std::move(entities)}, {"edges", std::move(edges)}};
225}
227 const auto *v = field(s, "schemaVersion"), *ns = field(s, "entities"), *es = field(s, "edges");
228 const auto* version = v ? v->getIf<int64_t>() : nullptr;
229 const auto* nodes = ns ? ns->getIf<EditorValue::Array>() : nullptr;
230 const auto* edges = es ? es->getIf<EditorValue::Array>() : nullptr;
231 if (!version || *version != 1 || !nodes || !edges)
232 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.snapshot"),
233 "Invalid social snapshot envelope");
235 for (const auto& e : *nodes) {
236 auto p = parseEntity(e);
237 if (!p.ok() || !c.entities_.emplace(p.value().id, p.value()).second)
238 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.snapshot-entity"),
239 "Invalid or duplicate snapshot entity");
240 }
241 for (const auto& e : *edges) {
242 auto p = parseEdge(e);
243 if (!p.ok() || !c.edges_.emplace(p.value().id, p.value()).second)
244 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.snapshot-edge"),
245 "Invalid or duplicate snapshot edge");
246 }
247 for (const auto& d : c.validate())
248 if (d.severity() == DiagnosticSeverity::Error)
249 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.social.snapshot-invariant"),
250 "Social snapshot violates graph invariants");
251 entities_ = std::move(c.entities_);
252 edges_ = std::move(c.edges_);
253 bumpRevision();
254 widenDirty(0, 0);
255 return eve::editing::applied<void>();
256}
257
258} // namespace eve::social_editing
LogicalId target
double value
Value::Object payload
float w
Definition AnimClip.cpp:738
const std::string & s
glm::vec4 p[6]
std::string label
std::map< std::string, Var > values
std::uint32_t key
double r
float v
std::int32_t c
TokenKind kind
std::vector< BvhNode > nodes
float f
std::string id
Definition PlayHost.cpp:108
float d
float t
double current
const char * category
const UnitySourceAsset & source
float m[16]
Move-only operation result carrying either a value or Status.
Definition Result.h:155
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
eve::OptionalRef< C > capability()
Query a capability through its interface-owned stable identity.
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.
UI-neutral revisioned social graph authoring document.
Result< DomainOperation > makeSetEntity(const SocialEntityRecord &entity) const
Plan reversible entity creation or replacement.
Result< void > loadSnapshot(const EditorValue &snapshot)
Atomically load a validated graph snapshot.
std::vector< EditorDiagnostic > validate() const
Validate references, uniqueness constraints and finite weights.
Result< DomainOperation > makeDeleteEntity(const StableId &id) const
Plan deletion of an entity and reject it while edges reference it.
Result< DomainOperation > makeDeleteEdge(const StableId &id) const
Plan reversible edge deletion.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget > candidate) override
Commits domain state.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
std::vector< SocialEntityRecord > entities() const
Enumerate entities in stable order.
EditorValue snapshotValue() const
Capture deterministic schema-version-one graph content.
std::vector< SocialEdgeRecord > edges() const
Enumerate edges in stable order.
SocialDocumentTarget(std::string id)
Social document target.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
void * queryCapability(const CapabilityId &capability) override
Query an optional target capability.
static CapabilityId editorCapabilityId()
Editor capability id.
TargetDescriptor describe() const override
Describe.
Result< DomainOperation > makeSetEdge(const SocialEdgeRecord &edge) const
Plan reversible edge creation or replacement.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
editing::StableId StableId
editing::RuleId RuleId
editing::TargetId TargetId
editing::Value EditorValue
DomainOperation public API.
Stable discovery metadata exposed by every editable target.
Directed ownership, control, assignment or weighted relation edge.
Stable social-graph entity metadata for graph presenters.