diff --git a/document/graph-storage/src/tests/round_trip.rs b/document/graph-storage/src/tests/round_trip.rs index 2fa8af0684..95c605f6a8 100644 --- a/document/graph-storage/src/tests/round_trip.rs +++ b/document/graph-storage/src/tests/round_trip.rs @@ -246,10 +246,10 @@ fn test_nested_network_flattening() { #[test] fn test_metadata_preservation() { // Create a network with nodes that have non-default metadata - let context_features = ContextDependencies { - extract: core_types::context::ContextFeatures::FOOTPRINT | core_types::context::ContextFeatures::REAL_TIME, - ..Default::default() - }; + let context_features = ContextDependencies::new( + core_types::context::ContextFeatures::FOOTPRINT | core_types::context::ContextFeatures::REAL_TIME, + core_types::context::ContextFeatures::empty(), + ); let network = NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0)], diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index f6fabb6e1c..7b850324c0 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -1008,7 +1008,7 @@ impl NodeNetwork { DocumentNodeMetadata::SourceId => { let source_id = Self::source_id_for_path(path); if let Some(context_features) = context_features.as_deref_mut() { - core_types::context::merge_sorted_sources(&mut context_features.sources, &[source_id]); + context_features.add_sources(&[source_id]); } (TaggedValue::U64(source_id).into(), false) } diff --git a/node-graph/interpreted-executor/benches/benchmark_util.rs b/node-graph/interpreted-executor/benches/benchmark_util.rs index 54449ff13e..89a9e5d57f 100644 --- a/node-graph/interpreted-executor/benches/benchmark_util.rs +++ b/node-graph/interpreted-executor/benches/benchmark_util.rs @@ -1,6 +1,5 @@ use criterion::BenchmarkGroup; use criterion::measurement::Measurement; -use futures::executor::block_on; use graph_craft::proto::ProtoNetwork; use graph_craft::util::{DEMO_ART, compile, load_from_name}; use graphene_std::application_io::EditorApi; @@ -14,7 +13,7 @@ pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) { let preprocessor = preprocessor::Preprocessor::new(); preprocessor.preprocess(&mut network, &|_| None).unwrap(); let proto_network = compile(network); - let executor = block_on(DynamicExecutor::new(proto_network.clone())).unwrap(); + let executor = DynamicExecutor::new(proto_network.clone()).unwrap(); (executor, proto_network) } diff --git a/node-graph/interpreted-executor/benches/run_cached.rs b/node-graph/interpreted-executor/benches/run_cached.rs index e62533bd32..a692047324 100644 --- a/node-graph/interpreted-executor/benches/run_cached.rs +++ b/node-graph/interpreted-executor/benches/run_cached.rs @@ -2,6 +2,7 @@ mod benchmark_util; use benchmark_util::{bench_for_each_demo, setup_network}; use criterion::{Criterion, criterion_group, criterion_main}; +use graph_craft::graphene_compiler::Executor; use graphene_std::application_io::RenderConfig; fn subsequent_evaluations(c: &mut Criterion) { @@ -9,9 +10,7 @@ fn subsequent_evaluations(c: &mut Criterion) { let context = RenderConfig::default(); bench_for_each_demo(&mut group, |name, g| { let (executor, _) = setup_network(name); - g.bench_function(name, |b| { - b.iter(|| futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), std::hint::black_box(context))).unwrap()) - }); + g.bench_function(name, |b| b.iter(|| Executor::execute(&&executor, std::hint::black_box(context)).unwrap())); }); group.finish(); } diff --git a/node-graph/interpreted-executor/benches/run_cached_gungraun.rs b/node-graph/interpreted-executor/benches/run_cached_gungraun.rs index 2222b37d96..64c9a051ab 100644 --- a/node-graph/interpreted-executor/benches/run_cached_gungraun.rs +++ b/node-graph/interpreted-executor/benches/run_cached_gungraun.rs @@ -1,6 +1,7 @@ mod benchmark_util; use benchmark_util::setup_network; +use graph_craft::graphene_compiler::Executor; use graphene_std::application_io::RenderConfig; use gungraun::prelude::*; use interpreted_executor::dynamic_executor::DynamicExecutor; @@ -11,7 +12,7 @@ fn setup_run_cached(name: &str) -> DynamicExecutor { // Warm up the cache by running once let context = RenderConfig::default(); - let _ = futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), context)); + let _ = Executor::execute(&&executor, context); executor } @@ -20,7 +21,7 @@ fn setup_run_cached(name: &str) -> DynamicExecutor { #[benches::with_setup(args = ["isometric-fountain", "painted-dreams", "parametric-dunescape", "red-dress", "valley-of-spires"], setup = setup_run_cached)] pub fn run_cached(executor: DynamicExecutor) -> DynamicExecutor { let context = RenderConfig::default(); - black_box(futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), black_box(context))).unwrap()); + black_box(Executor::execute(&&executor, black_box(context)).unwrap()); // Return the executor so its teardown happens outside the measured section executor diff --git a/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs b/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs index 7f58565665..996e84f7fe 100644 --- a/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs +++ b/node-graph/interpreted-executor/benches/run_demo_art_criterion.rs @@ -9,14 +9,11 @@ use interpreted_executor::dynamic_executor::DynamicExecutor; fn update_executor(name: &str, c: &mut BenchmarkGroup) { let network = load_from_name(name); let proto_network = compile(network); - let empty = ProtoNetwork::default(); - - let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); c.bench_function(name, |b| { b.iter_batched( - || (executor.clone(), proto_network.clone()), - |(mut executor, network)| futures::executor::block_on(executor.update(std::hint::black_box(network))), + || (DynamicExecutor::new(ProtoNetwork::default()).unwrap(), proto_network.clone()), + |(mut executor, network)| executor.update(std::hint::black_box(network)), criterion::BatchSize::SmallInput, ) }); @@ -33,10 +30,10 @@ fn run_once(name: &str, c: &mut BenchmarkGroup) { let network = load_from_name(name); let proto_network = compile(network); - let executor = futures::executor::block_on(DynamicExecutor::new(proto_network)).unwrap(); + let executor = DynamicExecutor::new(proto_network).unwrap(); let footprint = Footprint::default(); - c.bench_function(name, |b| b.iter(|| futures::executor::block_on((&executor).execute(footprint)))); + c.bench_function(name, |b| b.iter(|| (&executor).execute(footprint))); } fn run_once_demo(c: &mut Criterion) { let mut g = c.benchmark_group("Run Once no render"); diff --git a/node-graph/interpreted-executor/benches/run_once.rs b/node-graph/interpreted-executor/benches/run_once.rs index a7bd7ac714..9f2e368ce7 100644 --- a/node-graph/interpreted-executor/benches/run_once.rs +++ b/node-graph/interpreted-executor/benches/run_once.rs @@ -2,6 +2,7 @@ mod benchmark_util; use benchmark_util::{bench_for_each_demo, setup_network}; use criterion::{Criterion, criterion_group, criterion_main}; +use graph_craft::graphene_compiler::Executor; use graphene_std::application_io::RenderConfig; fn run_once(c: &mut Criterion) { @@ -11,7 +12,7 @@ fn run_once(c: &mut Criterion) { g.bench_function(name, |b| { b.iter_batched( || setup_network(name), - |(executor, _)| futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), std::hint::black_box(context))).unwrap(), + |(executor, _)| Executor::execute(&&executor, std::hint::black_box(context)).unwrap(), criterion::BatchSize::SmallInput, ) }); diff --git a/node-graph/interpreted-executor/benches/run_once_gungraun.rs b/node-graph/interpreted-executor/benches/run_once_gungraun.rs index a46f5388f2..da9377b4ac 100644 --- a/node-graph/interpreted-executor/benches/run_once_gungraun.rs +++ b/node-graph/interpreted-executor/benches/run_once_gungraun.rs @@ -1,6 +1,7 @@ mod benchmark_util; use benchmark_util::setup_network; +use graph_craft::graphene_compiler::Executor; use graphene_std::application_io; use gungraun::prelude::*; use interpreted_executor::dynamic_executor::DynamicExecutor; @@ -15,7 +16,7 @@ fn setup_run_once(name: &str) -> DynamicExecutor { #[benches::with_setup(args = ["isometric-fountain", "painted-dreams", "procedural-string-lights", "parametric-dunescape", "red-dress", "valley-of-spires"], setup = setup_run_once)] pub fn run_once(executor: DynamicExecutor) -> DynamicExecutor { let context = application_io::RenderConfig::default(); - black_box(futures::executor::block_on(executor.tree().eval_tagged_value(executor.output(), black_box(context))).unwrap()); + black_box(Executor::execute(&&executor, black_box(context)).unwrap()); // Return the executor so its teardown happens outside the measured section executor diff --git a/node-graph/interpreted-executor/benches/update_executor.rs b/node-graph/interpreted-executor/benches/update_executor.rs index ca5f4c0a4a..081cba763a 100644 --- a/node-graph/interpreted-executor/benches/update_executor.rs +++ b/node-graph/interpreted-executor/benches/update_executor.rs @@ -13,10 +13,10 @@ fn update_executor(c: &mut Criterion) { || { let (_, proto_network) = setup_network(name); let empty = ProtoNetwork::default(); - let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); + let executor = DynamicExecutor::new(empty).unwrap(); (executor, proto_network) }, - |(mut executor, network)| futures::executor::block_on(executor.update(std::hint::black_box(network))), + |(mut executor, network)| executor.update(std::hint::black_box(network)), criterion::BatchSize::SmallInput, ) }); diff --git a/node-graph/interpreted-executor/benches/update_executor_gungraun.rs b/node-graph/interpreted-executor/benches/update_executor_gungraun.rs index 91017ee3b8..42dbbe508a 100644 --- a/node-graph/interpreted-executor/benches/update_executor_gungraun.rs +++ b/node-graph/interpreted-executor/benches/update_executor_gungraun.rs @@ -9,7 +9,7 @@ use std::hint::black_box; fn setup_update_executor(name: &str) -> (DynamicExecutor, ProtoNetwork) { let (_, proto_network) = setup_network(name); let empty = ProtoNetwork::default(); - let executor = futures::executor::block_on(DynamicExecutor::new(empty)).unwrap(); + let executor = DynamicExecutor::new(empty).unwrap(); (executor, proto_network) } @@ -17,7 +17,7 @@ fn setup_update_executor(name: &str) -> (DynamicExecutor, ProtoNetwork) { #[benches::with_setup(args = ["isometric-fountain", "painted-dreams", "procedural-string-lights", "parametric-dunescape", "red-dress", "valley-of-spires"], setup = setup_update_executor)] pub fn update_executor(setup: (DynamicExecutor, ProtoNetwork)) -> DynamicExecutor { let (mut executor, network) = setup; - let _ = black_box(futures::executor::block_on(executor.update(black_box(network)))); + let _ = black_box(executor.update(black_box(network))); // Return the executor so its teardown happens outside the measured section executor diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 78b7e8d5c3..9aec1e605b 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -17,6 +17,13 @@ use std::sync::{Arc, Mutex, PoisonError}; const ARENA_CAPACITY: usize = 1 << 20; +fn new_arena() -> Arena { + Arena::new(ARENA_CAPACITY).unwrap_or_else(|| { + log::error!("arena generations exhausted; continuing without frame caching"); + Arena::parked() + }) +} + /// An executor of a node graph that does not require an online compilation server, and instead uses `Box`. pub struct DynamicExecutor { output: NodeId, @@ -42,7 +49,7 @@ impl Default for DynamicExecutor { tree: Default::default(), typing_context: TypingContext::new(&node_registry::NODE_REGISTRY), orphaned_nodes: HashSet::new(), - arena: Mutex::new(Arena::new(ARENA_CAPACITY)), + arena: Mutex::new(new_arena()), runtime: noop_runtime(), live_sources: Vec::new(), } @@ -78,7 +85,7 @@ impl DynamicExecutor { output, typing_context, orphaned_nodes: HashSet::new(), - arena: Mutex::new(Arena::new(ARENA_CAPACITY)), + arena: Mutex::new(new_arena()), runtime, live_sources: sources, }) @@ -480,7 +487,7 @@ mod test { #[test] fn eval_root_builds_the_bare_root_with_the_call_argument_as_vararg_0() { - let mut arena = Arena::new(64); + let mut arena = Arena::new(64).unwrap(); let runtime = GraphRuntime::new(InertSpawner); let argument = 21.5f64; let result = eval_root(&mut arena, &runtime, &argument, |ctx| { @@ -492,7 +499,7 @@ mod test { #[test] fn eval_root_resets_the_arena_at_eval_start() { - let mut arena = Arena::new(64); + let mut arena = Arena::new(64).unwrap(); let runtime = GraphRuntime::new(InertSpawner); let cell = ArenaCell::new(); eval_root(&mut arena, &runtime, &(), |ctx| { @@ -509,7 +516,7 @@ mod test { #[test] fn a_panicking_eval_reports_the_error_and_resets_the_arena() { - let mut arena = Arena::new(64); + let mut arena = Arena::new(64).unwrap(); let runtime = GraphRuntime::new(InertSpawner); let cell = ArenaCell::new(); let result: GPoll<()> = eval_root(&mut arena, &runtime, &(), |ctx| { @@ -530,7 +537,7 @@ mod test { tree.push_node(NodeId(0), val_1_protonode, &context).unwrap(); let _node = tree.get(NodeId(0)).unwrap(); - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = EvalScope::new(None, None, None, &generations, &arena); let ctx = ContextImpl::root(&scope); diff --git a/node-graph/libraries/core-types/src/arena.rs b/node-graph/libraries/core-types/src/arena.rs index 3cf61bd31b..e5fdaf1e10 100644 --- a/node-graph/libraries/core-types/src/arena.rs +++ b/node-graph/libraries/core-types/src/arena.rs @@ -84,6 +84,18 @@ impl Arena { }) } + /// An arena that refuses every allocation and resolves no handle, so a caller that + /// cannot fail can degrade instead of propagating exhaustion. + pub fn parked() -> Self { + LIVE_ARENAS.fetch_add(1, Ordering::Release); + Self { + generation: AtomicU64::new(PARKED_GENERATION), + offset: AtomicUsize::new(0), + buf: Box::new([]), + drops: Mutex::new(Vec::new()), + } + } + pub fn generation(&self) -> u64 { self.generation.load(Ordering::Acquire) } diff --git a/node-graph/libraries/core-types/src/frame_table.rs b/node-graph/libraries/core-types/src/frame_table.rs index accbbb8f18..31a1da78be 100644 --- a/node-graph/libraries/core-types/src/frame_table.rs +++ b/node-graph/libraries/core-types/src/frame_table.rs @@ -17,6 +17,13 @@ struct FrameSlot { value: UnsafeCell>, } +// SAFETY: the key CAS reserves a slot for one writer, and the Release store of its +// state publishes the value to every Acquire load in `lookup`, so concurrent access +// is ordered. Sharing the table hands out `&T` and drops `T` on whichever thread +// drops the table, which is what the `Send + Sync` bounds cover. +unsafe impl Sync for FrameTable {} +unsafe impl Send for FrameTable {} + pub enum Lookup<'t, T> { Hit(Finality, &'t T), Vacant(VacantSlot<'t, T>), diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 6c6abdad63..14464dd89c 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -333,7 +333,7 @@ mod tests { type ErasedSplitEdge = dyn for<'c> Node, Output = SplitBorrow<'c>> + Send + Sync; - let arena = Arena::new(4096); + let arena = Arena::new(4096).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -391,7 +391,7 @@ mod tests { } } - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -443,7 +443,7 @@ mod tests { } } - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -493,7 +493,7 @@ mod tests { #[test] fn duplicated_edges_share_one_instance_and_outlive_each_other() { - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); diff --git a/node-graph/libraries/core-types/src/runtime.rs b/node-graph/libraries/core-types/src/runtime.rs index a7a23e9072..2634321242 100644 --- a/node-graph/libraries/core-types/src/runtime.rs +++ b/node-graph/libraries/core-types/src/runtime.rs @@ -281,7 +281,7 @@ mod tests { #[test] fn async_source_spawns_once_and_lands_via_the_slot() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -301,7 +301,7 @@ mod tests { #[test] fn async_source_reports_the_placeholder_while_in_flight() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -316,7 +316,7 @@ mod tests { #[test] fn no_partial_maps_the_placeholder_frame_to_pending() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -331,7 +331,7 @@ mod tests { #[test] fn prologue_runs_sync_and_spawns_once() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -350,7 +350,7 @@ mod tests { #[test] fn prologue_interrupt_defers_the_spawn() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -369,7 +369,7 @@ mod tests { #[test] fn async_kernels_read_captured_varargs() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let root = ContextImpl::root(&scope); @@ -390,7 +390,7 @@ mod tests { #[test] fn async_kernels_read_the_captured_context_snapshot() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let root = ContextImpl::root(&scope); @@ -484,7 +484,7 @@ mod tests { #[test] fn a_source_slot_lands_through_the_runtime_while_downstream_keys_invalidate() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let runtime = Arc::new(GraphRuntime::new(CollectSpawner::default())); runtime.retain_sources(&[11]); let graph = EpilogueDoubleNode::new(SourceNode(21.0f64), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(11u64)); diff --git a/node-graph/nodes/gcore/src/context_modification.rs b/node-graph/nodes/gcore/src/context_modification.rs index dd4640928c..f9338886b3 100644 --- a/node-graph/nodes/gcore/src/context_modification.rs +++ b/node-graph/nodes/gcore/src/context_modification.rs @@ -46,6 +46,6 @@ fn context_modification( /// The parts of the context to keep when evaluating the input value. All other parts are nullified. modification: ContextModification, ) -> GPoll { - let scope = ctx.scope().nullified(modification.features, Some(&modification.sources)); + let scope = ctx.scope().nullified(modification.features, Some(modification.sources())); value.eval(&ctx.nullified(modification.features, &scope)) } diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index 3b2efebd25..7066eb9db5 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -41,7 +41,11 @@ where } #[node_macro::node(category(""), path(graphene_core::memo), skip_impl, extent(frame_memo_extent))] -fn frame_memo<'e, T: Clone + 'static>(ctx: impl Ctx + CacheHash + ExtractArena<'e>, #[data] cell: ArenaCell>, content: impl Node, Output = T>) -> GPoll<&'e T> { +fn frame_memo<'e, T: Clone + 'static + Send + Sync>( + ctx: impl Ctx + CacheHash + ExtractArena<'e>, + #[data] cell: ArenaCell>, + content: impl Node, Output = T>, +) -> GPoll<&'e T> { let arena = ctx.arena(); let table = match cell.load(arena) { Some(table) => table, @@ -70,13 +74,13 @@ fn frame_memo<'e, T: Clone + 'static>(ctx: impl Ctx + CacheHash + ExtractArena<' fn frame_memo_extent(node: &FrameMemoNode, ctx: &C) -> GPoll where - T: Clone + 'static, + T: Clone + 'static + Send + Sync, NodeContent: Node, { node.content.extent(ctx) } -pub fn park(arena: &Arena, result: GPoll) -> GPoll<&T> { +pub fn park(arena: &Arena, result: GPoll) -> GPoll<&T> { match result { GPoll::Final(value) => match arena.alloc(value) { Some((parked, _)) => GPoll::Final(parked), @@ -126,8 +130,6 @@ fn serialize_monitor(io: &MonitorValue) -> mod tests { use super::*; use core_types::SourceId; - use core_types::Type; - use core_types::concrete; use core_types::context::{ContextImpl, EvalScope}; use core_types::registry::{EdgeHandle, ErasedLendNode, ErasedNode}; use std::sync::atomic::{AtomicU32, Ordering}; @@ -168,7 +170,7 @@ mod tests { #[test] fn monitor_serialize_exposes_the_io_record_through_the_edge() { - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -186,7 +188,7 @@ mod tests { #[test] fn memoize_caches_across_evals() { - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -199,7 +201,7 @@ mod tests { #[test] fn memo_invalidates_on_generation_bump() { - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let source: SourceId = 7; let before = [(source, 1)]; let after = [(source, 2)]; @@ -215,7 +217,7 @@ mod tests { #[test] fn memo_replays_partiality_on_hit() { - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -228,7 +230,7 @@ mod tests { #[test] fn memoized_edges_stack_and_rewire() { - let arena = Arena::new(1024); + let arena = Arena::new(1024).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); @@ -243,7 +245,7 @@ mod tests { #[test] fn frame_memo_turns_an_owned_edge_into_a_lending_edge() { - let arena = Arena::new(4096); + let arena = Arena::new(4096).unwrap(); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index 0afcb626a5..2b9cef9e15 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -221,7 +221,7 @@ mod tests { #[test] fn create_context_builds_the_render_context_from_the_root_vararg() { - let arena = Arena::new(256); + let arena = Arena::new(256).unwrap(); let generations = []; let scope = EvalScope::new(None, None, None, &generations, &arena); let root = ContextImpl::root(&scope); diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index d2263e78d4..f485f44d7d 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -989,8 +989,6 @@ fn normalize(_: impl Ctx, vector: DVec2) -> DVec2 { #[cfg(test)] mod test { use super::*; - use core_types::Node; - use core_types::generic::FnNode; #[test] pub fn dot_product_function() { @@ -1029,12 +1027,6 @@ mod test { assert_eq!(result, 0.); } - #[test] - pub fn foo() { - let fnn = FnNode::new(|(a, b)| (b, a)); - assert_eq!(fnn.eval((1u32, 2u32)), (2, 1)); - } - #[test] pub fn add_vectors() { assert_eq!(super::add(&(), DVec2::ONE, DVec2::ONE), DVec2::ONE * 2.); @@ -1098,7 +1090,7 @@ mod graphene_test { #[test] fn generated_add_evaluates_through_the_node_path() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); @@ -1108,7 +1100,7 @@ mod graphene_test { #[test] fn generated_add_batches_through_the_erased_edge() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); @@ -1118,13 +1110,13 @@ mod graphene_test { let BatchStatus::Filled(lanes, finality) = status else { panic!("expected filled, got {status:?}"); }; - assert_eq!(lanes, &[12.0, 13.0, 14.0, 15.0]); + assert_eq!(lanes.values(), &[12.0, 13.0, 14.0, 15.0]); assert_eq!(finality, Finality::AllFinal); } #[test] fn generated_wire_constructor_resolves_and_wires() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); @@ -1148,16 +1140,16 @@ mod graphene_test { #[test] fn generic_add_registers_one_entry_per_implementation() { - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); let entries = add_entries(); assert_eq!(entries.len(), 6); assert_eq!(entries[0].io.inputs, vec![core_types::concrete!(f64), core_types::concrete!(f64)]); - assert_eq!(entries[0].io.output, core_types::concrete!(f64)); + assert_eq!(entries[0].io.return_value, core_types::concrete!(f64)); assert_eq!(entries[3].io.inputs, vec![core_types::concrete!(DVec2), core_types::concrete!(DVec2)]); - assert_eq!(entries[3].io.output, core_types::concrete!(DVec2)); + assert_eq!(entries[3].io.return_value, core_types::concrete!(DVec2)); let augend = EdgeHandle::new(Arc::new(SourceNode(1.5f64)) as Arc>); let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc>); @@ -1182,7 +1174,7 @@ mod graphene_test { } } - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); @@ -1217,7 +1209,7 @@ mod graphene_test { } } - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); @@ -1240,7 +1232,7 @@ mod graphene_test { } } - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); @@ -1260,7 +1252,7 @@ mod graphene_test { } } - let arena = Arena::new(64); + let arena = Arena::new(64).unwrap(); let scope = scope_fixture(&arena); let ctx = ContextImpl::root(&scope); diff --git a/node-graph/nodes/raster/src/blending_nodes.rs b/node-graph/nodes/raster/src/blending_nodes.rs index 1b6c4f6977..ab7378b636 100644 --- a/node-graph/nodes/raster/src/blending_nodes.rs +++ b/node-graph/nodes/raster/src/blending_nodes.rs @@ -201,8 +201,8 @@ mod test { use raster_types::Image; use raster_types::Raster; - #[tokio::test] - async fn color_overlay_multiply() { + #[test] + fn color_overlay_multiply() { let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4); let image = Image::new(1, 1, image_color); @@ -212,7 +212,7 @@ mod test { // 100% of the output should come from the multiplied value let opacity = 100.; - let result = super::color_overlay((), List::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity); + let result = super::color_overlay(&(), List::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity); let result = result.element(0).unwrap().clone(); // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) diff --git a/node-graph/nodes/raster/src/image_color_palette.rs b/node-graph/nodes/raster/src/image_color_palette.rs index 9bcd4b7e86..b91e487f18 100644 --- a/node-graph/nodes/raster/src/image_color_palette.rs +++ b/node-graph/nodes/raster/src/image_color_palette.rs @@ -68,7 +68,7 @@ mod test { #[test] fn test_image_color_palette() { let result = image_color_palette( - (), + &(), List::new_from_element(Raster::new_cpu(Image { width: 100, height: 100, @@ -77,6 +77,6 @@ mod test { })), 1, ); - assert_eq!(futures::executor::block_on(result), List::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap())); + assert_eq!(result, List::new_from_element(Color::from_rgbaf32(0., 0., 0., 1.).unwrap())); } } diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 30c4e8c3d7..8f5a67044c 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -411,11 +411,11 @@ mod tests { #[test] fn isometric_grid_test() { // Doesn't crash with weird angles - grid((), (), GridType::Isometric, 0., 5, 5, (0., 0.).into()); - grid((), (), GridType::Isometric, 90., 5, 5, (90., 90.).into()); + grid(&(), (), GridType::Isometric, 0., 5, 5, (0., 0.).into()); + grid(&(), (), GridType::Isometric, 90., 5, 5, (90., 90.).into()); // Works properly - let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into()); + let grid = grid(&(), (), GridType::Isometric, 10., 5, 5, (30., 30.).into()); assert_eq!(grid.element(0).unwrap().point_domain.ids().len(), 5 * 5); assert_eq!(grid.element(0).unwrap().segment_bezier_iter().count(), 4 * 5 + 4 * 9); for (_, bezier, _, _) in grid.element(0).unwrap().segment_bezier_iter() { @@ -430,7 +430,7 @@ mod tests { #[test] fn skew_isometric_grid_test() { - let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into()); + let grid = grid(&(), (), GridType::Isometric, 10., 5, 5, (40., 30.).into()); assert_eq!(grid.element(0).unwrap().point_domain.ids().len(), 5 * 5); assert_eq!(grid.element(0).unwrap().segment_bezier_iter().count(), 4 * 5 + 4 * 9); for (_, bezier, _, _) in grid.element(0).unwrap().segment_bezier_iter() { @@ -443,7 +443,7 @@ mod tests { #[test] fn qr_code_test() { - let qr = qr_code((), (), "https://graphite.art".to_string(), false, 1., QRCodeErrorCorrectionLevel::Low, true); + let qr = qr_code(&(), (), "https://graphite.art".to_string(), false, 1., QRCodeErrorCorrectionLevel::Low, true); assert!(qr.element(0).unwrap().point_domain.ids().len() > 0); assert!(qr.element(0).unwrap().segment_domain.ids().len() > 0); } diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 98e2b1c537..bb2202505e 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -3261,24 +3261,10 @@ fn centroid(ctx: impl Ctx + DeriveCtx, content: impl Node, Output = #[cfg(test)] mod test { use super::*; - use core_types::Node; use kurbo::{CubicBez, Ellipse, Point, Rect}; - use std::future::Future; - use std::pin::Pin; use vector_types::vector::algorithms::bezpath_algorithms::{TValue, trim_pathseg}; use vector_types::vector::misc::pathseg_abs_diff_eq; - #[derive(Clone)] - pub struct FutureWrapperNode(T); - - impl<'i, T: 'i + Clone + Send> Node<'i, Footprint> for FutureWrapperNode { - type Output = Pin + 'i + Send>>; - fn eval(&'i self, _input: Footprint) -> Self::Output { - let value = self.0.clone(); - Box::pin(async move { value }) - } - } - fn vector_node_from_bezpath(bezpath: BezPath) -> List { List::new_from_element(Vector::from_bezpath(bezpath)) } @@ -3289,9 +3275,9 @@ mod test { Item::new_from_element(row).with_attribute(ATTR_TRANSFORM, transform) } - #[tokio::test] - async fn bounding_box() { - let bounding_box = super::bounding_box((), vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))).await; + #[test] + fn bounding_box() { + let bounding_box = super::bounding_box(&(), vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))); let bounding_box = bounding_box.element(0).unwrap(); assert_eq!(bounding_box.region_manipulator_groups().count(), 1); let manipulator_groups_anchors = bounding_box @@ -3309,7 +3295,7 @@ mod test { let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)); let mut square = List::new_from_element(square); square.with_attribute_mut_or_default(ATTR_TRANSFORM, 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4)); - let bounding_box = BoundingBoxNode { content: FutureWrapperNode(square) }.eval(Footprint::default()).await; + let bounding_box = super::bounding_box(&(), square); let bounding_box = bounding_box.element(0).unwrap(); assert_eq!(bounding_box.region_manipulator_groups().count(), 1); let manipulator_groups_anchors = bounding_box @@ -3326,15 +3312,15 @@ mod test { assert_eq!(manipulator_groups_anchors[i], expected_bounding_box[i]); } } - #[tokio::test] - async fn copy_to_points() { + #[test] + fn copy_to_points() { let points = Rect::new(-10., -10., 10., 10.).to_path(DEFAULT_ACCURACY); let element = Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY); let expected_points = Vector::from_bezpath(points.clone()).point_domain.positions().to_vec(); - let copy_to_points = super::copy_to_points(Footprint::default(), vector_node_from_bezpath(points), vector_node_from_bezpath(element), 1., 1., 0., 0, 0., 0).await; - let flatten_path = super::flatten_path(Footprint::default(), copy_to_points).await; + let copy_to_points = super::copy_to_points(&Footprint::default(), vector_node_from_bezpath(points), vector_node_from_bezpath(element), 1., 1., 0., 0, 0., 0); + let flatten_path = super::flatten_path(&Footprint::default(), copy_to_points); let flattened_copy_to_points = flatten_path.element(0).unwrap(); assert_eq!(flattened_copy_to_points.region_manipulator_groups().count(), expected_points.len()); @@ -3349,35 +3335,34 @@ mod test { } } - #[tokio::test] - async fn sample_polyline() { + #[test] + fn sample_polyline() { let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]); - let sample_polyline = super::sample_polyline(Footprint::default(), vector_node_from_bezpath(path), PointSpacingType::Separation, 30., 0, 0., 0., false).await; + let sample_polyline = super::sample_polyline(&Footprint::default(), vector_node_from_bezpath(path), PointSpacingType::Separation, 30., 0, 0., 0., false); let sample_polyline = sample_polyline.element(0).unwrap(); assert_eq!(sample_polyline.point_domain.positions().len(), 4); for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) { assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}"); } } - #[tokio::test] - async fn sample_polyline_adaptive_spacing() { + #[test] + fn sample_polyline_adaptive_spacing() { let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]); - let sample_polyline = super::sample_polyline(Footprint::default(), vector_node_from_bezpath(path), PointSpacingType::Separation, 18., 0, 45., 10., true).await; + let sample_polyline = super::sample_polyline(&Footprint::default(), vector_node_from_bezpath(path), PointSpacingType::Separation, 18., 0, 45., 10., true); let sample_polyline = sample_polyline.element(0).unwrap(); assert_eq!(sample_polyline.point_domain.positions().len(), 4); for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) { assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}"); } } - #[tokio::test] - async fn poisson() { + #[test] + fn poisson() { let poisson_points = super::scatter_points( - Footprint::default(), + &Footprint::default(), vector_node_from_bezpath(Ellipse::from_rect(Rect::new(-50., -50., 50., 50.)).to_path(DEFAULT_ACCURACY)), 10. * std::f64::consts::SQRT_2, 0, - ) - .await; + ); let poisson_points = poisson_points.element(0).unwrap(); assert!( (20..=40).contains(&poisson_points.point_domain.positions().len()), @@ -3388,33 +3373,33 @@ mod test { assert!(point.length() < 50. + 1., "Expected point in circle {point}") } } - #[tokio::test] - async fn path_length() { + #[test] + fn path_length() { let bezpath = Rect::new(100., 100., 201., 201.).to_path(DEFAULT_ACCURACY); let transform = DAffine2::from_scale(DVec2::new(2., 2.)); let row = create_vector_item(bezpath, transform); let list = (0..5).map(|_| row.clone()).collect::>(); - let length = super::path_length(Footprint::default(), list).await; + let length = super::path_length(&Footprint::default(), list); // 101 (each rectangle edge length) * 4 (rectangle perimeter) * 2 (scale) * 5 (number of rows) assert_eq!(length, 101. * 4. * 2. * 5.); } - #[tokio::test] - async fn spline() { - let spline = super::spline(Footprint::default(), vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY))).await; + #[test] + fn spline() { + let spline = super::spline(&Footprint::default(), vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY))); let spline = spline.element(0).unwrap(); assert_eq!(spline.stroke_bezpath_iter().count(), 1); assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]); } - #[tokio::test] - async fn morph() { + #[test] + fn morph() { let mut rectangles = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY)); let mut second_rectangle = rectangles.clone_item(0).unwrap(); *second_rectangle.attribute_mut_or_insert_default::(ATTR_TRANSFORM) *= DAffine2::from_translation((-100., -100.).into()); rectangles.push(second_rectangle); - let morphed = super::morph(Footprint::default(), rectangles, 0.5, false, InterpolationDistribution::default(), List::default()).await; + let morphed = super::morph(&Footprint::default(), rectangles, 0.5, false, InterpolationDistribution::default(), List::default()); let morphed_element = morphed.element(0).unwrap(); // Geometry stays in local space (original rectangle coordinates) assert_eq!( @@ -3425,8 +3410,8 @@ mod test { assert!((morphed.attribute_cloned_or_default::(ATTR_TRANSFORM, 0).translation - DVec2::new(-50., -50.)).length() < 1e-3); } - #[tokio::test] - async fn morph_interpolates_fill() { + #[test] + fn morph_interpolates_fill() { let rect = || { let mut v = Vector::default(); v.append_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY)); @@ -3443,7 +3428,7 @@ mod test { let mut content = List::new_from_item(item_a); content.push(item_b); - let morphed = super::morph(Footprint::default(), content, 0.5, false, InterpolationDistribution::default(), List::default()).await; + let morphed = super::morph(&Footprint::default(), content, 0.5, false, InterpolationDistribution::default(), List::default()); let fill = graphic_list_at(&morphed, 0, ATTR_FILL).expect("Morph should keep the fill paint at the midpoint"); @@ -3470,10 +3455,10 @@ mod test { ); } - #[tokio::test] - async fn bevel_rect() { + #[test] + fn bevel_rect() { let source = Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY); - let beveled = super::bevel(Footprint::default(), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.); + let beveled = super::bevel(&Footprint::default(), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.); let beveled = beveled.element(0).unwrap(); assert_eq!(beveled.point_domain.positions().len(), 8); @@ -3492,8 +3477,8 @@ mod test { contains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(10., 100.), Point::new(0., 90.)))); } - #[tokio::test] - async fn bevel_open_curve() { + #[test] + fn bevel_open_curve() { let curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(10., 0.), Point::new(10., 100.), Point::new(100., 0.))); let mut source = BezPath::new(); @@ -3501,7 +3486,7 @@ mod test { source.line_to(Point::ZERO); source.push(curve.as_path_el()); - let beveled = super::bevel((), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.); + let beveled = super::bevel(&(), vector_node_from_bezpath(source), 2_f64.sqrt() * 10.); let beveled = beveled.element(0).unwrap(); assert_eq!(beveled.point_domain.positions().len(), 4); @@ -3516,8 +3501,8 @@ mod test { contains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(-8.2, 0.), trimmed.start()))); } - #[tokio::test] - async fn bevel_with_transform() { + #[test] + fn bevel_with_transform() { let curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(10., 0.), Point::new(10., 100.), Point::new(100., 0.))); let mut source = BezPath::new(); @@ -3530,7 +3515,7 @@ mod test { vector_list.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.))); - let beveled = super::bevel((), List::new_from_element(vector), 2_f64.sqrt() * 10.); + let beveled = super::bevel(&(), List::new_from_element(vector), 2_f64.sqrt() * 10.); let beveled = beveled.element(0).unwrap(); assert_eq!(beveled.point_domain.positions().len(), 4); @@ -3545,15 +3530,15 @@ mod test { contains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(-8.2, 0.), trimmed.start()))); } - #[tokio::test] - async fn bevel_too_high() { + #[test] + fn bevel_too_high() { let mut source = BezPath::new(); source.move_to(Point::ZERO); source.line_to(Point::new(100., 0.)); source.line_to(Point::new(100., 100.)); source.line_to(Point::new(0., 100.)); - let beveled = super::bevel(Footprint::default(), vector_node_from_bezpath(source), 999.); + let beveled = super::bevel(&Footprint::default(), vector_node_from_bezpath(source), 999.); let beveled = beveled.element(0).unwrap(); assert_eq!(beveled.point_domain.positions().len(), 6); @@ -3569,15 +3554,15 @@ mod test { contains_segment(beveled.clone(), PathSeg::Line(Line::new(Point::new(100., 50.), Point::new(50., 100.)))); } - #[tokio::test] - async fn bevel_repeated_point() { + #[test] + fn bevel_repeated_point() { let line = PathSeg::Line(Line::new(Point::ZERO, Point::new(100., 0.))); let point = PathSeg::Cubic(CubicBez::new(Point::new(100., 0.), Point::ZERO, Point::ZERO, Point::new(100., 0.))); let curve = PathSeg::Cubic(CubicBez::new(Point::new(100., 0.), Point::new(110., 0.), Point::new(110., 200.), Point::new(200., 0.))); let subpath = BezPath::from_path_segments([line, point, curve].into_iter()); - let beveled_list = super::bevel(Footprint::default(), vector_node_from_bezpath(subpath), 5.); + let beveled_list = super::bevel(&Footprint::default(), vector_node_from_bezpath(subpath), 5.); let beveled = beveled_list.element(0).unwrap(); assert_eq!(beveled.point_domain.positions().len(), 6); diff --git a/test.rs b/test.rs new file mode 100644 index 0000000000..202ae5607d --- /dev/null +++ b/test.rs @@ -0,0 +1,22 @@ +trait Attr { + fn name() -> &'static str +} + +impl Attr for bool { + fn name() {"condition"} +} + +struct Opacity(f64); + +impl Attr for Opacity { + fn name() {"opacity"} +} + + +#[node] +fn opacity(_: impl Ctx, input: T, x: ReadAttr, opacity: WriteAttr) -> T { + if x { + *opacity = 1.; + } + input +}