Files
EVEngine/src/modules/editor/EditorPropertyModel.h

132 lines
5.8 KiB
C++

#pragma once
#include "editor/EditorHostProfile.h"
#include "editor/EditorPropertyPresenter.h"
#include "property_access/PropertyAccess.h"
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
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<EditorResult<void>(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<eve::Value> 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<void> 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<void> 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<TransactionId> beginTransaction(std::string label = "Edit property");
/** @brief Validate pending property writes without changing the target. */
[[nodiscard]] EditorResult<EditorDryRunReport> previewTransaction();
/** @brief Commit pending property writes and refresh observers after success. */
[[nodiscard]] EditorResult<TransactionReceipt> commitTransaction();
/** @brief Discard pending property writes without publishing them. */
[[nodiscard]] EditorResult<void> rollbackTransaction();
/** @brief Retry a retained failed commit. */
[[nodiscard]] EditorResult<TransactionReceipt> retryTransaction();
/** @brief Undo the latest committed property transaction. */
[[nodiscard]] EditorResult<TransactionReceipt> undo();
/** @brief Redo the latest undone property transaction. */
[[nodiscard]] EditorResult<TransactionReceipt> 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<void> refresh();
/** @brief Explicit spelling for refreshing and rebasing this model. */
[[nodiscard]] EditorResult<void> rebase();
private:
struct CachedProperty {
property_access::PropertyChangeState state = property_access::PropertyChangeState::Missing;
std::optional<eve::Value> value;
bool operator==(const CachedProperty &) const = default;
};
struct ObserverState;
void rebuildSchema();
void dispatch(std::vector<property_access::PropertyChange> changes);
[[nodiscard]] EditorResult<eve::Revision> readProviderRevision() const;
[[nodiscard]] EditorResult<void> ensureCurrentRevision() const;
PropertySchema editorSchema_;
SelectionSnapshot selection_;
const IPropertyProvider *provider_ = nullptr;
PropertyModelSurface surface_ = PropertyModelSurface::Developer;
HostProfile profile_;
property_access::PropertySchema presentationSchema_;
std::map<std::string, CachedProperty> cachedProperties_;
EditSink sink_;
IEditorTransactionBackend *transactionBackend_ = nullptr;
std::set<std::string> pendingPaths_;
eve::Revision targetRevision_;
bool bound_ = false;
std::uint64_t revision_ = 0;
std::shared_ptr<ObserverState> observers_;
std::vector<std::vector<property_access::PropertyChange>> notificationQueue_;
bool dispatchingNotifications_ = false;
};
} // namespace eve::editor