Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"frontend/wrapper",
"libraries/dyn-any",
"libraries/math-parser",
"node-graph/libraries/graphene-hash",
"node-graph/libraries/*",
"node-graph/nodes/*",
"node-graph/nodes/raster/shaders",
Expand Down Expand Up @@ -63,6 +64,7 @@ dyn-any = { path = "libraries/dyn-any", features = [
"log-bad-types",
"rc",
] }
graphene-hash = { path = "node-graph/libraries/graphene-hash", features = ["derive"] }
preprocessor = { path = "node-graph/preprocessor" }
math-parser = { path = "libraries/math-parser" }
graphene-application-io = { path = "node-graph/libraries/application-io" }
Expand Down
2 changes: 1 addition & 1 deletion editor/src/messages/tool/tool_messages/gradient_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub struct GradientOptions {

#[impl_message(Message, ToolMessage, Gradient)]
#[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 {
// Standard messages
Abort,
Expand Down
3 changes: 2 additions & 1 deletion frontend/wrapper/src/editor_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use editor::messages::portfolio::utility_types::{DockingSplitDirection, FontCata
use editor::messages::prelude::*;
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
use graph_craft::document::NodeId;
use graphene_std::graphene_hash::CacheHashWrapper;
use graphene_std::raster::color::Color;
use graphene_std::vector::GradientStops;
use serde::Serialize;
Expand Down Expand Up @@ -131,7 +132,7 @@ impl EditorWrapper {
// Sends a FrontendMessage to JavaScript
pub(crate) fn send_frontend_message_to_js(&self, message: FrontendMessage) {
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);

if new_hash != prev_hash {
Expand Down
6 changes: 6 additions & 0 deletions node-graph/graph-craft/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,12 @@ 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) {
core::hash::Hash::hash(self, state);
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
114 changes: 31 additions & 83 deletions node-graph/graph-craft/src/document/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -495,96 +494,45 @@ 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 {

impl Hash for RenderOutputType {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.data.hash(state)
CacheHash::cache_hash(self, 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))
}
// 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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: RenderOutput::cache_hash ignores metadata, which can cause incorrect memo cache hits when only metadata changes.

(Based on your team's feedback about avoiding misleading/incomplete hashing semantics.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/graph-craft/src/document/value.rs, line 528:

<comment>`RenderOutput::cache_hash` ignores `metadata`, which can cause incorrect memo cache hits when only metadata changes.

(Based on your team's feedback about avoiding misleading/incomplete hashing semantics.) </comment>

<file context>
@@ -495,96 +494,43 @@ pub enum RenderOutputType {
-		}
+impl CacheHash for RenderOutput {
+	fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
+		self.data.cache_hash(state);
 	}
-	impl FakeHash for (f64, Color) {
</file context>

}
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)
}
}

impl Hash for RenderOutput {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
CacheHash::cache_hash(self, state);
}
}
6 changes: 6 additions & 0 deletions node-graph/libraries/application-io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ impl<Io> Hash for EditorApi<Io> {
}
}

impl<Io> core_types::graphene_hash::CacheHash for EditorApi<Io> {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(self, state);
}
}

impl<Io> PartialEq for EditorApi<Io> {
fn eq(&self, other: &Self) -> bool {
self.font_cache == other.font_cache
Expand Down
1 change: 1 addition & 0 deletions node-graph/libraries/core-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ wasm = ["tsify", "wasm-bindgen", "no-std-types/wasm"]
[dependencies]
# Local dependencies
no-std-types = { workspace = true, features = ["std"] }
graphene-hash = { workspace = true, features = ["derive"] }

# Workspace dependencies
bitflags = { workspace = true }
Expand Down
26 changes: 16 additions & 10 deletions node-graph/libraries/core-types/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ bitflags! {
}
}

impl graphene_hash::CacheHash for ContextFeatures {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(self, state);
}
}

impl ContextFeatures {
pub fn name(&self) -> &'static str {
match *self {
Expand Down Expand Up @@ -536,14 +542,14 @@ impl Default for OwnedContextImpl {
}
}

impl Hash for OwnedContextImpl {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.footprint.hash(state);
self.real_time.map(|x| x.to_bits()).hash(state);
self.animation_time.map(|x| x.to_bits()).hash(state);
self.pointer_position.map(|v| (v.x.to_bits(), v.y.to_bits())).hash(state);
self.position.iter().flat_map(|x| x.iter()).map(|v| (v.x.to_bits(), v.y.to_bits())).for_each(|v| v.hash(state));
self.index.hash(state);
impl graphene_hash::CacheHash for OwnedContextImpl {
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.footprint.cache_hash(state);
self.real_time.cache_hash(state);
self.animation_time.cache_hash(state);
self.pointer_position.cache_hash(state);
self.position.cache_hash(state);
self.index.cache_hash(state);
self.hash_varargs(state);
}
}
Expand Down Expand Up @@ -600,9 +606,9 @@ pub trait DynHash {
fn dyn_hash(&self, state: &mut dyn Hasher);
}

impl<H: Hash + ?Sized> DynHash for H {
impl<H: graphene_hash::CacheHash + ?Sized> DynHash for H {
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
graphene_hash::CacheHash::cache_hash(self, &mut state);
}
}

Expand Down
Loading
Loading