520 lines
19 KiB
Rust
520 lines
19 KiB
Rust
use super::DocumentNode;
|
|
use crate::application_io::PlatformEditorApi;
|
|
use crate::proto::{Any as DAny, FutureAny};
|
|
use brush_nodes::brush_cache::BrushCache;
|
|
use brush_nodes::brush_stroke::BrushStroke;
|
|
use core_types::table::Table;
|
|
use core_types::transform::Footprint;
|
|
use core_types::uuid::NodeId;
|
|
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type};
|
|
use dyn_any::DynAny;
|
|
pub use dyn_any::StaticType;
|
|
use glam::{Affine2, Vec2};
|
|
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
|
use graphic_types::raster_types::{CPU, Image, Raster};
|
|
use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
|
use graphic_types::vector_types::vector::{self, ReferencePoint};
|
|
use graphic_types::{Artboard, Graphic, Vector};
|
|
use raster_nodes::curve::Curve;
|
|
use rendering::RenderMetadata;
|
|
use std::fmt::Display;
|
|
use std::hash::Hash;
|
|
use std::marker::PhantomData;
|
|
use std::str::FromStr;
|
|
pub use std::sync::Arc;
|
|
use text_nodes::Font;
|
|
use text_nodes::vector_types::GradientStop;
|
|
use vector::VectorModification;
|
|
|
|
pub struct TaggedValueTypeError;
|
|
|
|
/// Macro to generate the tagged value enum.
|
|
macro_rules! tagged_value {
|
|
($ ($( #[$meta:meta] )* $identifier:ident ($ty:ty) ),* $(,)?) => {
|
|
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
|
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
#[allow(clippy::large_enum_variant)] // TODO(TrueDoctor): Properly solve this disparity between the size of the largest and next largest variants
|
|
pub enum TaggedValue {
|
|
None,
|
|
$( $(#[$meta] ) *$identifier( $ty ), )*
|
|
RenderOutput(RenderOutput),
|
|
#[serde(skip)]
|
|
EditorApi(Arc<PlatformEditorApi>)
|
|
}
|
|
|
|
impl CacheHash for TaggedValue {
|
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
core::mem::discriminant(self).hash(state);
|
|
match self {
|
|
Self::None => {}
|
|
$( Self::$identifier(x) => { x.cache_hash(state) }),*
|
|
Self::RenderOutput(x) => x.cache_hash(state),
|
|
Self::EditorApi(x) => x.cache_hash(state),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> TaggedValue {
|
|
/// Converts to a Box<dyn DynAny>
|
|
pub fn to_dynany(self) -> DAny<'a> {
|
|
match self {
|
|
Self::None => Box::new(()),
|
|
$( Self::$identifier(x) => Box::new(x), )*
|
|
Self::RenderOutput(x) => Box::new(x),
|
|
Self::EditorApi(x) => Box::new(x),
|
|
}
|
|
}
|
|
/// Converts to a Arc<dyn Any + Send + Sync + 'static>
|
|
pub fn to_any(self) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
|
|
match self {
|
|
Self::None => Arc::new(()),
|
|
$( Self::$identifier(x) => Arc::new(x), )*
|
|
Self::RenderOutput(x) => Arc::new(x),
|
|
Self::EditorApi(x) => Arc::new(x),
|
|
}
|
|
}
|
|
/// Creates a core_types::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
|
|
pub fn ty(&self) -> Type {
|
|
match self {
|
|
Self::None => concrete!(()),
|
|
$( Self::$identifier(_) => concrete!($ty), )*
|
|
Self::RenderOutput(_) => concrete!(RenderOutput),
|
|
Self::EditorApi(_) => concrete!(&PlatformEditorApi)
|
|
}
|
|
}
|
|
/// Attempts to downcast the dynamic type to a tagged value
|
|
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
|
|
use dyn_any::downcast;
|
|
use std::any::TypeId;
|
|
|
|
match DynAny::type_id(input.as_ref()) {
|
|
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
|
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*
|
|
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
|
|
|
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
|
}
|
|
}
|
|
/// Attempts to downcast the dynamic type to a tagged value
|
|
pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result<Self, String> {
|
|
use std::any::TypeId;
|
|
|
|
match input.type_id() {
|
|
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
|
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )*
|
|
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))),
|
|
_ => Err(format!("Cannot convert {:?} to TaggedValue", std::any::type_name_of_val(input))),
|
|
}
|
|
}
|
|
/// Returns a TaggedValue from the type, where that value is its type's `Default::default()`
|
|
pub fn from_type(input: &Type) -> Option<Self> {
|
|
match input {
|
|
Type::Generic(_) => None,
|
|
Type::Concrete(concrete_type) => {
|
|
use std::any::TypeId;
|
|
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
|
|
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
|
Some(match concrete_type.id? {
|
|
x if x == TypeId::of::<()>() => TaggedValue::None,
|
|
// Table-wrapped types need a single-item default with the element's default, not an empty table
|
|
x if x == TypeId::of::<Table<Color>>() => TaggedValue::Color(Table::new_from_element(Color::default())),
|
|
x if x == TypeId::of::<Table<GradientStops>>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())),
|
|
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
|
|
_ => return None,
|
|
})
|
|
}
|
|
Type::Fn(_, output) => TaggedValue::from_type(output),
|
|
Type::Future(output) => {
|
|
TaggedValue::from_type(output)
|
|
}
|
|
}
|
|
}
|
|
pub fn from_type_or_none(input: &Type) -> Self {
|
|
Self::from_type(input).unwrap_or(TaggedValue::None)
|
|
}
|
|
pub fn to_debug_string(&self) -> String {
|
|
match self {
|
|
Self::None => "()".to_string(),
|
|
$( Self::$identifier(x) => format!("{:?}", x), )*
|
|
Self::RenderOutput(_) => "RenderOutput".to_string(),
|
|
Self::EditorApi(_) => "PlatformEditorApi".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
$(
|
|
impl From<$ty> for TaggedValue {
|
|
fn from(value: $ty) -> Self {
|
|
Self::$identifier(value)
|
|
}
|
|
}
|
|
)*
|
|
|
|
$(
|
|
impl<'a> TryFrom<&'a TaggedValue> for &'a $ty {
|
|
type Error = TaggedValueTypeError;
|
|
fn try_from(value: &'a TaggedValue) -> Result<Self, Self::Error> {
|
|
match value{
|
|
TaggedValue::$identifier(value) => Ok(value),
|
|
_ => Err(TaggedValueTypeError),
|
|
}
|
|
}
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
|
|
tagged_value! {
|
|
// ===========
|
|
// TABLE TYPES
|
|
// ===========
|
|
StringTable(Table<String>),
|
|
#[serde(deserialize_with = "core_types::misc::migrate_vec_f64_to_table")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "VecF64", alias = "VecF32", alias = "F64Array4")]
|
|
F64Table(Table<f64>),
|
|
NodeIdTable(Table<NodeId>),
|
|
#[serde(deserialize_with = "graphic_types::migrations::migrate_vector")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "VectorData")]
|
|
Vector(Table<Vector>),
|
|
#[serde(deserialize_with = "graphic_types::raster_types::image::migrate_image_frame")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "ImageFrame", alias = "RasterData", alias = "Image")]
|
|
Raster(Table<Raster<CPU>>),
|
|
#[serde(deserialize_with = "graphic_types::graphic::migrate_graphic")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "GraphicGroup", alias = "Group")]
|
|
Graphic(Table<Graphic>),
|
|
#[serde(deserialize_with = "graphic_types::artboard::migrate_artboard")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "ArtboardGroup")]
|
|
Artboard(Table<Artboard>),
|
|
#[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
|
|
Color(Table<Color>),
|
|
#[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "GradientPositions", alias = "GradientStops")]
|
|
GradientTable(Table<GradientStops>),
|
|
#[serde(deserialize_with = "brush_nodes::migrations::migrate_brush_strokes_to_table")] // TODO: Eventually remove this migration document upgrade code
|
|
#[serde(alias = "BrushStrokes")]
|
|
BrushStrokeTable(Table<BrushStroke>),
|
|
// ============
|
|
// SCALAR TYPES
|
|
// ============
|
|
F32(f32),
|
|
F64(f64),
|
|
U32(u32),
|
|
U64(u64),
|
|
Bool(bool),
|
|
String(String),
|
|
FVec2(Vec2),
|
|
FAffine2(Affine2),
|
|
#[serde(alias = "IVec2", alias = "UVec2")]
|
|
DVec2(DVec2),
|
|
DAffine2(DAffine2),
|
|
Stroke(Stroke),
|
|
Gradient(Gradient),
|
|
Font(Font),
|
|
BrushCache(BrushCache),
|
|
DocumentNode(DocumentNode),
|
|
ContextFeatures(ContextFeatures),
|
|
Curve(Curve),
|
|
Footprint(Footprint),
|
|
VectorModification(Box<VectorModification>),
|
|
ImageData(Image<Color>),
|
|
// ==========
|
|
// ENUM TYPES
|
|
// ==========
|
|
Fill(vector::style::Fill),
|
|
BlendMode(core_types::blending::BlendMode),
|
|
LuminanceCalculation(raster_nodes::adjustments::LuminanceCalculation),
|
|
QRCodeErrorCorrectionLevel(vector_nodes::generator_nodes::QRCodeErrorCorrectionLevel),
|
|
XY(graphene_core::extract_xy::XY),
|
|
StringCapitalization(text_nodes::StringCapitalization),
|
|
RedGreenBlue(raster_nodes::adjustments::RedGreenBlue),
|
|
RedGreenBlueAlpha(raster_nodes::adjustments::RedGreenBlueAlpha),
|
|
RealTimeMode(graphene_core::animation::RealTimeMode),
|
|
NoiseType(raster_nodes::adjustments::NoiseType),
|
|
FractalType(raster_nodes::adjustments::FractalType),
|
|
CellularDistanceFunction(raster_nodes::adjustments::CellularDistanceFunction),
|
|
CellularReturnType(raster_nodes::adjustments::CellularReturnType),
|
|
DomainWarpType(raster_nodes::adjustments::DomainWarpType),
|
|
RelativeAbsolute(raster_nodes::adjustments::RelativeAbsolute),
|
|
SelectiveColorChoice(raster_nodes::adjustments::SelectiveColorChoice),
|
|
GridType(vector::misc::GridType),
|
|
ArcType(vector::misc::ArcType),
|
|
RowsOrColumns(vector::misc::RowsOrColumns),
|
|
MergeByDistanceAlgorithm(vector::misc::MergeByDistanceAlgorithm),
|
|
ExtrudeJoiningAlgorithm(vector::misc::ExtrudeJoiningAlgorithm),
|
|
PointSpacingType(vector::misc::PointSpacingType),
|
|
SpiralType(vector::misc::SpiralType),
|
|
InterpolationDistribution(vector::misc::InterpolationDistribution),
|
|
#[serde(alias = "LineCap")]
|
|
StrokeCap(vector::style::StrokeCap),
|
|
#[serde(alias = "LineJoin")]
|
|
StrokeJoin(vector::style::StrokeJoin),
|
|
StrokeAlign(vector::style::StrokeAlign),
|
|
PaintOrder(vector::style::PaintOrder),
|
|
FillType(vector::style::FillType),
|
|
GradientType(vector::style::GradientType),
|
|
ReferencePoint(vector::ReferencePoint),
|
|
CentroidType(vector::misc::CentroidType),
|
|
BooleanOperation(vector::misc::BooleanOperation),
|
|
TextAlign(text_nodes::TextAlign),
|
|
ScaleType(core_types::transform::ScaleType),
|
|
}
|
|
|
|
impl TaggedValue {
|
|
pub fn to_primitive_string(&self) -> String {
|
|
match self {
|
|
TaggedValue::None => "()".to_string(),
|
|
TaggedValue::String(x) => format!("\"{x}\""),
|
|
TaggedValue::U32(x) => x.to_string() + "_u32",
|
|
TaggedValue::U64(x) => x.to_string() + "_u64",
|
|
TaggedValue::F32(x) => x.to_string() + "_f32",
|
|
TaggedValue::F64(x) => x.to_string() + "_f64",
|
|
TaggedValue::Bool(x) => x.to_string(),
|
|
TaggedValue::BlendMode(x) => "BlendMode::".to_string() + &x.to_string(),
|
|
_ => panic!("Cannot convert to primitive string"),
|
|
}
|
|
}
|
|
|
|
pub fn from_primitive_string(string: &str, ty: &Type) -> Option<Self> {
|
|
fn to_dvec2(input: &str) -> Option<DVec2> {
|
|
let mut split = input.split(',');
|
|
let x = split.next()?.trim().parse().ok()?;
|
|
let y = split.next()?.trim().parse().ok()?;
|
|
Some(DVec2::new(x, y))
|
|
}
|
|
|
|
fn to_color(input: &str) -> Option<Color> {
|
|
// String syntax (e.g. "000000ff")
|
|
if input.starts_with('"') && input.ends_with('"') {
|
|
let hex = input.trim().trim_matches('"').trim().trim_start_matches('#');
|
|
let color = Color::from_hex_str(hex);
|
|
if color.is_none() {
|
|
log::error!("Invalid default value color string: {input}");
|
|
}
|
|
return color;
|
|
}
|
|
|
|
// Color constant syntax (e.g. Color::BLACK)
|
|
let mut choices = input.split("::");
|
|
let (first, second) = (choices.next()?.trim(), choices.next()?.trim());
|
|
if first == "Color" {
|
|
return Some(match second {
|
|
"BLACK" => Color::BLACK,
|
|
"WHITE" => Color::WHITE,
|
|
"RED" => Color::RED,
|
|
"GREEN" => Color::GREEN,
|
|
"BLUE" => Color::BLUE,
|
|
"YELLOW" => Color::YELLOW,
|
|
"CYAN" => Color::CYAN,
|
|
"MAGENTA" => Color::MAGENTA,
|
|
"TRANSPARENT" => Color::TRANSPARENT,
|
|
_ => {
|
|
log::error!("Invalid default value color constant: {input}");
|
|
return None;
|
|
}
|
|
});
|
|
}
|
|
|
|
log::error!("Invalid default value color: {input}");
|
|
None
|
|
}
|
|
|
|
fn to_gradient(input: &str) -> Option<GradientStops> {
|
|
// String syntax: (e.g. "000000ff, ff0000ff")
|
|
let stops = input.split(',').filter_map(|s| to_color(s.trim())).collect::<Vec<_>>();
|
|
if stops.len() == 1 {
|
|
Some(GradientStops::new(vec![
|
|
GradientStop {
|
|
position: 0.,
|
|
midpoint: 0.5,
|
|
color: stops[0],
|
|
},
|
|
GradientStop {
|
|
position: 1.,
|
|
midpoint: 0.5,
|
|
color: stops[0],
|
|
},
|
|
]))
|
|
} else if stops.len() >= 2 {
|
|
let step = 1. / (stops.len() - 1) as f64;
|
|
Some(GradientStops::new(stops.into_iter().enumerate().map(|(i, color)| GradientStop {
|
|
position: i as f64 * step,
|
|
midpoint: 0.5,
|
|
color,
|
|
})))
|
|
} else {
|
|
log::error!("Invalid default value gradient string: {input}");
|
|
None
|
|
}
|
|
}
|
|
|
|
fn to_reference_point(input: &str) -> Option<ReferencePoint> {
|
|
let mut choices = input.split("::");
|
|
let (first, second) = (choices.next()?.trim(), choices.next()?.trim());
|
|
if first == "ReferencePoint" {
|
|
return Some(match second {
|
|
"None" => ReferencePoint::None,
|
|
"TopLeft" => ReferencePoint::TopLeft,
|
|
"TopCenter" => ReferencePoint::TopCenter,
|
|
"TopRight" => ReferencePoint::TopRight,
|
|
"CenterLeft" => ReferencePoint::CenterLeft,
|
|
"Center" => ReferencePoint::Center,
|
|
"CenterRight" => ReferencePoint::CenterRight,
|
|
"BottomLeft" => ReferencePoint::BottomLeft,
|
|
"BottomCenter" => ReferencePoint::BottomCenter,
|
|
"BottomRight" => ReferencePoint::BottomRight,
|
|
_ => {
|
|
log::error!("Invalid ReferencePoint default type variant: {input}");
|
|
return None;
|
|
}
|
|
});
|
|
}
|
|
|
|
log::error!("Invalid ReferencePoint default type: {input}");
|
|
None
|
|
}
|
|
|
|
match ty {
|
|
Type::Generic(_) => None,
|
|
Type::Concrete(concrete_type) => {
|
|
let ty = concrete_type.id?;
|
|
use std::any::TypeId;
|
|
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
|
|
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
|
let ty = match () {
|
|
() if ty == TypeId::of::<()>() => TaggedValue::None,
|
|
() if ty == TypeId::of::<String>() => TaggedValue::String(string.into()),
|
|
() if ty == TypeId::of::<f64>() => FromStr::from_str(string).map(TaggedValue::F64).ok()?,
|
|
() if ty == TypeId::of::<f32>() => FromStr::from_str(string).map(TaggedValue::F32).ok()?,
|
|
() if ty == TypeId::of::<u64>() => FromStr::from_str(string).map(TaggedValue::U64).ok()?,
|
|
() if ty == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
|
|
() if ty == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
|
|
() if ty == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
|
|
// `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants
|
|
() if ty == TypeId::of::<Color>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
|
|
() if ty == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
|
|
() if ty == TypeId::of::<Table<GradientStops>>() => to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))?,
|
|
() if ty == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
|
|
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
|
|
_ => return None,
|
|
};
|
|
Some(ty)
|
|
}
|
|
Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output),
|
|
Type::Future(fut) => TaggedValue::from_primitive_string(string, fut),
|
|
}
|
|
}
|
|
|
|
pub fn to_u32(&self) -> u32 {
|
|
match self {
|
|
TaggedValue::U32(x) => *x,
|
|
_ => panic!("Passed value is not of type u32"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for TaggedValue {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
TaggedValue::String(x) => f.write_str(x),
|
|
TaggedValue::U32(x) => f.write_fmt(format_args!("{x}")),
|
|
TaggedValue::U64(x) => f.write_fmt(format_args!("{x}")),
|
|
TaggedValue::F32(x) => f.write_fmt(format_args!("{x}")),
|
|
TaggedValue::F64(x) => f.write_fmt(format_args!("{x}")),
|
|
TaggedValue::Bool(x) => f.write_fmt(format_args!("{x}")),
|
|
_ => panic!("Cannot convert to string"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct UpcastNode {
|
|
value: MemoHash<TaggedValue>,
|
|
}
|
|
impl<'input> Node<'input, DAny<'input>> for UpcastNode {
|
|
type Output = FutureAny<'input>;
|
|
|
|
fn eval(&'input self, _: DAny<'input>) -> Self::Output {
|
|
let memo_clone = MemoHash::clone(&self.value);
|
|
Box::pin(async move { memo_clone.into_inner().as_ref().clone().to_dynany() })
|
|
}
|
|
}
|
|
impl UpcastNode {
|
|
pub fn new(value: MemoHash<TaggedValue>) -> Self {
|
|
Self { value }
|
|
}
|
|
}
|
|
#[derive(Default, Debug, Clone, Copy)]
|
|
pub struct UpcastAsRefNode<T: AsRef<U> + Sync + Send, U: Sync + Send>(pub T, PhantomData<U>);
|
|
|
|
impl<'i, T: 'i + AsRef<U> + Sync + Send, U: 'i + StaticType + Sync + Send> Node<'i, DAny<'i>> for UpcastAsRefNode<T, U> {
|
|
type Output = FutureAny<'i>;
|
|
#[inline(always)]
|
|
fn eval(&'i self, _: DAny<'i>) -> Self::Output {
|
|
Box::pin(async move { Box::new(self.0.as_ref()) as DAny<'i> })
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<U> + Sync + Send, U: Sync + Send> UpcastAsRefNode<T, U> {
|
|
pub const fn new(value: T) -> UpcastAsRefNode<T, U> {
|
|
UpcastAsRefNode(value, PhantomData)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
|
pub struct RenderOutput {
|
|
pub data: RenderOutputType,
|
|
pub metadata: RenderMetadata,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
|
pub enum RenderOutputType {
|
|
#[serde(skip)]
|
|
Texture(graphene_application_io::ImageTexture),
|
|
#[serde(skip)]
|
|
Buffer {
|
|
data: Vec<u8>,
|
|
width: u32,
|
|
height: u32,
|
|
},
|
|
Svg {
|
|
svg: String,
|
|
image_data: Vec<(u64, Image<Color>)>,
|
|
},
|
|
#[cfg(target_family = "wasm")]
|
|
CanvasFrame {
|
|
canvas_id: u64,
|
|
resolution: DVec2,
|
|
},
|
|
}
|
|
|
|
impl CacheHash for RenderOutputType {
|
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
core::mem::discriminant(self).hash(state);
|
|
match self {
|
|
Self::Texture(texture) => texture.hash(state),
|
|
Self::Buffer { data, width, height } => {
|
|
data.cache_hash(state);
|
|
width.cache_hash(state);
|
|
height.cache_hash(state);
|
|
}
|
|
Self::Svg { svg, image_data } => {
|
|
svg.cache_hash(state);
|
|
image_data.cache_hash(state);
|
|
}
|
|
#[cfg(target_family = "wasm")]
|
|
Self::CanvasFrame { canvas_id, resolution } => {
|
|
canvas_id.cache_hash(state);
|
|
resolution.cache_hash(state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Metadata is excluded because it's editor-side auxiliary data (click targets, transforms)
|
|
// that shouldn't affect render cache invalidation, and it contains HashMaps with non-deterministic iteration order
|
|
impl CacheHash for RenderOutput {
|
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
self.data.cache_hash(state);
|
|
}
|
|
}
|