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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 313 additions and 62 deletions

View file

@ -1786,7 +1786,7 @@ impl DocumentMessageHandler {
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
.or_else(|e| {
log::warn!("failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler");
log::warn!("Failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler.");
// TODO: Eventually remove this document upgrade code
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct OldDocumentMessageHandler {

View file

@ -16,7 +16,13 @@ pub enum EditorError {
#[error("The operation caused a document error:\n{0:?}")]
Document(String),
#[error("This document was created in an older version of the editor.\n\nBackwards compatibility is, regrettably, not present in the current alpha release.\n\nTechnical details:\n{0:?}")]
#[error(
"This document was created in an older version of the editor.\n\
\n\
Full backwards compatibility is not guaranteed in the current alpha release.\n\
\n\
If this document is critical, ask for support in Graphite's Discord community."
)]
DocumentDeserialization(String),
#[error("{0}")]

View file

@ -568,7 +568,7 @@ impl NodeNetworkInterface {
let skip_footprint = 1;
let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else {
log::error!("Could not get type for {node_id_path:?}, input: {input_index}");
// log::warn!("Could not get type for {node_id_path:?}, input: {input_index}");
return (concrete!(()), TypeSource::Error("could not get the protonode's input"));
};
@ -2629,7 +2629,7 @@ impl NodeNetworkInterface {
InputConnector::Node { node_id, input_index } => {
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { return };
let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else {
log::error!("Node metadata must exist on node: {input:?}");
// log::warn!("Node metadata must exist on node: {input:?}");
return;
};
let wire_update = WirePathUpdate {
@ -2721,7 +2721,7 @@ impl NodeNetworkInterface {
return;
};
let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else {
log::error!("Node metadata must exist on node: {input:?}");
// log::warn!("Node metadata must exist on node: {input:?}");
return;
};
input_metadata.transient_metadata.wire = TransientMetadata::Unloaded;

View file

@ -113,6 +113,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::math_nodes::root::IDENTIFIER,
aliases: &["graphene_core::ops::RootNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::absolute_value::IDENTIFIER,
aliases: &["graphene_core::ops::AbsoluteValueNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::logarithm::IDENTIFIER,
aliases: &["graphene_core::ops::LogarithmNode"],

View file

@ -79,6 +79,7 @@ pub enum PortfolioMessage {
document_is_saved: bool,
document_serialized_content: String,
to_front: bool,
select_after_open: bool,
},
ToggleResetNodesToDefinitionsOnOpen,
PasteIntoFolder {

View file

@ -422,17 +422,16 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_path,
document_serialized_content,
} => {
let document_id = DocumentId(generate_uuid());
responses.add(PortfolioMessage::OpenDocumentFileWithId {
document_id,
document_id: DocumentId(generate_uuid()),
document_name,
document_path,
document_is_auto_saved: false,
document_is_saved: true,
document_serialized_content,
to_front: false,
select_after_open: true,
});
responses.add(PortfolioMessage::SelectDocument { document_id });
}
PortfolioMessage::ToggleResetNodesToDefinitionsOnOpen => {
self.reset_node_definitions_on_open = !self.reset_node_definitions_on_open;
@ -446,6 +445,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
document_is_saved,
document_serialized_content,
to_front,
select_after_open,
} => {
// Upgrade the document being opened to use fresh copies of all nodes
let reset_node_definitions_on_open = reset_node_definitions_on_open || document_migration_reset_node_definition(&document_serialized_content);
@ -540,6 +540,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// Load the document into the portfolio so it opens in the editor
self.load_document(document, document_id, self.layers_panel_open, responses, to_front);
if select_after_open {
responses.add(PortfolioMessage::SelectDocument { document_id });
}
}
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
let mut all_new_ids = Vec::new();
@ -954,14 +958,15 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
let node_to_inspect = self.node_to_inspect();
let result = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
document_id,
ipp.viewport_bounds.size().as_uvec2(),
timing_information,
node_to_inspect,
ignore_hash,
);
let Some(document) = self.documents.get_mut(&document_id) else {
log::error!("Tried to render non-existent document");
return;
};
let viewport_resolution = ipp.viewport_bounds.size().as_uvec2();
let result = self
.executor
.submit_node_graph_evaluation(document, document_id, viewport_resolution, timing_information, node_to_inspect, ignore_hash);
match result {
Err(description) => {
@ -1173,7 +1178,7 @@ impl PortfolioMessageHandler {
/// Returns an iterator over the open documents in order.
pub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {
self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap"))
self.document_ids.iter().map(|id| self.documents.get(id).expect("Document id was not found in the document hashmap"))
}
fn document_index(&self, document_id: DocumentId) -> usize {

View file

@ -466,6 +466,7 @@ impl EditorHandle {
document_is_saved,
document_serialized_content,
to_front,
select_after_open: false,
};
self.dispatch(message);
}

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
// =================