mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Memoize hashing (#1876)
* Implement memoization wrapper for hashing * Fix pattern matching errors * Revert proper point modification hash calculiton * Remove unused hashing code * Code review and bug fixes * Improve pattern matching * Fix tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -43,6 +43,7 @@ pub mod application_io;
|
||||
pub mod quantization;
|
||||
|
||||
use core::any::TypeId;
|
||||
pub use memo::MemoHash;
|
||||
pub use raster::Color;
|
||||
pub use types::Cow;
|
||||
|
||||
|
||||
@@ -142,3 +142,101 @@ impl<I, T, N> MonitorNode<I, T, N> {
|
||||
MonitorNode { io: Arc::new(Mutex::new(None)), node }
|
||||
}
|
||||
}
|
||||
|
||||
use core::hash::{Hash, Hasher};
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct MemoHash<T: Hash> {
|
||||
hash: u64,
|
||||
value: T,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de, T: serde::Deserialize<'de> + Hash> serde::Deserialize<'de> for MemoHash<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
T::deserialize(deserializer).map(|value| Self::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<T: Hash + serde::Serialize> serde::Serialize for MemoHash<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.value.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Hash> MemoHash<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
let hash = Self::calc_hash(&value);
|
||||
Self { hash, value }
|
||||
}
|
||||
pub fn new_with_hash(value: T, hash: u64) -> Self {
|
||||
Self { hash, value }
|
||||
}
|
||||
|
||||
fn calc_hash(data: &T) -> u64 {
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
data.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn inner_mut<'a>(&'a mut self) -> MemoHashGuard<'a, T> {
|
||||
MemoHashGuard { inner: self }
|
||||
}
|
||||
pub fn into_inner<'a>(self) -> T {
|
||||
self.value
|
||||
}
|
||||
pub fn hash_code(&self) -> u64 {
|
||||
self.hash
|
||||
}
|
||||
}
|
||||
impl<T: Hash> From<T> for MemoHash<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for MemoHash<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.hash.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> core::ops::Deref for MemoHash<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoHashGuard<'a, T: Hash> {
|
||||
inner: &'a mut MemoHash<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Hash> core::ops::Drop for MemoHashGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let hash = MemoHash::<T>::calc_hash(&self.inner.value);
|
||||
self.inner.hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Hash> core::ops::Deref for MemoHashGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Hash> core::ops::DerefMut for MemoHashGuard<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,9 +830,9 @@ impl Color {
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color1 = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61).to_gamma_srgb();
|
||||
/// assert_eq!("3240a261", color1.rgb_optional_a_hex())
|
||||
/// let color2 = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61).to_gamma_srgb();
|
||||
/// assert_eq!("3240a2", color2.rgb_optional_a_hex())
|
||||
/// assert_eq!("3240a261", color1.rgb_optional_a_hex());
|
||||
/// let color2 = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0xFF).to_gamma_srgb();
|
||||
/// assert_eq!("5267fa", color2.rgb_optional_a_hex());
|
||||
/// ```
|
||||
#[cfg(feature = "std")]
|
||||
pub fn rgb_optional_a_hex(&self) -> String {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::uuid::generate_uuid;
|
||||
use crate::Node;
|
||||
|
||||
use bezier_rs::BezierHandles;
|
||||
@@ -18,15 +19,7 @@ pub struct PointModification {
|
||||
|
||||
impl Hash for PointModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut delta = self.delta.iter().map(|(&a, &b)| (a, [b.x.to_bits(), b.y.to_bits()])).collect::<Vec<_>>();
|
||||
delta.sort_unstable();
|
||||
delta.hash(state);
|
||||
generate_uuid().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,36 +97,6 @@ pub struct SegmentModification {
|
||||
stroke: HashMap<SegmentId, StrokeId>,
|
||||
}
|
||||
|
||||
impl Hash for SegmentModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut start_point = self.start_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
start_point.sort_unstable();
|
||||
start_point.hash(state);
|
||||
|
||||
let mut end_point = self.end_point.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
end_point.sort_unstable();
|
||||
end_point.hash(state);
|
||||
|
||||
let mut handle_primary = self.handle_primary.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::<Vec<_>>();
|
||||
handle_primary.sort_unstable();
|
||||
handle_primary.hash(state);
|
||||
|
||||
let mut handle_end = self.handle_end.iter().map(|(&a, &b)| (a, b.map(|b| [b.x.to_bits(), b.y.to_bits()]))).collect::<Vec<_>>();
|
||||
handle_end.sort_unstable();
|
||||
handle_end.hash(state);
|
||||
|
||||
let mut stroke = self.stroke.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
stroke.sort_unstable();
|
||||
stroke.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl SegmentModification {
|
||||
/// Apply this modification to the specified [`SegmentDomain`].
|
||||
pub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) {
|
||||
@@ -289,24 +252,6 @@ pub struct RegionModification {
|
||||
fill: HashMap<RegionId, FillId>,
|
||||
}
|
||||
|
||||
impl Hash for RegionModification {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.add.hash(state);
|
||||
|
||||
let mut remove = self.remove.iter().collect::<Vec<_>>();
|
||||
remove.sort_unstable();
|
||||
remove.hash(state);
|
||||
|
||||
let mut segment_range = self.segment_range.iter().map(|(&a, b)| (a, (*b.start(), *b.end()))).collect::<Vec<_>>();
|
||||
segment_range.sort_unstable();
|
||||
segment_range.hash(state);
|
||||
|
||||
let mut fill = self.fill.iter().map(|(&a, &b)| (a, b)).collect::<Vec<_>>();
|
||||
fill.sort_unstable();
|
||||
fill.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl RegionModification {
|
||||
/// Apply this modification to the specified [`RegionDomain`].
|
||||
pub fn apply(&self, region_domain: &mut RegionDomain) {
|
||||
@@ -460,19 +405,7 @@ impl VectorModification {
|
||||
|
||||
impl core::hash::Hash for VectorModification {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.points.hash(state);
|
||||
|
||||
self.segments.hash(state);
|
||||
|
||||
self.regions.hash(state);
|
||||
|
||||
let mut add_g1_continuous = self.add_g1_continuous.iter().copied().collect::<Vec<_>>();
|
||||
add_g1_continuous.sort_unstable();
|
||||
add_g1_continuous.hash(state);
|
||||
|
||||
let mut remove_g1_continuous = self.remove_g1_continuous.iter().copied().collect::<Vec<_>>();
|
||||
remove_g1_continuous.sort_unstable();
|
||||
remove_g1_continuous.hash(state);
|
||||
generate_uuid().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
pub use graphene_core::uuid::generate_uuid;
|
||||
use graphene_core::{Cow, ProtoNodeIdentifier, Type};
|
||||
use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type};
|
||||
|
||||
use glam::IVec2;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
@@ -442,7 +442,7 @@ pub enum NodeInput {
|
||||
Node { node_id: NodeId, output_index: usize, lambda: bool },
|
||||
|
||||
/// A hardcoded value that can't change after the graph is compiled. Gets converted into a value node during graph compilation.
|
||||
Value { tagged_value: TaggedValue, exposed: bool },
|
||||
Value { tagged_value: MemoHash<TaggedValue>, exposed: bool },
|
||||
|
||||
// TODO: Remove import_type and get type from parent node input
|
||||
/// Input that is provided by the parent network to this document node, instead of from a hardcoded value or another node within the same network.
|
||||
@@ -478,7 +478,8 @@ impl NodeInput {
|
||||
Self::Node { node_id, output_index, lambda: true }
|
||||
}
|
||||
|
||||
pub const fn value(tagged_value: TaggedValue, exposed: bool) -> Self {
|
||||
pub fn value(tagged_value: TaggedValue, exposed: bool) -> Self {
|
||||
let tagged_value = tagged_value.into();
|
||||
Self::Value { tagged_value, exposed }
|
||||
}
|
||||
|
||||
@@ -527,6 +528,13 @@ impl NodeInput {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn as_non_exposed_value(&self) -> Option<&TaggedValue> {
|
||||
if let NodeInput::Value { tagged_value, exposed: false } = self {
|
||||
Some(tagged_value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_node(&self) -> Option<NodeId> {
|
||||
if let NodeInput::Node { node_id, .. } = self {
|
||||
@@ -1610,7 +1618,7 @@ mod test {
|
||||
assert_eq!(extraction_network.nodes.len(), 1);
|
||||
let inputs = extraction_network.nodes.get(&NodeId(1)).unwrap().inputs.clone();
|
||||
assert_eq!(inputs.len(), 1);
|
||||
assert!(matches!(&inputs[0], &NodeInput::Value{ tagged_value: TaggedValue::DocumentNode(ref network), ..} if network == &id_node));
|
||||
assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(ref network), ..) if network == &id_node));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1621,13 +1629,7 @@ mod test {
|
||||
NodeId(1),
|
||||
DocumentNode {
|
||||
name: "Inc".into(),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(u32), 0),
|
||||
NodeInput::Value {
|
||||
tagged_value: TaggedValue::U32(2),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(TaggedValue::U32(2), false)],
|
||||
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1712,7 +1714,7 @@ mod test {
|
||||
ProtoNode {
|
||||
identifier: "graphene_core::value::ClonedNode".into(),
|
||||
input: ProtoNodeInput::None,
|
||||
construction_args: ConstructionArgs::Value(TaggedValue::U32(2)),
|
||||
construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(4)]),
|
||||
inputs_source: HashMap::new(),
|
||||
@@ -1759,10 +1761,7 @@ mod test {
|
||||
NodeId(14),
|
||||
DocumentNode {
|
||||
name: "Value".into(),
|
||||
inputs: vec![NodeInput::Value {
|
||||
tagged_value: TaggedValue::U32(2),
|
||||
exposed: false,
|
||||
}],
|
||||
inputs: vec![NodeInput::value(TaggedValue::U32(2), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(4)]),
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::wasm_application_io::WasmEditorApi;
|
||||
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::{BlendMode, LuminanceCalculation};
|
||||
use graphene_core::{Color, Node, Type};
|
||||
use graphene_core::{Color, MemoHash, Node, Type};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
pub use dyn_any::StaticType;
|
||||
@@ -207,17 +207,17 @@ impl Display for TaggedValue {
|
||||
}
|
||||
|
||||
pub struct UpcastNode {
|
||||
value: TaggedValue,
|
||||
value: MemoHash<TaggedValue>,
|
||||
}
|
||||
impl<'input> Node<'input, DAny<'input>> for UpcastNode {
|
||||
type Output = FutureAny<'input>;
|
||||
|
||||
fn eval(&'input self, _: DAny<'input>) -> Self::Output {
|
||||
Box::pin(async move { self.value.clone().to_any() })
|
||||
Box::pin(async move { self.value.clone().into_inner().to_any() })
|
||||
}
|
||||
}
|
||||
impl UpcastNode {
|
||||
pub fn new(value: TaggedValue) -> Self {
|
||||
pub fn new(value: MemoHash<TaggedValue>) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ impl core::fmt::Display for ProtoNetwork {
|
||||
/// Defines the arguments used to construct the boxed node struct. This is used to call the constructor function in the `node_registry.rs` file - which is hidden behind a wall of macros.
|
||||
pub enum ConstructionArgs {
|
||||
/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
|
||||
Value(value::TaggedValue),
|
||||
Value(MemoHash<value::TaggedValue>),
|
||||
// TODO: use a struct for clearer naming.
|
||||
/// A list of nodes used as inputs to the constructor function in `node_registry.rs`.
|
||||
/// The bool indicates whether to treat the node as lambda node.
|
||||
@@ -230,7 +230,7 @@ impl Default for ProtoNode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
identifier: ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|
||||
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0)),
|
||||
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()),
|
||||
input: ProtoNodeInput::None,
|
||||
original_location: OriginalLocation::default(),
|
||||
skip_deduplication: false,
|
||||
@@ -940,12 +940,12 @@ mod test {
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![
|
||||
NodeId(5686040524603683634),
|
||||
NodeId(13787140740513543798),
|
||||
NodeId(1280393769237740322),
|
||||
NodeId(3100442468152897091),
|
||||
NodeId(14834729712909816752),
|
||||
NodeId(8678825113056010444)
|
||||
NodeId(12083027370457564588),
|
||||
NodeId(10127202135369428481),
|
||||
NodeId(3781642984881236270),
|
||||
NodeId(9447822059040146367),
|
||||
NodeId(15916837829094140504),
|
||||
NodeId(1758919868423328454)
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -996,7 +996,7 @@ mod test {
|
||||
ProtoNode {
|
||||
identifier: "value".into(),
|
||||
input: ProtoNodeInput::None,
|
||||
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(2)),
|
||||
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(2).into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ use graphene_core::{transform::Footprint, GraphicGroup};
|
||||
use graphene_core::{vector::misc::BooleanOperation, GraphicElement};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub struct BinaryBooleanOperationNode<LowerVectorData, BooleanOp> {
|
||||
|
||||
@@ -221,7 +221,7 @@ impl BorrowTree {
|
||||
|
||||
match &proto_node.construction_args {
|
||||
ConstructionArgs::Value(value) => {
|
||||
let node = if let TaggedValue::EditorApi(api) = value {
|
||||
let node = if let TaggedValue::EditorApi(api) = &**value {
|
||||
let editor_api = UpcastAsRefNode::new(api.clone());
|
||||
let node = Box::new(editor_api) as TypeErasedBox<'_>;
|
||||
NodeContainer::new(node)
|
||||
@@ -263,7 +263,7 @@ mod test {
|
||||
#[test]
|
||||
fn push_node_sync() {
|
||||
let mut tree = BorrowTree::default();
|
||||
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32)), vec![]);
|
||||
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![]);
|
||||
let context = TypingContext::default();
|
||||
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
|
||||
futures::executor::block_on(future).unwrap();
|
||||
|
||||
@@ -48,13 +48,7 @@ mod tests {
|
||||
NodeId(0),
|
||||
DocumentNode {
|
||||
name: "Inc".into(),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(u32), 0),
|
||||
NodeInput::Value {
|
||||
tagged_value: graph_craft::document::value::TaggedValue::U32(1u32),
|
||||
exposed: false,
|
||||
},
|
||||
],
|
||||
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(graph_craft::document::value::TaggedValue::U32(1u32), false)],
|
||||
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user