Fix most known issues with migrations failing to open documents from the past year (#3148)

This commit is contained in:
Keavon Chambers
2025-09-07 11:10:03 -07:00
committed by GitHub
parent 89c9cf1352
commit a2c0693038
18 changed files with 313 additions and 62 deletions

View File

@@ -10,21 +10,23 @@ use std::hash::Hasher;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten.
// TODO: This is a temporary hack, be sure to not reuse this when the brush system is replaced/rewritten.
static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);
#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
struct BrushCacheImpl {
#[serde(default = "new_unique_id")]
unique_id: u64,
// The full previous input that was cached.
#[serde(default)]
prev_input: Vec<BrushStroke>,
// The strokes that have been fully processed and blended into the background.
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
#[serde(default, deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
background: TableRow<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
#[serde(default, deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
blended_image: TableRow<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
#[serde(default, deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
last_stroke_texture: TableRow<Raster<CPU>>,
// A cache for brush textures.
@@ -98,7 +100,7 @@ impl BrushCacheImpl {
impl Default for BrushCacheImpl {
fn default() -> Self {
Self {
unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst),
unique_id: new_unique_id(),
prev_input: Vec::new(),
background: Default::default(),
blended_image: Default::default(),
@@ -120,6 +122,10 @@ impl Hash for BrushCacheImpl {
}
}
fn new_unique_id() -> u64 {
NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst)
}
#[derive(Clone, Debug, Default)]
pub struct BrushPlan {
pub strokes: Vec<BrushStroke>,

View File

@@ -68,13 +68,22 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
enum ArtboardFormat {
ArtboardGroup(ArtboardGroup),
OldArtboardTable(OldTable<Artboard>),
ArtboardTable(Table<Artboard>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ArtboardGroup(artboard_group) => {
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OldTable<T> {
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
transform: Vec<DAffine2>,
alpha_blending: Vec<AlphaBlending>,
}
Ok(match ArtboardFormat::deserialize(deserializer)? {
ArtboardFormat::ArtboardGroup(artboard_group) => {
let mut table = Table::new();
for (artboard, source_node_id) in artboard_group.artboards {
table.push(TableRow {
@@ -86,7 +95,18 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
}
table
}
EitherFormat::ArtboardTable(artboard_table) => artboard_table,
ArtboardFormat::OldArtboardTable(old_table) => old_table
.element
.into_iter()
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
.map(|(element, (transform, alpha_blending))| TableRow {
element,
transform,
alpha_blending,
source_node_id: None,
})
.collect(),
ArtboardFormat::ArtboardTable(artboard_table) => artboard_table,
})
}

View File

@@ -3,6 +3,7 @@ use crate::context::{CloneVarArgs, Context, ContextFeatures, Ctx, ExtractAll};
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::transform::Footprint;
use crate::uuid::NodeId;
use crate::vector::Vector;
use crate::{Graphic, OwnedContextImpl};
@@ -24,6 +25,7 @@ async fn context_modification<T>(
Context -> f64,
Context -> String,
Context -> DAffine2,
Context -> Footprint,
Context -> DVec2,
Context -> Vec<DVec2>,
Context -> Vec<NodeId>,

View File

@@ -507,15 +507,34 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
elements: Vec<(Graphic, Option<NodeId>)>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OlderTable<T> {
id: Vec<u64>,
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OldTable<T> {
id: Vec<u64>,
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
transform: Vec<DAffine2>,
alpha_blending: Vec<AlphaBlending>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
enum GraphicFormat {
OldGraphicGroup(OldGraphicGroup),
OlderTableOldGraphicGroup(OlderTable<OldGraphicGroup>),
OldTableOldGraphicGroup(OldTable<OldGraphicGroup>),
OldTableGraphicGroup(OldTable<GraphicGroup>),
Table(serde_json::Value),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::OldGraphicGroup(old) => {
Ok(match GraphicFormat::deserialize(deserializer)? {
GraphicFormat::OldGraphicGroup(old) => {
let mut graphic_table = Table::new();
for (graphic, source_node_id) in old.elements {
graphic_table.push(TableRow {
@@ -527,7 +546,43 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
}
graphic_table
}
EitherFormat::Table(value) => {
GraphicFormat::OlderTableOldGraphicGroup(old) => old
.element
.into_iter()
.flat_map(|element| {
element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {
element: graphic,
transform: element.transform,
alpha_blending: element.alpha_blending,
source_node_id,
})
})
.collect(),
GraphicFormat::OldTableOldGraphicGroup(old) => old
.element
.into_iter()
.flat_map(|element| {
element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {
element: graphic,
transform: element.transform,
alpha_blending: element.alpha_blending,
source_node_id,
})
})
.collect(),
GraphicFormat::OldTableGraphicGroup(old) => old
.element
.into_iter()
.flat_map(|element| {
element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow {
element: graphic,
transform: Default::default(),
alpha_blending: Default::default(),
source_node_id,
})
})
.collect(),
GraphicFormat::Table(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
let mut graphic_table = Table::new();

View File

@@ -69,21 +69,21 @@ pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resul
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
enum ColorFormat {
Color(Color),
OptionalColor(Option<Color>),
ColorTable(Table<Color>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::Color(color) => Table::new_from_element(color),
EitherFormat::OptionalColor(color) => {
Ok(match ColorFormat::deserialize(deserializer)? {
ColorFormat::Color(color) => Table::new_from_element(color),
ColorFormat::OptionalColor(color) => {
if let Some(color) = color {
Table::new_from_element(color)
} else {
Table::new()
}
}
EitherFormat::ColorTable(color_table) => color_table,
ColorFormat::ColorTable(color_table) => color_table,
})
}

View File

@@ -218,7 +218,6 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
enum RasterFrame {
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(Table<Image<Color>>),
}
impl<'de> serde::Deserialize<'de> for RasterFrame {
@@ -236,9 +235,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Table<Vector>),
RasterFrame(RasterFrame),
}
@@ -283,11 +280,73 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
OlderImageFrameTable(OlderTable<ImageFrame<Color>>),
OldImageFrameTable(OldTable<ImageFrame<Color>>),
OldImageTable(OldTable<Image<Color>>),
OldRasterTable(OldTable<Raster<CPU>>),
ImageFrameTable(Table<ImageFrame<Color>>),
ImageTable(Table<Image<Color>>),
RasterTable(Table<Raster<CPU>>),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OldTable<T> {
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
transform: Vec<DAffine2>,
alpha_blending: Vec<AlphaBlending>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OlderTable<T> {
id: Vec<u64>,
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
}
fn from_image_table(table: Table<Image<Color>>) -> Table<Raster<CPU>> {
Table::new_from_element(Raster::new_cpu(table.iter().next().unwrap().element.clone()))
}
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
old_table
.element
.into_iter()
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
.map(|(element, (transform, alpha_blending))| TableRow {
element,
transform,
alpha_blending,
source_node_id: None,
})
.collect()
}
fn older_table_to_new_table<T>(old_table: OlderTable<T>) -> Table<T> {
old_table
.element
.into_iter()
.map(|element| TableRow {
element,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: None,
})
.collect()
}
fn from_image_frame_table(image_frame: Table<ImageFrame<Color>>) -> Table<Raster<CPU>> {
Table::new_from_element(Raster::new_cpu(
image_frame
.iter()
.next()
.unwrap_or(Table::new_from_element(ImageFrame::default()).iter().next().unwrap())
.element
.image
.clone(),
))
}
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
@@ -296,16 +355,12 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
*image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrameTable(image_frame) => Table::new_from_element(Raster::new_cpu(
image_frame
.iter()
.next()
.unwrap_or(Table::new_from_element(ImageFrame::default()).iter().next().unwrap())
.element
.image
.clone(),
)),
FormatVersions::ImageTable(table) => Table::new_from_element(Raster::new_cpu(table.iter().next().unwrap().element.clone())),
FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)),
FormatVersions::OldImageFrameTable(old_table) => from_image_frame_table(old_table_to_new_table(old_table)),
FormatVersions::OldImageTable(old_table) => from_image_table(old_table_to_new_table(old_table)),
FormatVersions::OldRasterTable(old_table) => old_table_to_new_table(old_table),
FormatVersions::ImageFrameTable(image_frame) => from_image_frame_table(image_frame),
FormatVersions::ImageTable(table) => from_image_table(table),
FormatVersions::RasterTable(table) => table,
})
}

View File

@@ -306,7 +306,10 @@ impl SegmentDomain {
}
pub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) {
debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain");
#[cfg(debug_assertions)]
if self.id.contains(&id) {
warn!("Tried to push an existing point to a point domain");
}
self.id.push(id);
self.start_point.push(start);

View File

@@ -5,7 +5,7 @@ pub use super::vector_modification::*;
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::math::quad::Quad;
use crate::subpath::{BezierHandles, ManipulatorGroup, Subpath};
use crate::table::Table;
use crate::table::{Table, TableRow};
use crate::transform::Transform;
use crate::vector::click_target::{ClickTargetType, FreePoint};
use crate::vector::misc::{HandleId, ManipulatorPointId};
@@ -490,18 +490,35 @@ pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resu
pub upstream_graphic_group: Option<Table<Graphic>>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OldTable<T> {
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
transform: Vec<DAffine2>,
alpha_blending: Vec<AlphaBlending>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct OlderTable<T> {
id: Vec<u64>,
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
enum VectorFormat {
Vector(Vector),
OldVectorData(OldVectorData),
OldVectorTable(OldTable<Vector>),
OlderVectorTable(OlderTable<Vector>),
VectorTable(Table<Vector>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::Vector(vector) => Table::new_from_element(vector),
EitherFormat::OldVectorData(old) => {
Ok(match VectorFormat::deserialize(deserializer)? {
VectorFormat::Vector(vector) => Table::new_from_element(vector),
VectorFormat::OldVectorData(old) => {
let mut vector_table = Table::new_from_element(Vector {
style: old.style,
colinear_manipulators: old.colinear_manipulators,
@@ -514,7 +531,19 @@ pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resu
*vector_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
vector_table
}
EitherFormat::VectorTable(vector_table) => vector_table,
VectorFormat::OlderVectorTable(older_table) => older_table.element.into_iter().map(|element| TableRow { element, ..Default::default() }).collect(),
VectorFormat::OldVectorTable(old_table) => old_table
.element
.into_iter()
.zip(old_table.transform.into_iter().zip(old_table.alpha_blending))
.map(|(element, (transform, alpha_blending))| TableRow {
element,
transform,
alpha_blending,
source_node_id: None,
})
.collect(),
VectorFormat::VectorTable(vector_table) => vector_table,
})
}

View File

@@ -45,7 +45,7 @@ pub struct DocumentNode {
#[cfg_attr(target_family = "wasm", serde(alias = "outputs"))]
pub inputs: Vec<NodeInput>,
/// Type of the argument which this node can be evaluated with.
#[serde(alias = "manual_composition", default)]
#[serde(default, alias = "manual_composition", deserialize_with = "migrate_call_argument")]
pub call_argument: Type,
// A nested document network or a proto-node identifier.
pub implementation: DocumentNodeImplementation,
@@ -57,12 +57,12 @@ pub struct DocumentNode {
/// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph.
#[serde(default)]
pub skip_deduplication: bool,
/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.
#[serde(skip)]
pub original_location: OriginalLocation,
/// List of Extract and Inject annotations for the Context.
#[serde(default)]
pub context_features: ContextDependencies,
/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.
#[serde(skip)]
pub original_location: OriginalLocation,
}
/// Represents the original location of a node input/output when [`NodeNetwork::generate_node_paths`] was called, allowing the types and errors to be derived.
@@ -1105,6 +1105,22 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
}
}
fn migrate_call_argument<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Type, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum CallArg {
New(Type),
Old(Option<Type>),
}
Ok(match CallArg::deserialize(deserializer)? {
CallArg::New(ty) => ty,
CallArg::Old(ty) => ty.unwrap_or_default(),
})
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -189,7 +189,7 @@ tagged_value! {
#[serde(alias = "VectorData")]
Vector(Table<Vector>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "ImageFrame", alias = "RasterData")]
#[serde(alias = "ImageFrame", alias = "RasterData", alias = "Image")]
Raster(Table<Raster<CPU>>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic::migrate_graphic"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "GraphicGroup", alias = "Group")]
@@ -361,7 +361,7 @@ impl TaggedValue {
x if x == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
x if x == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
x if x == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::ColorNotInTable(color))?,
x if x == TypeId::of::<Color>() => to_color(string).map(TaggedValue::ColorNotInTable)?,
x if x == TypeId::of::<Option<Color>>() => TaggedValue::ColorNotInTable(to_color(string)?),
x if x == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
x if x == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,

View File

@@ -22,6 +22,7 @@ use graphene_std::brush::brush_cache::BrushCache;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::gradient::GradientStops;
use graphene_std::table::Table;
use graphene_std::transform::Footprint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::Vector;
#[cfg(feature = "gpu")]
@@ -153,13 +154,15 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => SurfaceFrame]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DVec2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DAffine2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi]),
@@ -167,6 +170,51 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Color]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::Stroke]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::Gradient]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::text::Font]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<BrushStroke>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => BrushCache]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::transform::Footprint]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Box<graphene_core::vector::VectorModification>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::Fill]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::blending::BlendMode]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::extract_xy::XY]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::animation::RealTimeMode]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::GridType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::ArcType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::MergeByDistanceAlgorithm]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::PointSpacingType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::StrokeCap]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::StrokeJoin]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::StrokeAlign]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::PaintOrder]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::FillType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::GradientType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::transform::ReferencePoint]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::CentroidType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_path_bool::BooleanOperation]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::text::TextAlign]),
// =================
// IMPURE MEMO NODES
// =================