mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 16:58:12 +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:
co-authored by
Keavon Chambers
parent
7bb01c9651
commit
3d84e63ef9
Generated
+31
@@ -350,6 +350,7 @@ dependencies = [
|
|||||||
"core-types",
|
"core-types",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"raster-nodes",
|
"raster-nodes",
|
||||||
"raster-types",
|
"raster-types",
|
||||||
@@ -873,6 +874,7 @@ dependencies = [
|
|||||||
"ctor",
|
"ctor",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"image",
|
"image",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
@@ -1916,6 +1918,7 @@ dependencies = [
|
|||||||
"graph-craft",
|
"graph-craft",
|
||||||
"graphene-application-io",
|
"graphene-application-io",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
|
"graphene-hash",
|
||||||
"graphic-types",
|
"graphic-types",
|
||||||
"iai-callgrind",
|
"iai-callgrind",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -1996,6 +1999,7 @@ dependencies = [
|
|||||||
"core-types",
|
"core-types",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"graphic-types",
|
"graphic-types",
|
||||||
"log",
|
"log",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
@@ -2005,6 +2009,24 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "graphene-hash"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"glam",
|
||||||
|
"graphene-hash-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "graphene-hash-derive"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"graphene-hash",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "graphene-std"
|
name = "graphene-std"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2065,6 +2087,7 @@ dependencies = [
|
|||||||
"core-types",
|
"core-types",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"raster-types",
|
"raster-types",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2182,6 +2205,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
"graph-craft",
|
"graph-craft",
|
||||||
|
"graphene-hash",
|
||||||
"graphene-std",
|
"graphene-std",
|
||||||
"graphite-proc-macros",
|
"graphite-proc-macros",
|
||||||
"image",
|
"image",
|
||||||
@@ -3296,6 +3320,7 @@ dependencies = [
|
|||||||
"core-types",
|
"core-types",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"half",
|
"half",
|
||||||
"log",
|
"log",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
@@ -4316,6 +4341,7 @@ dependencies = [
|
|||||||
"fastnoise-lite",
|
"fastnoise-lite",
|
||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"image",
|
"image",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
@@ -4361,6 +4387,7 @@ dependencies = [
|
|||||||
"core-types",
|
"core-types",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"image",
|
"image",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4501,6 +4528,7 @@ dependencies = [
|
|||||||
"core-types",
|
"core-types",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"graphic-types",
|
"graphic-types",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
@@ -5513,6 +5541,7 @@ dependencies = [
|
|||||||
"dyn-any",
|
"dyn-any",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"log",
|
"log",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"parley",
|
"parley",
|
||||||
@@ -6157,6 +6186,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
|
"graphene-hash",
|
||||||
"graphic-types",
|
"graphic-types",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
@@ -6182,6 +6212,7 @@ dependencies = [
|
|||||||
"dyn-any",
|
"dyn-any",
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graphene-hash",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"lyon_geom",
|
"lyon_geom",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ members = [
|
|||||||
"frontend/wrapper",
|
"frontend/wrapper",
|
||||||
"libraries/dyn-any",
|
"libraries/dyn-any",
|
||||||
"libraries/math-parser",
|
"libraries/math-parser",
|
||||||
|
"node-graph/libraries/graphene-hash",
|
||||||
"node-graph/libraries/*",
|
"node-graph/libraries/*",
|
||||||
"node-graph/nodes/*",
|
"node-graph/nodes/*",
|
||||||
"node-graph/nodes/raster/shaders",
|
"node-graph/nodes/raster/shaders",
|
||||||
@@ -63,6 +64,7 @@ dyn-any = { path = "libraries/dyn-any", features = [
|
|||||||
"log-bad-types",
|
"log-bad-types",
|
||||||
"rc",
|
"rc",
|
||||||
] }
|
] }
|
||||||
|
graphene-hash = { path = "node-graph/libraries/graphene-hash", features = ["derive"] }
|
||||||
preprocessor = { path = "node-graph/preprocessor" }
|
preprocessor = { path = "node-graph/preprocessor" }
|
||||||
math-parser = { path = "libraries/math-parser" }
|
math-parser = { path = "libraries/math-parser" }
|
||||||
graphene-application-io = { path = "node-graph/libraries/application-io" }
|
graphene-application-io = { path = "node-graph/libraries/application-io" }
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ gpu = ["interpreted-executor/gpu", "dep:wgpu-executor"]
|
|||||||
# Local dependencies
|
# Local dependencies
|
||||||
graphite-proc-macros = { workspace = true }
|
graphite-proc-macros = { workspace = true }
|
||||||
graph-craft = { workspace = true }
|
graph-craft = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
interpreted-executor = { workspace = true }
|
interpreted-executor = { workspace = true }
|
||||||
graphene-std = { workspace = true } # NOTE: `core-types` should not be added here because `graphene-std` re-exports its contents
|
graphene-std = { workspace = true } # NOTE: `core-types` should not be added here because `graphene-std` re-exports its contents
|
||||||
preprocessor = { workspace = true }
|
preprocessor = { workspace = true }
|
||||||
|
|||||||
+3
-4
@@ -1,6 +1,5 @@
|
|||||||
use graph_craft::document::NodeNetwork;
|
use graph_craft::document::NodeNetwork;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct MemoNetwork {
|
pub struct MemoNetwork {
|
||||||
@@ -26,9 +25,9 @@ impl serde::Serialize for MemoNetwork {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for MemoNetwork {
|
impl graphene_hash::CacheHash for MemoNetwork {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.current_hash().hash(state);
|
self.current_hash().cache_hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub struct GradientOptions {
|
|||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Gradient)]
|
#[impl_message(Message, ToolMessage, Gradient)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum GradientToolMessage {
|
pub enum GradientToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use editor::messages::portfolio::utility_types::{DockingSplitDirection, FontCata
|
|||||||
use editor::messages::prelude::*;
|
use editor::messages::prelude::*;
|
||||||
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
|
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
use graphene_std::graphene_hash::CacheHashWrapper;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
use graphene_std::vector::GradientStops;
|
use graphene_std::vector::GradientStops;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -131,7 +132,7 @@ impl EditorWrapper {
|
|||||||
// Sends a FrontendMessage to JavaScript
|
// Sends a FrontendMessage to JavaScript
|
||||||
pub(crate) fn send_frontend_message_to_js(&self, message: FrontendMessage) {
|
pub(crate) fn send_frontend_message_to_js(&self, message: FrontendMessage) {
|
||||||
if let FrontendMessage::UpdateImageData { ref image_data } = message {
|
if let FrontendMessage::UpdateImageData { ref image_data } = message {
|
||||||
let new_hash = calculate_hash(image_data);
|
let new_hash = calculate_hash(&CacheHashWrapper(image_data));
|
||||||
let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);
|
let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);
|
||||||
|
|
||||||
if new_hash != prev_hash {
|
if new_hash != prev_hash {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ wasm = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
dyn-any = { workspace = true }
|
dyn-any = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
brush-nodes = { workspace = true }
|
brush-nodes = { workspace = true }
|
||||||
graphene-core = { workspace = true }
|
graphene-core = { workspace = true }
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use core_types::{Context, ContextDependencies, Cow, MemoHash, ProtoNodeIdentifie
|
|||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use log::Metadata;
|
use log::Metadata;
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
use rustc_hash::FxHashMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
@@ -32,7 +32,7 @@ fn return_true() -> bool {
|
|||||||
/// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`].
|
/// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`].
|
||||||
/// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition.
|
/// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition.
|
||||||
/// But we will want to change it in the future so it merely references its definition.
|
/// But we will want to change it in the future so it merely references its definition.
|
||||||
#[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct DocumentNode {
|
pub struct DocumentNode {
|
||||||
/// The inputs to a node, which are either:
|
/// The inputs to a node, which are either:
|
||||||
/// - From other nodes within this graph [`NodeInput::Node`],
|
/// - From other nodes within this graph [`NodeInput::Node`],
|
||||||
@@ -172,7 +172,7 @@ impl DocumentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the possible inputs to a node.
|
/// Represents the possible inputs to a node.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Hash, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum NodeInput {
|
pub enum NodeInput {
|
||||||
/// A reference to another node in the same network from which this node can receive its input.
|
/// A reference to another node in the same network from which this node can receive its input.
|
||||||
Node { node_id: NodeId, output_index: usize },
|
Node { node_id: NodeId, output_index: usize },
|
||||||
@@ -196,7 +196,7 @@ pub enum NodeInput {
|
|||||||
Inline(InlineRust),
|
Inline(InlineRust),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Hash, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct InlineRust {
|
pub struct InlineRust {
|
||||||
pub expr: String,
|
pub expr: String,
|
||||||
pub ty: Type,
|
pub ty: Type,
|
||||||
@@ -208,7 +208,7 @@ impl InlineRust {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Hash, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum DocumentNodeMetadata {
|
pub enum DocumentNodeMetadata {
|
||||||
DocumentNodePath,
|
DocumentNodePath,
|
||||||
}
|
}
|
||||||
@@ -292,7 +292,7 @@ pub enum OldDocumentNodeImplementation {
|
|||||||
Extract,
|
Extract,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or `Extract`.
|
/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or `Extract`.
|
||||||
pub enum DocumentNodeImplementation {
|
pub enum DocumentNodeImplementation {
|
||||||
/// This describes a (document) node built out of a subgraph of other (document) nodes.
|
/// This describes a (document) node built out of a subgraph of other (document) nodes.
|
||||||
@@ -546,29 +546,42 @@ pub struct NodeNetwork {
|
|||||||
pub generated: bool,
|
pub generated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for NodeNetwork {
|
impl core_types::CacheHash for NodeNetwork {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.exports.hash(state);
|
self.exports.cache_hash(state);
|
||||||
|
|
||||||
let mut nodes: Vec<_> = self.nodes.iter().collect();
|
let mut nodes: Vec<_> = self.nodes.iter().collect();
|
||||||
nodes.sort_by_key(|(id, _)| *id);
|
nodes.sort_by_key(|(id, _)| *id);
|
||||||
for (id, node) in nodes {
|
for (id, node) in nodes {
|
||||||
id.hash(state);
|
id.cache_hash(state);
|
||||||
node.hash(state);
|
node.cache_hash(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut scope_injections: Vec<_> = self.scope_injections.iter().collect();
|
||||||
|
scope_injections.sort_by_key(|(key, _)| key.as_str());
|
||||||
|
for (key, (node_id, ty)) in scope_injections {
|
||||||
|
key.cache_hash(state);
|
||||||
|
node_id.cache_hash(state);
|
||||||
|
ty.cache_hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for NodeNetwork {
|
impl PartialEq for NodeNetwork {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.exports == other.exports
|
self.exports == other.exports && self.nodes == other.nodes && self.scope_injections == other.scope_injections
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Graph modification functions
|
/// Graph modification functions
|
||||||
impl NodeNetwork {
|
impl NodeNetwork {
|
||||||
pub fn current_hash(&self) -> u64 {
|
pub fn current_hash(&self) -> u64 {
|
||||||
use std::hash::BuildHasher;
|
use core_types::graphene_hash::CacheHash;
|
||||||
FxBuildHasher.hash_one(self)
|
use rustc_hash::FxHasher;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
let mut hasher = FxHasher::default();
|
||||||
|
self.cache_hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value_network(node: DocumentNode) -> Self {
|
pub fn value_network(node: DocumentNode) -> Self {
|
||||||
@@ -1136,6 +1149,17 @@ fn migrate_call_argument<'de, D: serde::Deserializer<'de>>(deserializer: D) -> R
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl core_types::graphene_hash::CacheHash for DocumentNode {
|
||||||
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.inputs.cache_hash(state);
|
||||||
|
self.call_argument.cache_hash(state);
|
||||||
|
self.implementation.cache_hash(state);
|
||||||
|
self.visible.cache_hash(state);
|
||||||
|
self.skip_deduplication.cache_hash(state);
|
||||||
|
self.context_features.cache_hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -1254,11 +1278,11 @@ mod test {
|
|||||||
};
|
};
|
||||||
network.populate_dependants();
|
network.populate_dependants();
|
||||||
network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), gen_node_id);
|
network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), gen_node_id);
|
||||||
let flat_network = flat_network();
|
let expected = flatten_add_expected();
|
||||||
println!("{flat_network:#?}");
|
println!("{expected:#?}");
|
||||||
println!("{network:#?}");
|
println!("{network:#?}");
|
||||||
|
|
||||||
assert_eq!(flat_network, network);
|
assert_eq!(expected, network);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1345,6 +1369,55 @@ mod test {
|
|||||||
pretty_assertions::assert_eq!(resolved_network[0], construction_network);
|
pretty_assertions::assert_eq!(resolved_network[0], construction_network);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flatten_add_expected() -> NodeNetwork {
|
||||||
|
NodeNetwork {
|
||||||
|
exports: vec![NodeInput::node(NodeId(11), 0)],
|
||||||
|
nodes: [
|
||||||
|
(
|
||||||
|
NodeId(10),
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::import(concrete!(u32), 0), NodeInput::node(NodeId(14), 0)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::structural::ConsNode")),
|
||||||
|
original_location: OriginalLocation {
|
||||||
|
inputs_source: [(Source { node: vec![], index: 0 }, 1)].into(),
|
||||||
|
dependants: vec![vec![NodeId(11)]],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeId(14),
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::U32(2), false)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::value::ClonedNode")),
|
||||||
|
original_location: OriginalLocation {
|
||||||
|
path: Some(vec![NodeId(4)]),
|
||||||
|
dependants: vec![vec![NodeId(1), NodeId(10)]],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeId(11),
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::node(NodeId(10), 0)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::ops::AddPairNode")),
|
||||||
|
original_location: OriginalLocation {
|
||||||
|
dependants: vec![vec![]],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn flat_network() -> NodeNetwork {
|
fn flat_network() -> NodeNetwork {
|
||||||
NodeNetwork {
|
NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(11), 0)],
|
exports: vec![NodeInput::node(NodeId(11), 0)],
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use brush_nodes::brush_cache::BrushCache;
|
|||||||
use brush_nodes::brush_stroke::BrushStroke;
|
use brush_nodes::brush_stroke::BrushStroke;
|
||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{Color, ContextFeatures, MemoHash, Node, Type};
|
use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
pub use dyn_any::StaticType;
|
pub use dyn_any::StaticType;
|
||||||
use glam::{Affine2, Vec2};
|
use glam::{Affine2, Vec2};
|
||||||
@@ -43,19 +43,18 @@ macro_rules! tagged_value {
|
|||||||
EditorApi(Arc<PlatformEditorApi>)
|
EditorApi(Arc<PlatformEditorApi>)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
impl CacheHash for TaggedValue {
|
||||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
impl Hash for TaggedValue {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
core::mem::discriminant(self).hash(state);
|
core::mem::discriminant(self).hash(state);
|
||||||
match self {
|
match self {
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
$( Self::$identifier(x) => {x.hash(state)}),*
|
$( Self::$identifier(x) => { x.cache_hash(state) }),*
|
||||||
Self::RenderOutput(x) => x.hash(state),
|
Self::RenderOutput(x) => x.cache_hash(state),
|
||||||
Self::EditorApi(x) => x.hash(state),
|
Self::EditorApi(x) => x.cache_hash(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TaggedValue {
|
impl<'a> TaggedValue {
|
||||||
/// Converts to a Box<dyn DynAny>
|
/// Converts to a Box<dyn DynAny>
|
||||||
pub fn to_dynany(self) -> DAny<'a> {
|
pub fn to_dynany(self) -> DAny<'a> {
|
||||||
@@ -495,96 +494,33 @@ pub enum RenderOutputType {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for RenderOutputType {
|
impl CacheHash for RenderOutputType {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
core::mem::discriminant(self).hash(state);
|
||||||
match self {
|
match self {
|
||||||
Self::Texture(texture) => {
|
Self::Texture(texture) => texture.hash(state),
|
||||||
texture.hash(state);
|
|
||||||
}
|
|
||||||
Self::Buffer { data, width, height } => {
|
Self::Buffer { data, width, height } => {
|
||||||
data.hash(state);
|
data.cache_hash(state);
|
||||||
width.hash(state);
|
width.cache_hash(state);
|
||||||
height.hash(state);
|
height.cache_hash(state);
|
||||||
}
|
}
|
||||||
Self::Svg { svg, image_data } => {
|
Self::Svg { svg, image_data } => {
|
||||||
svg.hash(state);
|
svg.cache_hash(state);
|
||||||
image_data.hash(state);
|
image_data.cache_hash(state);
|
||||||
}
|
}
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
Self::CanvasFrame { canvas_id, resolution } => {
|
Self::CanvasFrame { canvas_id, resolution } => {
|
||||||
canvas_id.hash(state);
|
canvas_id.cache_hash(state);
|
||||||
resolution.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
resolution.cache_hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Hash for RenderOutput {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.data.hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack)
|
// Metadata is excluded because it's editor-side auxiliary data (click targets, transforms)
|
||||||
trait FakeHash {
|
// that shouldn't affect render cache invalidation, and it contains HashMaps with non-deterministic iteration order
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H);
|
impl CacheHash for RenderOutput {
|
||||||
}
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
mod fake_hash {
|
self.data.cache_hash(state);
|
||||||
use super::*;
|
|
||||||
impl FakeHash for f64 {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.to_bits().hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FakeHash for f32 {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.to_bits().hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FakeHash for DVec2 {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.to_array().iter().for_each(|x| x.to_bits().hash(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FakeHash for Vec2 {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.to_array().iter().for_each(|x| x.to_bits().hash(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FakeHash for DAffine2 {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FakeHash for Affine2 {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: FakeHash> FakeHash for Option<T> {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
if let Some(x) = self {
|
|
||||||
1.hash(state);
|
|
||||||
x.hash(state);
|
|
||||||
} else {
|
|
||||||
0.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: FakeHash> FakeHash for Vec<T> {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.len().hash(state);
|
|
||||||
self.iter().for_each(|x| x.hash(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: FakeHash, const N: usize> FakeHash for [T; N] {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.iter().for_each(|x| x.hash(state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FakeHash for (f64, Color) {
|
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.0.to_bits().hash(state);
|
|
||||||
self.1.hash(state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ pub async fn export_document(
|
|||||||
}
|
}
|
||||||
RenderOutputType::Texture(image_texture) => {
|
RenderOutputType::Texture(image_texture) => {
|
||||||
// Convert GPU texture to CPU buffer
|
// Convert GPU texture to CPU buffer
|
||||||
let gpu_raster = Raster::<GPU>::new_gpu(image_texture.texture.as_ref().clone());
|
let gpu_raster = Raster::<GPU>::new_gpu(image_texture.as_ref().clone());
|
||||||
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;
|
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;
|
||||||
let (data, width, height) = cpu_raster.to_flat_u8();
|
let (data, width, height) = cpu_raster.to_flat_u8();
|
||||||
// Explicitly drop texture to make sure it lives long enough
|
// Explicitly drop texture to make sure it lives long enough
|
||||||
@@ -202,7 +202,7 @@ pub async fn export_gif(
|
|||||||
let (data, img_width, img_height) = match result {
|
let (data, img_width, img_height) = match result {
|
||||||
TaggedValue::RenderOutput(output) => match output.data {
|
TaggedValue::RenderOutput(output) => match output.data {
|
||||||
RenderOutputType::Texture(image_texture) => {
|
RenderOutputType::Texture(image_texture) => {
|
||||||
let gpu_raster = Raster::<GPU>::new_gpu(image_texture.texture.as_ref().clone());
|
let gpu_raster = Raster::<GPU>::new_gpu(image_texture.as_ref().clone());
|
||||||
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;
|
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor).await;
|
||||||
// Explicitly drop texture to make sure it lives long enough
|
// Explicitly drop texture to make sure it lives long enough
|
||||||
std::mem::drop(image_texture);
|
std::mem::drop(image_texture);
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ use clap::{Args, Parser, Subcommand};
|
|||||||
use fern::colors::{Color, ColoredLevelConfig};
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use graph_craft::application_io::EditorPreferences;
|
use graph_craft::application_io::EditorPreferences;
|
||||||
|
use graph_craft::application_io::{PlatformApplicationIo, PlatformEditorApi};
|
||||||
use graph_craft::document::*;
|
use graph_craft::document::*;
|
||||||
use graph_craft::graphene_compiler::Compiler;
|
use graph_craft::graphene_compiler::Compiler;
|
||||||
use graph_craft::proto::ProtoNetwork;
|
use graph_craft::proto::ProtoNetwork;
|
||||||
use graph_craft::util::load_network;
|
use graph_craft::util::load_network;
|
||||||
use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
|
use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
|
||||||
use graphene_std::application_io::{PlatformEditorApi, WasmApplicationIo};
|
|
||||||
use graphene_std::text::FontCache;
|
use graphene_std::text::FontCache;
|
||||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||||
use interpreted_executor::util::wrap_network_in_scope;
|
use interpreted_executor::util::wrap_network_in_scope;
|
||||||
@@ -121,7 +121,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let document_string = std::fs::read_to_string(document_path).expect("Failed to read document");
|
let document_string = std::fs::read_to_string(document_path).expect("Failed to read document");
|
||||||
|
|
||||||
log::info!("Creating GPU context");
|
log::info!("Creating GPU context");
|
||||||
let mut application_io = block_on(WasmApplicationIo::new());
|
let mut application_io = block_on(PlatformApplicationIo::new());
|
||||||
|
|
||||||
if let Command::Export { image: Some(ref image_path), .. } = app.command {
|
if let Command::Export { image: Some(ref image_path), .. } = app.command {
|
||||||
application_io.resources.insert("null".to_string(), Arc::from(std::fs::read(image_path).expect("Failed to read image")));
|
application_io.resources.insert("null".to_string(), Arc::from(std::fs::read(image_path).expect("Failed to read image")));
|
||||||
|
|||||||
@@ -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> {
|
impl<Io> PartialEq for EditorApi<Io> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.font_cache == other.font_cache
|
self.font_cache == other.font_cache
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ wasm = ["tsify", "wasm-bindgen", "no-std-types/wasm"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
no-std-types = { workspace = true, features = ["std"] }
|
no-std-types = { workspace = true, features = ["std"] }
|
||||||
|
graphene-hash = { workspace = true, features = ["derive"] }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
bitflags = { workspace = true }
|
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 {
|
impl ContextFeatures {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match *self {
|
match *self {
|
||||||
@@ -182,7 +188,7 @@ impl ContextFeatures {
|
|||||||
// CONTEXT DEPENDENCIES
|
// 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 struct ContextDependencies {
|
||||||
pub extract: ContextFeatures,
|
pub extract: ContextFeatures,
|
||||||
pub inject: ContextFeatures,
|
pub inject: ContextFeatures,
|
||||||
@@ -536,14 +542,14 @@ impl Default for OwnedContextImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for OwnedContextImpl {
|
impl graphene_hash::CacheHash for OwnedContextImpl {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.footprint.hash(state);
|
self.footprint.cache_hash(state);
|
||||||
self.real_time.map(|x| x.to_bits()).hash(state);
|
self.real_time.cache_hash(state);
|
||||||
self.animation_time.map(|x| x.to_bits()).hash(state);
|
self.animation_time.cache_hash(state);
|
||||||
self.pointer_position.map(|v| (v.x.to_bits(), v.y.to_bits())).hash(state);
|
self.pointer_position.cache_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.position.cache_hash(state);
|
||||||
self.index.hash(state);
|
self.index.cache_hash(state);
|
||||||
self.hash_varargs(state);
|
self.hash_varargs(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,9 +606,9 @@ pub trait DynHash {
|
|||||||
fn dyn_hash(&self, state: &mut dyn Hasher);
|
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) {
|
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 context::*;
|
||||||
pub use ctor;
|
pub use ctor;
|
||||||
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
|
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
|
||||||
|
pub use graphene_hash;
|
||||||
|
pub use graphene_hash::CacheHash;
|
||||||
pub use memo::MemoHash;
|
pub use memo::MemoHash;
|
||||||
pub use no_std_types::AsU32;
|
pub use no_std_types::AsU32;
|
||||||
pub use no_std_types::blending;
|
pub use no_std_types::blending;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use graphene_hash::CacheHash;
|
||||||
use std::hash::DefaultHasher;
|
use std::hash::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@@ -11,12 +12,12 @@ pub struct IORecord<I, O> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
pub struct MemoHash<T: Hash> {
|
pub struct MemoHash<T: CacheHash> {
|
||||||
hash: u64,
|
hash: u64,
|
||||||
value: Arc<T>,
|
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>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
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>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
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 {
|
pub fn new(value: T) -> Self {
|
||||||
let hash = Self::calc_hash(&value);
|
let hash = Self::calc_hash(&value);
|
||||||
Self { hash, value: value.into() }
|
Self { hash, value: value.into() }
|
||||||
@@ -45,7 +46,7 @@ impl<T: Hash> MemoHash<T> {
|
|||||||
|
|
||||||
fn calc_hash(data: &T) -> u64 {
|
fn calc_hash(data: &T) -> u64 {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
data.hash(&mut hasher);
|
data.cache_hash(&mut hasher);
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,19 +60,26 @@ impl<T: Hash> MemoHash<T> {
|
|||||||
self.hash
|
self.hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Hash> From<T> for MemoHash<T> {
|
|
||||||
|
impl<T: CacheHash> From<T> for MemoHash<T> {
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self::new(value)
|
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) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.hash.hash(state)
|
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;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
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>,
|
inner: &'a mut MemoHash<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Hash> Drop for MemoHashGuard<'_, T> {
|
impl<T: CacheHash> Drop for MemoHashGuard<'_, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let hash = MemoHash::<T>::calc_hash(&self.inner.value);
|
let hash = MemoHash::<T>::calc_hash(&self.inner.value);
|
||||||
self.inner.hash = hash;
|
self.inner.hash = hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Hash> Deref for MemoHashGuard<'_, T> {
|
impl<T: CacheHash> Deref for MemoHashGuard<'_, T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
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 {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
Arc::make_mut(&mut self.inner.value)
|
Arc::make_mut(&mut self.inner.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::uuid::NodeId;
|
|||||||
use crate::{AlphaBlending, math::quad::Quad};
|
use crate::{AlphaBlending, math::quad::Quad};
|
||||||
use dyn_any::{StaticType, StaticTypeSized};
|
use dyn_any::{StaticType, StaticTypeSized};
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Table<T> {
|
pub struct Table<T> {
|
||||||
@@ -198,16 +197,16 @@ impl<T> Default for Table<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Hash> Hash for Table<T> {
|
impl<T: graphene_hash::CacheHash> graphene_hash::CacheHash for Table<T> {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
for element in &self.element {
|
for element in &self.element {
|
||||||
element.hash(state);
|
element.cache_hash(state);
|
||||||
}
|
}
|
||||||
for transform in &self.transform {
|
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 {
|
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.
|
/// Controls whether the Decompose Scale node returns axis-length magnitudes or pure scale factors.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum ScaleType {
|
pub enum ScaleType {
|
||||||
/// The visual length of each axis (always positive, includes any skew contribution).
|
/// 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 {
|
pub enum RenderQuality {
|
||||||
/// Low quality, fast rendering
|
/// Low quality, fast rendering
|
||||||
Preview,
|
Preview,
|
||||||
@@ -154,7 +154,7 @@ pub enum RenderQuality {
|
|||||||
/// Render at full quality
|
/// Render at full quality
|
||||||
Full,
|
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 {
|
pub struct Footprint {
|
||||||
/// Inverse of the transform which will be applied to the node output during the rendering process
|
/// Inverse of the transform which will be applied to the node output during the rendering process
|
||||||
pub transform: DAffine2,
|
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 {
|
pub trait ApplyTransform {
|
||||||
fn apply_transform(&mut self, modification: &DAffine2);
|
fn apply_transform(&mut self, modification: &DAffine2);
|
||||||
fn left_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
|
// 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 struct NodeIOTypes {
|
||||||
pub call_argument: Type,
|
pub call_argument: Type,
|
||||||
pub return_value: Type,
|
pub return_value: Type,
|
||||||
@@ -126,7 +126,7 @@ impl std::fmt::Debug for NodeIOTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 {
|
pub struct ProtoNodeIdentifier {
|
||||||
name: Cow<'static, str>,
|
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 {
|
impl std::fmt::Display for TypeDescriptor {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let text = make_type_user_readable(&simplify_identifier_name(&self.name));
|
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.
|
/// Graph runtime type information used for type inference.
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 {
|
pub enum Type {
|
||||||
/// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete 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>),
|
Generic(Cow<'static, str>),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ mod uuid_generation {
|
|||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
#[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);
|
pub struct NodeId(pub u64);
|
||||||
|
|
||||||
impl NodeId {
|
impl NodeId {
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -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"] }
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
raster-types = { workspace = true, features = ["wgpu"] }
|
raster-types = { workspace = true, features = ["wgpu"] }
|
||||||
vector-types = { workspace = true }
|
vector-types = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ use core_types::transform::Transform;
|
|||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
use std::hash::Hash;
|
use graphene_hash::CacheHash;
|
||||||
|
|
||||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
/// 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 struct Artboard {
|
||||||
pub content: Table<Graphic>,
|
pub content: Table<Graphic>,
|
||||||
pub label: String,
|
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> {
|
pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
|
||||||
use serde::Deserialize;
|
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 struct ArtboardGroup {
|
||||||
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
use core_types::blending::AlphaBlending;
|
use core_types::blending::AlphaBlending;
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::ops::TableConvert;
|
use core_types::ops::TableConvert;
|
||||||
use core_types::render_complexity::RenderComplexity;
|
use core_types::render_complexity::RenderComplexity;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
@@ -8,14 +9,13 @@ use core_types::uuid::NodeId;
|
|||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
use std::hash::Hash;
|
|
||||||
use vector_types::GradientStops;
|
use vector_types::GradientStops;
|
||||||
// use vector_types::Vector;
|
// use vector_types::Vector;
|
||||||
|
|
||||||
pub type Vector = vector_types::Vector<Option<Table<Graphic>>>;
|
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.
|
/// 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 {
|
pub enum Graphic {
|
||||||
Graphic(Table<Graphic>),
|
Graphic(Table<Graphic>),
|
||||||
Vector(Table<Vector>),
|
Vector(Table<Vector>),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
# should be in this list instead of `[workspace.dependency]`
|
# should be in this list instead of `[workspace.dependency]`
|
||||||
std = [
|
std = [
|
||||||
"dep:dyn-any",
|
"dep:dyn-any",
|
||||||
|
"dep:graphene-hash",
|
||||||
"dep:serde",
|
"dep:serde",
|
||||||
"dep:log",
|
"dep:log",
|
||||||
"glam/debug-glam-assert",
|
"glam/debug-glam-assert",
|
||||||
@@ -32,6 +33,7 @@ node-macro = { workspace = true }
|
|||||||
|
|
||||||
# Local std dependencies
|
# Local std dependencies
|
||||||
dyn-any = { workspace = true, optional = true }
|
dyn-any = { workspace = true, optional = true }
|
||||||
|
graphene-hash = { workspace = true, optional = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
use core::hash::{Hash, Hasher};
|
|
||||||
use node_macro::BufferStruct;
|
use node_macro::BufferStruct;
|
||||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use num_traits::float::Float;
|
use num_traits::float::Float;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, BufferStruct)]
|
#[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 = "wasm", derive(tsify::Tsify))]
|
||||||
#[cfg_attr(feature = "std", serde(default))]
|
#[cfg_attr(feature = "std", serde(default))]
|
||||||
pub struct AlphaBlending {
|
pub struct AlphaBlending {
|
||||||
@@ -20,14 +19,6 @@ impl Default for AlphaBlending {
|
|||||||
Self::new()
|
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 {
|
impl Display for AlphaBlending {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
let round = |x: f32| (x * 1e3).round() / 1e3;
|
let round = |x: f32| (x * 1e3).round() / 1e3;
|
||||||
@@ -71,6 +62,7 @@ impl AlphaBlending {
|
|||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
#[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)]
|
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, BufferStruct, FromPrimitive, IntoPrimitive)]
|
||||||
pub enum BlendMode {
|
pub enum BlendMode {
|
||||||
// Basic group
|
// 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 super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use core::hash::Hash;
|
|
||||||
use glam::Vec4;
|
use glam::Vec4;
|
||||||
use half::f16;
|
use half::f16;
|
||||||
use node_macro::BufferStruct;
|
use node_macro::BufferStruct;
|
||||||
@@ -220,6 +219,7 @@ impl Pixel for Luma {}
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))]
|
#[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)]
|
#[derive(Debug, Default, Clone, Copy, Pod, Zeroable, BufferStruct)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
red: f32,
|
red: f32,
|
||||||
@@ -236,16 +236,6 @@ impl PartialEq for Color {
|
|||||||
|
|
||||||
impl Eq 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 {
|
impl RGB for Color {
|
||||||
type ColorChannel = f32;
|
type ColorChannel = f32;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use core_types::Color;
|
|||||||
use core_types::color::float_to_srgb_u8;
|
use core_types::color::float_to_srgb_u8;
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently
|
// 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 core_types::color::*;
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType};
|
||||||
use glam::{DAffine2, DVec2};
|
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)]
|
#[derive(Debug, Clone, dyn_any::DynAny, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct TransformImage(pub DAffine2);
|
pub struct TransformImage(pub DAffine2);
|
||||||
|
|
||||||
impl Hash for TransformImage {
|
impl core_types::CacheHash for TransformImage {
|
||||||
fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
|
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> {
|
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> {
|
impl<P: core_types::CacheHash + Pixel> core_types::CacheHash for Image<P> {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.width.hash(state);
|
core_types::CacheHash::cache_hash(&self.width, state);
|
||||||
self.height.hash(state);
|
core_types::CacheHash::cache_hash(&self.height, state);
|
||||||
self.data.hash(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> {
|
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
#[derive(Clone, Debug, core_types::CacheHash, PartialEq, DynAny)]
|
||||||
enum RasterFrame {
|
enum RasterFrame {
|
||||||
ImageFrame(Table<Image<Color>>),
|
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 {
|
pub enum GraphicElement {
|
||||||
GraphicGroup(Table<GraphicElement>),
|
GraphicGroup(Table<GraphicElement>),
|
||||||
RasterFrame(RasterFrame),
|
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> {
|
pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||||
enum RasterFrame {
|
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
|
/// 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>>),
|
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 {
|
pub enum GraphicElement {
|
||||||
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
|
||||||
GraphicGroup(Table<GraphicElement>),
|
GraphicGroup(Table<GraphicElement>),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub trait Storage: __private::Sealed + Clone + Debug + 'static {
|
|||||||
fn is_empty(&self) -> bool;
|
fn is_empty(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Hash, Default)]
|
#[derive(Clone, Debug, PartialEq, Default)]
|
||||||
pub struct Raster<T>
|
pub struct Raster<T>
|
||||||
where
|
where
|
||||||
Raster<T>: Storage,
|
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;
|
pub use cpu::CPU;
|
||||||
|
|
||||||
mod cpu {
|
mod cpu {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::raster_types::__private::Sealed;
|
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>);
|
pub struct CPU(Image<Color>);
|
||||||
|
|
||||||
impl Sealed for Raster<CPU> {}
|
impl Sealed for Raster<CPU> {}
|
||||||
@@ -140,6 +150,13 @@ mod gpu {
|
|||||||
pub texture: wgpu::Texture,
|
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 Sealed for Raster<GPU> {}
|
||||||
|
|
||||||
impl Storage for Raster<GPU> {
|
impl Storage for Raster<GPU> {
|
||||||
@@ -164,7 +181,7 @@ mod gpu {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::raster_types::__private::Sealed;
|
use crate::raster_types::__private::Sealed;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Hash, core_types::CacheHash)]
|
||||||
pub struct GPU;
|
pub struct GPU;
|
||||||
|
|
||||||
impl Sealed for Raster<GPU> {}
|
impl Sealed for Raster<GPU> {}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
# Local dependencies
|
# Local dependencies
|
||||||
dyn-any = { workspace = true }
|
dyn-any = { workspace = true }
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
glam = { workspace = true }
|
glam = { workspace = true }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::render_ext::RenderExt;
|
use crate::render_ext::RenderExt;
|
||||||
use crate::to_peniko::BlendModeExt;
|
use crate::to_peniko::BlendModeExt;
|
||||||
|
use core_types::CacheHash;
|
||||||
use core_types::blending::BlendMode;
|
use core_types::blending::BlendMode;
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::color::{Alpha, Color};
|
use core_types::color::{Alpha, Color};
|
||||||
@@ -10,6 +11,7 @@ use core_types::transform::{Footprint, Transform};
|
|||||||
use core_types::uuid::{NodeId, generate_uuid};
|
use core_types::uuid::{NodeId, generate_uuid};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphene_hash::CacheHashWrapper;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
|
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster};
|
||||||
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
||||||
@@ -21,7 +23,6 @@ use kurbo::{Affine, Cap, Join, Shape};
|
|||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::{Arc, LazyLock};
|
use std::sync::{Arc, LazyLock};
|
||||||
use vector_types::gradient::GradientSpreadMethod;
|
use vector_types::gradient::GradientSpreadMethod;
|
||||||
@@ -95,7 +96,7 @@ pub struct SvgRender {
|
|||||||
pub svg: Vec<SvgSegment>,
|
pub svg: Vec<SvgSegment>,
|
||||||
pub svg_defs: String,
|
pub svg_defs: String,
|
||||||
pub transform: DAffine2,
|
pub transform: DAffine2,
|
||||||
pub image_data: HashMap<Image<Color>, u64>,
|
pub image_data: HashMap<CacheHashWrapper<Image<Color>>, u64>,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +192,7 @@ pub struct RenderContext {
|
|||||||
pub resource_overrides: Vec<(peniko::ImageBrush, wgpu::Texture)>,
|
pub resource_overrides: Vec<(peniko::ImageBrush, wgpu::Texture)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Hash)]
|
#[derive(Default, Clone, Copy, Hash, graphene_hash::CacheHash)]
|
||||||
pub enum RenderOutputType {
|
pub enum RenderOutputType {
|
||||||
#[default]
|
#[default]
|
||||||
Svg,
|
Svg,
|
||||||
@@ -199,12 +200,13 @@ pub enum RenderOutputType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Static state used whilst rendering
|
/// Static state used whilst rendering
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone, CacheHash)]
|
||||||
pub struct RenderParams {
|
pub struct RenderParams {
|
||||||
pub render_mode: RenderMode,
|
pub render_mode: RenderMode,
|
||||||
pub footprint: Footprint,
|
pub footprint: Footprint,
|
||||||
/// Ratio of physical pixels to logical pixels. `scale := physical_pixels / logical_pixels`
|
/// Ratio of physical pixels to logical pixels. `scale := physical_pixels / logical_pixels`
|
||||||
/// Ignored when rendering to SVG.
|
/// Ignored when rendering to SVG.
|
||||||
|
#[cache_hash(skip)]
|
||||||
pub scale: f64,
|
pub scale: f64,
|
||||||
pub render_output_type: RenderOutputType,
|
pub render_output_type: RenderOutputType,
|
||||||
pub thumbnail: bool,
|
pub thumbnail: bool,
|
||||||
@@ -223,25 +225,6 @@ pub struct RenderParams {
|
|||||||
pub viewport_zoom: f64,
|
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 {
|
impl RenderParams {
|
||||||
pub fn for_clipper(&self) -> Self {
|
pub fn for_clipper(&self) -> Self {
|
||||||
Self { for_mask: true, ..*self }
|
Self { for_mask: true, ..*self }
|
||||||
@@ -1426,7 +1409,7 @@ impl Render for Table<Raster<CPU>> {
|
|||||||
if render_params.to_canvas() {
|
if render_params.to_canvas() {
|
||||||
let mut image_copy = image.clone();
|
let mut image_copy = image.clone();
|
||||||
image_copy.data_mut().map_pixels(|p| p.to_unassociated_alpha());
|
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(
|
render.parent_tag(
|
||||||
"foreignObject",
|
"foreignObject",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use dyn_any::DynAny;
|
|||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum GradientType {
|
pub enum GradientType {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -15,7 +15,7 @@ pub enum GradientType {
|
|||||||
// TODO: Use linear not gamma colors
|
// TODO: Use linear not gamma colors
|
||||||
/// A list of colors associated with positions (in the range 0 to 1) along a gradient.
|
/// A list of colors associated with positions (in the range 0 to 1) along a gradient.
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 {
|
pub struct GradientStops {
|
||||||
/// The position of this stop, a factor from 0-1 along the length of the full gradient.
|
/// The position of this stop, a factor from 0-1 along the length of the full gradient.
|
||||||
pub position: Vec<f64>,
|
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 {
|
impl Default for GradientStops {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -336,7 +325,7 @@ impl GradientStops {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum GradientSpreadMethod {
|
pub enum GradientSpreadMethod {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -360,7 +349,7 @@ impl GradientSpreadMethod {
|
|||||||
/// Contains the start and end points, along with the colors at varying points along the length.
|
/// Contains the start and end points, along with the colors at varying points along the length.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 struct Gradient {
|
||||||
pub stops: GradientStops,
|
pub stops: GradientStops,
|
||||||
pub gradient_type: GradientType,
|
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 {
|
impl std::fmt::Display for Gradient {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::ops::{Index, IndexMut};
|
|||||||
pub use structs::*;
|
pub use structs::*;
|
||||||
|
|
||||||
/// Structure used to represent a path composed of [Bezier] curves.
|
/// 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))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Subpath<PointId: Identifier> {
|
pub struct Subpath<PointId: Identifier> {
|
||||||
manipulator_groups: Vec<ManipulatorGroup<PointId>>,
|
manipulator_groups: Vec<ManipulatorGroup<PointId>>,
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ use std::fmt::{Debug, Formatter, Result};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
/// An id type used for each [ManipulatorGroup].
|
/// 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;
|
fn new() -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
/// 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))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct ManipulatorGroup<PointId: Identifier> {
|
pub struct ManipulatorGroup<PointId: Identifier> {
|
||||||
pub anchor: DVec2,
|
pub anchor: DVec2,
|
||||||
@@ -20,22 +20,6 @@ pub struct ManipulatorGroup<PointId: Identifier> {
|
|||||||
pub id: PointId,
|
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> {
|
impl<PointId: Identifier> Debug for ManipulatorGroup<PointId> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
f.debug_struct("ManipulatorGroup")
|
f.debug_struct("ManipulatorGroup")
|
||||||
@@ -119,7 +103,7 @@ pub enum AppendType {
|
|||||||
SmoothJoin(f64),
|
SmoothJoin(f64),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, graphene_hash::CacheHash)]
|
||||||
pub enum ArcType {
|
pub enum ArcType {
|
||||||
Open,
|
Open,
|
||||||
Closed,
|
Closed,
|
||||||
@@ -127,7 +111,7 @@ pub enum ArcType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of the handle point(s) in a bezier segment.
|
/// 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))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum BezierHandles {
|
pub enum BezierHandles {
|
||||||
Linear,
|
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 {
|
impl BezierHandles {
|
||||||
pub fn is_cubic(&self) -> bool {
|
pub fn is_cubic(&self) -> bool {
|
||||||
matches!(self, Self::Cubic { .. })
|
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).
|
/// 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 {
|
pub enum ManipulatorPointId {
|
||||||
/// A control anchor - the start or end point of a bézier.
|
/// A control anchor - the start or end point of a bézier.
|
||||||
Anchor(PointId),
|
Anchor(PointId),
|
||||||
@@ -479,7 +479,7 @@ impl ManipulatorPointId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The type of handle found on a bézier curve.
|
/// 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 {
|
pub enum HandleType {
|
||||||
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
|
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||||
Primary,
|
Primary,
|
||||||
@@ -488,7 +488,7 @@ pub enum HandleType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a primary or end handle found in a particular segment.
|
/// 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 struct HandleId {
|
||||||
pub ty: HandleType,
|
pub ty: HandleType,
|
||||||
pub segment: SegmentId,
|
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.
|
/// 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,
|
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;
|
use glam::DVec2;
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 {
|
pub enum ReferencePoint {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
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.
|
/// In the future we'll probably also add a pattern fill. This will probably be named "Paint" in the future.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 {
|
pub enum Fill {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
@@ -161,7 +161,7 @@ impl From<Gradient> for Fill {
|
|||||||
/// In the future we'll probably also add a pattern fill.
|
/// In the future we'll probably also add a pattern fill.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 {
|
pub enum FillChoice {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
@@ -209,7 +209,7 @@ impl From<Fill> for FillChoice {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum FillType {
|
pub enum FillType {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -220,7 +220,7 @@ pub enum FillType {
|
|||||||
/// The stroke (outline) style of an SVG element.
|
/// The stroke (outline) style of an SVG element.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum StrokeCap {
|
pub enum StrokeCap {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -241,7 +241,7 @@ impl StrokeCap {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum StrokeJoin {
|
pub enum StrokeJoin {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -262,7 +262,7 @@ impl StrokeJoin {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum StrokeAlign {
|
pub enum StrokeAlign {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -279,7 +279,7 @@ impl StrokeAlign {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[widget(Radio)]
|
||||||
pub enum PaintOrder {
|
pub enum PaintOrder {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -299,7 +299,7 @@ fn daffine2_identity() -> DAffine2 {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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)]
|
#[serde(default)]
|
||||||
pub struct Stroke {
|
pub struct Stroke {
|
||||||
/// Stroke color
|
/// Stroke color
|
||||||
@@ -322,24 +322,6 @@ pub struct Stroke {
|
|||||||
pub paint_order: PaintOrder,
|
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 {
|
impl Stroke {
|
||||||
pub const fn new(color: Option<Color>, weight: f64) -> Self {
|
pub const fn new(color: Option<Color>, weight: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -512,19 +494,12 @@ impl Default for Stroke {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 struct PathStyle {
|
||||||
pub stroke: Option<Stroke>,
|
pub stroke: Option<Stroke>,
|
||||||
pub fill: Fill,
|
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 {
|
impl std::fmt::Display for PathStyle {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let fill = &self.fill;
|
let fill = &self.fill;
|
||||||
@@ -680,7 +655,7 @@ impl PathStyle {
|
|||||||
|
|
||||||
/// Ways the user can choose to view the artwork in the viewport.
|
/// Ways the user can choose to view the artwork in the viewport.
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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 {
|
pub enum RenderMode {
|
||||||
/// Render with normal coloration at the current viewport resolution
|
/// Render with normal coloration at the current viewport resolution
|
||||||
#[default]
|
#[default]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::iter::zip;
|
|||||||
macro_rules! create_ids {
|
macro_rules! create_ids {
|
||||||
($($id:ident),*) => {
|
($($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)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
/// A strongly typed ID
|
/// A strongly typed ID
|
||||||
pub struct $id(u64);
|
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.
|
/// 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 {
|
pub struct PointDomain {
|
||||||
id: Vec<PointId>,
|
id: Vec<PointId>,
|
||||||
@@ -87,13 +87,6 @@ pub struct PointDomain {
|
|||||||
pub(crate) position: Vec<DVec2>,
|
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 {
|
impl PointDomain {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self { id: Vec::new(), position: Vec::new() }
|
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.
|
/// 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 {
|
pub struct SegmentDomain {
|
||||||
#[serde(alias = "ids")]
|
#[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
|
/// 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.
|
/// [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes.
|
||||||
pub struct RegionDomain {
|
pub struct RegionDomain {
|
||||||
@@ -849,7 +842,7 @@ struct Faces {
|
|||||||
face_start: Vec<usize>,
|
face_start: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FaceIterator<'a, Upstream> {
|
pub struct FaceIterator<'a, Upstream> {
|
||||||
vector: &'a Vector<Upstream>,
|
vector: &'a Vector<Upstream>,
|
||||||
faces: Faces,
|
faces: Faces,
|
||||||
|
|||||||
@@ -17,12 +17,6 @@ pub struct PointModification {
|
|||||||
delta: HashMap<PointId, DVec2>,
|
delta: HashMap<PointId, DVec2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for PointModification {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
generate_uuid().hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PointModification {
|
impl PointModification {
|
||||||
/// Apply this modification to the specified [`PointDomain`].
|
/// Apply this modification to the specified [`PointDomain`].
|
||||||
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
|
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
|
||||||
@@ -511,9 +505,13 @@ impl VectorModification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for VectorModification {
|
// Intentionally non-deterministic: fields contain HashMaps with non-deterministic iteration order,
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
// so we use a UUID to always bust the cache and force re-evaluation when any modification is present.
|
||||||
generate_uuid().hash(state)
|
// 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> {
|
impl<Upstream> graphene_hash::CacheHash for Vector<Upstream> {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.point_domain.hash(state);
|
self.point_domain.cache_hash(state);
|
||||||
self.segment_domain.hash(state);
|
self.segment_domain.cache_hash(state);
|
||||||
self.region_domain.hash(state);
|
self.region_domain.cache_hash(state);
|
||||||
self.style.hash(state);
|
self.style.cache_hash(state);
|
||||||
self.colinear_manipulators.hash(state);
|
self.colinear_manipulators.cache_hash(state);
|
||||||
// We don't hash the upstream_data intentionally
|
// We don't hash the upstream_data intentionally
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ serde = ["dep:serde"]
|
|||||||
# Local dependencies
|
# Local dependencies
|
||||||
dyn-any = { workspace = true }
|
dyn-any = { workspace = true }
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
raster-types = { workspace = true }
|
raster-types = { workspace = true }
|
||||||
raster-nodes = { workspace = true }
|
raster-nodes = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::brush_stroke::BrushStroke;
|
use crate::brush_stroke::BrushStroke;
|
||||||
use crate::brush_stroke::BrushStyle;
|
use crate::brush_stroke::BrushStyle;
|
||||||
|
use core_types::graphene_hash::CacheHashWrapper;
|
||||||
use core_types::table::TableRow;
|
use core_types::table::TableRow;
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use raster_types::CPU;
|
use raster_types::CPU;
|
||||||
@@ -31,7 +32,7 @@ struct BrushCacheImpl {
|
|||||||
|
|
||||||
// A cache for brush textures.
|
// A cache for brush textures.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
brush_texture_cache: HashMap<BrushStyle, Raster<CPU>>,
|
brush_texture_cache: HashMap<CacheHashWrapper<BrushStyle>, Raster<CPU>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BrushCacheImpl {
|
impl BrushCacheImpl {
|
||||||
@@ -165,6 +166,12 @@ impl Hash for BrushCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl graphene_hash::CacheHash for BrushCache {
|
||||||
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
core::hash::Hash::hash(&self.0.lock().unwrap().unique_id, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BrushCache {
|
impl BrushCache {
|
||||||
pub fn compute_brush_plan(&self, background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
|
pub fn compute_brush_plan(&self, background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
|
||||||
let mut inner = self.0.lock().unwrap();
|
let mut inner = self.0.lock().unwrap();
|
||||||
@@ -178,11 +185,11 @@ impl BrushCache {
|
|||||||
|
|
||||||
pub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> {
|
pub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> {
|
||||||
let inner = self.0.lock().unwrap();
|
let inner = self.0.lock().unwrap();
|
||||||
inner.brush_texture_cache.get(style).cloned()
|
inner.brush_texture_cache.get(&CacheHashWrapper(style.clone())).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) {
|
pub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) {
|
||||||
let mut inner = self.0.lock().unwrap();
|
let mut inner = self.0.lock().unwrap();
|
||||||
inner.brush_texture_cache.insert(style, brush);
|
inner.brush_texture_cache.insert(CacheHashWrapper(style), brush);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
use core_types::CacheHash;
|
||||||
use core_types::blending::BlendMode;
|
use core_types::blending::BlendMode;
|
||||||
use core_types::color::Color;
|
use core_types::color::Color;
|
||||||
use core_types::math::bbox::AxisAlignedBbox;
|
use core_types::math::bbox::AxisAlignedBbox;
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
/// The style of a brush.
|
/// The style of a brush.
|
||||||
#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct BrushStyle {
|
pub struct BrushStyle {
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub diameter: f64,
|
pub diameter: f64,
|
||||||
@@ -29,17 +28,6 @@ impl Default for BrushStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for BrushStyle {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.color.hash(state);
|
|
||||||
self.diameter.to_bits().hash(state);
|
|
||||||
self.hardness.to_bits().hash(state);
|
|
||||||
self.flow.to_bits().hash(state);
|
|
||||||
self.spacing.to_bits().hash(state);
|
|
||||||
self.blend_mode.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for BrushStyle {}
|
impl Eq for BrushStyle {}
|
||||||
|
|
||||||
impl PartialEq for BrushStyle {
|
impl PartialEq for BrushStyle {
|
||||||
@@ -54,23 +42,13 @@ impl PartialEq for BrushStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A single sample of brush parameters across the brush stroke.
|
/// A single sample of brush parameters across the brush stroke.
|
||||||
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct BrushInputSample {
|
pub struct BrushInputSample {
|
||||||
// The position of the sample in layer space, in pixels.
|
|
||||||
// The origin of layer space is not specified.
|
|
||||||
pub position: DVec2,
|
pub position: DVec2,
|
||||||
// Future work: pressure, stylus angle, etc.
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for BrushInputSample {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.position.x.to_bits().hash(state);
|
|
||||||
self.position.y.to_bits().hash(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The parameters for a single stroke brush.
|
/// The parameters for a single stroke brush.
|
||||||
#[derive(Clone, Debug, PartialEq, Hash, Default, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, core_types::CacheHash, Default, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct BrushStroke {
|
pub struct BrushStroke {
|
||||||
pub style: BrushStyle,
|
pub style: BrushStyle,
|
||||||
pub trace: Vec<BrushInputSample>,
|
pub trace: Vec<BrushInputSample>,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ wasm = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
raster-types = { workspace = true }
|
raster-types = { workspace = true }
|
||||||
graphic-types = { workspace = true }
|
graphic-types = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
|
use core_types::{CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::vector_types::GradientStops;
|
use graphic_types::vector_types::GradientStops;
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Artboard, Graphic, Vector};
|
||||||
@@ -9,7 +9,7 @@ use raster_types::{CPU, GPU, Raster};
|
|||||||
|
|
||||||
const DAY: f64 = 1000. * 3600. * 24.;
|
const DAY: f64 = 1000. * 3600. * 24.;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, dyn_any::DynAny, Default, Hash, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, dyn_any::DynAny, Default, Hash, CacheHash, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum RealTimeMode {
|
pub enum RealTimeMode {
|
||||||
#[label("UTC")]
|
#[label("UTC")]
|
||||||
Utc,
|
Utc,
|
||||||
|
|||||||
@@ -51,17 +51,17 @@ async fn context_modification<T>(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hasher;
|
||||||
|
|
||||||
/// Test that the hash of a nullified context remains stable even when nullified inputs change
|
/// Verifies that nullified context fields don't affect the cache hash — only the kept features matter.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nullified_context_hash_stability() {
|
fn test_nullified_context_hash_stability() {
|
||||||
use core_types::Context;
|
use core_types::Context;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
// Create original contexts using the Context type (Option<Arc<OwnedContextImpl>>)
|
|
||||||
let original_ctx: Context = Some(Arc::new(
|
let original_ctx: Context = Some(Arc::new(
|
||||||
OwnedContextImpl::empty()
|
OwnedContextImpl::empty()
|
||||||
.with_footprint(Footprint::default())
|
.with_footprint(Footprint::default())
|
||||||
@@ -71,53 +71,48 @@ mod tests {
|
|||||||
.with_animation_time(20.25),
|
.with_animation_time(20.25),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Test nullifying different features - hash should remain stable for each nullification
|
// A second context with different values for the nullified fields
|
||||||
let features_to_keep = ContextFeatures::empty(); // Nullify everything
|
|
||||||
|
|
||||||
// Create nullified context - this should only keep features specified in features_to_keep
|
|
||||||
let nullified_ctx = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), features_to_keep);
|
|
||||||
|
|
||||||
// Calculate hash of nullified context
|
|
||||||
let mut hasher1 = DefaultHasher::new();
|
|
||||||
nullified_ctx.hash(&mut hasher1);
|
|
||||||
let hash1 = hasher1.finish();
|
|
||||||
|
|
||||||
// Create a different original context with changed values
|
|
||||||
let changed_ctx: Context = Some(Arc::new(
|
let changed_ctx: Context = Some(Arc::new(
|
||||||
OwnedContextImpl::empty()
|
OwnedContextImpl::empty()
|
||||||
.with_footprint(Footprint::default()) // Same footprint
|
.with_footprint(Footprint::default())
|
||||||
.with_index(2)
|
.with_index(2)
|
||||||
.with_real_time(999.9) // Different real time
|
.with_real_time(999.9)
|
||||||
.with_vararg(Box::new("test"))
|
.with_vararg(Box::new("test"))
|
||||||
.with_animation_time(888.8), // Different animation time
|
.with_animation_time(888.8),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Create nullified context from the changed original - should have same hash since everything is nullified
|
// Nullify everything — both should hash the same regardless of their field values
|
||||||
let nullified_changed_ctx = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), features_to_keep);
|
let features_to_keep = ContextFeatures::empty();
|
||||||
|
let nullified1 = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), features_to_keep);
|
||||||
|
let nullified2 = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), features_to_keep);
|
||||||
|
|
||||||
|
let mut hasher1 = DefaultHasher::new();
|
||||||
|
nullified1.cache_hash(&mut hasher1);
|
||||||
|
|
||||||
let mut hasher2 = DefaultHasher::new();
|
let mut hasher2 = DefaultHasher::new();
|
||||||
nullified_changed_ctx.hash(&mut hasher2);
|
nullified2.cache_hash(&mut hasher2);
|
||||||
let hash2 = hasher2.finish();
|
|
||||||
|
|
||||||
// Hash should be the same because all features were nullified
|
assert_eq!(
|
||||||
assert_eq!(hash1, hash2, "Hash of nullified context should remain stable regardless of input changes when features are nullified");
|
hasher1.finish(),
|
||||||
|
hasher2.finish(),
|
||||||
|
"Hash of nullified context should remain stable regardless of input changes when features are nullified"
|
||||||
|
);
|
||||||
|
|
||||||
// Test partial nullification - keep only footprint
|
// Keep only footprint and varargs — both have the same footprint and vararg, so hash should still match
|
||||||
let partial_features = ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS;
|
let partial_features = ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS;
|
||||||
|
let partial1 = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), partial_features);
|
||||||
let partial_nullified1 = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), partial_features);
|
let partial2 = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), partial_features);
|
||||||
let partial_nullified2 = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), partial_features);
|
|
||||||
|
|
||||||
let mut hasher3 = DefaultHasher::new();
|
let mut hasher3 = DefaultHasher::new();
|
||||||
partial_nullified1.hash(&mut hasher3);
|
partial1.cache_hash(&mut hasher3);
|
||||||
let hash3 = hasher3.finish();
|
|
||||||
|
|
||||||
let mut hasher4 = DefaultHasher::new();
|
let mut hasher4 = DefaultHasher::new();
|
||||||
partial_nullified2.hash(&mut hasher4);
|
partial2.cache_hash(&mut hasher4);
|
||||||
let hash4 = hasher4.finish();
|
|
||||||
|
|
||||||
// These should be the same because both have the same footprint (Footprint::default()) and varargs
|
assert_eq!(
|
||||||
// and other features are nullified
|
hasher3.finish(),
|
||||||
assert_eq!(hash3, hash4, "Hash should be stable when keeping only footprint and footprint values are the same");
|
hasher4.finish(),
|
||||||
|
"Hash should be stable when keeping only footprint and varargs and their values are the same"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use core_types::Ctx;
|
use core_types::{CacheHash, Ctx};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DVec2, IVec2, UVec2};
|
use glam::{DVec2, IVec2, UVec2};
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2
|
|||||||
|
|
||||||
/// The X or Y component of a vec2.
|
/// The X or Y component of a vec2.
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, CacheHash, DynAny, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
||||||
#[widget(Radio)]
|
#[widget(Radio)]
|
||||||
pub enum XY {
|
pub enum XY {
|
||||||
#[default]
|
#[default]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use core_types::WasmNotSend;
|
use core_types::WasmNotSend;
|
||||||
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::memo::*;
|
use core_types::memo::*;
|
||||||
use std::hash::DefaultHasher;
|
use std::hash::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hasher;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
@@ -13,9 +14,9 @@ use std::sync::Mutex;
|
|||||||
///
|
///
|
||||||
/// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.
|
/// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.
|
||||||
#[node_macro::node(category(""), path(graphene_core::memo), skip_impl)]
|
#[node_macro::node(category(""), path(graphene_core::memo), skip_impl)]
|
||||||
async fn memo<I: Hash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T)>>>, node: impl Node<I, Output = T>) -> T {
|
async fn memo<I: CacheHash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T)>>>, node: impl Node<I, Output = T>) -> T {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
input.hash(&mut hasher);
|
input.cache_hash(&mut hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
if let Some(data) = cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) {
|
if let Some(data) = cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("General"))]
|
#[node_macro::node(category("General"))]
|
||||||
async fn map<Item: AnyHash + Send + Sync + std::hash::Hash>(
|
async fn map<Item: AnyHash + Send + Sync + core_types::CacheHash>(
|
||||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ fn string_to_bytes(_: impl Ctx, string: String) -> Vec<u8> {
|
|||||||
#[node_macro::node(category("Web Request"), name("Image to Bytes"))]
|
#[node_macro::node(category("Web Request"), name("Image to Bytes"))]
|
||||||
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
|
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
|
||||||
let Some(image) = image.iter().next() else { return vec![] };
|
let Some(image) = image.iter().next() else { return vec![] };
|
||||||
image.element.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>()
|
image.element.data.iter().flat_map(|color| color.to_rgba8_srgb().into_iter()).collect::<Vec<u8>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use wgpu_executor::RenderContext;
|
|||||||
pub use crate::render_cache::render_output_cache;
|
pub use crate::render_cache::render_output_cache;
|
||||||
|
|
||||||
/// List of (canvas id, image data) pairs for embedding images as canvases in the final SVG string.
|
/// List of (canvas id, image data) pairs for embedding images as canvases in the final SVG string.
|
||||||
type ImageData = HashMap<Image<Color>, u64>;
|
type ImageData = HashMap<core_types::graphene_hash::CacheHashWrapper<Image<Color>>, u64>;
|
||||||
|
|
||||||
#[derive(Clone, dyn_any::DynAny)]
|
#[derive(Clone, dyn_any::DynAny)]
|
||||||
pub enum RenderIntermediateType {
|
pub enum RenderIntermediateType {
|
||||||
@@ -191,7 +191,7 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito
|
|||||||
rendering.wrap_with_transform(footprint.transform, Some(logical_resolution));
|
rendering.wrap_with_transform(footprint.transform, Some(logical_resolution));
|
||||||
RenderOutputType::Svg {
|
RenderOutputType::Svg {
|
||||||
svg: rendering.svg.to_svg_string(),
|
svg: rendering.svg.to_svg_string(),
|
||||||
image_data: rendering.image_data.into_iter().map(|(image, id)| (id, image)).collect(),
|
image_data: rendering.image_data.into_iter().map(|(image, id)| (id, image.0)).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => {
|
(RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ shader-nodes = ["std", "dep:raster-nodes-shaders", "dep:wgpu-executor"]
|
|||||||
std = [
|
std = [
|
||||||
"dep:core-types",
|
"dep:core-types",
|
||||||
"dep:dyn-any",
|
"dep:dyn-any",
|
||||||
|
"dep:graphene-hash",
|
||||||
"dep:raster-types",
|
"dep:raster-types",
|
||||||
"dep:vector-types",
|
"dep:vector-types",
|
||||||
"dep:image",
|
"dep:image",
|
||||||
@@ -41,6 +42,7 @@ node-macro = { workspace = true }
|
|||||||
# Local std dependencies
|
# Local std dependencies
|
||||||
dyn-any = { workspace = true, optional = true }
|
dyn-any = { workspace = true, optional = true }
|
||||||
core-types = { workspace = true, optional = true }
|
core-types = { workspace = true, optional = true }
|
||||||
|
graphene-hash = { workspace = true, optional = true }
|
||||||
raster-types = { workspace = true, optional = true }
|
raster-types = { workspace = true, optional = true }
|
||||||
vector-types = { workspace = true, optional = true }
|
vector-types = { workspace = true, optional = true }
|
||||||
wgpu-executor = { workspace = true, optional = true }
|
wgpu-executor = { workspace = true, optional = true }
|
||||||
|
|||||||
@@ -1015,3 +1015,20 @@ fn exposure<T: Adjust<Color>>(
|
|||||||
});
|
});
|
||||||
input
|
input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
mod _graphene_hash_impls {
|
||||||
|
use super::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, SelectiveColorChoice};
|
||||||
|
graphene_hash::impl_via_hash!(
|
||||||
|
LuminanceCalculation,
|
||||||
|
RedGreenBlue,
|
||||||
|
RedGreenBlueAlpha,
|
||||||
|
NoiseType,
|
||||||
|
FractalType,
|
||||||
|
CellularDistanceFunction,
|
||||||
|
CellularReturnType,
|
||||||
|
DomainWarpType,
|
||||||
|
RelativeAbsolute,
|
||||||
|
SelectiveColorChoice
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use core_types::Node;
|
use core_types::Node;
|
||||||
use core_types::color::{Channel, Linear, LuminanceMut};
|
use core_types::color::{Channel, Linear, LuminanceMut};
|
||||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::ops::{Add, Mul, Sub};
|
use std::ops::{Add, Mul, Sub};
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Debug, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Curve {
|
pub struct Curve {
|
||||||
#[serde(rename = "manipulatorGroups")]
|
#[serde(rename = "manipulatorGroups")]
|
||||||
pub manipulator_groups: Vec<CurveManipulatorGroup>,
|
pub manipulator_groups: Vec<CurveManipulatorGroup>,
|
||||||
@@ -25,28 +24,13 @@ impl Default for Curve {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Curve {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.manipulator_groups.hash(state);
|
|
||||||
[self.first_handle, self.last_handle].iter().flatten().for_each(|f| f.to_bits().hash(state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, core_types::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct CurveManipulatorGroup {
|
pub struct CurveManipulatorGroup {
|
||||||
pub anchor: [f32; 2],
|
pub anchor: [f32; 2],
|
||||||
pub handles: [[f32; 2]; 2],
|
pub handles: [[f32; 2]; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for CurveManipulatorGroup {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
for c in self.handles.iter().chain([&self.anchor]).flatten() {
|
|
||||||
c.to_bits().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ValueMapperNode<C> {
|
pub struct ValueMapperNode<C> {
|
||||||
lut: Vec<C>,
|
lut: Vec<C>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
raster-types = { workspace = true }
|
raster-types = { workspace = true }
|
||||||
vector-types = { workspace = true }
|
vector-types = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use core_types::graphene_hash::CacheHash;
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use parley::fontique::Blob;
|
use parley::fontique::Blob;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -23,6 +24,14 @@ impl std::hash::Hash for Font {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CacheHash for Font {
|
||||||
|
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.font_family.cache_hash(state);
|
||||||
|
self.font_style.cache_hash(state);
|
||||||
|
// Don't consider `font_style_to_restore` in the HashMaps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for Font {
|
impl PartialEq for Font {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
// Don't consider `font_style_to_restore` in the HashMaps
|
// Don't consider `font_style_to_restore` in the HashMaps
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ mod to_path;
|
|||||||
|
|
||||||
use convert_case::{Boundary, Converter, pattern};
|
use convert_case::{Boundary, Converter, pattern};
|
||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::registry::types::{SignedInteger, TextArea};
|
use core_types::registry::types::{SignedInteger, TextArea};
|
||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
|
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
|
||||||
@@ -25,7 +26,7 @@ pub use vector_types;
|
|||||||
/// Alignment of lines of type within a text block.
|
/// Alignment of lines of type within a text block.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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, CacheHash, DynAny, node_macro::ChoiceType)]
|
||||||
#[widget(Radio)]
|
#[widget(Radio)]
|
||||||
pub enum TextAlign {
|
pub enum TextAlign {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -116,7 +117,7 @@ fn escape_string(input: String) -> String {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, dyn_any::DynAny, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, CacheHash, dyn_any::DynAny, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)]
|
||||||
#[widget(Dropdown)]
|
#[widget(Dropdown)]
|
||||||
pub enum StringCapitalization {
|
pub enum StringCapitalization {
|
||||||
/// "on the origin of species" — Converts all letters to lower case.
|
/// "on the origin of species" — Converts all letters to lower case.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ wasm = ["core-types/wasm", "tsify", "wasm-bindgen"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
core-types = { workspace = true }
|
core-types = { workspace = true }
|
||||||
|
graphene-hash = { workspace = true }
|
||||||
vector-types = { workspace = true }
|
vector-types = { workspace = true }
|
||||||
graphic-types = { workspace = true }
|
graphic-types = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use core_types::Ctx;
|
|
||||||
use core_types::registry::types::{Angle, PixelLength, PixelSize};
|
use core_types::registry::types::{Angle, PixelLength, PixelSize};
|
||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
|
use core_types::{CacheHash, Ctx};
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
@@ -188,7 +188,7 @@ fn star<T: AsU64>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[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, CacheHash, DynAny, node_macro::ChoiceType)]
|
||||||
#[widget(Radio)]
|
#[widget(Radio)]
|
||||||
pub enum QRCodeErrorCorrectionLevel {
|
pub enum QRCodeErrorCorrectionLevel {
|
||||||
/// Allows recovery from up to 7% data loss.
|
/// Allows recovery from up to 7% data loss.
|
||||||
|
|||||||
Reference in New Issue
Block a user