Skip to content

Commit fcae3fa

Browse files
committed
Make the Brush node store its cache as internal #[data] state instead of a serialized node input
1 parent 525e49f commit fcae3fa

6 files changed

Lines changed: 87 additions & 23 deletions

File tree

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,7 +1760,12 @@ impl DocumentMessageHandler {
17601760
}
17611761

17621762
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
1763-
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
1763+
// Walk the document JSON and rewrite any `TaggedValue` variants that have been removed since being released as `"None"` so the document still deserializes.
1764+
// `migrate_node` then drops the resulting orphan node inputs so the value never reaches graph execution.
1765+
let mut json_value: serde_json::Value = serde_json::from_str(serialized_content).map_err(|e| EditorError::DocumentDeserialization(e.to_string()))?;
1766+
graph_craft::document::value::TaggedValue::scrub_removed_variants_from_json(&mut json_value);
1767+
1768+
let document_message_handler = serde_json::from_value::<DocumentMessageHandler>(json_value.clone())
17641769
.or_else(|e| {
17651770
log::warn!("Failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler.");
17661771
// TODO: Eventually remove this document upgrade code
@@ -1799,7 +1804,7 @@ impl DocumentMessageHandler {
17991804
pub snapping_state: SnappingState,
18001805
}
18011806

1802-
serde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {
1807+
serde_json::from_value::<OldDocumentMessageHandler>(json_value).map(|old_message_handler| DocumentMessageHandler {
18031808
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
18041809
collapsed: old_message_handler.collapsed,
18051810
commit_hash: old_message_handler.commit_hash,

editor/src/messages/portfolio/document_migration.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,16 +1622,27 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
16221622
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
16231623
}
16241624

1625+
// Old shape: [background, bounds, trace, cache]. Both "bounds" (input 1) and "cache" (input 3) are dropped, and "cache" is now stored as
1626+
// internal node state via `#[data]` on the brush node, so it is not a node input at all in the new shape.
16251627
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && inputs_count == 4 {
16261628
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
16271629
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
16281630

16291631
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
16301632

16311633
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
1632-
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
16331634
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
1634-
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
1635+
}
1636+
1637+
// Old shape: [background, trace, cache]. The "cache" input is dropped because the brush node now stores its cache as internal `#[data]` state.
1638+
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && inputs_count == 3 {
1639+
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
1640+
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
1641+
1642+
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
1643+
1644+
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
1645+
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
16351646
}
16361647

16371648
if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::RemoveHandlesNode")) {

node-graph/graph-craft/src/document/value.rs

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use super::DocumentNode;
22
use crate::application_io::PlatformEditorApi;
33
use crate::proto::{Any as DAny, FutureAny};
4-
use brush_nodes::brush_cache::BrushCache;
54
use brush_nodes::brush_stroke::BrushStroke;
65
use core_types::table::Table;
76
use core_types::transform::Footprint;
@@ -39,7 +38,7 @@ macro_rules! tagged_value {
3938
$( $(#[$meta] ) *$identifier( $ty ), )*
4039
RenderOutput(RenderOutput),
4140
#[serde(skip)]
42-
EditorApi(Arc<PlatformEditorApi>)
41+
EditorApi(Arc<PlatformEditorApi>),
4342
}
4443

4544
impl CacheHash for TaggedValue {
@@ -79,7 +78,7 @@ macro_rules! tagged_value {
7978
Self::None => concrete!(()),
8079
$( Self::$identifier(_) => concrete!($ty), )*
8180
Self::RenderOutput(_) => concrete!(RenderOutput),
82-
Self::EditorApi(_) => concrete!(&PlatformEditorApi)
81+
Self::EditorApi(_) => concrete!(&PlatformEditorApi),
8382
}
8483
}
8584
/// Attempts to downcast the dynamic type to a tagged value
@@ -211,7 +210,6 @@ tagged_value! {
211210
Stroke(Stroke),
212211
Gradient(Gradient),
213212
Font(Font),
214-
BrushCache(BrushCache),
215213
DocumentNode(DocumentNode),
216214
ContextFeatures(ContextFeatures),
217215
Curve(Curve),
@@ -412,6 +410,35 @@ impl TaggedValue {
412410
_ => panic!("Passed value is not of type u32"),
413411
}
414412
}
413+
414+
/// Walks a JSON document tree and replaces any externally-tagged `TaggedValue` whose discriminant is in `REMOVED_VARIANTS` with the unit variant `"None"`.
415+
/// Lets documents written before a variant was removed continue to deserialize. The document migration step then removes any orphan node inputs that result.
416+
#[cfg(feature = "loading")]
417+
pub fn scrub_removed_variants_from_json(value: &mut serde_json::Value) {
418+
// Names of `TaggedValue` variants that have been removed since being released. Any object of the form `{"<name>": <payload>}` is rewritten to `"None"` on load.
419+
const REMOVED_VARIANTS: &[&str] = &["BrushCache"];
420+
421+
match value {
422+
serde_json::Value::Object(map) => {
423+
if map.len() == 1
424+
&& let Some(key) = map.keys().next()
425+
&& REMOVED_VARIANTS.contains(&key.as_str())
426+
{
427+
*value = serde_json::Value::String("None".to_string());
428+
return;
429+
}
430+
for child in map.values_mut() {
431+
Self::scrub_removed_variants_from_json(child);
432+
}
433+
}
434+
serde_json::Value::Array(array) => {
435+
for child in array {
436+
Self::scrub_removed_variants_from_json(child);
437+
}
438+
}
439+
_ => {}
440+
}
441+
}
415442
}
416443

417444
impl Display for TaggedValue {
@@ -518,3 +545,35 @@ impl CacheHash for RenderOutput {
518545
self.data.cache_hash(state);
519546
}
520547
}
548+
549+
#[cfg(all(test, feature = "loading"))]
550+
mod tests {
551+
use super::*;
552+
553+
#[test]
554+
fn scrub_replaces_removed_variant_with_none_unit() {
555+
let mut value = serde_json::json!({
556+
"Value": {
557+
"tagged_value": { "BrushCache": { "unique_id": 1, "prev_input": [] } },
558+
"exposed": false
559+
}
560+
});
561+
TaggedValue::scrub_removed_variants_from_json(&mut value);
562+
assert_eq!(value, serde_json::json!({ "Value": { "tagged_value": "None", "exposed": false } }));
563+
}
564+
565+
#[test]
566+
fn scrub_leaves_live_variants_unchanged() {
567+
let mut value = serde_json::json!({ "Value": { "tagged_value": { "F64": 1.5 }, "exposed": false } });
568+
let original = value.clone();
569+
TaggedValue::scrub_removed_variants_from_json(&mut value);
570+
assert_eq!(value, original);
571+
}
572+
573+
#[test]
574+
fn scrub_recurses_through_arrays_and_nested_objects() {
575+
let mut value = serde_json::json!([{ "BrushCache": { "any": "payload" } }, { "F32": 0.5 }]);
576+
TaggedValue::scrub_removed_variants_from_json(&mut value);
577+
assert_eq!(value, serde_json::json!(["None", { "F32": 0.5 }]));
578+
}
579+
}

node-graph/interpreted-executor/src/node_registry.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use graph_craft::document::value::RenderOutput;
66
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
77
use graphene_std::any::DynAnyNode;
88
use graphene_std::application_io::ImageTexture;
9-
use graphene_std::brush::brush_cache::BrushCache;
109
use graphene_std::brush::brush_stroke::BrushStroke;
1110
use graphene_std::gradient::GradientStops;
1211
#[cfg(target_family = "wasm")]
@@ -158,7 +157,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
158157
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
159158
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
160159
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<BrushStroke>]),
161-
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BrushCache]),
162160
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DocumentNode]),
163161
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),
164162
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),
@@ -246,7 +244,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
246244
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),
247245
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
248246
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<BrushStroke>]),
249-
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => BrushCache]),
250247
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
251248
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),
252249
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),

node-graph/nodes/brush/src/brush.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ async fn brush(
195195
/// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles.
196196
trace: Table<BrushStroke>,
197197
/// Internal cache data used to accelerate rendering of the brush content.
198+
#[data]
198199
cache: BrushCache,
199200
) -> Table<Raster<CPU>> {
200201
if background.is_empty() {
@@ -419,6 +420,7 @@ mod test {
419420
async fn test_brush_output_size() {
420421
let image = brush(
421422
(),
423+
&BrushCache::default(),
422424
Table::new_from_element(Raster::new_cpu(Image::<Color>::default())),
423425
Table::new_from_element(BrushStroke {
424426
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
@@ -431,7 +433,6 @@ mod test {
431433
blend_mode: BlendMode::Normal,
432434
},
433435
}),
434-
BrushCache::default(),
435436
)
436437
.await;
437438
assert_eq!(image.element(0).unwrap().width, 20);

node-graph/nodes/brush/src/brush_cache.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::brush_stroke::BrushStyle;
33
use core_types::ATTR_TRANSFORM;
44
use core_types::graphene_hash::CacheHashWrapper;
55
use core_types::table::TableRow;
6-
use dyn_any::DynAny;
76
use raster_types::CPU;
87
use raster_types::Raster;
98
use std::collections::HashMap;
@@ -15,25 +14,18 @@ use std::sync::{Arc, Mutex};
1514
// TODO: This is a temporary hack, be sure to not reuse this when the brush system is replaced/rewritten.
1615
static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);
1716

18-
#[derive(Clone, Debug, DynAny)]
19-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17+
#[derive(Clone, Debug)]
2018
struct BrushCacheImpl {
21-
#[cfg_attr(feature = "serde", serde(default = "new_unique_id"))]
2219
unique_id: u64,
2320
// The full previous input that was cached.
24-
#[cfg_attr(feature = "serde", serde(default))]
2521
prev_input: Vec<BrushStroke>,
2622

2723
// The strokes that have been fully processed and blended into the background.
28-
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
2924
background: TableRow<Raster<CPU>>,
30-
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
3125
blended_image: TableRow<Raster<CPU>>,
32-
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
3326
last_stroke_texture: TableRow<Raster<CPU>>,
3427

3528
// A cache for brush textures.
36-
#[cfg_attr(feature = "serde", serde(skip))]
3729
brush_texture_cache: HashMap<CacheHashWrapper<BrushStyle>, Raster<CPU>>,
3830
}
3931

@@ -134,8 +126,7 @@ pub struct BrushPlan {
134126
pub first_stroke_point_skip: usize,
135127
}
136128

137-
#[derive(Debug, Default, DynAny)]
138-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129+
#[derive(Debug, Default)]
139130
pub struct BrushCache(Arc<Mutex<BrushCacheImpl>>);
140131

141132
// A bit of a cursed implementation to work around the current node system.

0 commit comments

Comments
 (0)