Files
Graphite/document/graph-storage/src/metadata_source.rs
Dennis Kobert b56af15e5e Add graph-storage crate (#4198)
* Add graph-storage crate

* Split graph-storage lib.rs into smaller modules

* Make graph_craft/core_types dependency optional

* Fix CRDT correctness issues flagged in graph-storage PR review

* Treat trailing empty export slots as value-equal in Network::value_equal

* Address graph-storage review: parameterize CrdtError, reject hot-log corruption, drop LamportClock Default

* Address graph-storage review: fix root-delta history walk, validate cross-network refs, make compute_deltas deterministic, harden Priority, index nodes by network

* Address graph-storage review: sort sources on deserialize, treat inputs_attributes length as structural, dedupe demo-artwork loader

* Add opt-in Delta rev validation, dedup resource sources on deserialize, fix doc grammar

* Persist scope injections through storage; harden gesture-end and embedded-source checks

* Review

* Review

* Review

* Use new types for NodeId and NetworkId

* Adress minor review comments

---------

Co-authored-by: Timon <me@timon.zip>
2026-06-13 16:47:04 +00:00

131 lines
4.9 KiB
Rust

//! Lets `from_runtime` read editor-side per-node metadata without depending on the editor crate.
//! The editor implements this on `NodeNetworkInterface`; tests pass [`NoMetadata`].
//!
//! `network_path` is the chain of runtime local `NodeId`s from the root down to (but not including)
//! the queried node, matching `NodeNetworkInterface::node_metadata(node_id, network_path)`.
use std::collections::HashMap;
use core_types::uuid::NodeId as RuntimeNodeId;
/// One node's editor-side metadata, produced by `Registry::to_runtime_with_metadata`.
#[derive(Clone, Debug, PartialEq)]
pub struct NodeMetadataEntry {
pub network_path: Vec<RuntimeNodeId>,
pub local_id: RuntimeNodeId,
pub position: Option<Position>,
pub is_layer: bool,
pub display_name: Option<String>,
pub locked: bool,
pub pinned: bool,
/// Always sized to match the runtime node's `inputs.len()`; absent slots use `Default`. The rebuild
/// returns an error if this length does not match the node's input count.
pub input_metadata: Vec<InputMetadataEntry>,
pub output_names: Vec<String>,
}
impl NodeMetadataEntry {
pub fn is_empty(&self) -> bool {
self.position.is_none()
&& !self.is_layer
&& self.display_name.is_none()
&& !self.locked
&& !self.pinned
&& self.output_names.is_empty()
&& self.input_metadata.iter().all(InputMetadataEntry::is_empty)
}
}
/// Per-network metadata (navigation, previewing). Separate from `NodeMetadataEntry` since these are
/// properties of a network, not of any node.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct NetworkMetadataEntry {
/// Owning-node chain from the root to (and including) the node containing this network.
/// Empty = root network.
pub network_path: Vec<RuntimeNodeId>,
/// Stable storage id of this network. Lets the editor associate per-network, per-peer view state
/// (node-graph nav + previewing, in `session.json`) with a network across reparenting.
pub network_id: crate::NetworkId,
/// Matches the runtime's `NodeNetworkPersistentMetadata::reference` — definition lineage tag.
pub reference: Option<String>,
}
impl NetworkMetadataEntry {
pub fn is_empty(&self) -> bool {
self.reference.is_none()
}
}
/// Per-input editor metadata. Mirrors `InputPersistentMetadata` but wraps strings in `Option` so
/// unset (`""` on the runtime side) is distinguishable from an explicit empty string.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct InputMetadataEntry {
pub input_name: Option<String>,
pub input_description: Option<String>,
pub widget_override: Option<String>,
/// Reassembled from `ui::input_data::<sub_key>` attributes.
pub input_data: HashMap<String, serde_json::Value>,
}
impl InputMetadataEntry {
pub fn is_empty(&self) -> bool {
self.input_name.is_none() && self.input_description.is_none() && self.widget_override.is_none() && self.input_data.is_empty()
}
}
/// Editor-side metadata source. Methods default to "no data" so implementors only override what
/// they carry. Returns are JSON-shaped where the underlying types live editor-side (PTZ, etc.).
pub trait NodeMetadataSource {
fn position(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId) -> Option<Position> {
None
}
fn is_layer(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId) -> bool {
false
}
fn display_name(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId) -> Option<&str> {
None
}
fn locked(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId) -> bool {
false
}
fn pinned(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId) -> bool {
false
}
/// Empty vec = no overrides. Stored as a single `ui::output_names` attribute (whole-vec LWW).
fn output_names(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId) -> Vec<String> {
Vec::new()
}
fn input_name(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId, _input_index: usize) -> Option<&str> {
None
}
fn input_description(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId, _input_index: usize) -> Option<&str> {
None
}
fn widget_override(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId, _input_index: usize) -> Option<&str> {
None
}
/// Returns owned to stay object-safe. Each entry is stored as `ui::input_data::<key>` for per-key LWW.
fn input_data(&self, _network_path: &[RuntimeNodeId], _local_id: RuntimeNodeId, _input_index: usize) -> HashMap<String, serde_json::Value> {
HashMap::new()
}
fn reference(&self, _network_path: &[RuntimeNodeId]) -> Option<&str> {
None
}
}
/// No-op metadata source. Use when there's nothing to attach (synthetic networks, CLI tools).
pub struct NoMetadata;
impl NodeMetadataSource for NoMetadata {}
/// Unified storage-side position. The valid variants depend on `attr::node::ui::IS_LAYER`:
/// layers use `Absolute` or `Stack`; non-layer nodes use `Absolute` or `Chain`.
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Position {
Absolute([i32; 2]),
Chain,
Stack(u32),
}