mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Migrate memo nodes to node macro and make implementing other persistent nodes easier (#3552)
* Add #[data] and #[serialize] attributes to node macro - Add #[data] attribute for struct fields that aren't node parameters - Data fields are initialized with Default::default() - Passed as references to the underlying function - Excluded from registry metadata (internal state) - Generic types in data fields allowed without #[implementations] - Add #[serialize] attribute for custom Node::serialize() implementation - Receives references to all data fields - Generates serialize() method in Node trait impl - Conditional derives based on data field presence - With data fields: Debug, Clone only - Without data fields: Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash * Refactor Memo and Monitor Node to use node macro * Move Complex type into type alias * Fix format * Update node-graph/nodes/gcore/src/memo.rs Co-authored-by: Keavon Chambers <keavon@keavon.com> * Update node-graph/nodes/gcore/src/memo.rs Co-authored-by: Keavon Chambers <keavon@keavon.com> --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
use core_types::WasmNotSend;
|
||||
use core_types::memo::*;
|
||||
use core_types::{Node, WasmNotSend};
|
||||
use dyn_any::DynFuture;
|
||||
use std::future::Future;
|
||||
use std::hash::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
@@ -14,94 +12,38 @@ use std::sync::Mutex;
|
||||
/// A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node.
|
||||
///
|
||||
/// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.
|
||||
#[derive(Default)]
|
||||
pub struct MemoNode<T, CachedNode> {
|
||||
cache: Arc<Mutex<Option<(u64, T)>>>,
|
||||
node: CachedNode,
|
||||
}
|
||||
impl<'i, I: Hash + 'i, T: 'i + Clone + WasmNotSend, CachedNode: 'i> Node<'i, I> for MemoNode<T, CachedNode>
|
||||
where
|
||||
CachedNode: for<'any_input> Node<'any_input, I>,
|
||||
for<'a> <CachedNode as Node<'a, I>>::Output: Future<Output = T> + WasmNotSend,
|
||||
{
|
||||
// TODO: This should return a reference to the cached cached_value
|
||||
// but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD
|
||||
type Output = DynFuture<'i, T>;
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
input.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
#[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 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
input.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
if let Some(data) = self.cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) {
|
||||
Box::pin(async move { data })
|
||||
} else {
|
||||
let fut = self.node.eval(input);
|
||||
let cache = self.cache.clone();
|
||||
Box::pin(async move {
|
||||
let value = fut.await;
|
||||
*cache.lock().unwrap() = Some((hash, value.clone()));
|
||||
value
|
||||
})
|
||||
}
|
||||
if let Some(data) = cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) {
|
||||
return data;
|
||||
}
|
||||
|
||||
fn reset(&self) {
|
||||
self.cache.lock().unwrap().take();
|
||||
}
|
||||
let value = node.eval(input).await;
|
||||
*cache.lock().unwrap() = Some((hash, value.clone()));
|
||||
value
|
||||
}
|
||||
|
||||
impl<T, CachedNode> MemoNode<T, CachedNode> {
|
||||
pub fn new(node: CachedNode) -> MemoNode<T, CachedNode> {
|
||||
MemoNode { cache: Default::default(), node }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod memo {
|
||||
use core_types::ProtoNodeIdentifier;
|
||||
|
||||
pub const IDENTIFIER: ProtoNodeIdentifier = ProtoNodeIdentifier::new("graphene_core::memo::MemoNode");
|
||||
}
|
||||
type MonitorValue<I, T> = Arc<Mutex<Option<Arc<IORecord<I, T>>>>>;
|
||||
|
||||
/// Caches the output of the last graph evaluation for introspection.
|
||||
#[derive(Default)]
|
||||
pub struct MonitorNode<I, T, N> {
|
||||
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), skip_impl)]
|
||||
async fn monitor<I: Clone + 'static + Send + Sync, T: Clone + 'static + Send + Sync>(
|
||||
input: I,
|
||||
#[allow(clippy::type_complexity)]
|
||||
io: Arc<Mutex<Option<Arc<IORecord<I, T>>>>>,
|
||||
node: N,
|
||||
#[data]
|
||||
io: MonitorValue<I, T>,
|
||||
node: impl Node<I, Output = T>,
|
||||
) -> T {
|
||||
let output = node.eval(input.clone()).await;
|
||||
*io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() }));
|
||||
output
|
||||
}
|
||||
|
||||
impl<'i, T, I, N> Node<'i, I> for MonitorNode<I, T, N>
|
||||
where
|
||||
I: Clone + 'static + Send + Sync,
|
||||
T: Clone + 'static + Send + Sync,
|
||||
for<'a> N: Node<'a, I, Output: Future<Output = T> + WasmNotSend> + 'i,
|
||||
{
|
||||
type Output = DynFuture<'i, T>;
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
let io = self.io.clone();
|
||||
let output_fut = self.node.eval(input.clone());
|
||||
Box::pin(async move {
|
||||
let output = output_fut.await;
|
||||
*io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() }));
|
||||
output
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
|
||||
let io = self.io.lock().unwrap();
|
||||
(io).as_ref().map(|output| output.clone() as Arc<dyn std::any::Any + Send + Sync>)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T, N> MonitorNode<I, T, N> {
|
||||
pub fn new(node: N) -> MonitorNode<I, T, N> {
|
||||
MonitorNode { io: Arc::new(Mutex::new(None)), node }
|
||||
}
|
||||
}
|
||||
|
||||
pub mod monitor {
|
||||
use core_types::ProtoNodeIdentifier;
|
||||
|
||||
pub const IDENTIFIER: ProtoNodeIdentifier = ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode");
|
||||
fn serialize_monitor<I: Clone + 'static + Send + Sync, T: Clone + 'static + Send + Sync>(io: &MonitorValue<I, T>) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
|
||||
let io = io.lock().unwrap();
|
||||
io.as_ref().map(|output| output.clone() as Arc<dyn std::any::Any + Send + Sync>)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user