mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 07:28:11 +08:00
Add the record-opaque kernel class to the node macro and define memoize through it
This commit is contained in:
@@ -189,30 +189,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
|||||||
// ==========
|
// ==========
|
||||||
// MEMO NODES
|
// MEMO NODES
|
||||||
// ==========
|
// ==========
|
||||||
(
|
|
||||||
ProtoNodeIdentifier::new("graphene_core::memo::MemoizeNode"),
|
|
||||||
RegistryEntry {
|
|
||||||
io: NodeIOTypes::new(
|
|
||||||
concrete!(Context),
|
|
||||||
core_types::Type::Record(Box::new(core_types::Type::Generic(std::borrow::Cow::Borrowed("T")))),
|
|
||||||
vec![core_types::registry::generic_record_edge_type("T")],
|
|
||||||
),
|
|
||||||
constructor: |inputs| {
|
|
||||||
if inputs.len() != 1 {
|
|
||||||
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
|
||||||
}
|
|
||||||
let mut inputs = inputs.into_iter();
|
|
||||||
let handle = inputs.next().unwrap();
|
|
||||||
let ty = handle.ty().clone();
|
|
||||||
let Some(layout) = handle.layout().cloned() else {
|
|
||||||
return Err(ConstructionError::MissingLayout);
|
|
||||||
};
|
|
||||||
let edge = handle.downcast_erased::<core_types::registry::ErasedRecordNode>(ty.clone())?;
|
|
||||||
let node = core_types::record::RecordMemo::new(edge, &layout);
|
|
||||||
Ok(EdgeHandle::new_erased(std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>, ty))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// ============
|
// ============
|
||||||
// REF ADAPTERS
|
// REF ADAPTERS
|
||||||
// ============
|
// ============
|
||||||
|
|||||||
@@ -364,6 +364,32 @@ pub fn lift_poll<'e, T: Send + Sync + 'static>(poll: GPoll<T>, layout: &Layout,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The raw lazy record edge handed to a record-opaque kernel: the wire plus
|
||||||
|
/// its wiring-proven layout, the pairing the kernel's unsafe record
|
||||||
|
/// operations rely on. The kernel must only pair the layout with values this
|
||||||
|
/// edge produced.
|
||||||
|
pub struct RecordEdgeInput<'a, N> {
|
||||||
|
node: &'a N,
|
||||||
|
layout: &'a Layout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, N> RecordEdgeInput<'a, N> {
|
||||||
|
pub fn new(node: &'a N, layout: &'a Layout) -> Self {
|
||||||
|
Self { node, layout }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layout(&self) -> &Layout {
|
||||||
|
self.layout
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval<'e, C>(&self, ctx: &C) -> GPoll<RecordValue<'e>>
|
||||||
|
where
|
||||||
|
N: Node<C, Output = RecordValue<'e>>,
|
||||||
|
{
|
||||||
|
self.node.eval(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The raw lazy edge handed to a poll kernel whose wire rides records while
|
/// The raw lazy edge handed to a poll kernel whose wire rides records while
|
||||||
/// the kernel consumes the plain element.
|
/// the kernel consumes the plain element.
|
||||||
pub struct ElementEdge<'a, El, N> {
|
pub struct ElementEdge<'a, El, N> {
|
||||||
@@ -799,6 +825,12 @@ pub struct OwnedRecord {
|
|||||||
fields: Vec<(usize, Box<dyn crate::list::AnyAttributeValue>)>,
|
fields: Vec<(usize, Box<dyn crate::list::AnyAttributeValue>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for OwnedRecord {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("OwnedRecord(..)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl OwnedRecord {
|
impl OwnedRecord {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `rec` must be a live record of `layout`.
|
/// `rec` must be a live record of `layout`.
|
||||||
@@ -846,71 +878,6 @@ impl OwnedRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Convert to a `#[node_macro::node]` node with the monitor, once the
|
|
||||||
// macro grows a capture capability.
|
|
||||||
/// Memoizes a record wire: a hit replays the deep copy into the current
|
|
||||||
/// evaluation, a miss evaluates the edge and copies the record out.
|
|
||||||
pub struct RecordMemo<N> {
|
|
||||||
edge: N,
|
|
||||||
layout: Layout,
|
|
||||||
cache: std::sync::Mutex<Option<(u64, OwnedRecord, crate::gpoll::Finality)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N> RecordMemo<N> {
|
|
||||||
pub fn new(edge: N, layout: &Layout) -> Self {
|
|
||||||
Self {
|
|
||||||
edge,
|
|
||||||
layout: layout.clone(),
|
|
||||||
cache: std::sync::Mutex::new(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'e, C, N> Node<C> for RecordMemo<N>
|
|
||||||
where
|
|
||||||
C: crate::graphene_hash::CacheHash + crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
|
||||||
N: Node<C, Output = RecordValue<'e>>,
|
|
||||||
{
|
|
||||||
type Output = RecordValue<'e>;
|
|
||||||
|
|
||||||
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
|
||||||
let key = crate::registry::cache_key(input);
|
|
||||||
{
|
|
||||||
let cache = self.cache.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
||||||
if let Some((hash, copy, finality)) = cache.as_ref()
|
|
||||||
&& *hash == key
|
|
||||||
{
|
|
||||||
return match copy.replay(&self.layout, input.arena()) {
|
|
||||||
Some(value) => match finality {
|
|
||||||
crate::gpoll::Finality::AllFinal => GPoll::Final(value),
|
|
||||||
crate::gpoll::Finality::Partial => GPoll::Partial(value),
|
|
||||||
},
|
|
||||||
None => GPoll::arena_exhausted(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let result = self.edge.eval(input);
|
|
||||||
let publishable = match &result {
|
|
||||||
GPoll::Final(record) => Some((record, crate::gpoll::Finality::AllFinal)),
|
|
||||||
GPoll::Partial(record) => Some((record, crate::gpoll::Finality::Partial)),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
if let Some((record, finality)) = publishable {
|
|
||||||
let copy = unsafe { OwnedRecord::copy_out(&self.layout, self.layout.rec(record)) };
|
|
||||||
*self.cache.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = Some((key, copy, finality));
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extent(&self, input: &C) -> GPoll<crate::gpoll::Extent> {
|
|
||||||
self.edge.extent(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(&self) -> Option<&Layout> {
|
|
||||||
Some(&self.layout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lifts a plain producer onto a record wire: the element lands at offset 0
|
/// Lifts a plain producer onto a record wire: the element lands at offset 0
|
||||||
/// of a fresh element-only record, parked when it carries drop glue.
|
/// of a fresh element-only record, parked when it carries drop glue.
|
||||||
pub struct RecordLift<El, N> {
|
pub struct RecordLift<El, N> {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let record = record_shape(parsed);
|
let record = record_shape(parsed);
|
||||||
let routing = routing_io(parsed);
|
let routing = routing_io(parsed);
|
||||||
let flip = record_flip(parsed);
|
let flip = record_flip(parsed);
|
||||||
|
let opaque = record_opaque(parsed);
|
||||||
let record_skips_carrier = record.as_ref().is_some_and(|shape| shape.skips_carrier());
|
let record_skips_carrier = record.as_ref().is_some_and(|shape| shape.skips_carrier());
|
||||||
// Record nodes with a `_: ()` primary input have no carrier edge; the unit
|
// Record nodes with a `_: ()` primary input have no carrier edge; the unit
|
||||||
// field stays visible in the metadata but claims no struct field.
|
// field stays visible in the metadata but claims no struct field.
|
||||||
@@ -171,7 +172,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}));
|
}));
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
None if routing.is_some() => vec![quote!(pub(super) __layout: gcore::record::Layout)],
|
None if routing.is_some() || opaque => vec![quote!(pub(super) __layout: gcore::record::Layout)],
|
||||||
None if flip => {
|
None if flip => {
|
||||||
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout), quote!(pub(super) __frame_bytes: usize)];
|
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout), quote!(pub(super) __frame_bytes: usize)];
|
||||||
state.extend((0..struct_regular_fields.len()).map(|index| {
|
state.extend((0..struct_regular_fields.len()).map(|index| {
|
||||||
@@ -331,8 +332,8 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
};
|
};
|
||||||
// Record nodes construct through the generated `wire` fn, which resolves
|
// Record nodes construct through the generated `wire` fn, which resolves
|
||||||
// offsets from the carrier layout; `new` cannot fill that state.
|
// offsets from the carrier layout; `new` cannot fill that state.
|
||||||
let routing_layout_param = routing.is_some().then(|| quote!(__layout: &gcore::record::Layout,)).into_iter();
|
let routing_layout_param = (routing.is_some() || opaque).then(|| quote!(__layout: &gcore::record::Layout,)).into_iter();
|
||||||
let routing_layout_init = routing.is_some().then(|| quote!(__layout: __layout.clone(),)).into_iter();
|
let routing_layout_init = (routing.is_some() || opaque).then(|| quote!(__layout: __layout.clone(),)).into_iter();
|
||||||
let flip_layout_params = flip
|
let flip_layout_params = flip
|
||||||
.then(|| {
|
.then(|| {
|
||||||
(0..struct_regular_fields.len()).map(|index| {
|
(0..struct_regular_fields.len()).map(|index| {
|
||||||
@@ -750,6 +751,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let skips_carrier = record.as_ref().is_some_and(|shape| shape.skips_carrier());
|
let skips_carrier = record.as_ref().is_some_and(|shape| shape.skips_carrier());
|
||||||
let routing = routing_io(parsed);
|
let routing = routing_io(parsed);
|
||||||
let flip = record_flip(parsed);
|
let flip = record_flip(parsed);
|
||||||
|
let opaque = record_opaque(parsed);
|
||||||
let snapshot_ctx = async_fn && matches!(&parsed.input.ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "CtxSnapshot"));
|
let snapshot_ctx = async_fn && matches!(&parsed.input.ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "CtxSnapshot"));
|
||||||
|
|
||||||
let mut ctx_bounds: Vec<TokenStream2> = match ctx_param {
|
let mut ctx_bounds: Vec<TokenStream2> = match ctx_param {
|
||||||
@@ -907,6 +909,14 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
generics.insert(0, quote!('__record));
|
generics.insert(0, quote!('__record));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if opaque {
|
||||||
|
for (index, field) in regular_fields.iter().enumerate() {
|
||||||
|
if let ParsedFieldType::Node(NodeParsedField { output_type, .. }) = &field.ty {
|
||||||
|
let source_generic = format_ident!("__Source{index}");
|
||||||
|
generics.push(quote!(#source_generic: #core_types::node::Node<#ctx_ident, Output = #output_type>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let data_field_generic_idents: Vec<Ident> = parsed
|
let data_field_generic_idents: Vec<Ident> = parsed
|
||||||
.fn_generics
|
.fn_generics
|
||||||
@@ -980,6 +990,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let source_generic = format_ident!("__Source{index}");
|
let source_generic = format_ident!("__Source{index}");
|
||||||
quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>)
|
quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>)
|
||||||
}
|
}
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if opaque && raw_lazy && is_record_value(output_type) => {
|
||||||
|
let source_generic = format_ident!("__Source{index}");
|
||||||
|
quote!(#pat: &#core_types::record::RecordEdgeInput<'_, #source_generic>)
|
||||||
|
}
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if flip && raw_lazy => {
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if flip && raw_lazy => {
|
||||||
let source_generic = format_ident!("__Source{index}");
|
let source_generic = format_ident!("__Source{index}");
|
||||||
quote!(#pat: &#core_types::record::ElementEdge<'_, #output_type, #source_generic>)
|
quote!(#pat: &#core_types::record::ElementEdge<'_, #output_type, #source_generic>)
|
||||||
@@ -1127,6 +1141,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let #name = #core_types::record::ElementLazyInput::<#output_type, _>::new(&self.#name, &__cell, #index, &self.#slot);
|
let #name = #core_types::record::ElementLazyInput::<#output_type, _>::new(&self.#name, &__cell, #index, &self.#slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if opaque && raw_lazy && is_record_value(output_type) => quote! {
|
||||||
|
let #name = #core_types::record::RecordEdgeInput::new(&self.#name, &self.__layout);
|
||||||
|
},
|
||||||
ParsedFieldType::Node(_) if raw_lazy => quote!(),
|
ParsedFieldType::Node(_) if raw_lazy => quote!(),
|
||||||
ParsedFieldType::Node(_) => quote! {
|
ParsedFieldType::Node(_) => quote! {
|
||||||
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
||||||
@@ -1153,6 +1170,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let name = &field.pat_ident.ident;
|
let name = &field.pat_ident.ident;
|
||||||
match &field.ty {
|
match &field.ty {
|
||||||
ParsedFieldType::Node(_) if flip && raw_lazy => quote!(&#name),
|
ParsedFieldType::Node(_) if flip && raw_lazy => quote!(&#name),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if opaque && raw_lazy && is_record_value(output_type) => quote!(&#name),
|
||||||
ParsedFieldType::Node(_) if raw_lazy => quote!(&self.#name),
|
ParsedFieldType::Node(_) if raw_lazy => quote!(&self.#name),
|
||||||
_ => quote!(#name),
|
_ => quote!(#name),
|
||||||
}
|
}
|
||||||
@@ -1534,7 +1552,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
false => Vec::new(),
|
false => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let record_layout_impl = match record.is_some() || routing.is_some() || flip {
|
let record_layout_impl = match record.is_some() || routing.is_some() || flip || opaque {
|
||||||
true => quote! {
|
true => quote! {
|
||||||
fn layout(&self) -> Option<&#core_types::record::Layout> {
|
fn layout(&self) -> Option<&#core_types::record::Layout> {
|
||||||
Some(&self.__layout)
|
Some(&self.__layout)
|
||||||
@@ -1927,6 +1945,17 @@ pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_record_value(ty: &Type) -> bool {
|
||||||
|
matches!(ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "RecordValue"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a kernel operates on whole records: it names `RecordValue` in its
|
||||||
|
/// output, receives raw record edges paired with the node's layout, and
|
||||||
|
/// takes on the record APIs' unsafe contracts itself.
|
||||||
|
pub(crate) fn record_opaque(parsed: &ParsedNodeFn) -> bool {
|
||||||
|
is_record_value(&slot_value_type(&parsed.output_type))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn routing_io(parsed: &ParsedNodeFn) -> Option<RoutingIo> {
|
pub(crate) fn routing_io(parsed: &ParsedNodeFn) -> Option<RoutingIo> {
|
||||||
if has_record_io(parsed) || parsed.is_async {
|
if has_record_io(parsed) || parsed.is_async {
|
||||||
return None;
|
return None;
|
||||||
@@ -2136,6 +2165,9 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
|||||||
if record_flip(parsed) {
|
if record_flip(parsed) {
|
||||||
return flip_entries_tokens(parsed, struct_name, regular_fields);
|
return flip_entries_tokens(parsed, struct_name, regular_fields);
|
||||||
}
|
}
|
||||||
|
if record_opaque(parsed) {
|
||||||
|
return record_opaque_entries_tokens(parsed, struct_name, regular_fields);
|
||||||
|
}
|
||||||
let Some(rows) = implementation_rows(parsed, regular_fields) else {
|
let Some(rows) = implementation_rows(parsed, regular_fields) else {
|
||||||
return quote!();
|
return quote!();
|
||||||
};
|
};
|
||||||
@@ -2483,6 +2515,84 @@ fn routing_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_opaque_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
||||||
|
let is_record = |field: &ParsedField| matches!(&field.ty, ParsedFieldType::Node(NodeParsedField { output_type, .. }) if is_record_value(output_type));
|
||||||
|
let values_concrete = regular_fields.iter().filter(|field| !is_record(field)).all(|field| {
|
||||||
|
let (ty, lend) = match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => (ty, lend.is_some()),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => (output_type, false),
|
||||||
|
};
|
||||||
|
!contains_open_generic(parsed, ty) && (lend || !type_disqualifies(ty))
|
||||||
|
});
|
||||||
|
if !values_concrete {
|
||||||
|
return quote!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let fn_name = &parsed.fn_name;
|
||||||
|
let entries_name = format_ident!("{}_entries", fn_name);
|
||||||
|
let arity = regular_fields.len();
|
||||||
|
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
||||||
|
|
||||||
|
let input_types = regular_fields.iter().map(|field| {
|
||||||
|
if is_record(field) {
|
||||||
|
return quote!(gcore::registry::generic_record_edge_type("T"));
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(gcore::registry::lend_edge_type::<#ty>()),
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(gcore::registry::edge_type::<#ty>()),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(gcore::registry::edge_type::<#output_type>()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let downcasts = regular_fields.iter().enumerate().map(|(index, field)| {
|
||||||
|
let name = &field.pat_ident.ident;
|
||||||
|
if is_record(field) {
|
||||||
|
let layout = format_ident!("__layout_{index}");
|
||||||
|
let handle = format_ident!("__handle_{index}");
|
||||||
|
let ty = format_ident!("__ty_{index}");
|
||||||
|
return quote! {
|
||||||
|
let #handle = inputs.next().unwrap();
|
||||||
|
let #ty = #handle.ty().clone();
|
||||||
|
let Some(#layout) = #handle.layout().cloned() else {
|
||||||
|
return Err(gcore::registry::ConstructionError::MissingLayout);
|
||||||
|
};
|
||||||
|
let #name = #handle.downcast_erased::<gcore::registry::ErasedRecordNode>(#ty.clone())?;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(let #name = inputs.next().unwrap().downcast_lend::<#ty>()?;),
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#output_type>()?;),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let first_record = regular_fields.iter().position(|field| is_record(field)).expect("record-opaque nodes have a record input");
|
||||||
|
let record_layout = format_ident!("__layout_{first_record}");
|
||||||
|
let record_ty = format_ident!("__ty_{first_record}");
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
|
||||||
|
vec![gcore::registry::RegistryEntry {
|
||||||
|
io: gcore::registry::NodeIOTypes::new(
|
||||||
|
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
||||||
|
gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed("T")))),
|
||||||
|
vec![#(#input_types),*],
|
||||||
|
),
|
||||||
|
constructor: |inputs| {
|
||||||
|
if inputs.len() != #arity {
|
||||||
|
return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() });
|
||||||
|
}
|
||||||
|
let mut inputs = inputs.into_iter();
|
||||||
|
#(#downcasts)*
|
||||||
|
let __node = #struct_name::new(#(#names,)* &#record_layout);
|
||||||
|
Ok(gcore::registry::EdgeHandle::new_erased(
|
||||||
|
::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>,
|
||||||
|
#record_ty,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
||||||
let Some(shape) = record_shape(parsed) else {
|
let Some(shape) = record_shape(parsed) else {
|
||||||
return quote!();
|
return quote!();
|
||||||
|
|||||||
@@ -5,37 +5,49 @@ use core_types::gpoll::{Extent, Finality, GPoll, Interrupt};
|
|||||||
use core_types::graphene_hash::CacheHash;
|
use core_types::graphene_hash::CacheHash;
|
||||||
use core_types::memo::*;
|
use core_types::memo::*;
|
||||||
use core_types::node::Node;
|
use core_types::node::Node;
|
||||||
|
use core_types::record::{OwnedRecord, RecordValue};
|
||||||
use core_types::registry::cache_key;
|
use core_types::registry::cache_key;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
/// Helps speed up repeated renders in a computationally-heavy part of the node graph.
|
/// Helps speed up repeated renders in a computationally-heavy part of the node graph.
|
||||||
///
|
///
|
||||||
/// Stores the last evaluated data that flowed through this node and immediately returns that data on subsequent renders if the context has not changed.
|
/// Stores a deep copy of the last record that flowed through this node and replays it on subsequent renders if the context has not changed.
|
||||||
#[node_macro::node(category("General"), path(graphene_core::memo), skip_impl, plain, extent(memoize_extent))]
|
#[node_macro::node(category("General"), path(graphene_core::memo), extent(memoize_extent))]
|
||||||
fn memoize<I: CacheHash, T: Clone>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T, Finality)>>>, content: impl Node<I, Output = T>) -> GPoll<T> {
|
fn memoize<'e>(
|
||||||
let key = cache_key(&input);
|
ctx: impl Ctx + CacheHash + ExtractArena<'e>,
|
||||||
if let Some((hash, value, finality)) = cache.lock().unwrap().as_ref()
|
#[data] cache: Arc<Mutex<Option<(u64, OwnedRecord, Finality)>>>,
|
||||||
|
content: impl Node<Context<'_>, Output = RecordValue<'e>>,
|
||||||
|
) -> GPoll<RecordValue<'e>> {
|
||||||
|
let key = cache_key(&ctx);
|
||||||
|
if let Some((hash, copy, finality)) = cache.lock().unwrap().as_ref()
|
||||||
&& *hash == key
|
&& *hash == key
|
||||||
{
|
{
|
||||||
return match finality {
|
return match copy.replay(content.layout(), ctx.arena()) {
|
||||||
Finality::AllFinal => GPoll::Final(value.clone()),
|
Some(value) => match finality {
|
||||||
Finality::Partial => GPoll::Partial(value.clone()),
|
Finality::AllFinal => GPoll::Final(value),
|
||||||
|
Finality::Partial => GPoll::Partial(value),
|
||||||
|
},
|
||||||
|
None => GPoll::arena_exhausted(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let result = content.eval(input);
|
let result = content.eval(&ctx);
|
||||||
match &result {
|
let publishable = match &result {
|
||||||
GPoll::Final(value) => *cache.lock().unwrap() = Some((key, value.clone(), Finality::AllFinal)),
|
GPoll::Final(value) => Some((value, Finality::AllFinal)),
|
||||||
GPoll::Partial(value) => *cache.lock().unwrap() = Some((key, value.clone(), Finality::Partial)),
|
GPoll::Partial(value) => Some((value, Finality::Partial)),
|
||||||
GPoll::Pending | GPoll::Fallback(_) | GPoll::Error(_) => {}
|
GPoll::Pending | GPoll::Fallback(_) | GPoll::Error(_) => None,
|
||||||
|
};
|
||||||
|
if let Some((value, finality)) = publishable {
|
||||||
|
// SAFETY: the value came from this edge, so it carries the edge's layout.
|
||||||
|
let copy = unsafe { OwnedRecord::copy_out(content.layout(), content.layout().rec(value)) };
|
||||||
|
*cache.lock().unwrap() = Some((key, copy, finality));
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memoize_extent<C, T, NodeContent>(node: &MemoizeNode<T, NodeContent>, ctx: &C) -> GPoll<Extent>
|
fn memoize_extent<C, NodeContent>(node: &MemoizeNode<NodeContent>, ctx: &C) -> GPoll<Extent>
|
||||||
where
|
where
|
||||||
T: Clone,
|
NodeContent: Node<C>,
|
||||||
NodeContent: Node<C, Output = T>,
|
|
||||||
{
|
{
|
||||||
node.content.extent(ctx)
|
node.content.extent(ctx)
|
||||||
}
|
}
|
||||||
@@ -137,7 +149,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use core_types::SourceId;
|
use core_types::SourceId;
|
||||||
use core_types::context::{ContextImpl, EvalScope};
|
use core_types::context::{ContextImpl, EvalScope};
|
||||||
use core_types::registry::{EdgeHandle, ErasedLendNode, ErasedNode};
|
use core_types::registry::{EdgeHandle, ErasedLendNode, ErasedNode, ErasedRecordNode};
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
struct CountingNode(AtomicU32);
|
struct CountingNode(AtomicU32);
|
||||||
@@ -174,6 +186,10 @@ mod tests {
|
|||||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn element_layout<T: Clone + Send + Sync + 'static>() -> core_types::record::Layout {
|
||||||
|
core_types::record::Layout::default().with_writes(0, core_types::record::element_write::<T>(), &[])
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn monitor_serialize_exposes_the_io_record_through_the_edge() {
|
fn monitor_serialize_exposes_the_io_record_through_the_edge() {
|
||||||
let arena = Arena::new(1024).unwrap();
|
let arena = Arena::new(1024).unwrap();
|
||||||
@@ -199,7 +215,9 @@ mod tests {
|
|||||||
let scope = scope_fixture(&generations, &arena);
|
let scope = scope_fixture(&generations, &arena);
|
||||||
let ctx = ContextImpl::root(&scope);
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
let memoized = MemoizeNode::new(CountingNode(AtomicU32::new(0)));
|
let layout = element_layout::<u32>();
|
||||||
|
let memoized = MemoizeNode::new(core_types::record::RecordLift::<u32, _>::new(CountingNode(AtomicU32::new(0))), &layout);
|
||||||
|
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||||
|
|
||||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
||||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
||||||
@@ -214,7 +232,9 @@ mod tests {
|
|||||||
let scope_before = scope_fixture(&before, &arena);
|
let scope_before = scope_fixture(&before, &arena);
|
||||||
let scope_after = scope_fixture(&after, &arena);
|
let scope_after = scope_fixture(&after, &arena);
|
||||||
|
|
||||||
let memoized = MemoizeNode::new(CountingNode(AtomicU32::new(0)));
|
let layout = element_layout::<u32>();
|
||||||
|
let memoized = MemoizeNode::new(core_types::record::RecordLift::<u32, _>::new(CountingNode(AtomicU32::new(0))), &layout);
|
||||||
|
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||||
|
|
||||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
||||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
||||||
@@ -228,7 +248,9 @@ mod tests {
|
|||||||
let scope = scope_fixture(&generations, &arena);
|
let scope = scope_fixture(&generations, &arena);
|
||||||
let ctx = ContextImpl::root(&scope);
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
let memoized = MemoizeNode::new(PartialCountingNode(AtomicU32::new(0)));
|
let layout = element_layout::<u32>();
|
||||||
|
let memoized = MemoizeNode::new(core_types::record::RecordLift::<u32, _>::new(PartialCountingNode(AtomicU32::new(0))), &layout);
|
||||||
|
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||||
|
|
||||||
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
||||||
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
||||||
@@ -241,9 +263,11 @@ mod tests {
|
|||||||
let scope = scope_fixture(&generations, &arena);
|
let scope = scope_fixture(&generations, &arena);
|
||||||
let ctx = ContextImpl::root(&scope);
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
let edge = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc<ErasedNode<u32>>);
|
let layout = element_layout::<u32>();
|
||||||
let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::<u32>().unwrap())) as Arc<ErasedNode<u32>>);
|
let edge = EdgeHandle::new_record::<u32>(Arc::new(core_types::record::RecordLift::<u32, _>::new(CountingNode(AtomicU32::new(0)))) as Arc<ErasedRecordNode>);
|
||||||
let stacked = MemoizeNode::new(memoized.downcast::<u32>().unwrap());
|
let memoized = EdgeHandle::new_record::<u32>(Arc::new(MemoizeNode::new(edge.downcast_record::<u32>().unwrap(), &layout)) as Arc<ErasedRecordNode>);
|
||||||
|
let stacked = MemoizeNode::new(memoized.downcast_record::<u32>().unwrap(), &layout);
|
||||||
|
let stacked = core_types::record::RecordExtract::<u32, _>::new(stacked, &layout);
|
||||||
|
|
||||||
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
||||||
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
||||||
|
|||||||
@@ -752,7 +752,7 @@ mod tests {
|
|||||||
let evals = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
|
let evals = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
|
||||||
let lift = core_types::record::RecordLift::<f64, _>::new(CountingValue(evals.clone()));
|
let lift = core_types::record::RecordLift::<f64, _>::new(CountingValue(evals.clone()));
|
||||||
let layout = Node::<ContextImpl>::layout(&lift).unwrap().clone();
|
let layout = Node::<ContextImpl>::layout(&lift).unwrap().clone();
|
||||||
let memo = core_types::record::RecordMemo::new(lift, &layout);
|
let memo = crate::memo::MemoizeNode::new(lift, &layout);
|
||||||
|
|
||||||
let GPoll::Final(value) = memo.eval(&ctx) else {
|
let GPoll::Final(value) = memo.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
@@ -781,7 +781,7 @@ mod tests {
|
|||||||
fields: vec![(layout.offset_of("opacity", 0).unwrap(), 0.5)],
|
fields: vec![(layout.offset_of("opacity", 0).unwrap(), 0.5)],
|
||||||
partial: true,
|
partial: true,
|
||||||
};
|
};
|
||||||
let memo = core_types::record::RecordMemo::new(source, &layout);
|
let memo = crate::memo::MemoizeNode::new(source, &layout);
|
||||||
|
|
||||||
let GPoll::Partial(_) = memo.eval(&ctx) else {
|
let GPoll::Partial(_) = memo.eval(&ctx) else {
|
||||||
panic!("expected a partial record");
|
panic!("expected a partial record");
|
||||||
@@ -801,7 +801,7 @@ mod tests {
|
|||||||
reserve_for(&[&labeled, &labeled]);
|
reserve_for(&[&labeled, &labeled]);
|
||||||
|
|
||||||
let chain = LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout);
|
let chain = LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout);
|
||||||
let memo = core_types::record::RecordMemo::new(chain, &labeled);
|
let memo = crate::memo::MemoizeNode::new(chain, &labeled);
|
||||||
|
|
||||||
let first_arena = Arena::new(1024).unwrap();
|
let first_arena = Arena::new(1024).unwrap();
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user