#pragma once #include "editor/EditorHostProfile.h" #include "editor/EditorPropertyPresenter.h" #include "property_access/PropertyAccess.h" #include #include #include #include #include #include #include namespace eve::editor { class IEditorTransactionBackend; struct EditorDryRunReport; /** @brief Property visibility/permission surface exposed by an editor model. */ enum class PropertyModelSurface { Developer, Runtime }; /** * @brief Adapts command-backed editor properties to the shared MVVM contract. * * The adapter never mutates a target directly. When a transaction backend is * injected, a UI write is converted into a PropertyEditIntent and a * DomainOperation, then staged and committed through that backend. The legacy * sink remains a one-way compatibility facade and is used only when no * transaction backend is configured. * * This model is owner-thread only. Observer callbacks run synchronously after * a complete snapshot is published; reentrant notifications are queued until * the current notification batch completes. */ class EditorPropertyModel final : public property_access::IPropertyAccess { public: using EditSink = std::function(const PropertyEditIntent &)>; EditorPropertyModel(PropertySchema schema, SelectionSnapshot selection, const IPropertyProvider *provider, PropertyModelSurface surface = PropertyModelSurface::Developer, HostProfile profile = HostProfile::developer(), IEditorTransactionBackend *transactionBackend = nullptr); ~EditorPropertyModel() override; const property_access::PropertySchema &schema() const override { return presentationSchema_; } std::optional read(const std::string &path) const override; [[nodiscard]] property_access::WriteResult write(const std::string &path, const eve::Value &value) override; std::uint64_t revision() const override { return revision_; } property_access::Subscription subscribe(ChangeCallback callback) override; /** @brief Return the authoritative provider revision captured by this model. */ [[nodiscard]] eve::Revision targetRevision() const noexcept { return targetRevision_; } /** * @brief Bind to the provider's current revision and values. * @return Applied after an atomic refresh, or a structured failure. */ [[nodiscard]] EditorResult bind(); /** * @brief Set the borrowed canonical transaction boundary. * @param backend Backend that outlives this model, or null to detach it. * @return Applied when changed; rejected while that backend has pending work. */ [[nodiscard]] EditorResult setTransactionBackend(IEditorTransactionBackend *backend); /** * @brief Begin an explicit property transaction. * @param label Human-readable history label. * @return The stable transaction identity, or a structured failure. */ [[nodiscard]] EditorResult beginTransaction(std::string label = "Edit property"); /** @brief Validate pending property writes without changing the target. */ [[nodiscard]] EditorResult previewTransaction(); /** @brief Commit pending property writes and refresh observers after success. */ [[nodiscard]] EditorResult commitTransaction(); /** @brief Discard pending property writes without publishing them. */ [[nodiscard]] EditorResult rollbackTransaction(); /** @brief Retry a retained failed commit. */ [[nodiscard]] EditorResult retryTransaction(); /** @brief Undo the latest committed property transaction. */ [[nodiscard]] EditorResult undo(); /** @brief Redo the latest undone property transaction. */ [[nodiscard]] EditorResult redo(); /** @brief Install the legacy command sink; canonical code should inject a backend. */ void setEditSink(EditSink sink) { sink_ = std::move(sink); } /** * @brief Re-read values and adopt the provider's current revision. * @return Applied after an atomic refresh; Conflict leaves the model * unchanged when the provider changes during the read. */ [[nodiscard]] EditorResult refresh(); /** @brief Explicit spelling for refreshing and rebasing this model. */ [[nodiscard]] EditorResult rebase(); private: struct CachedProperty { property_access::PropertyChangeState state = property_access::PropertyChangeState::Missing; std::optional value; bool operator==(const CachedProperty &) const = default; }; struct ObserverState; void rebuildSchema(); void dispatch(std::vector changes); [[nodiscard]] EditorResult readProviderRevision() const; [[nodiscard]] EditorResult ensureCurrentRevision() const; PropertySchema editorSchema_; SelectionSnapshot selection_; const IPropertyProvider *provider_ = nullptr; PropertyModelSurface surface_ = PropertyModelSurface::Developer; HostProfile profile_; property_access::PropertySchema presentationSchema_; std::map cachedProperties_; EditSink sink_; IEditorTransactionBackend *transactionBackend_ = nullptr; std::set pendingPaths_; eve::Revision targetRevision_; bool bound_ = false; std::uint64_t revision_ = 0; std::shared_ptr observers_; std::vector> notificationQueue_; bool dispatchingNotifications_ = false; }; } // namespace eve::editor