mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Migrate usage of the Hash trait for cache invalidation to the dedicated CacheHash trait (#4051)
* WIP start migrating usages of hash for cache invalidadion to dedicated trait * Finish migrating usages * Code review * Add comments clearifying the reasoning for using random ids in the VectorModification cach hash impl * Fix some remaining hash violations * Finish migration and fix compilation * Fix import ordering * Cleanup * Fix code review stuff --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -164,6 +164,12 @@ impl<Io> Hash for EditorApi<Io> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io> core_types::graphene_hash::CacheHash for EditorApi<Io> {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(self, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io> PartialEq for EditorApi<Io> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.font_cache == other.font_cache
|
||||
|
||||
@@ -16,6 +16,7 @@ wasm = ["tsify", "wasm-bindgen", "no-std-types/wasm"]
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
no-std-types = { workspace = true, features = ["std"] }
|
||||
graphene-hash = { workspace = true, features = ["derive"] }
|
||||
|
||||
# Workspace dependencies
|
||||
bitflags = { workspace = true }
|
||||
|
||||
@@ -163,6 +163,12 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
impl graphene_hash::CacheHash for ContextFeatures {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(self, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextFeatures {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match *self {
|
||||
@@ -182,7 +188,7 @@ impl ContextFeatures {
|
||||
// CONTEXT DEPENDENCIES
|
||||
// ====================
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]
|
||||
pub struct ContextDependencies {
|
||||
pub extract: ContextFeatures,
|
||||
pub inject: ContextFeatures,
|
||||
@@ -536,14 +542,14 @@ impl Default for OwnedContextImpl {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for OwnedContextImpl {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.footprint.hash(state);
|
||||
self.real_time.map(|x| x.to_bits()).hash(state);
|
||||
self.animation_time.map(|x| x.to_bits()).hash(state);
|
||||
self.pointer_position.map(|v| (v.x.to_bits(), v.y.to_bits())).hash(state);
|
||||
self.position.iter().flat_map(|x| x.iter()).map(|v| (v.x.to_bits(), v.y.to_bits())).for_each(|v| v.hash(state));
|
||||
self.index.hash(state);
|
||||
impl graphene_hash::CacheHash for OwnedContextImpl {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.footprint.cache_hash(state);
|
||||
self.real_time.cache_hash(state);
|
||||
self.animation_time.cache_hash(state);
|
||||
self.pointer_position.cache_hash(state);
|
||||
self.position.cache_hash(state);
|
||||
self.index.cache_hash(state);
|
||||
self.hash_varargs(state);
|
||||
}
|
||||
}
|
||||
@@ -600,9 +606,9 @@ pub trait DynHash {
|
||||
fn dyn_hash(&self, state: &mut dyn Hasher);
|
||||
}
|
||||
|
||||
impl<H: Hash + ?Sized> DynHash for H {
|
||||
impl<H: graphene_hash::CacheHash + ?Sized> DynHash for H {
|
||||
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
|
||||
self.hash(&mut state);
|
||||
graphene_hash::CacheHash::cache_hash(self, &mut state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ pub use color::Color;
|
||||
pub use context::*;
|
||||
pub use ctor;
|
||||
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
|
||||
pub use graphene_hash;
|
||||
pub use graphene_hash::CacheHash;
|
||||
pub use memo::MemoHash;
|
||||
pub use no_std_types::AsU32;
|
||||
pub use no_std_types::blending;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use graphene_hash::CacheHash;
|
||||
use std::hash::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
@@ -11,12 +12,12 @@ pub struct IORecord<I, O> {
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct MemoHash<T: Hash> {
|
||||
pub struct MemoHash<T: CacheHash> {
|
||||
hash: u64,
|
||||
value: Arc<T>,
|
||||
}
|
||||
|
||||
impl<'de, T: serde::Deserialize<'de> + Hash> serde::Deserialize<'de> for MemoHash<T> {
|
||||
impl<'de, T: serde::Deserialize<'de> + CacheHash> serde::Deserialize<'de> for MemoHash<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
@@ -25,7 +26,7 @@ impl<'de, T: serde::Deserialize<'de> + Hash> serde::Deserialize<'de> for MemoHas
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash + serde::Serialize> serde::Serialize for MemoHash<T> {
|
||||
impl<T: CacheHash + serde::Serialize> serde::Serialize for MemoHash<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
@@ -34,7 +35,7 @@ impl<T: Hash + serde::Serialize> serde::Serialize for MemoHash<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> MemoHash<T> {
|
||||
impl<T: CacheHash> MemoHash<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
let hash = Self::calc_hash(&value);
|
||||
Self { hash, value: value.into() }
|
||||
@@ -45,7 +46,7 @@ impl<T: Hash> MemoHash<T> {
|
||||
|
||||
fn calc_hash(data: &T) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
data.hash(&mut hasher);
|
||||
data.cache_hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
@@ -59,19 +60,26 @@ impl<T: Hash> MemoHash<T> {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
impl<T: Hash> From<T> for MemoHash<T> {
|
||||
|
||||
impl<T: CacheHash> From<T> for MemoHash<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for MemoHash<T> {
|
||||
impl<T: CacheHash> Hash for MemoHash<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.hash.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Deref for MemoHash<T> {
|
||||
impl<T: CacheHash> CacheHash for MemoHash<T> {
|
||||
fn cache_hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.hash.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CacheHash> Deref for MemoHash<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -79,18 +87,18 @@ impl<T: Hash> Deref for MemoHash<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoHashGuard<'a, T: Hash> {
|
||||
pub struct MemoHashGuard<'a, T: CacheHash> {
|
||||
inner: &'a mut MemoHash<T>,
|
||||
}
|
||||
|
||||
impl<T: Hash> Drop for MemoHashGuard<'_, T> {
|
||||
impl<T: CacheHash> Drop for MemoHashGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
let hash = MemoHash::<T>::calc_hash(&self.inner.value);
|
||||
self.inner.hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Deref for MemoHashGuard<'_, T> {
|
||||
impl<T: CacheHash> Deref for MemoHashGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -98,7 +106,7 @@ impl<T: Hash> Deref for MemoHashGuard<'_, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash + Clone> std::ops::DerefMut for MemoHashGuard<'_, T> {
|
||||
impl<T: CacheHash + Clone> std::ops::DerefMut for MemoHashGuard<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
Arc::make_mut(&mut self.inner.value)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::uuid::NodeId;
|
||||
use crate::{AlphaBlending, math::quad::Quad};
|
||||
use dyn_any::{StaticType, StaticTypeSized};
|
||||
use glam::DAffine2;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Table<T> {
|
||||
@@ -198,16 +197,16 @@ impl<T> Default for Table<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for Table<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
impl<T: graphene_hash::CacheHash> graphene_hash::CacheHash for Table<T> {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
for element in &self.element {
|
||||
element.hash(state);
|
||||
element.cache_hash(state);
|
||||
}
|
||||
for transform in &self.transform {
|
||||
transform.to_cols_array().map(|x| x.to_bits()).hash(state);
|
||||
graphene_hash::CacheHash::cache_hash(transform, state);
|
||||
}
|
||||
for alpha_blending in &self.alpha_blending {
|
||||
alpha_blending.hash(state);
|
||||
alpha_blending.cache_hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use glam::{DAffine2, DMat2, DVec2, UVec2};
|
||||
/// Controls whether the Decompose Scale node returns axis-length magnitudes or pure scale factors.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum ScaleType {
|
||||
/// The visual length of each axis (always positive, includes any skew contribution).
|
||||
@@ -141,7 +141,7 @@ impl TransformMut for Footprint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum RenderQuality {
|
||||
/// Low quality, fast rendering
|
||||
Preview,
|
||||
@@ -154,7 +154,7 @@ pub enum RenderQuality {
|
||||
/// Render at full quality
|
||||
Full,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Footprint {
|
||||
/// Inverse of the transform which will be applied to the node output during the rendering process
|
||||
pub transform: DAffine2,
|
||||
@@ -214,13 +214,6 @@ impl From<()> for Footprint {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Footprint {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_le_bytes().hash(state));
|
||||
self.resolution.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ApplyTransform {
|
||||
fn apply_transform(&mut self, modification: &DAffine2);
|
||||
fn left_apply_transform(&mut self, modification: &DAffine2);
|
||||
|
||||
@@ -77,7 +77,7 @@ macro_rules! fn_type_fut {
|
||||
}
|
||||
|
||||
// TODO: Rename to NodeSignatureMonomorphization
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct NodeIOTypes {
|
||||
pub call_argument: Type,
|
||||
pub return_value: Type,
|
||||
@@ -126,7 +126,7 @@ impl std::fmt::Debug for NodeIOTypes {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ProtoNodeIdentifier {
|
||||
name: Cow<'static, str>,
|
||||
}
|
||||
@@ -200,6 +200,12 @@ impl std::hash::Hash for TypeDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
impl graphene_hash::CacheHash for TypeDescriptor {
|
||||
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||
graphene_hash::CacheHash::cache_hash(&self.name, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TypeDescriptor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let text = make_type_user_readable(&simplify_identifier_name(&self.name));
|
||||
@@ -222,7 +228,7 @@ impl PartialEq for TypeDescriptor {
|
||||
|
||||
/// Graph runtime type information used for type inference.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Type {
|
||||
/// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type.
|
||||
Generic(Cow<'static, str>),
|
||||
|
||||
@@ -68,7 +68,7 @@ mod uuid_generation {
|
||||
|
||||
#[repr(transparent)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, graphene_hash::CacheHash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
pub struct NodeId(pub u64);
|
||||
|
||||
impl NodeId {
|
||||
|
||||
17
node-graph/libraries/graphene-hash/Cargo.toml
Normal file
17
node-graph/libraries/graphene-hash/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "graphene-hash"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
authors = ["Graphite Authors <contact@graphite.art>"]
|
||||
description = "CacheHash trait and derive macro for cache invalidation hashing in Graphite"
|
||||
license = "MIT OR Apache-2.0"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
derive = ["graphene-hash-derive"]
|
||||
|
||||
[dependencies]
|
||||
graphene-hash-derive = { path = "derive", optional = true }
|
||||
glam = { workspace = true }
|
||||
19
node-graph/libraries/graphene-hash/derive/Cargo.toml
Normal file
19
node-graph/libraries/graphene-hash/derive/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "graphene-hash-derive"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
authors = ["Graphite Authors <contact@graphite.art>"]
|
||||
description = "#[derive(CacheHash)]"
|
||||
license = "MIT OR Apache-2.0"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
graphene-hash = { path = "..", features = ["derive"] }
|
||||
129
node-graph/libraries/graphene-hash/derive/src/lib.rs
Normal file
129
node-graph/libraries/graphene-hash/derive/src/lib.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||
|
||||
/// Derives `CacheHash` for a struct or enum.
|
||||
///
|
||||
/// All fields must implement `CacheHash`. Fields annotated with `#[cache_hash(skip)]`
|
||||
/// are excluded from hashing.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use graphene_hash::CacheHash;
|
||||
/// #[derive(CacheHash)]
|
||||
/// pub struct MyNode {
|
||||
/// pub value: f64,
|
||||
/// pub count: u32,
|
||||
/// #[cache_hash(skip)]
|
||||
/// pub debug_label: String,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(CacheHash, attributes(cache_hash))]
|
||||
pub fn derive_cache_hash(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let name = &ast.ident;
|
||||
let mut generics = ast.generics.clone();
|
||||
for param in &mut generics.params {
|
||||
if let syn::GenericParam::Type(type_param) = param {
|
||||
type_param.bounds.push(syn::parse_quote!(graphene_hash::CacheHash));
|
||||
}
|
||||
}
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let body = match &ast.data {
|
||||
Data::Struct(s) => hash_fields(&s.fields, quote! { self }),
|
||||
Data::Enum(e) => {
|
||||
let arms = e.variants.iter().map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
let (pattern, hash_body) = match &variant.fields {
|
||||
Fields::Unit => (quote! {}, quote! {}),
|
||||
Fields::Unnamed(fields) => {
|
||||
let bindings: Vec<_> = (0..fields.unnamed.len())
|
||||
.map(|i| {
|
||||
let ident = proc_macro2::Ident::new(&format!("f{i}"), proc_macro2::Span::call_site());
|
||||
quote! { #ident }
|
||||
})
|
||||
.collect();
|
||||
let hash_stmts = fields.unnamed.iter().enumerate().filter_map(|(i, field)| {
|
||||
if has_skip_attr(&field.attrs) {
|
||||
return None;
|
||||
}
|
||||
let ident = proc_macro2::Ident::new(&format!("f{i}"), proc_macro2::Span::call_site());
|
||||
Some(quote! { graphene_hash::CacheHash::cache_hash(#ident, state); })
|
||||
});
|
||||
(quote! { (#(#bindings,)*) }, quote! { #(#hash_stmts)* })
|
||||
}
|
||||
Fields::Named(fields) => {
|
||||
let names: Vec<_> = fields.named.iter().map(|f| f.ident.as_ref().unwrap()).collect();
|
||||
let hash_stmts = fields.named.iter().filter_map(|field| {
|
||||
if has_skip_attr(&field.attrs) {
|
||||
return None;
|
||||
}
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
Some(quote! { graphene_hash::CacheHash::cache_hash(#ident, state); })
|
||||
});
|
||||
(quote! { { #(#names,)* } }, quote! { #(#hash_stmts)* })
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
Self::#variant_name #pattern => { #hash_body }
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
::core::hash::Hash::hash(&::core::mem::discriminant(self), state);
|
||||
match self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
Data::Union(_) => return syn::Error::new(ast.ident.span(), "CacheHash cannot be derived for unions").to_compile_error().into(),
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #impl_generics graphene_hash::CacheHash for #name #ty_generics #where_clause {
|
||||
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||
#body
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn hash_fields(fields: &Fields, self_expr: TokenStream2) -> TokenStream2 {
|
||||
match fields {
|
||||
Fields::Unit => quote! {},
|
||||
Fields::Unnamed(fields) => {
|
||||
let stmts = fields.unnamed.iter().enumerate().filter_map(|(i, field)| {
|
||||
if has_skip_attr(&field.attrs) {
|
||||
return None;
|
||||
}
|
||||
let index = syn::Index::from(i);
|
||||
Some(quote! { graphene_hash::CacheHash::cache_hash(&#self_expr.#index, state); })
|
||||
});
|
||||
quote! { #(#stmts)* }
|
||||
}
|
||||
Fields::Named(fields) => {
|
||||
let stmts = fields.named.iter().filter_map(|field| {
|
||||
if has_skip_attr(&field.attrs) {
|
||||
return None;
|
||||
}
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
Some(quote! { graphene_hash::CacheHash::cache_hash(&#self_expr.#ident, state); })
|
||||
});
|
||||
quote! { #(#stmts)* }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_skip_attr(attrs: &[syn::Attribute]) -> bool {
|
||||
attrs.iter().any(|attr| {
|
||||
if !attr.path().is_ident("cache_hash") {
|
||||
return false;
|
||||
}
|
||||
attr.parse_args::<syn::Ident>().map(|id| id == "skip").unwrap_or(false)
|
||||
})
|
||||
}
|
||||
237
node-graph/libraries/graphene-hash/src/lib.rs
Normal file
237
node-graph/libraries/graphene-hash/src/lib.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
|
||||
#[cfg(feature = "derive")]
|
||||
pub use graphene_hash_derive::CacheHash;
|
||||
|
||||
pub trait CacheHash {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H);
|
||||
}
|
||||
|
||||
/// Wrapper that implements `std::hash::Hash` by delegating to `CacheHash`.
|
||||
///
|
||||
/// Use this to store `CacheHash` types in `HashMap`/`HashSet` keys,
|
||||
/// making it explicit that float fields are hashed via bit patterns.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct CacheHashWrapper<T>(pub T);
|
||||
|
||||
impl<T: CacheHash> core::hash::Hash for CacheHashWrapper<T> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.cache_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CacheHash> CacheHash for core::ops::RangeInclusive<T> {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.start().cache_hash(state);
|
||||
self.end().cache_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> core::ops::Deref for CacheHashWrapper<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
// Bulk impl for types that already implement std::hash::Hash — delegates directly.
|
||||
#[macro_export]
|
||||
macro_rules! impl_via_hash {
|
||||
($($t:ty),* $(,)?) => {
|
||||
$(
|
||||
impl $crate::CacheHash for $t {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(self, state);
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_via_hash! {
|
||||
bool, char,
|
||||
u8, u16, u32, u64, u128, usize,
|
||||
i8, i16, i32, i64, i128, isize,
|
||||
// glam integer vector types have Hash
|
||||
glam::UVec2, glam::UVec3, glam::UVec4,
|
||||
glam::IVec2, glam::IVec3, glam::IVec4,
|
||||
glam::I64Vec2, glam::I64Vec3, glam::I64Vec4,
|
||||
glam::U64Vec2, glam::U64Vec3, glam::U64Vec4,
|
||||
glam::BVec2, glam::BVec3, glam::BVec4,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl_via_hash! {
|
||||
String,
|
||||
}
|
||||
|
||||
impl<'a> CacheHash for std::borrow::Cow<'a, str> {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(self, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheHash for str {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(self, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheHash for () {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, _state: &mut H) {}
|
||||
}
|
||||
|
||||
// f32 and f64: hash via bit pattern so NaN is handled deterministically.
|
||||
impl CacheHash for f32 {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(&self.to_bits(), state);
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheHash for f64 {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(&self.to_bits(), state);
|
||||
}
|
||||
}
|
||||
|
||||
// glam float vector/matrix types: hash each component via to_bits().
|
||||
macro_rules! impl_glam_array {
|
||||
($($t:ty),* $(,)?) => {
|
||||
$(
|
||||
impl CacheHash for $t {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
for v in self.to_array() {
|
||||
CacheHash::cache_hash(&v, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_glam_cols {
|
||||
($($t:ty),* $(,)?) => {
|
||||
$(
|
||||
impl CacheHash for $t {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
for v in self.to_cols_array() {
|
||||
CacheHash::cache_hash(&v, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_glam_array! {
|
||||
glam::Vec2, glam::Vec3, glam::Vec3A, glam::Vec4,
|
||||
glam::DVec2, glam::DVec3, glam::DVec4,
|
||||
}
|
||||
|
||||
impl_glam_cols! {
|
||||
glam::Mat2, glam::Mat3, glam::Mat3A, glam::Mat4,
|
||||
glam::DMat2, glam::DMat3, glam::DMat4,
|
||||
glam::Affine2, glam::Affine3A,
|
||||
glam::DAffine2, glam::DAffine3,
|
||||
}
|
||||
|
||||
// Quat / DQuat — to_array gives [x, y, z, w] as floats
|
||||
impl_glam_array! {
|
||||
glam::Quat, glam::DQuat,
|
||||
}
|
||||
|
||||
// Generic container impls.
|
||||
impl<T: CacheHash> CacheHash for Option<T> {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
None => core::hash::Hash::hash(&0u8, state),
|
||||
Some(v) => {
|
||||
core::hash::Hash::hash(&1u8, state);
|
||||
v.cache_hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CacheHash> CacheHash for [T] {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(&self.len(), state);
|
||||
for item in self {
|
||||
item.cache_hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CacheHash, const N: usize> CacheHash for [T; N] {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
for item in self {
|
||||
item.cache_hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: CacheHash> CacheHash for Vec<T> {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.as_slice().cache_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: CacheHash + ?Sized> CacheHash for Box<T> {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
(**self).cache_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: CacheHash + ?Sized> CacheHash for std::sync::Arc<T> {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
(**self).cache_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CacheHash + ?Sized> CacheHash for &T {
|
||||
#[inline]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
(**self).cache_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
// Tuple impls.
|
||||
macro_rules! impl_tuple {
|
||||
($($T:ident),+) => {
|
||||
impl<$($T: CacheHash),+> CacheHash for ($($T,)+) {
|
||||
#[inline]
|
||||
#[allow(non_snake_case)]
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
let ($($T,)+) = self;
|
||||
$($T.cache_hash(state);)+
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_tuple!(A, B);
|
||||
impl_tuple!(A, B, C);
|
||||
impl_tuple!(A, B, C, D);
|
||||
impl_tuple!(A, B, C, D, E);
|
||||
impl_tuple!(A, B, C, D, E, F);
|
||||
@@ -18,6 +18,7 @@ wasm = [
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
core-types = { workspace = true }
|
||||
graphene-hash = { workspace = true }
|
||||
raster-types = { workspace = true, features = ["wgpu"] }
|
||||
vector-types = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
@@ -9,10 +9,10 @@ use core_types::transform::Transform;
|
||||
use core_types::uuid::NodeId;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use std::hash::Hash;
|
||||
use graphene_hash::CacheHash;
|
||||
|
||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Artboard {
|
||||
pub content: Table<Graphic>,
|
||||
pub label: String,
|
||||
@@ -76,7 +76,7 @@ impl Transform for Artboard {
|
||||
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Default, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ArtboardGroup {
|
||||
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use core_types::Color;
|
||||
use core_types::blending::AlphaBlending;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::ops::TableConvert;
|
||||
use core_types::render_complexity::RenderComplexity;
|
||||
use core_types::table::{Table, TableRow};
|
||||
@@ -8,14 +9,13 @@ use core_types::uuid::NodeId;
|
||||
use dyn_any::DynAny;
|
||||
use glam::DAffine2;
|
||||
use raster_types::{CPU, GPU, Raster};
|
||||
use std::hash::Hash;
|
||||
use vector_types::GradientStops;
|
||||
// use vector_types::Vector;
|
||||
|
||||
pub type Vector = vector_types::Vector<Option<Table<Graphic>>>;
|
||||
|
||||
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Graphic {
|
||||
Graphic(Table<Graphic>),
|
||||
Vector(Table<Vector>),
|
||||
|
||||
@@ -14,6 +14,7 @@ license = "MIT OR Apache-2.0"
|
||||
# should be in this list instead of `[workspace.dependency]`
|
||||
std = [
|
||||
"dep:dyn-any",
|
||||
"dep:graphene-hash",
|
||||
"dep:serde",
|
||||
"dep:log",
|
||||
"glam/debug-glam-assert",
|
||||
@@ -32,6 +33,7 @@ node-macro = { workspace = true }
|
||||
|
||||
# Local std dependencies
|
||||
dyn-any = { workspace = true, optional = true }
|
||||
graphene-hash = { workspace = true, optional = true }
|
||||
|
||||
# Workspace dependencies
|
||||
bytemuck = { workspace = true }
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use core::fmt::Display;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use node_macro::BufferStruct;
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use num_traits::float::Float;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, BufferStruct)]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize, graphene_hash::CacheHash))]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", serde(default))]
|
||||
pub struct AlphaBlending {
|
||||
@@ -20,14 +19,6 @@ impl Default for AlphaBlending {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
impl Hash for AlphaBlending {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.opacity.to_bits().hash(state);
|
||||
self.fill.to_bits().hash(state);
|
||||
self.blend_mode.hash(state);
|
||||
self.clip.hash(state);
|
||||
}
|
||||
}
|
||||
impl Display for AlphaBlending {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let round = |x: f32| (x * 1e3).round() / 1e3;
|
||||
@@ -71,6 +62,7 @@ impl AlphaBlending {
|
||||
#[repr(i32)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(graphene_hash::CacheHash))]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||
pub enum BlendMode {
|
||||
// Basic group
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::color_traits::{Alpha, AlphaMut, AssociatedAlpha, Luminance, Luminance
|
||||
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use core::fmt::Debug;
|
||||
use core::hash::Hash;
|
||||
use glam::Vec4;
|
||||
use half::f16;
|
||||
use node_macro::BufferStruct;
|
||||
@@ -220,6 +219,7 @@ impl Pixel for Luma {}
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "std", derive(graphene_hash::CacheHash))]
|
||||
#[derive(Debug, Default, Clone, Copy, Pod, Zeroable, BufferStruct)]
|
||||
pub struct Color {
|
||||
red: f32,
|
||||
@@ -236,16 +236,6 @@ impl PartialEq for Color {
|
||||
|
||||
impl Eq for Color {}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
impl Hash for Color {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.red.to_bits().hash(state);
|
||||
self.green.to_bits().hash(state);
|
||||
self.blue.to_bits().hash(state);
|
||||
self.alpha.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl RGB for Color {
|
||||
type ColorChannel = f32;
|
||||
#[inline(always)]
|
||||
|
||||
@@ -14,6 +14,7 @@ wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
core-types = { workspace = true }
|
||||
graphene-hash = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
|
||||
@@ -5,7 +5,6 @@ use core_types::Color;
|
||||
use core_types::color::float_to_srgb_u8;
|
||||
use core_types::table::{Table, TableRow};
|
||||
// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core_types::color::*;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -64,8 +63,10 @@ impl<P: Pixel + PartialEq> PartialEq for Image<P> {
|
||||
#[derive(Debug, Clone, dyn_any::DynAny, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TransformImage(pub DAffine2);
|
||||
|
||||
impl Hash for TransformImage {
|
||||
fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
|
||||
impl core_types::CacheHash for TransformImage {
|
||||
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||
core_types::CacheHash::cache_hash(&self.0, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel + std::fmt::Debug> std::fmt::Debug for Image<P> {
|
||||
@@ -109,11 +110,11 @@ impl<P: Copy + Pixel> BitmapMut for Image<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Hash + Pixel> Hash for Image<P> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.width.hash(state);
|
||||
self.height.hash(state);
|
||||
self.data.hash(state);
|
||||
impl<P: core_types::CacheHash + Pixel> core_types::CacheHash for Image<P> {
|
||||
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||
core_types::CacheHash::cache_hash(&self.width, state);
|
||||
core_types::CacheHash::cache_hash(&self.height, state);
|
||||
core_types::CacheHash::cache_hash(&self.data, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ impl<P: Pixel> IntoIterator for Image<P> {
|
||||
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny)]
|
||||
enum RasterFrame {
|
||||
ImageFrame(Table<Image<Color>>),
|
||||
}
|
||||
@@ -237,7 +238,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphicElement {
|
||||
GraphicGroup(Table<GraphicElement>),
|
||||
RasterFrame(RasterFrame),
|
||||
@@ -372,7 +373,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
#[derive(Clone, Debug, 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>>),
|
||||
@@ -390,7 +391,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, 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>),
|
||||
|
||||
@@ -16,7 +16,7 @@ pub trait Storage: __private::Sealed + Clone + Debug + 'static {
|
||||
fn is_empty(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, Default)]
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct Raster<T>
|
||||
where
|
||||
Raster<T>: Storage,
|
||||
@@ -60,13 +60,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> core_types::CacheHash for Raster<T>
|
||||
where
|
||||
Raster<T>: Storage,
|
||||
T: core_types::CacheHash,
|
||||
{
|
||||
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||
core_types::CacheHash::cache_hash(&self.storage, state);
|
||||
}
|
||||
}
|
||||
|
||||
pub use cpu::CPU;
|
||||
|
||||
mod cpu {
|
||||
use super::*;
|
||||
use crate::raster_types::__private::Sealed;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, core_types::CacheHash, DynAny)]
|
||||
pub struct CPU(Image<Color>);
|
||||
|
||||
impl Sealed for Raster<CPU> {}
|
||||
@@ -140,6 +150,13 @@ mod gpu {
|
||||
pub texture: wgpu::Texture,
|
||||
}
|
||||
|
||||
impl core_types::CacheHash for GPU {
|
||||
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||
use ::core::hash::Hash;
|
||||
self.texture.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Sealed for Raster<GPU> {}
|
||||
|
||||
impl Storage for Raster<GPU> {
|
||||
@@ -164,7 +181,7 @@ mod gpu {
|
||||
use super::*;
|
||||
use crate::raster_types::__private::Sealed;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Hash, core_types::CacheHash)]
|
||||
pub struct GPU;
|
||||
|
||||
impl Sealed for Raster<GPU> {}
|
||||
|
||||
@@ -10,6 +10,7 @@ license = "MIT OR Apache-2.0"
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
core-types = { workspace = true }
|
||||
graphene-hash = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::render_ext::RenderExt;
|
||||
use crate::to_peniko::BlendModeExt;
|
||||
use core_types::CacheHash;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::color::{Alpha, Color};
|
||||
@@ -10,6 +11,7 @@ use core_types::transform::{Footprint, Transform};
|
||||
use core_types::uuid::{NodeId, generate_uuid};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_hash::CacheHashWrapper;
|
||||
use graphic_types::Vector;
|
||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
|
||||
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
||||
@@ -21,7 +23,6 @@ use kurbo::{Affine, Cap, Join, Shape};
|
||||
use num_traits::Zero;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use vector_types::gradient::GradientSpreadMethod;
|
||||
@@ -95,7 +96,7 @@ pub struct SvgRender {
|
||||
pub svg: Vec<SvgSegment>,
|
||||
pub svg_defs: String,
|
||||
pub transform: DAffine2,
|
||||
pub image_data: HashMap<Image<Color>, u64>,
|
||||
pub image_data: HashMap<CacheHashWrapper<Image<Color>>, u64>,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
@@ -191,7 +192,7 @@ pub struct RenderContext {
|
||||
pub resource_overrides: Vec<(peniko::ImageBrush, wgpu::Texture)>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Hash)]
|
||||
#[derive(Default, Clone, Copy, Hash, graphene_hash::CacheHash)]
|
||||
pub enum RenderOutputType {
|
||||
#[default]
|
||||
Svg,
|
||||
@@ -199,12 +200,13 @@ pub enum RenderOutputType {
|
||||
}
|
||||
|
||||
/// Static state used whilst rendering
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Default, Clone, CacheHash)]
|
||||
pub struct RenderParams {
|
||||
pub render_mode: RenderMode,
|
||||
pub footprint: Footprint,
|
||||
/// Ratio of physical pixels to logical pixels. `scale := physical_pixels / logical_pixels`
|
||||
/// Ignored when rendering to SVG.
|
||||
#[cache_hash(skip)]
|
||||
pub scale: f64,
|
||||
pub render_output_type: RenderOutputType,
|
||||
pub thumbnail: bool,
|
||||
@@ -223,25 +225,6 @@ pub struct RenderParams {
|
||||
pub viewport_zoom: f64,
|
||||
}
|
||||
|
||||
impl Hash for RenderParams {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.render_mode.hash(state);
|
||||
self.footprint.hash(state);
|
||||
self.render_output_type.hash(state);
|
||||
self.thumbnail.hash(state);
|
||||
self.hide_artboards.hash(state);
|
||||
self.for_export.hash(state);
|
||||
self.for_mask.hash(state);
|
||||
if let Some(x) = self.alignment_parent_transform {
|
||||
x.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
|
||||
}
|
||||
self.aligned_strokes.hash(state);
|
||||
self.override_paint_order.hash(state);
|
||||
self.artboard_background.hash(state);
|
||||
self.viewport_zoom.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderParams {
|
||||
pub fn for_clipper(&self) -> Self {
|
||||
Self { for_mask: true, ..*self }
|
||||
@@ -1426,7 +1409,7 @@ impl Render for Table<Raster<CPU>> {
|
||||
if render_params.to_canvas() {
|
||||
let mut image_copy = image.clone();
|
||||
image_copy.data_mut().map_pixels(|p| p.to_unassociated_alpha());
|
||||
let id = *render.image_data.entry(image_copy.into_data()).or_insert_with(generate_uuid);
|
||||
let id = *render.image_data.entry(CacheHashWrapper(image_copy.into_data())).or_insert_with(generate_uuid);
|
||||
|
||||
render.parent_tag(
|
||||
"foreignObject",
|
||||
|
||||
@@ -13,6 +13,7 @@ wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
core-types = { workspace = true }
|
||||
graphene-hash = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
|
||||
@@ -3,7 +3,7 @@ use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum GradientType {
|
||||
#[default]
|
||||
@@ -15,7 +15,7 @@ pub enum GradientType {
|
||||
// TODO: Use linear not gamma colors
|
||||
/// A list of colors associated with positions (in the range 0 to 1) along a gradient.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, DynAny)]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, DynAny)]
|
||||
pub struct GradientStops {
|
||||
/// The position of this stop, a factor from 0-1 along the length of the full gradient.
|
||||
pub position: Vec<f64>,
|
||||
@@ -60,17 +60,6 @@ impl<'de> serde::Deserialize<'de> for GradientStops {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for GradientStops {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.position.len().hash(state);
|
||||
for i in 0..self.position.len() {
|
||||
self.position[i].to_bits().hash(state);
|
||||
self.midpoint[i].to_bits().hash(state);
|
||||
self.color[i].hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GradientStops {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -336,7 +325,7 @@ impl GradientStops {
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum GradientSpreadMethod {
|
||||
#[default]
|
||||
@@ -360,7 +349,7 @@ impl GradientSpreadMethod {
|
||||
/// Contains the start and end points, along with the colors at varying points along the length.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
pub struct Gradient {
|
||||
pub stops: GradientStops,
|
||||
pub gradient_type: GradientType,
|
||||
@@ -382,21 +371,6 @@ impl Default for Gradient {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Gradient {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.stops.len().hash(state);
|
||||
[].iter()
|
||||
.chain(self.start.to_array().iter())
|
||||
.chain(self.end.to_array().iter())
|
||||
.chain(self.stops.position.iter())
|
||||
.chain(self.stops.midpoint.iter())
|
||||
.for_each(|x| x.to_bits().hash(state));
|
||||
self.stops.color.iter().for_each(|color| color.hash(state));
|
||||
self.gradient_type.hash(state);
|
||||
self.spread_method.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Gradient {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::ops::{Index, IndexMut};
|
||||
pub use structs::*;
|
||||
|
||||
/// Structure used to represent a path composed of [Bezier] curves.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, PartialEq, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Subpath<PointId: Identifier> {
|
||||
manipulator_groups: Vec<ManipulatorGroup<PointId>>,
|
||||
|
||||
@@ -6,12 +6,12 @@ use std::fmt::{Debug, Formatter, Result};
|
||||
use std::hash::Hash;
|
||||
|
||||
/// An id type used for each [ManipulatorGroup].
|
||||
pub trait Identifier: Sized + Clone + PartialEq + Hash + 'static {
|
||||
pub trait Identifier: Sized + Clone + PartialEq + Hash + graphene_hash::CacheHash + 'static {
|
||||
fn new() -> Self;
|
||||
}
|
||||
|
||||
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Copy, Clone, PartialEq, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorGroup<PointId: Identifier> {
|
||||
pub anchor: DVec2,
|
||||
@@ -20,22 +20,6 @@ pub struct ManipulatorGroup<PointId: Identifier> {
|
||||
pub id: PointId,
|
||||
}
|
||||
|
||||
// TODO: Remove once we no longer need to hash floats in Graphite
|
||||
impl<PointId: Identifier> Hash for ManipulatorGroup<PointId> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.in_handle.is_some().hash(state);
|
||||
if let Some(in_handle) = self.in_handle {
|
||||
in_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.out_handle.is_some().hash(state);
|
||||
if let Some(out_handle) = self.out_handle {
|
||||
out_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Debug for ManipulatorGroup<PointId> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("ManipulatorGroup")
|
||||
@@ -119,7 +103,7 @@ pub enum AppendType {
|
||||
SmoothJoin(f64),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, graphene_hash::CacheHash)]
|
||||
pub enum ArcType {
|
||||
Open,
|
||||
Closed,
|
||||
@@ -127,7 +111,7 @@ pub enum ArcType {
|
||||
}
|
||||
|
||||
/// Representation of the handle point(s) in a bezier segment.
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum BezierHandles {
|
||||
Linear,
|
||||
@@ -145,17 +129,6 @@ pub enum BezierHandles {
|
||||
},
|
||||
}
|
||||
|
||||
impl std::hash::Hash for BezierHandles {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
BezierHandles::Linear => {}
|
||||
BezierHandles::Quadratic { handle } => handle.to_array().map(|v| v.to_bits()).hash(state),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end].map(|handle| handle.to_array().map(|v| v.to_bits())).hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BezierHandles {
|
||||
pub fn is_cubic(&self) -> bool {
|
||||
matches!(self, Self::Cubic { .. })
|
||||
|
||||
@@ -368,7 +368,7 @@ impl Tangent for kurbo::PathSeg {
|
||||
}
|
||||
|
||||
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ManipulatorPointId {
|
||||
/// A control anchor - the start or end point of a bézier.
|
||||
Anchor(PointId),
|
||||
@@ -479,7 +479,7 @@ impl ManipulatorPointId {
|
||||
}
|
||||
|
||||
/// The type of handle found on a bézier curve.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum HandleType {
|
||||
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||
Primary,
|
||||
@@ -488,7 +488,7 @@ pub enum HandleType {
|
||||
}
|
||||
|
||||
/// Represents a primary or end handle found in a particular segment.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, graphene_hash::CacheHash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct HandleId {
|
||||
pub ty: HandleType,
|
||||
pub segment: SegmentId,
|
||||
@@ -572,3 +572,16 @@ pub enum InterpolationDistribution {
|
||||
/// All slants (changes in skew angle) between objects are covered at a constant rate, meaning more time is spent skewing through larger changes in slant.
|
||||
Slants,
|
||||
}
|
||||
|
||||
graphene_hash::impl_via_hash!(
|
||||
BooleanOperation,
|
||||
CentroidType,
|
||||
RowsOrColumns,
|
||||
GridType,
|
||||
ArcType,
|
||||
MergeByDistanceAlgorithm,
|
||||
ExtrudeJoiningAlgorithm,
|
||||
PointSpacingType,
|
||||
SpiralType,
|
||||
InterpolationDistribution
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ use core_types::math::bbox::AxisAlignedBbox;
|
||||
use glam::DVec2;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, graphene_hash::CacheHash, Eq, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ReferencePoint {
|
||||
#[default]
|
||||
None,
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::f64::consts::{PI, TAU};
|
||||
/// In the future we'll probably also add a pattern fill. This will probably be named "Paint" in the future.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
pub enum Fill {
|
||||
#[default]
|
||||
None,
|
||||
@@ -161,7 +161,7 @@ impl From<Gradient> for Fill {
|
||||
/// In the future we'll probably also add a pattern fill.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
pub enum FillChoice {
|
||||
#[default]
|
||||
None,
|
||||
@@ -209,7 +209,7 @@ impl From<Fill> for FillChoice {
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, graphene_hash::CacheHash, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum FillType {
|
||||
#[default]
|
||||
@@ -220,7 +220,7 @@ pub enum FillType {
|
||||
/// The stroke (outline) style of an SVG element.
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum StrokeCap {
|
||||
#[default]
|
||||
@@ -241,7 +241,7 @@ impl StrokeCap {
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum StrokeJoin {
|
||||
#[default]
|
||||
@@ -262,7 +262,7 @@ impl StrokeJoin {
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum StrokeAlign {
|
||||
#[default]
|
||||
@@ -279,7 +279,7 @@ impl StrokeAlign {
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny, node_macro::ChoiceType)]
|
||||
#[widget(Radio)]
|
||||
pub enum PaintOrder {
|
||||
#[default]
|
||||
@@ -299,7 +299,7 @@ fn daffine2_identity() -> DAffine2 {
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Debug, Clone, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[serde(default)]
|
||||
pub struct Stroke {
|
||||
/// Stroke color
|
||||
@@ -322,24 +322,6 @@ pub struct Stroke {
|
||||
pub paint_order: PaintOrder,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Stroke {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.color.hash(state);
|
||||
self.weight.to_bits().hash(state);
|
||||
{
|
||||
self.dash_lengths.len().hash(state);
|
||||
self.dash_lengths.iter().for_each(|length| length.to_bits().hash(state));
|
||||
}
|
||||
self.dash_offset.to_bits().hash(state);
|
||||
self.cap.hash(state);
|
||||
self.join.hash(state);
|
||||
self.join_miter_limit.to_bits().hash(state);
|
||||
self.align.hash(state);
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.paint_order.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stroke {
|
||||
pub const fn new(color: Option<Color>, weight: f64) -> Self {
|
||||
Self {
|
||||
@@ -512,19 +494,12 @@ impl Default for Stroke {
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize, DynAny)]
|
||||
pub struct PathStyle {
|
||||
pub stroke: Option<Stroke>,
|
||||
pub fill: Fill,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for PathStyle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.stroke.hash(state);
|
||||
self.fill.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PathStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let fill = &self.fill;
|
||||
@@ -680,7 +655,7 @@ impl PathStyle {
|
||||
|
||||
/// Ways the user can choose to view the artwork in the viewport.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, graphene_hash::CacheHash, DynAny)]
|
||||
pub enum RenderMode {
|
||||
/// Render with normal coloration at the current viewport resolution
|
||||
#[default]
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::iter::zip;
|
||||
macro_rules! create_ids {
|
||||
($($id:ident),*) => {
|
||||
$(
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, DynAny)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, graphene_hash::CacheHash, DynAny)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
/// A strongly typed ID
|
||||
pub struct $id(u64);
|
||||
@@ -79,7 +79,7 @@ impl std::hash::BuildHasher for NoHashBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct PointDomain {
|
||||
id: Vec<PointId>,
|
||||
@@ -87,13 +87,6 @@ pub struct PointDomain {
|
||||
pub(crate) position: Vec<DVec2>,
|
||||
}
|
||||
|
||||
impl Hash for PointDomain {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.position.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state));
|
||||
}
|
||||
}
|
||||
|
||||
impl PointDomain {
|
||||
pub const fn new() -> Self {
|
||||
Self { id: Vec::new(), position: Vec::new() }
|
||||
@@ -212,7 +205,7 @@ impl PointDomain {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
/// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct SegmentDomain {
|
||||
#[serde(alias = "ids")]
|
||||
@@ -594,7 +587,7 @@ impl SegmentDomain {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Hash, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
/// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the
|
||||
/// [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct RegionDomain {
|
||||
@@ -849,7 +842,7 @@ struct Faces {
|
||||
face_start: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FaceIterator<'a, Upstream> {
|
||||
vector: &'a Vector<Upstream>,
|
||||
faces: Faces,
|
||||
|
||||
@@ -17,12 +17,6 @@ pub struct PointModification {
|
||||
delta: HashMap<PointId, DVec2>,
|
||||
}
|
||||
|
||||
impl Hash for PointModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
generate_uuid().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl PointModification {
|
||||
/// Apply this modification to the specified [`PointDomain`].
|
||||
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
|
||||
@@ -511,9 +505,13 @@ impl VectorModification {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for VectorModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
generate_uuid().hash(state)
|
||||
// Intentionally non-deterministic: fields contain HashMaps with non-deterministic iteration order,
|
||||
// so we use a UUID to always bust the cache and force re-evaluation when any modification is present.
|
||||
// This will not actually lead to a cache invalidation in most cases due to the
|
||||
// graph inputs being wrapped in a `MemoHash` wrapper.
|
||||
impl graphene_hash::CacheHash for VectorModification {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(&generate_uuid(), state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ impl<Upstream: Default + 'static> Default for Vector<Upstream> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Upstream> std::hash::Hash for Vector<Upstream> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.point_domain.hash(state);
|
||||
self.segment_domain.hash(state);
|
||||
self.region_domain.hash(state);
|
||||
self.style.hash(state);
|
||||
self.colinear_manipulators.hash(state);
|
||||
impl<Upstream> graphene_hash::CacheHash for Vector<Upstream> {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.point_domain.cache_hash(state);
|
||||
self.segment_domain.cache_hash(state);
|
||||
self.region_domain.cache_hash(state);
|
||||
self.style.cache_hash(state);
|
||||
self.colinear_manipulators.cache_hash(state);
|
||||
// We don't hash the upstream_data intentionally
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user