Skip to content

Commit 0834bff

Browse files
authored
Make the Brush node store its cache as internal #[data] state instead of a serialized node input (#4126)
* Make the Brush node store its cache as internal #[data] state instead of a serialized node input * Remove BrushCacheImpl::unique_id
1 parent 525e49f commit 0834bff

7 files changed

Lines changed: 88 additions & 94 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 & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,22 @@ 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;
10-
use std::hash::Hash;
11-
use std::hash::Hasher;
12-
use std::sync::atomic::{AtomicU64, Ordering};
139
use std::sync::{Arc, Mutex};
1410

15-
// TODO: This is a temporary hack, be sure to not reuse this when the brush system is replaced/rewritten.
16-
static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);
17-
18-
#[derive(Clone, Debug, DynAny)]
19-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11+
#[derive(Clone, Debug, Default)]
2012
struct BrushCacheImpl {
21-
#[cfg_attr(feature = "serde", serde(default = "new_unique_id"))]
22-
unique_id: u64,
2313
// The full previous input that was cached.
24-
#[cfg_attr(feature = "serde", serde(default))]
2514
prev_input: Vec<BrushStroke>,
2615

2716
// 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"))]
2917
background: TableRow<Raster<CPU>>,
30-
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
3118
blended_image: TableRow<Raster<CPU>>,
32-
#[cfg_attr(feature = "serde", serde(default, deserialize_with = "raster_types::image::migrate_image_frame_row"))]
3319
last_stroke_texture: TableRow<Raster<CPU>>,
3420

3521
// A cache for brush textures.
36-
#[cfg_attr(feature = "serde", serde(skip))]
3722
brush_texture_cache: HashMap<CacheHashWrapper<BrushStyle>, Raster<CPU>>,
3823
}
3924

@@ -97,35 +82,6 @@ impl BrushCacheImpl {
9782
}
9883
}
9984

100-
impl Default for BrushCacheImpl {
101-
fn default() -> Self {
102-
Self {
103-
unique_id: new_unique_id(),
104-
prev_input: Vec::new(),
105-
background: Default::default(),
106-
blended_image: Default::default(),
107-
last_stroke_texture: Default::default(),
108-
brush_texture_cache: HashMap::new(),
109-
}
110-
}
111-
}
112-
113-
impl PartialEq for BrushCacheImpl {
114-
fn eq(&self, other: &Self) -> bool {
115-
self.unique_id == other.unique_id
116-
}
117-
}
118-
119-
impl Hash for BrushCacheImpl {
120-
fn hash<H: Hasher>(&self, state: &mut H) {
121-
self.unique_id.hash(state);
122-
}
123-
}
124-
125-
fn new_unique_id() -> u64 {
126-
NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst)
127-
}
128-
12985
#[derive(Clone, Debug, Default)]
13086
pub struct BrushPlan {
13187
pub strokes: Vec<BrushStroke>,
@@ -134,44 +90,9 @@ pub struct BrushPlan {
13490
pub first_stroke_point_skip: usize,
13591
}
13692

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

141-
// A bit of a cursed implementation to work around the current node system.
142-
// The original object is a 'prototype' that when cloned gives you a independent
143-
// new object. Any further clones however are all the same underlying cache object.
144-
impl Clone for BrushCache {
145-
fn clone(&self) -> Self {
146-
Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone())))
147-
}
148-
}
149-
150-
impl PartialEq for BrushCache {
151-
fn eq(&self, other: &Self) -> bool {
152-
if Arc::ptr_eq(&self.0, &other.0) {
153-
return true;
154-
}
155-
156-
let s = self.0.lock().unwrap();
157-
let o = other.0.lock().unwrap();
158-
159-
*s == *o
160-
}
161-
}
162-
163-
impl Hash for BrushCache {
164-
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
165-
self.0.lock().unwrap().hash(state);
166-
}
167-
}
168-
169-
impl graphene_hash::CacheHash for BrushCache {
170-
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
171-
core::hash::Hash::hash(&self.0.lock().unwrap().unique_id, state);
172-
}
173-
}
174-
17596
impl BrushCache {
17697
pub fn compute_brush_plan(&self, background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
17798
let mut inner = self.0.lock().unwrap();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub mod brush;
2-
pub mod brush_cache;
2+
mod brush_cache;
33
pub mod brush_stroke;
44

55
pub mod migrations {

0 commit comments

Comments
 (0)