载入中...
搜索中...
未找到
EditorPropertyModel.cpp
浏览该文件的文档.
3
4#include <algorithm>
5#include <atomic>
6#include <chrono>
7#include <cstdint>
8#include <span>
9#include <utility>
10
11namespace eve::editor {
12namespace {
13
14template <class T>
15property_access::WriteResult writeFailure(const Result<T>& result, std::string fallbackCode,
16 std::string fallbackMessage) {
17 if (!result.diagnostics().empty()) {
18 const EditorDiagnostic& diagnostic = result.diagnostics().front();
19 const RuleId rule = eve::editing::diagnosticRule(diagnostic);
20 if (!rule.empty() && !diagnostic.message().empty())
21 return property_access::WriteResult::reject(rule.value(), diagnostic.message());
22 if (!diagnostic.message().empty())
23 return property_access::WriteResult::reject(std::move(fallbackCode), diagnostic.message());
24 }
25 return property_access::WriteResult::reject(std::move(fallbackCode), std::move(fallbackMessage));
26}
27
28TransactionId newPropertyTransactionId() {
29 static std::atomic<std::uint64_t> entropySequence{1};
30 const eve::UuidV7Generator generator([](std::span<std::uint8_t> bytes) {
31 const std::uint64_t seed = entropySequence.fetch_add(1, std::memory_order_relaxed);
32 for (std::size_t index = 0; index < bytes.size(); ++index)
33 bytes[index] = static_cast<std::uint8_t>(seed >> ((index % sizeof(seed)) * 8u));
34 return true;
35 });
36 const auto generated = generator.generate(std::chrono::system_clock::now());
37 if (!generated) {
38 // A clock before the UUID epoch is not expected for a live editor, but
39 // retain a valid canonical UUID path if a platform clock is malformed.
40 const auto epoch = generator.generate(std::chrono::system_clock::time_point{});
41 if (!epoch) return TransactionId{};
42 using CanonicalUuid = typename TransactionId::Uuid;
43 return TransactionId::fromUuid(CanonicalUuid(epoch->bytes()));
44 }
45 using CanonicalUuid = typename TransactionId::Uuid;
46 return TransactionId::fromUuid(CanonicalUuid(generated->bytes()));
47}
48
49TargetId selectionTarget(const SelectionSnapshot& selection) {
50 if (selection.primary && !selection.primary->target.empty()) return selection.primary->target;
51 for (const SelectionItem& item : selection.items)
52 if (!item.target.empty()) return item.target;
53 return {};
54}
55
56EditorStatus editorStatusFor(eve::StatusCode code) {
57 switch (code) {
58 case eve::StatusCode::Conflict: return EditorStatus::Conflict;
59 case eve::StatusCode::NotFound: return EditorStatus::NotFound;
60 case eve::StatusCode::Unsupported: return EditorStatus::Unsupported;
61 case eve::StatusCode::Cancelled: return EditorStatus::Cancelled;
62 case eve::StatusCode::Rejected: return EditorStatus::Rejected;
67 case eve::StatusCode::Failed: return EditorStatus::Failed;
68 }
69 return EditorStatus::Failed;
70}
71
72Result<eve::Revision> providerRevisionFailure(const eve::Result<eve::Revision>& result) {
73 const eve::Status& status = result.status();
74 const char* rule = "editor.property.current-revision";
75 switch (status.code()) {
76 case eve::StatusCode::Conflict: rule = "editor.property.revision-conflict"; break;
77 case eve::StatusCode::Unsupported: rule = "editor.property.revision-unsupported"; break;
78 case eve::StatusCode::NotFound: rule = "editor.property.target"; break;
79 default: break;
80 }
81 return eve::editing::failed<eve::Revision>(editorStatusFor(status.code()), RuleId(rule),
82 status.describe());
83}
84
85Result<void> externalRevisionConflict(eve::Revision expected, eve::Revision actual) {
86 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.property.revision-conflict"),
87 "Property target revision changed from " + std::to_string(expected.value()) +
88 " to " + std::to_string(actual.value()) + "; refresh/rebase before writing");
89}
90
91} // namespace
92
94 struct Entry {
95 std::uint64_t id = 0;
97 };
98 std::uint64_t nextId = 1;
99 std::vector<Entry> entries;
100};
101
103 const IPropertyProvider* provider, PropertyModelSurface surface,
104 HostProfile profile, IEditorTransactionBackend* transactionBackend)
105 : editorSchema_(std::move(schema)),
106 selection_(std::move(selection)),
107 provider_(provider),
108 surface_(surface),
109 profile_(std::move(profile)),
110 transactionBackend_(transactionBackend),
111 observers_(std::make_shared<ObserverState>()) {
112 rebuildSchema();
113 const Result<void> initialBinding = bind();
114 if (!initialBinding.ok()) {
115 // A constructor cannot return a Result. Keep the model unbound so
116 // every write fails closed until the caller explicitly refreshes.
117 }
118}
119
121
122void EditorPropertyModel::rebuildSchema() {
123 presentationSchema_.typeId = editorSchema_.typeId;
124 presentationSchema_.version = editorSchema_.version;
125 presentationSchema_.properties.clear();
126 if (surface_ == PropertyModelSurface::Runtime && !profile_.hasFeatures(HostFeature::RuntimeWorld)) return;
127 for (const PropertyDescriptor& property : editorSchema_.properties) {
128 if (surface_ == PropertyModelSurface::Runtime && (!hasPropertyFlag(property.flags, PropertyFlag::Runtime) ||
129 hasPropertyFlag(property.flags, PropertyFlag::EditorOnly)))
130 continue;
131 presentationSchema_.properties.push_back(toPresentationDescriptor(property));
132 }
133}
134
135std::optional<eve::Value> EditorPropertyModel::read(const std::string& path) const {
136 if (!presentationSchema_.find(path)) return std::nullopt;
137 const auto found = cachedProperties_.find(path);
138 if (found == cachedProperties_.end() || found->second.state != property_access::PropertyChangeState::Value)
139 return std::nullopt;
140 return found->second.value;
141}
142
144 if (!provider_) return property_access::WriteResult::reject("editor.property.provider", "No property provider");
145 const Result<void> revisionCheck = ensureCurrentRevision();
146 if (!revisionCheck.ok())
147 return writeFailure(revisionCheck, "editor.property.revision", "Property revision is unavailable");
148 if (transactionBackend_ && transactionBackend_->active() && pendingPaths_.contains(path))
149 return property_access::WriteResult::reject("editor.property.duplicate-path",
150 "A property may be written only once per transaction");
151
152 const EditorValue editorValue = toEditorValue(value);
153 Result<PropertyEditIntent> intent = [&]() {
154 if (surface_ == PropertyModelSurface::Runtime) {
155 RuntimePropertyPresenter presenter;
156 return presenter.editIntent(editorSchema_, selection_, PropertyPath(path), editorValue, profile_);
157 }
159 return presenter.editIntent(editorSchema_, selection_, PropertyPath(path), editorValue);
160 }();
161 if (!intent.ok()) {
162 return writeFailure(intent, "editor.property.intent", "Property edit was rejected");
163 }
164
165 if (transactionBackend_) {
167 provider_->makeSet(selection_, PropertyPath(path), editorValue, PropertySetMode::Absolute);
168 if (!operation.ok())
169 return writeFailure(operation, "editor.property.operation", "Property operation was rejected");
170 if (operation.value().target.empty())
171 return property_access::WriteResult::reject("editor.property.target", "Property operation has no target");
172 if (operation.value().mergeKey.empty())
173 operation.value().mergeKey = intent.value().command.value() + ":" + path;
174
175 bool started = false;
176 if (!transactionBackend_->active()) {
177 TransactionSpec specification;
178 specification.id = newPropertyTransactionId();
179 specification.label = intent.value().command.value();
180 specification.target = operation.value().target;
181 specification.mergeKey = operation.value().mergeKey;
182 specification.baseRevision = targetRevision_.value();
183 if (specification.id.empty())
184 return property_access::WriteResult::reject("editor.property.transaction-id",
185 "Could not allocate a property transaction identity");
186 Result<TransactionId> begun = transactionBackend_->begin(std::move(specification));
187 if (!begun.ok() || !begun.ok())
188 return writeFailure(begun, "editor.property.transaction.begin", "Could not begin property transaction");
189 started = true;
190 }
191
192 Result<void> appended = transactionBackend_->append(std::move(operation.value()));
193 if (!appended.ok()) {
194 if (started) {
195 Result<void> discarded = transactionBackend_->discard();
196 if (!discarded.ok())
197 return writeFailure(discarded, "editor.property.transaction.discard",
198 "Could not discard rejected property transaction");
199 }
200 return writeFailure(appended, "editor.property.transaction.append", "Could not stage property operation");
201 }
202 pendingPaths_.insert(path);
203
204 // An already-open transaction is intentionally left staged for the
205 // caller's explicit preview/commit boundary.
207
208 Result<EditorDryRunReport> previewed = transactionBackend_->preview();
209 if (!previewed.ok() || !previewed.ok()) {
210 Result<void> discarded = transactionBackend_->discard();
211 pendingPaths_.clear();
212 if (!discarded.ok())
213 return writeFailure(discarded, "editor.property.transaction.discard",
214 "Could not discard failed property preview");
215 return writeFailure(previewed, "editor.property.transaction.preview",
216 "Property transaction preview was rejected");
217 }
218
219 Result<TransactionReceipt> committed = transactionBackend_->commit();
220 if (!committed.ok() || !committed.ok())
221 // Commit failure is deliberately retained by the backend so the
222 // caller can inspect diagnostics, retry, or explicitly discard.
223 return writeFailure(committed, "editor.property.transaction.commit", "Property transaction commit failed");
224 targetRevision_ = eve::Revision(committed.value().afterRevision);
225 pendingPaths_.clear();
226 // The mutation already committed. Do not report a successful commit
227 // as a rejected write; force an explicit refresh if observation fails.
228 if (!refresh().ok()) bound_ = false;
230 }
231
232 if (!sink_) return property_access::WriteResult::reject("editor.property.sink", "No command sink is connected");
233 // The sink is a compatibility path and may not have an authority that can
234 // perform a commit-time CAS. Recheck immediately before handing it the
235 // intent; stale compatibility writes must fail closed as well.
236 const Result<void> sinkRevisionCheck = ensureCurrentRevision();
237 if (!sinkRevisionCheck.ok())
238 return writeFailure(sinkRevisionCheck, "editor.property.revision", "Property revision is unavailable");
239 const Result<void> applied = sink_(intent.value());
240 if (!applied.ok()) return writeFailure(applied, "editor.property.command", "Property command failed");
241 // A legacy sink may mutate the provider without returning a receipt. Read
242 // the authoritative post-command revision so the next write does not use
243 // the pre-command baseline. If observation fails, retain command success
244 // and require an explicit refresh before another write.
245 if (!refresh().ok()) bound_ = false;
247}
248
250 if (transactionBackend_ && transactionBackend_->active())
251 return eve::editing::failed<void>(
252 EditorStatus::Conflict, RuleId("editor.property.active-transaction"),
253 "Cannot replace a property transaction backend while a transaction is active");
254 if (backend && backend->active())
255 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.property.backend-active"),
256 "Cannot attach a backend that already has an active transaction");
257 transactionBackend_ = backend;
258 pendingPaths_.clear();
259 return eve::editing::applied<void>();
260}
261
263 if (!transactionBackend_)
264 return eve::editing::failed<TransactionId>(EditorStatus::Unsupported,
265 RuleId("editor.property.transaction-backend"),
266 "A transaction backend is not configured");
267 if (transactionBackend_->active())
268 return eve::editing::failed<TransactionId>(EditorStatus::Conflict, RuleId("editor.property.transaction-active"),
269 "A transaction is already active");
270 const TargetId target = selectionTarget(selection_);
271 if (target.empty())
272 return eve::editing::failed<TransactionId>(EditorStatus::Rejected, RuleId("editor.property.target"),
273 "An explicit property transaction requires a selected target");
274 const Result<void> revisionCheck = ensureCurrentRevision();
275 if (!revisionCheck.ok()) {
276 return Result<TransactionId>::failure(revisionCheck.status());
277 }
278 TransactionSpec specification;
279 specification.id = newPropertyTransactionId();
280 specification.label = std::move(label);
281 specification.target = target;
282 specification.baseRevision = targetRevision_.value();
283 specification.mergeKey = "editor.property";
284 if (specification.id.empty())
285 return eve::editing::failed<TransactionId>(EditorStatus::Failed, RuleId("editor.property.transaction-id"),
286 "Could not allocate a property transaction identity");
287 Result<TransactionId> result = transactionBackend_->begin(std::move(specification));
288 if (result.ok()) pendingPaths_.clear();
289 return result;
290}
291
293 if (!transactionBackend_)
294 return eve::editing::failed<EditorDryRunReport>(EditorStatus::Unsupported,
295 RuleId("editor.property.transaction-backend"),
296 "A transaction backend is not configured");
297 Result<EditorDryRunReport> result = transactionBackend_->preview();
298 if (!result.ok()) return result;
299 return result;
300}
301
303 if (!transactionBackend_)
304 return eve::editing::failed<TransactionReceipt>(EditorStatus::Unsupported,
305 RuleId("editor.property.transaction-backend"),
306 "A transaction backend is not configured");
307 Result<TransactionReceipt> result = transactionBackend_->commit();
308 if (!result.ok()) return result;
309 targetRevision_ = eve::Revision(result.value().afterRevision);
310 pendingPaths_.clear();
311 const Result<void> refreshed = refresh();
312 if (!refreshed.ok())
314 eve::Status(EditorStatus::Applied, refreshed.diagnostics()));
315 return result;
316}
317
319 if (!transactionBackend_)
320 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.property.transaction-backend"),
321 "A transaction backend is not configured");
322 Result<void> result = transactionBackend_->discard();
323 if (result.ok()) pendingPaths_.clear();
324 return result;
325}
326
328 if (!transactionBackend_)
329 return eve::editing::failed<TransactionReceipt>(EditorStatus::Unsupported,
330 RuleId("editor.property.transaction-backend"),
331 "A transaction backend is not configured");
332 Result<TransactionReceipt> result = transactionBackend_->retry();
333 if (!result.ok()) return result;
334 targetRevision_ = eve::Revision(result.value().afterRevision);
335 pendingPaths_.clear();
336 const Result<void> refreshed = refresh();
337 if (!refreshed.ok())
339 eve::Status(EditorStatus::Applied, refreshed.diagnostics()));
340 return result;
341}
342
344 if (!transactionBackend_)
345 return eve::editing::failed<TransactionReceipt>(EditorStatus::Unsupported,
346 RuleId("editor.property.transaction-backend"),
347 "A transaction backend is not configured");
348 Result<TransactionReceipt> result = transactionBackend_->undo();
349 if (!result.ok()) return result;
350 targetRevision_ = eve::Revision(result.value().afterRevision);
351 const Result<void> refreshed = refresh();
352 if (!refreshed.ok())
354 eve::Status(EditorStatus::Applied, refreshed.diagnostics()));
355 return result;
356}
357
359 if (!transactionBackend_)
360 return eve::editing::failed<TransactionReceipt>(EditorStatus::Unsupported,
361 RuleId("editor.property.transaction-backend"),
362 "A transaction backend is not configured");
363 Result<TransactionReceipt> result = transactionBackend_->redo();
364 if (!result.ok()) return result;
365 targetRevision_ = eve::Revision(result.value().afterRevision);
366 const Result<void> refreshed = refresh();
367 if (!refreshed.ok())
369 eve::Status(EditorStatus::Applied, refreshed.diagnostics()));
370 return result;
371}
372
374 const std::uint64_t id = observers_->nextId++;
375 observers_->entries.push_back({id, std::move(callback)});
376 std::weak_ptr<ObserverState> weak = observers_;
377 return property_access::Subscription([weak, id]() {
378 if (const auto state = weak.lock())
379 std::erase_if(state->entries, [id](const ObserverState::Entry& entry) { return entry.id == id; });
380 });
381}
382
383Result<eve::Revision> EditorPropertyModel::readProviderRevision() const {
384 if (!provider_)
385 return eve::editing::failed<eve::Revision>(EditorStatus::Unsupported, RuleId("editor.property.provider"),
386 "No property provider is connected");
387 eve::Result<eve::Revision> result = provider_->currentRevision(selection_);
388 if (!result.ok()) return providerRevisionFailure(result);
389 return eve::editing::applied<eve::Revision>(eve::Revision(result.value().value()));
390}
391
392Result<void> EditorPropertyModel::ensureCurrentRevision() const {
393 const Result<eve::Revision> current = readProviderRevision();
394 if (!current.ok()) return Result<void>::failure(current.status());
395 if (!bound_)
396 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.property.unbound"),
397 "Property model is not bound to a provider revision; call refresh/rebase");
398 if (current.value() != targetRevision_) return externalRevisionConflict(targetRevision_, current.value());
399 return eve::editing::applied<void>();
400}
401
403
405
407 if (!provider_)
408 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.property.provider"),
409 "No property provider is connected");
410 if (transactionBackend_ && transactionBackend_->active())
411 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.property.active-transaction"),
412 "Cannot refresh or rebase while a property transaction is active");
413
414 const Result<eve::Revision> first = readProviderRevision();
415 if (!first.ok()) return Result<void>::failure(first.status());
416
417 std::map<std::string, CachedProperty> nextProperties;
418 for (const property_access::PropertyDescriptor& property : presentationSchema_.properties) {
419 const PropertyReadResult current = provider_->read(selection_, PropertyPath(property.path));
420 if (current.state == PropertyReadState::Error) {
421 if (!current.diagnostics.empty())
422 return Result<void>::failure(eve::Status(EditorStatus::Failed, current.diagnostics));
423 return eve::editing::failed<void>(EditorStatus::Failed, RuleId("editor.property.read"),
424 "Property provider returned a read error");
425 }
426 CachedProperty cached;
427 switch (current.state) {
428 case PropertyReadState::Value:
430 cached.value = toPresentationValue(current.value);
431 break;
432 case PropertyReadState::Mixed: cached.state = property_access::PropertyChangeState::Mixed; break;
433 case PropertyReadState::Missing: cached.state = property_access::PropertyChangeState::Missing; break;
434 case PropertyReadState::Error: break;
435 }
436 nextProperties.emplace(property.path, std::move(cached));
437 }
438
439 const Result<eve::Revision> last = readProviderRevision();
440 if (!last.ok()) return Result<void>::failure(last.status());
441 if (first.value() != last.value()) return externalRevisionConflict(first.value(), last.value());
442
443 std::vector<property_access::PropertyChange> changes;
444 for (const auto& [path, property] : nextProperties) {
445 const auto found = cachedProperties_.find(path);
446 if (found == cachedProperties_.end() || found->second != property)
447 changes.push_back({path, property.value.value_or(eve::Value{}), 0, property.state});
448 }
449
450 if (!changes.empty()) {
451 const std::uint64_t snapshotRevision = ++revision_;
452 for (property_access::PropertyChange& change : changes) change.revision = snapshotRevision;
453 }
454 cachedProperties_ = std::move(nextProperties);
455 targetRevision_ = last.value();
456 bound_ = true;
457 dispatch(std::move(changes));
458 return eve::editing::applied<void>();
459}
460
461void EditorPropertyModel::dispatch(std::vector<property_access::PropertyChange> changes) {
462 if (changes.empty()) return;
463 notificationQueue_.push_back(std::move(changes));
464 if (dispatchingNotifications_) return;
465
466 dispatchingNotifications_ = true;
467 try {
468 while (!notificationQueue_.empty()) {
469 std::vector<property_access::PropertyChange> batch = std::move(notificationQueue_.front());
470 notificationQueue_.erase(notificationQueue_.begin());
471 for (const property_access::PropertyChange& change : batch) {
472 const auto observers = observers_->entries;
473 for (const ObserverState::Entry& entry : observers)
474 if (entry.callback) entry.callback(change);
475 }
476 }
477 dispatchingNotifications_ = false;
478 } catch (...) {
479 notificationQueue_.clear();
480 dispatchingNotifications_ = false;
481 throw;
482 }
483}
484
485} // namespace eve::editor
LogicalId target
ActionParameterOperation operation
double value
std::string label
DiagnosticCode code
wgpu::PopErrorScopeStatus status
std::int32_t first
std::map< std::string, PropertyRule > properties
std::uint64_t bytes
uint64_t epoch
Definition OnnxGpgpu.cpp:28
std::uint64_t revision
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::uint32_t seed
Definition PointSet.cpp:807
bool found
double current
uint32_t index
ViewPreparation callback
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
const T & value() const &
Borrow the value from a const lvalue after checking success.
Definition Result.h:308
const std::vector< Diagnostic > & diagnostics() const noexcept
Inspect all diagnostics; this counts as checking the Result.
Definition Result.h:283
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:255
const Status & status() const noexcept
Inspect the structured operation status.
Definition Result.h:269
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
Move-only RAII token that owns one observer registration.
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
static UuidIdAdapter fromUuid(Uuid value)
Builds an adapter from an explicit canonical UUID.
Definition Identity.h:343
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
StrongUuid< Tag > Uuid
Definition Identity.h:316
bool empty() const noexcept
True when no compatibility spelling has been assigned.
Definition Identity.h:374
Injectable UUIDv7 generator.
Definition Identity.h:478
std::optional< PersistentId > generate() const
Generates a UUIDv7 using the injected clock.
Definition Identity.cpp:80
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
UI-independent property access for an immutable selection snapshot.
virtual Result< DomainOperation > makeSet(const SelectionSnapshot &selection, const PropertyPath &path, const Value &value, PropertySetMode mode) const =0
Make set.
virtual PropertyReadResult read(const SelectionSnapshot &selection, const PropertyPath &path) const =0
Reads read.
virtual eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &selection) const
Current revision.
Deterministic owning value tree shared by authoring hosts.
Shared presenter logic that exposes developer-oriented property rows.
Result< PropertyEditIntent > editIntent(const PropertySchema &schema, const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, PropertySetMode mode=PropertySetMode::Absolute) const
Validate a candidate and emit a command payload without mutating the provider.
property_access::WriteResult write(const std::string &path, const eve::Value &value) override
Writes .
Result< TransactionReceipt > undo()
Undo the latest committed property transaction.
Result< void > setTransactionBackend(IEditorTransactionBackend *backend)
Set the borrowed canonical transaction boundary.
property_access::Subscription subscribe(ChangeCallback callback) override
Subscribe.
Result< void > rollbackTransaction()
Discard pending property writes without publishing them.
~EditorPropertyModel() override
Editor property model.
Result< EditorDryRunReport > previewTransaction()
Validate pending property writes without changing the target.
Result< TransactionReceipt > redo()
Redo the latest undone property transaction.
EditorPropertyModel(PropertySchema schema, SelectionSnapshot selection, const IPropertyProvider *provider, PropertyModelSurface surface=PropertyModelSurface::Developer, HostProfile profile=HostProfile::developer(), IEditorTransactionBackend *transactionBackend=nullptr)
Editor property model.
Result< TransactionReceipt > commitTransaction()
Commit pending property writes and refresh observers after success.
Result< TransactionId > beginTransaction(std::string label="Edit property")
Begin an explicit property transaction.
Result< TransactionReceipt > retryTransaction()
Retry a retained failed commit.
Result< void > refresh()
Re-read values and adopt the provider's current revision.
Result< void > bind()
Bind to the provider's current revision and values.
Result< void > rebase()
Explicit spelling for refreshing and rebasing this model.
std::optional< eve::Value > read(const std::string &path) const override
Reads .
Upper capability boundary for one editor host.
bool hasFeatures(HostFeature required) const
True when features.
Transaction boundary consumed by editor property and other UI models.
virtual Result< TransactionReceipt > redo()=0
Reapply the latest compensated transaction.
virtual Result< void > discard()=0
Discard pending work after reverting any provisional preview.
virtual Result< TransactionReceipt > undo()=0
Compensate the latest committed transaction.
virtual Result< TransactionReceipt > commit()=0
Publish the pending transaction and retain it for undo.
virtual Result< TransactionReceipt > retry()=0
Retry a failed commit while retaining its original operation list.
virtual Result< void > append(DomainOperation operation)=0
Append a serializable operation to the pending transaction.
virtual bool active() const noexcept=0
Whether a transaction is currently pending.
virtual Result< EditorDryRunReport > preview()=0
Validate the pending transaction without publishing target state.
virtual Result< TransactionId > begin(TransactionSpec specification)=0
Begin an explicit transaction without mutating its target.
Shared presenter logic that exposes only runtime-safe property rows.
Result< PropertyEditIntent > editIntent(const PropertySchema &schema, const SelectionSnapshot &selection, const PropertyPath &path, const EditorValue &value, const HostProfile &profile, PropertySetMode mode=PropertySetMode::Absolute) const
Validate a runtime-visible candidate and emit the same command payload as developer UI.
std::function< void(const PropertyChange &)> ChangeCallback
RuleId diagnosticRule(const Diagnostic &diagnostic)
Return the projected editing rule, or an empty RuleId when absent.
EditorValue toEditorValue(const eve::Value &value)
Compatibility spelling for conversion into the authoring value tree.
eve::editing::PropertyPath PropertyPath
Definition EditorIds.h:37
eve::editing::SelectionItem SelectionItem
Stable item stored in a selection snapshot.
eve::editing::TargetId TargetId
Definition EditorIds.h:29
eve::Diagnostic EditorDiagnostic
eve::editing::RuleId RuleId
Definition EditorIds.h:32
eve::editing::SelectionSnapshot SelectionSnapshot
Immutable multi-selection captured for property and command work.
PropertyModelSurface
Property visibility/permission surface exposed by an editor model.
eve::editing::TransactionId TransactionId
Definition EditorIds.h:34
eve::StatusCode EditorStatus
Definition EditorResult.h:8
eve::Subscription Subscription
Compatibility alias for the common observer lifetime token.
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
detail::StrongUint64< detail::RevisionTag > Revision
Monotonic content/state revision used for optimistic-concurrency checks.
Definition Revision.h:13
PropertyDescriptor public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
SelectionSnapshot public API.
TransactionSpec public API.
Immutable notification emitted after a model property changes.
One stable property description consumed by any UI shell.
eve::OptionalRef< const PropertyDescriptor > find(const std::string &path) const
Find a property by stable path, or an empty immediate borrow.
std::vector< PropertyDescriptor > properties
Result of a model write, including a stable diagnostic code.
static WriteResult success()
Success.
static WriteResult reject(std::string code, std::string message)
Reject.
bool started
Definition Graphics.cpp:183