Skip to content

Commit 9b01dd2

Browse files
committed
Graph storage fixes
- deprecate postcard in favor of rmp-serde - fix gc bugs - No longer store graph ptz in history
1 parent d37ca01 commit 9b01dd2

8 files changed

Lines changed: 172 additions & 145 deletions

File tree

Cargo.lock

Lines changed: 21 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

document/graph-storage/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ graph-craft = { workspace = true }
1313
graphene-resource = { workspace = true }
1414
core-types = { workspace = true }
1515
blake3 = "1.5"
16-
postcard = { version = "1.0", features = ["use-std"] }
16+
rmp-serde = "1.3"
1717
rustc-hash = "2.0"
1818
thiserror = "2.0"
1919

document/graph-storage/src/crdt_tests.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,3 +604,41 @@ fn compute_deltas_diffs_resources_and_round_trips() {
604604
"applying the resource diff did not reproduce the target registry"
605605
);
606606
}
607+
608+
/// Resource GC must keep an undone gesture's resources alive: undo removes a gesture's `AddResource`
609+
/// from the working registry, but redo still needs those bytes. `all_referenced_resource_hashes` must
610+
/// therefore report history-referenced resources even after they leave the current registry, so the
611+
/// editor's GC "used" set doesn't evict them between an undo and a redo.
612+
#[test]
613+
fn all_referenced_resource_hashes_survives_undo() {
614+
use crate::ResourceId;
615+
616+
let mut session = Session::with_peer(PeerId(1));
617+
let resources = graphene_resource::ResourceRegistry::new();
618+
619+
// Base gesture: the first gesture is intentionally not undoable (the mount-base floor), so commit a
620+
// network first. Undoing the later resource gesture then lands on this base rather than the root.
621+
session.stage_from_runtime(&tiny_network(), &NoMetadata, &resources).expect("stage base");
622+
let base_up_to = session.hot_log().last().expect("staged base").timestamp;
623+
let base_revs = session.retire(base_up_to).expect("retire base");
624+
session.mark_gesture_end(*base_revs.last().expect("one base delta"));
625+
626+
// Second gesture: add a resource and mark the retired delta as a gesture boundary.
627+
let hash = ResourceHash::from(&b"declaration-bytes"[..]);
628+
let id = ResourceId::new();
629+
let hot_ops = session.stage_embedded_resource(id, hash).expect("stage resource");
630+
let up_to = hot_ops.last().expect("staged one op").timestamp;
631+
let revs = session.retire(up_to).expect("retire");
632+
session.mark_gesture_end(*revs.last().expect("one retired delta"));
633+
634+
assert!(session.registry().resources.contains_key(&id), "resource is present after the gesture");
635+
assert!(session.all_referenced_resource_hashes().contains(&hash));
636+
637+
// Undo the gesture: the resource leaves the working registry but stays in history.
638+
session.undo().expect("undo");
639+
assert!(!session.registry().resources.contains_key(&id), "undo drops the resource from the working registry");
640+
assert!(
641+
session.all_referenced_resource_hashes().contains(&hash),
642+
"the undone gesture's resource must still be reported so GC keeps its bytes for redo"
643+
);
644+
}

document/graph-storage/src/from_runtime.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::collections::HashMap;
2-
use std::sync::Arc;
32

43
use core_types::Context;
54
use core_types::context::ContextDependencies;
@@ -43,7 +42,7 @@ impl NodePath {
4342
}
4443

4544
fn to_global_id(&self, peer: PeerId) -> NodeId {
46-
let bytes = postcard::to_stdvec(&(peer, self)).expect("NodePath must serialize");
45+
let bytes = rmp_serde::to_vec(&(peer, self)).expect("NodePath must serialize");
4746
let digest = blake3::hash(&bytes);
4847
let mut truncated = [0u8; 8];
4948
truncated.copy_from_slice(&digest.as_bytes()[..8]);
@@ -55,7 +54,7 @@ impl NodePath {
5554
/// depending on traversal order. A domain tag keeps it from colliding with this node's own
5655
/// `to_global_id`. The root network is `ROOT_NETWORK` and never goes through here.
5756
fn owned_network_id(&self, peer: PeerId) -> NetworkId {
58-
let bytes = postcard::to_stdvec(&("network", peer, self)).expect("NodePath must serialize");
57+
let bytes = rmp_serde::to_vec(&("network", peer, self)).expect("NodePath must serialize");
5958
let digest = blake3::hash(&bytes);
6059
let mut truncated = [0u8; 8];
6160
truncated.copy_from_slice(&digest.as_bytes()[..8]);
@@ -96,6 +95,9 @@ pub type DeclarationBytes = HashMap<ResourceHash, Vec<u8>>;
9695
pub struct RuntimeConversion {
9796
pub registry: Registry,
9897
pub declaration_bytes: DeclarationBytes,
98+
/// Each network's runtime `metadata_path` mapped to its stable storage `NetworkId`, for associating
99+
/// per-network, per-peer view state (`session.json`) without re-deriving ids.
100+
pub network_ids: HashMap<Vec<RuntimeNodeId>, NetworkId>,
99101
}
100102

101103
impl RuntimeConversion {
@@ -107,13 +109,27 @@ impl RuntimeConversion {
107109
self.declaration_bytes
108110
.iter()
109111
.map(|(hash, bytes)| {
110-
let proto: ProtoNode = postcard::from_bytes(bytes).map_err(|error| ConversionError::SerializationError(format!("declaration {hash}: {error}")))?;
112+
let proto = decode_declaration(bytes).map_err(|error| ConversionError::SerializationError(format!("declaration {hash}: {error}")))?;
111113
Ok((ResourceId::from_hash(hash), proto))
112114
})
113115
.collect()
114116
}
115117
}
116118

119+
/// Encode a [`ProtoNode`] declaration to its content-addressed bytes: through a self-describing
120+
/// `serde_json::Value` (so serde aliases keep working and the on-disk shape stays migratable), then
121+
/// rmp-serialized (which encodes the intermediate `Value` compactly). Paired with [`decode_declaration`].
122+
pub fn encode_declaration(proto: &ProtoNode) -> Result<Vec<u8>, String> {
123+
let value = serde_json::to_value(proto).map_err(|error| error.to_string())?;
124+
rmp_serde::to_vec(&value).map_err(|error| error.to_string())
125+
}
126+
127+
/// Decode a [`ProtoNode`] declaration from the bytes [`encode_declaration`] produced.
128+
pub fn decode_declaration(bytes: &[u8]) -> Result<ProtoNode, String> {
129+
let value: serde_json::Value = rmp_serde::from_slice(bytes).map_err(|error| error.to_string())?;
130+
serde_json::from_value(value).map_err(|error| error.to_string())
131+
}
132+
117133
impl Registry {
118134
/// Convenience wrapper returning only the registry (declaration bytes discarded). For callers
119135
/// that don't persist a byte store — e.g. the graph-only `TryFrom` and value-comparison tests.
@@ -133,6 +149,7 @@ impl Registry {
133149
let mut ctx = ConversionContext {
134150
declaration_ids: HashMap::new(),
135151
declaration_bytes: HashMap::new(),
152+
network_ids: HashMap::new(),
136153
metadata,
137154
peer,
138155
};
@@ -150,6 +167,7 @@ impl Registry {
150167
Ok(RuntimeConversion {
151168
registry,
152169
declaration_bytes: ctx.declaration_bytes,
170+
network_ids: ctx.network_ids,
153171
})
154172
}
155173
}
@@ -228,6 +246,9 @@ struct ConversionContext<'m, M: NodeMetadataSource + ?Sized> {
228246
declaration_ids: HashMap<String, ResourceId>,
229247
/// Extracted declaration content keyed by hash, handed back for the caller's byte store.
230248
declaration_bytes: DeclarationBytes,
249+
/// Maps each network's runtime `metadata_path` to its stable storage `NetworkId`, so the caller can
250+
/// associate per-network, per-peer view state (in `session.json`) with networks without re-deriving ids.
251+
network_ids: HashMap<Vec<RuntimeNodeId>, NetworkId>,
231252
metadata: &'m M,
232253
peer: PeerId,
233254
}
@@ -272,6 +293,7 @@ fn convert_network<M: NodeMetadataSource + ?Sized>(
272293
write_ui_network_attributes(&mut attributes, ctx.metadata, metadata_path, TimeStamp::ORIGIN)?;
273294

274295
registry.networks.insert(network_id, Network { exports, attributes });
296+
ctx.network_ids.insert(metadata_path.to_vec(), network_id);
275297

276298
Ok(())
277299
}
@@ -396,18 +418,6 @@ fn write_ui_attributes<M: NodeMetadataSource + ?Sized>(
396418
}
397419

398420
fn write_ui_network_attributes<M: NodeMetadataSource + ?Sized>(attributes: &mut crate::Attributes, metadata: &M, network_path: &[RuntimeNodeId], timestamp: TimeStamp) -> Result<(), ConversionError> {
399-
if let Some(value) = metadata.navigation_ptz(network_path) {
400-
attributes.set(UI_NAV_PTZ, value, timestamp);
401-
}
402-
if let Some(value) = metadata.navigation_transform(network_path) {
403-
attributes.set(UI_NAV_TRANSFORM, value, timestamp);
404-
}
405-
if let Some(width) = metadata.navigation_width(network_path) {
406-
attributes.set_serialized(UI_NAV_WIDTH, &width, timestamp).map_err(map_serialization_error("ui::nav::width"))?;
407-
}
408-
if let Some(value) = metadata.previewing(network_path) {
409-
attributes.set(UI_PREVIEWING, value, timestamp);
410-
}
411421
if let Some(reference) = metadata.reference(network_path) {
412422
attributes.set(UI_REFERENCE, serde_json::Value::String(reference.to_string()), timestamp);
413423
}
@@ -451,11 +461,8 @@ fn convert_input(input: &GraphCraftNodeInput, parent_path: Option<&NodePath>, ne
451461
output_index: *output_index,
452462
},
453463
GraphCraftNodeInput::Value { tagged_value, exposed } => {
454-
let serialized = postcard::to_stdvec(&**tagged_value).map_err(|e| ConversionError::SerializationError(format!("{e:?}")))?;
455-
NodeInput::Value {
456-
raw_value: Arc::from(serialized.into_boxed_slice()),
457-
exposed: *exposed,
458-
}
464+
let value = serde_json::to_value(&**tagged_value).map_err(|e| ConversionError::SerializationError(format!("{e:?}")))?;
465+
NodeInput::Value { value, exposed: *exposed }
459466
}
460467
GraphCraftNodeInput::Scope(s) => NodeInput::Scope(s.clone()),
461468
GraphCraftNodeInput::Import { import_index, .. } => NodeInput::Import { import_idx: *import_index },
@@ -508,7 +515,7 @@ fn convert_implementation<M: NodeMetadataSource + ?Sized>(
508515
attributes: Default::default(),
509516
};
510517
// Content-address the declaration: serialize, hash, derive a deterministic id.
511-
let bytes = postcard::to_stdvec(&proto).map_err(|error| ConversionError::SerializationError(format!("proto-node {identifier_str}: {error}")))?;
518+
let bytes = encode_declaration(&proto).map_err(|error| ConversionError::SerializationError(format!("proto-node {identifier_str}: {error}")))?;
512519
let hash = ResourceHash::from(bytes.as_slice());
513520
let id = ResourceId::from_hash(&hash);
514521

0 commit comments

Comments
 (0)