mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Convert create_context to a sync derivation kernel over the bare-root contract
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2038,6 +2038,7 @@ dependencies = [
|
||||
"core-types",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"graphene-hash",
|
||||
"graphene-resource",
|
||||
"log",
|
||||
"raster-types",
|
||||
|
||||
@@ -84,12 +84,12 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
call_argument: concrete!(graphene_std::application_io::RenderConfig),
|
||||
call_argument: concrete!(Context),
|
||||
inputs: vec![NodeInput::node(NodeId(4), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::create_context::IDENTIFIER),
|
||||
context_features: graphene_std::ContextDependencies {
|
||||
// We add the extract index annotation here to force the compiler to add a context nullification node before this node so the render context is properly nullified so the render cache node can do its's work
|
||||
extract: ContextFeatures::INDEX,
|
||||
extract: ContextFeatures::INDEX | ContextFeatures::VARARGS,
|
||||
inject: ContextFeatures::REAL_TIME | ContextFeatures::ANIMATION_TIME | ContextFeatures::POINTER_POSITION | ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||
},
|
||||
..Default::default()
|
||||
|
||||
@@ -16,6 +16,7 @@ wgpu = ["dep:raster-types", "raster-types/wgpu"]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
core-types = { workspace = true }
|
||||
graphene-hash = { workspace = true }
|
||||
vector-types = { workspace = true }
|
||||
text-nodes = { workspace = true }
|
||||
graphene-resource = { workspace = true }
|
||||
|
||||
@@ -61,7 +61,7 @@ pub trait GetEditorPreferences {
|
||||
fn max_render_region_area(&self) -> u32;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ExportFormat {
|
||||
#[default]
|
||||
@@ -69,14 +69,14 @@ pub enum ExportFormat {
|
||||
Raster,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TimingInformation {
|
||||
pub time: f64,
|
||||
pub animation_time: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RenderConfig {
|
||||
pub viewport: Footprint,
|
||||
|
||||
@@ -770,6 +770,12 @@ impl<'a> EvalScope<'a> {
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn with_pointer_position(&self, pointer_position: Option<DVec2>) -> EvalScope<'a> {
|
||||
let mut scope = EvalScope { pointer_position, ..*self };
|
||||
scope.hash = scope.compute_hash(None);
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn nullified(&self, keep: ContextFeatures) -> EvalScope<'a> {
|
||||
let mut scope = EvalScope {
|
||||
real_time: self.real_time.filter(|_| keep.contains(ContextFeatures::REAL_TIME)),
|
||||
|
||||
@@ -225,7 +225,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending);
|
||||
assert_eq!(runtime.drain(), vec![], "an interrupted prologue must not spawn or claim the slot");
|
||||
assert_eq!(runtime.drain(), Vec::<SourceId>::new(), "an interrupted prologue must not spawn or claim the slot");
|
||||
gate.store(true, Ordering::Relaxed);
|
||||
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending);
|
||||
assert_eq!(runtime.drain(), vec![9]);
|
||||
|
||||
@@ -68,6 +68,7 @@ impl_via_hash! {
|
||||
#[cfg(feature = "std")]
|
||||
impl_via_hash! {
|
||||
String,
|
||||
core::time::Duration,
|
||||
}
|
||||
|
||||
impl<'a> CacheHash for std::borrow::Cow<'a, str> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use core_types::gpoll::Interrupt;
|
||||
use core_types::list::List;
|
||||
use core_types::transform::{Footprint, Transform};
|
||||
use core_types::{Color, Context, Ctx, DeriveCtx, ExtractFootprint, ExtractVarArgs, OwnedContextImpl, WasmNotSend};
|
||||
use core_types::{Color, Context, Ctx, DeriveCtx, ExtractFootprint, ExtractVarArgs, VarArgLink, VarArgSlots, WasmNotSend};
|
||||
use graph_craft::document::value::{RenderOutput, RenderOutputType};
|
||||
use graphene_application_io::{ExportFormat, RenderConfig};
|
||||
use graphic_types::raster_types::{CPU, Raster};
|
||||
@@ -141,11 +141,13 @@ fn render<'a>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn create_context<'a>(
|
||||
// Context injections are defined in the wrap_network_in_scope function
|
||||
render_config: RenderConfig,
|
||||
data: impl Node<Context<'_>, Output = RenderOutput>,
|
||||
) -> RenderOutput {
|
||||
fn create_context(ctx: impl Ctx + ExtractVarArgs + DeriveCtx, data: impl Node<Context<'_>, Output = RenderOutput>) -> Result<RenderOutput, Interrupt> {
|
||||
let render_config = *ctx
|
||||
.vararg(0)
|
||||
.expect("Did not find var args")
|
||||
.downcast_ref::<RenderConfig>()
|
||||
.expect("Downcasting render config yielded invalid type");
|
||||
|
||||
let render_output_type = match render_config.export_format {
|
||||
ExportFormat::Svg => RenderOutputTypeRequest::Svg,
|
||||
ExportFormat::Raster => RenderOutputTypeRequest::Vello,
|
||||
@@ -166,16 +168,78 @@ fn create_context<'a>(
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ctx = OwnedContextImpl::default()
|
||||
.with_footprint(footprint)
|
||||
.with_real_time(render_config.time.time)
|
||||
.with_animation_time(render_config.time.animation_time.as_secs_f64())
|
||||
.with_pointer_position(render_config.pointer)
|
||||
.with_vararg(Box::new(render_params))
|
||||
.into_context();
|
||||
|
||||
let mut result = data.eval(ctx).await;
|
||||
let scope = ctx
|
||||
.scope()
|
||||
.with_real_time(Some(render_config.time.time))
|
||||
.with_animation_time(Some(render_config.time.animation_time.as_secs_f64()))
|
||||
.with_pointer_position(Some(render_config.pointer));
|
||||
let varargs = VarArgLink {
|
||||
args: VarArgSlots::Single(&render_params),
|
||||
outer: None,
|
||||
};
|
||||
let scoped = ctx.with_scope(&scope);
|
||||
let with_params = scoped.with_varargs(&varargs);
|
||||
let mut result = data.eval(&with_params.with_footprint(&footprint))?;
|
||||
|
||||
result.metadata.apply_transform(glam::DAffine2::from_scale(glam::DVec2::splat(1. / render_config.scale)));
|
||||
result
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope, VarArgsResult};
|
||||
use core_types::gnode::GNode;
|
||||
use core_types::gpoll::GPoll;
|
||||
use core_types::{ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime};
|
||||
use graphene_application_io::TimingInformation;
|
||||
|
||||
struct ProbeNode;
|
||||
|
||||
impl<'a> GNode<ContextImpl<'a>> for ProbeNode {
|
||||
type Output = RenderOutput;
|
||||
|
||||
fn eval(&self, ctx: &ContextImpl<'a>) -> GPoll<RenderOutput> {
|
||||
let render_params = ctx.vararg(0).unwrap().downcast_ref::<RenderParams>().expect("the vararg chain must start with RenderParams");
|
||||
assert_eq!(render_params.scale, 2.0);
|
||||
assert!(matches!(ctx.vararg(1), Err(VarArgsResult::IndexOutOfBounds)), "the RenderConfig must not leak downstream");
|
||||
assert_eq!(ctx.footprint().transform, glam::DAffine2::from_scale(glam::DVec2::splat(2.0)) * Footprint::DEFAULT.transform);
|
||||
assert_eq!(ctx.try_real_time(), Some(1.5));
|
||||
assert_eq!(ctx.try_animation_time(), Some(2.0));
|
||||
assert_eq!(ctx.try_pointer_position(), Some(glam::DVec2::new(3.0, 4.0)));
|
||||
GPoll::Final(RenderOutput {
|
||||
data: RenderOutputType::Buffer { data: Vec::new(), width: 0, height: 0 },
|
||||
metadata: RenderMetadata::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_context_builds_the_render_context_from_the_root_vararg() {
|
||||
let arena = Arena::new(256);
|
||||
let generations = [];
|
||||
let scope = EvalScope::new(None, None, None, &generations, &arena);
|
||||
let root = ContextImpl::root(&scope);
|
||||
let render_config = RenderConfig {
|
||||
scale: 2.0,
|
||||
time: TimingInformation {
|
||||
time: 1.5,
|
||||
animation_time: std::time::Duration::from_secs(2),
|
||||
},
|
||||
pointer: glam::DVec2::new(3.0, 4.0),
|
||||
..Default::default()
|
||||
};
|
||||
let varargs = VarArgLink {
|
||||
args: VarArgSlots::Single(&render_config),
|
||||
outer: None,
|
||||
};
|
||||
let ctx = root.with_varargs(&varargs);
|
||||
|
||||
let graph = CreateContextNode::new(ProbeNode);
|
||||
let GPoll::Final(result) = <CreateContextNode<ProbeNode> as GNode<ContextImpl>>::eval(&graph, &ctx) else {
|
||||
panic!("create_context must complete synchronously");
|
||||
};
|
||||
assert_eq!(result.data, RenderOutputType::Buffer { data: Vec::new(), width: 0, height: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user