mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48: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:
@@ -9,7 +9,7 @@ use core_types::{Context, ContextDependencies, Cow, MemoHash, ProtoNodeIdentifie
|
||||
use dyn_any::DynAny;
|
||||
use glam::IVec2;
|
||||
use log::Metadata;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
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`].
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, PartialEq, Hash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DocumentNode {
|
||||
/// The inputs to a node, which are either:
|
||||
/// - From other nodes within this graph [`NodeInput::Node`],
|
||||
@@ -172,7 +172,7 @@ impl DocumentNode {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// A reference to another node in the same network from which this node can receive its input.
|
||||
Node { node_id: NodeId, output_index: usize },
|
||||
@@ -196,7 +196,7 @@ pub enum NodeInput {
|
||||
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 expr: String,
|
||||
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 {
|
||||
DocumentNodePath,
|
||||
}
|
||||
@@ -292,7 +292,7 @@ pub enum OldDocumentNodeImplementation {
|
||||
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`.
|
||||
pub enum DocumentNodeImplementation {
|
||||
/// This describes a (document) node built out of a subgraph of other (document) nodes.
|
||||
@@ -546,29 +546,42 @@ pub struct NodeNetwork {
|
||||
pub generated: bool,
|
||||
}
|
||||
|
||||
impl Hash for NodeNetwork {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.exports.hash(state);
|
||||
impl core_types::CacheHash for NodeNetwork {
|
||||
fn cache_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.exports.cache_hash(state);
|
||||
|
||||
let mut nodes: Vec<_> = self.nodes.iter().collect();
|
||||
nodes.sort_by_key(|(id, _)| *id);
|
||||
for (id, node) in nodes {
|
||||
id.hash(state);
|
||||
node.hash(state);
|
||||
id.cache_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 {
|
||||
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
|
||||
impl NodeNetwork {
|
||||
pub fn current_hash(&self) -> u64 {
|
||||
use std::hash::BuildHasher;
|
||||
FxBuildHasher.hash_one(self)
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
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 {
|
||||
@@ -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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@@ -1254,11 +1278,11 @@ mod test {
|
||||
};
|
||||
network.populate_dependants();
|
||||
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();
|
||||
println!("{flat_network:#?}");
|
||||
let expected = flatten_add_expected();
|
||||
println!("{expected:#?}");
|
||||
println!("{network:#?}");
|
||||
|
||||
assert_eq!(flat_network, network);
|
||||
assert_eq!(expected, network);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1345,6 +1369,55 @@ mod test {
|
||||
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 {
|
||||
NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(11), 0)],
|
||||
|
||||
@@ -5,7 +5,7 @@ use brush_nodes::brush_cache::BrushCache;
|
||||
use brush_nodes::brush_stroke::BrushStroke;
|
||||
use core_types::table::Table;
|
||||
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;
|
||||
pub use dyn_any::StaticType;
|
||||
use glam::{Affine2, Vec2};
|
||||
@@ -43,19 +43,18 @@ macro_rules! tagged_value {
|
||||
EditorApi(Arc<PlatformEditorApi>)
|
||||
}
|
||||
|
||||
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
impl Hash for TaggedValue {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
impl CacheHash for TaggedValue {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
Self::None => {}
|
||||
$( Self::$identifier(x) => {x.hash(state)}),*
|
||||
Self::RenderOutput(x) => x.hash(state),
|
||||
Self::EditorApi(x) => x.hash(state),
|
||||
$( Self::$identifier(x) => { x.cache_hash(state) }),*
|
||||
Self::RenderOutput(x) => x.cache_hash(state),
|
||||
Self::EditorApi(x) => x.cache_hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TaggedValue {
|
||||
/// Converts to a Box<dyn DynAny>
|
||||
pub fn to_dynany(self) -> DAny<'a> {
|
||||
@@ -495,96 +494,33 @@ pub enum RenderOutputType {
|
||||
},
|
||||
}
|
||||
|
||||
impl Hash for RenderOutputType {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
impl CacheHash for RenderOutputType {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
Self::Texture(texture) => {
|
||||
texture.hash(state);
|
||||
}
|
||||
Self::Texture(texture) => texture.hash(state),
|
||||
Self::Buffer { data, width, height } => {
|
||||
data.hash(state);
|
||||
width.hash(state);
|
||||
height.hash(state);
|
||||
data.cache_hash(state);
|
||||
width.cache_hash(state);
|
||||
height.cache_hash(state);
|
||||
}
|
||||
Self::Svg { svg, image_data } => {
|
||||
svg.hash(state);
|
||||
image_data.hash(state);
|
||||
svg.cache_hash(state);
|
||||
image_data.cache_hash(state);
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
Self::CanvasFrame { canvas_id, resolution } => {
|
||||
canvas_id.hash(state);
|
||||
resolution.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
canvas_id.cache_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)
|
||||
trait FakeHash {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H);
|
||||
}
|
||||
mod fake_hash {
|
||||
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)
|
||||
}
|
||||
// Metadata is excluded because it's editor-side auxiliary data (click targets, transforms)
|
||||
// that shouldn't affect render cache invalidation, and it contains HashMaps with non-deterministic iteration order
|
||||
impl CacheHash for RenderOutput {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.data.cache_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user