mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 05:28:11 +08:00
Carry erased element and re-park glue on layouts and collapse memoize to a generic record row
This commit is contained in:
@@ -655,6 +655,27 @@ mod test {
|
|||||||
assert!(fields.is_empty(), "an element-only record has no attribute fields");
|
assert!(fields.is_empty(), "an element-only record has no attribute fields");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_memoize_row_wires_generically_and_replays_over_record_wires() {
|
||||||
|
let network = ProtoNetwork {
|
||||||
|
inputs: vec![],
|
||||||
|
output: NodeId(2),
|
||||||
|
nodes: vec![
|
||||||
|
(NodeId(0), ProtoNode::value(ConstructionArgs::Value(TaggedValue::String(String::from("cached")).into()), vec![])),
|
||||||
|
(NodeId(1), proto_node("graphene_core::memo::MemoizeNode", vec![NodeId(0)])),
|
||||||
|
(NodeId(2), proto_node("core_types::record::RecordExtractNode", vec![NodeId(1)])),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let executor = DynamicExecutor::new(network).unwrap();
|
||||||
|
assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::String(String::from("cached"))));
|
||||||
|
assert_eq!(
|
||||||
|
(&executor).execute(()).unwrap(),
|
||||||
|
GPoll::Final(TaggedValue::String(String::from("cached"))),
|
||||||
|
"the second execution replays the deep copy against a reset arena"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn modification_value() -> ProtoNode {
|
fn modification_value() -> ProtoNode {
|
||||||
let modification = core_types::ContextModification::from_sources(core_types::context::ContextFeatures::all(), &[]);
|
let modification = core_types::ContextModification::from_sources(core_types::context::ContextFeatures::all(), &[]);
|
||||||
ProtoNode::value(ConstructionArgs::Value(TaggedValue::ContextModification(modification).into()), vec![])
|
ProtoNode::value(ConstructionArgs::Value(TaggedValue::ContextModification(modification).into()), vec![])
|
||||||
|
|||||||
@@ -189,93 +189,30 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
|||||||
// ==========
|
// ==========
|
||||||
// MEMO NODES
|
// MEMO NODES
|
||||||
// ==========
|
// ==========
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => ()]),
|
(
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RuntimeHandle]),
|
ProtoNodeIdentifier::new("graphene_core::memo::MemoizeNode"),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => SourceId]),
|
RegistryEntry {
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => bool]),
|
io: NodeIOTypes::new(
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Artboard>]),
|
concrete!(Context),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Graphic>]),
|
core_types::Type::Record(Box::new(core_types::Type::Generic(std::borrow::Cow::Borrowed("T")))),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Vector>]),
|
vec![core_types::registry::generic_record_edge_type("T")],
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Raster<CPU>>]),
|
),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Color>]),
|
constructor: |inputs| {
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
|
if inputs.len() != 1 {
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<GradientStops>]),
|
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<String>]),
|
}
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<NodeId>]),
|
let mut inputs = inputs.into_iter();
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<f64>]),
|
let handle = inputs.next().unwrap();
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<u8>]),
|
let ty = handle.ty().clone();
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<bool>]),
|
let Some(layout) = handle.layout().cloned() else {
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<DAffine2>]),
|
return Err(ConstructionError::MissingLayout);
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<BlendMode>]),
|
};
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientType>]),
|
let edge = handle.downcast_erased::<core_types::registry::ErasedRecordNode>(ty.clone())?;
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<graphene_std::vector::style::GradientSpreadMethod>]),
|
let node = core_types::record::RecordMemo::new(edge, &layout);
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => AttributeDyn]),
|
Ok(EdgeHandle::new_erased(std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>, ty))
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn]),
|
},
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => ListDyn]),
|
},
|
||||||
#[cfg(target_family = "wasm")]
|
),
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f64]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f32]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => u32]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => u64]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DVec2]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => String]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DAffine2]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Footprint]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc<PlatformEditorApi>]),
|
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Raster<GPU>>]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Graphic]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<BrushStroke>]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text_nodes::StringCapitalization]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientSpreadMethod]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<DAffine2>]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuExecutorHandle]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuExecutorHandle>]),
|
|
||||||
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuPipelineCache]),
|
|
||||||
// ============
|
// ============
|
||||||
// REF ADAPTERS
|
// REF ADAPTERS
|
||||||
// ============
|
// ============
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ pub trait Attribute: 'static {
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
/// `ptr` must point at a live field of this marker's value type.
|
/// `ptr` must point at a live field of this marker's value type.
|
||||||
unsafe fn read_erased(ptr: *const u8) -> Box<dyn AnyAttributeValue>;
|
unsafe fn read_erased(ptr: *const u8) -> Box<dyn AnyAttributeValue>;
|
||||||
|
|
||||||
|
/// Re-parks the owned clone [`Self::read_erased`] produced into fresh
|
||||||
|
/// field storage; `None` for plain values, which ride the byte copy.
|
||||||
|
const REPARK: Option<unsafe fn(&dyn AnyAttributeValue, *mut u8, &crate::arena::Arena) -> Option<()>> = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A kernel-facing attribute value. A parameter `Attr<A>` is a read of `A`
|
/// A kernel-facing attribute value. A parameter `Attr<A>` is a read of `A`
|
||||||
@@ -162,6 +166,16 @@ macro_rules! attribute {
|
|||||||
unsafe fn read_erased(ptr: *const u8) -> ::std::boxed::Box<dyn $crate::list::AnyAttributeValue> {
|
unsafe fn read_erased(ptr: *const u8) -> ::std::boxed::Box<dyn $crate::list::AnyAttributeValue> {
|
||||||
::std::boxed::Box::new(unsafe { ptr.cast::<&$value>().read() }.to_owned())
|
::std::boxed::Box::new(unsafe { ptr.cast::<&$value>().read() }.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const REPARK: ::core::option::Option<unsafe fn(&dyn $crate::list::AnyAttributeValue, *mut u8, &$crate::arena::Arena) -> ::core::option::Option<()>> = {
|
||||||
|
unsafe fn repark(value: &dyn $crate::list::AnyAttributeValue, dst: *mut u8, arena: &$crate::arena::Arena) -> ::core::option::Option<()> {
|
||||||
|
let owned: &<$value as ::std::borrow::ToOwned>::Owned = value.as_any().downcast_ref().expect("a reference attribute replays its owned clone");
|
||||||
|
let (parked, _) = arena.alloc(<$value as ::std::borrow::ToOwned>::to_owned(::std::borrow::Borrow::borrow(owned)))?;
|
||||||
|
unsafe { dst.cast::<&$value>().write(::std::borrow::Borrow::borrow(parked)) };
|
||||||
|
::core::option::Option::Some(())
|
||||||
|
}
|
||||||
|
::core::option::Option::Some(repark)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
$crate::attribute!(@register $marker);
|
$crate::attribute!(@register $marker);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub struct FieldWrite {
|
|||||||
pub size: usize,
|
pub size: usize,
|
||||||
pub align: usize,
|
pub align: usize,
|
||||||
pub read_erased: unsafe fn(*const u8) -> Box<dyn crate::list::AnyAttributeValue>,
|
pub read_erased: unsafe fn(*const u8) -> Box<dyn crate::list::AnyAttributeValue>,
|
||||||
|
pub repark: Option<unsafe fn(&dyn crate::list::AnyAttributeValue, *mut u8, &crate::arena::Arena) -> Option<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldWrite {
|
impl FieldWrite {
|
||||||
@@ -30,6 +31,7 @@ impl FieldWrite {
|
|||||||
size: size_of::<A::Value<'static>>(),
|
size: size_of::<A::Value<'static>>(),
|
||||||
align: align_of::<A::Value<'static>>(),
|
align: align_of::<A::Value<'static>>(),
|
||||||
read_erased: A::read_erased,
|
read_erased: A::read_erased,
|
||||||
|
repark: A::REPARK,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +49,7 @@ pub struct FieldDesc {
|
|||||||
pub size: usize,
|
pub size: usize,
|
||||||
pub align: usize,
|
pub align: usize,
|
||||||
pub read_erased: unsafe fn(*const u8) -> Box<dyn crate::list::AnyAttributeValue>,
|
pub read_erased: unsafe fn(*const u8) -> Box<dyn crate::list::AnyAttributeValue>,
|
||||||
|
pub repark: Option<unsafe fn(&dyn crate::list::AnyAttributeValue, *mut u8, &crate::arena::Arena) -> Option<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for FieldDesc {
|
impl PartialEq for FieldDesc {
|
||||||
@@ -57,14 +60,52 @@ impl PartialEq for FieldDesc {
|
|||||||
|
|
||||||
impl Eq for FieldDesc {}
|
impl Eq for FieldDesc {}
|
||||||
|
|
||||||
|
/// The element slot of a layout: its dimensions plus erased glue bound where
|
||||||
|
/// the element type is statically known, so generic consumers read or
|
||||||
|
/// deep-copy the element without it. Equality is structural: glue pointers
|
||||||
|
/// are excluded for the same reason as [`FieldDesc`]'s.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ElementWrite {
|
||||||
|
pub size: usize,
|
||||||
|
pub align: usize,
|
||||||
|
pub parked: bool,
|
||||||
|
pub clone_out: unsafe fn(*const u8) -> Box<dyn std::any::Any + Send + Sync>,
|
||||||
|
pub repark: unsafe fn(&(dyn std::any::Any + Send + Sync), *mut u8, &crate::arena::Arena) -> Option<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ElementWrite {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
(self.size, self.align, self.parked) == (other.size, other.align, other.parked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ElementWrite {}
|
||||||
|
|
||||||
|
impl Default for ElementWrite {
|
||||||
|
fn default() -> Self {
|
||||||
|
unsafe fn clone_out(_ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||||
|
Box::new(())
|
||||||
|
}
|
||||||
|
unsafe fn repark(_value: &(dyn std::any::Any + Send + Sync), _dst: *mut u8, _arena: &crate::arena::Arena) -> Option<()> {
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
size: 0,
|
||||||
|
align: 0,
|
||||||
|
parked: false,
|
||||||
|
clone_out,
|
||||||
|
repark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A record layout: the element at offset 0, then the written attributes in
|
/// A record layout: the element at offset 0, then the written attributes in
|
||||||
/// canonical order (descending alignment, then size, then name, then level).
|
/// canonical order (descending alignment, then size, then name, then level).
|
||||||
/// Layouts are derived data, a pure function of the upstream write set.
|
/// Layouts are derived data, a pure function of the upstream write set.
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
pub depth: u8,
|
pub depth: u8,
|
||||||
pub element_size: usize,
|
pub element: ElementWrite,
|
||||||
pub element_align: usize,
|
|
||||||
pub fields: Vec<FieldDesc>,
|
pub fields: Vec<FieldDesc>,
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
pub align: usize,
|
pub align: usize,
|
||||||
@@ -92,11 +133,11 @@ impl Layout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The union of this layout's fields and `writes` over an element of
|
/// The union of this layout's fields and `writes` over `element` at
|
||||||
/// (size, align) at `depth`, in canonical order. A (name, level) written
|
/// `depth`, in canonical order. A (name, level) written at a different
|
||||||
/// at a different size is a type conflict and panics; the census keeps
|
/// size is a type conflict and panics; the census keeps declared names to
|
||||||
/// declared names to one type, so this only fires on wiring bugs.
|
/// one type, so this only fires on wiring bugs.
|
||||||
pub fn with_writes(&self, depth: u8, element: (usize, usize), writes: &[FieldWrite]) -> Layout {
|
pub fn with_writes(&self, depth: u8, element: ElementWrite, writes: &[FieldWrite]) -> Layout {
|
||||||
let mut merged: Vec<FieldWrite> = self
|
let mut merged: Vec<FieldWrite> = self
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
@@ -106,6 +147,7 @@ impl Layout {
|
|||||||
size: field.size,
|
size: field.size,
|
||||||
align: field.align,
|
align: field.align,
|
||||||
read_erased: field.read_erased,
|
read_erased: field.read_erased,
|
||||||
|
repark: field.repark,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
for &write in writes {
|
for &write in writes {
|
||||||
@@ -115,9 +157,8 @@ impl Layout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
merged.sort_by(|a, b| b.align.cmp(&a.align).then(b.size.cmp(&a.size)).then(a.name.cmp(b.name)).then(a.level.cmp(&b.level)));
|
merged.sort_by(|a, b| b.align.cmp(&a.align).then(b.size.cmp(&a.size)).then(a.name.cmp(b.name)).then(a.level.cmp(&b.level)));
|
||||||
let (element_size, element_align) = element;
|
let mut offset = element.size;
|
||||||
let mut offset = element_size;
|
let mut align = element.align.max(1);
|
||||||
let mut align = element_align.max(1);
|
|
||||||
let fields = merged
|
let fields = merged
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|write| {
|
.map(|write| {
|
||||||
@@ -130,6 +171,7 @@ impl Layout {
|
|||||||
size: write.size,
|
size: write.size,
|
||||||
align: write.align,
|
align: write.align,
|
||||||
read_erased: write.read_erased,
|
read_erased: write.read_erased,
|
||||||
|
repark: write.repark,
|
||||||
};
|
};
|
||||||
offset += write.size;
|
offset += write.size;
|
||||||
desc
|
desc
|
||||||
@@ -137,8 +179,7 @@ impl Layout {
|
|||||||
.collect();
|
.collect();
|
||||||
Layout {
|
Layout {
|
||||||
depth,
|
depth,
|
||||||
element_size,
|
element,
|
||||||
element_align,
|
|
||||||
fields,
|
fields,
|
||||||
size: offset,
|
size: offset,
|
||||||
align,
|
align,
|
||||||
@@ -148,10 +189,9 @@ impl Layout {
|
|||||||
/// The union of several layouts over the same element and depth.
|
/// The union of several layouts over the same element and depth.
|
||||||
pub fn union(layouts: &[&Layout]) -> Layout {
|
pub fn union(layouts: &[&Layout]) -> Layout {
|
||||||
let first = layouts.first().expect("a union needs at least one layout");
|
let first = layouts.first().expect("a union needs at least one layout");
|
||||||
let mut union = Layout::default().with_writes(first.depth, (first.element_size, first.element_align), &[]);
|
let mut union = Layout::default().with_writes(first.depth, first.element, &[]);
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
assert_eq!(union.element_size, layout.element_size, "union layouts must share the element size");
|
assert_eq!(union.element, layout.element, "union layouts must share the element");
|
||||||
assert_eq!(union.element_align, layout.element_align, "union layouts must share the element alignment");
|
|
||||||
assert_eq!(union.depth, layout.depth, "union layouts must share the depth");
|
assert_eq!(union.depth, layout.depth, "union layouts must share the depth");
|
||||||
let writes: Vec<FieldWrite> = layout
|
let writes: Vec<FieldWrite> = layout
|
||||||
.fields
|
.fields
|
||||||
@@ -162,9 +202,10 @@ impl Layout {
|
|||||||
size: field.size,
|
size: field.size,
|
||||||
align: field.align,
|
align: field.align,
|
||||||
read_erased: field.read_erased,
|
read_erased: field.read_erased,
|
||||||
|
repark: field.repark,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
union = union.with_writes(union.depth, (union.element_size, union.element_align), &writes);
|
union = union.with_writes(union.depth, union.element, &writes);
|
||||||
}
|
}
|
||||||
union
|
union
|
||||||
}
|
}
|
||||||
@@ -500,9 +541,9 @@ pub mod stack {
|
|||||||
pub fn copy_plan(from: &Layout, to: &Layout, carry_element: bool) -> Vec<(usize, usize, usize)> {
|
pub fn copy_plan(from: &Layout, to: &Layout, carry_element: bool) -> Vec<(usize, usize, usize)> {
|
||||||
let mut plan = Vec::new();
|
let mut plan = Vec::new();
|
||||||
if carry_element {
|
if carry_element {
|
||||||
assert_eq!(from.element_size, to.element_size, "a carried element must keep its size");
|
assert_eq!(from.element.size, to.element.size, "a carried element must keep its size");
|
||||||
if from.element_size > 0 {
|
if from.element.size > 0 {
|
||||||
plan.push((0, 0, from.element_size));
|
plan.push((0, 0, from.element.size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for field in &from.fields {
|
for field in &from.fields {
|
||||||
@@ -534,6 +575,26 @@ pub fn element_dims<T>() -> (usize, usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The element slot a record wire of `T` carries, its erased glue bound at
|
||||||
|
/// the statically-known type.
|
||||||
|
pub fn element_write<T: Clone + Send + Sync + 'static>() -> ElementWrite {
|
||||||
|
unsafe fn clone_out<T: Clone + Send + Sync + 'static>(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||||
|
Box::new(unsafe { read_element::<T>(Rec::new(ptr)) })
|
||||||
|
}
|
||||||
|
unsafe fn repark<T: Clone + Send + Sync + 'static>(value: &(dyn std::any::Any + Send + Sync), dst: *mut u8, arena: &crate::arena::Arena) -> Option<()> {
|
||||||
|
let value = value.downcast_ref::<T>().expect("an element replays at its own type");
|
||||||
|
unsafe { write_element(dst, value.clone(), arena) }
|
||||||
|
}
|
||||||
|
let (size, align) = element_dims::<T>();
|
||||||
|
ElementWrite {
|
||||||
|
size,
|
||||||
|
align,
|
||||||
|
parked: element_parked::<T>(),
|
||||||
|
clone_out: clone_out::<T>,
|
||||||
|
repark: repark::<T>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The record's element must be a `T` in the form [`element_parked`] picks,
|
/// The record's element must be a `T` in the form [`element_parked`] picks,
|
||||||
/// and the borrow is only valid while the record is.
|
/// and the borrow is only valid while the record is.
|
||||||
@@ -728,6 +789,128 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A record deep-copied out of its evaluation: the packed bytes plus owned
|
||||||
|
/// clones of every parked payload, replayable into a later evaluation's
|
||||||
|
/// storage through the layout's erased glue. The layout stays with the
|
||||||
|
/// holder, which proved it at wiring.
|
||||||
|
pub struct OwnedRecord {
|
||||||
|
bytes: Box<[u8]>,
|
||||||
|
element: Option<Box<dyn std::any::Any + Send + Sync>>,
|
||||||
|
fields: Vec<(usize, Box<dyn crate::list::AnyAttributeValue>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedRecord {
|
||||||
|
/// # Safety
|
||||||
|
/// `rec` must be a live record of `layout`.
|
||||||
|
pub unsafe fn copy_out(layout: &Layout, rec: Rec) -> OwnedRecord {
|
||||||
|
let bytes: Box<[u8]> = unsafe { std::slice::from_raw_parts(rec.ptr(), layout.size) }.into();
|
||||||
|
let element = layout.element.parked.then(|| unsafe { (layout.element.clone_out)(rec.ptr()) });
|
||||||
|
let fields = layout
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, field)| field.repark.is_some())
|
||||||
|
.map(|(index, field)| (index, unsafe { (field.read_erased)(rec.ptr().add(field.offset)) }))
|
||||||
|
.collect();
|
||||||
|
OwnedRecord { bytes, element, fields }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replays the copy into fresh storage of `layout`, the layout it was
|
||||||
|
/// copied out under, re-parking droppable payloads against `arena`;
|
||||||
|
/// `None` reports arena exhaustion.
|
||||||
|
pub fn replay<'e>(&self, layout: &Layout, arena: &'e crate::arena::Arena) -> Option<RecordValue<'e>> {
|
||||||
|
let mut value = RecordValue::zeroed();
|
||||||
|
let dst = match layout.frame_bytes() {
|
||||||
|
0 => value.as_mut_ptr(),
|
||||||
|
bytes => stack::push(bytes),
|
||||||
|
};
|
||||||
|
let written = self.write_into(layout, dst, arena);
|
||||||
|
if layout.frame_bytes() != 0 {
|
||||||
|
stack::pop(dst);
|
||||||
|
value = RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) });
|
||||||
|
}
|
||||||
|
written.map(|()| value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_into(&self, layout: &Layout, dst: *mut u8, arena: &crate::arena::Arena) -> Option<()> {
|
||||||
|
unsafe { std::ptr::copy_nonoverlapping(self.bytes.as_ptr(), dst, self.bytes.len()) };
|
||||||
|
if let Some(element) = &self.element {
|
||||||
|
unsafe { (layout.element.repark)(&**element, dst, arena) }?;
|
||||||
|
}
|
||||||
|
for (index, value) in &self.fields {
|
||||||
|
let field = &layout.fields[*index];
|
||||||
|
let repark = field.repark.expect("copied fields carry re-park glue");
|
||||||
|
unsafe { repark(&**value, dst.add(field.offset), arena) }?;
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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> {
|
||||||
@@ -736,11 +919,11 @@ pub struct RecordLift<El, N> {
|
|||||||
_marker: std::marker::PhantomData<fn() -> El>,
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<El: 'static, N> RecordLift<El, N> {
|
impl<El: Clone + Send + Sync + 'static, N> RecordLift<El, N> {
|
||||||
pub fn new(edge: N) -> Self {
|
pub fn new(edge: N) -> Self {
|
||||||
Self {
|
Self {
|
||||||
edge,
|
edge,
|
||||||
layout: Layout::default().with_writes(0, element_dims::<El>(), &[]),
|
layout: Layout::default().with_writes(0, element_write::<El>(), &[]),
|
||||||
_marker: std::marker::PhantomData,
|
_marker: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -771,11 +954,11 @@ pub struct RecordLiftLend<El, N> {
|
|||||||
_marker: std::marker::PhantomData<fn() -> El>,
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<El: 'static, N> RecordLiftLend<El, N> {
|
impl<El: Clone + Send + Sync + 'static, N> RecordLiftLend<El, N> {
|
||||||
pub fn new(edge: N) -> Self {
|
pub fn new(edge: N) -> Self {
|
||||||
Self {
|
Self {
|
||||||
edge,
|
edge,
|
||||||
layout: Layout::default().with_writes(0, element_dims::<El>(), &[]),
|
layout: Layout::default().with_writes(0, element_write::<El>(), &[]),
|
||||||
_marker: std::marker::PhantomData,
|
_marker: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -930,7 +1113,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn sized_field(name: &'static str, size: usize, align: usize) -> FieldWrite {
|
fn sized_field(name: &'static str, size: usize, align: usize) -> FieldWrite {
|
||||||
FieldWrite { name, level: 0, size, align, read_erased: unread }
|
FieldWrite {
|
||||||
|
name,
|
||||||
|
level: 0,
|
||||||
|
size,
|
||||||
|
align,
|
||||||
|
read_erased: unread,
|
||||||
|
repark: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn f64_field(name: &'static str) -> FieldWrite {
|
fn f64_field(name: &'static str) -> FieldWrite {
|
||||||
@@ -939,7 +1129,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn canonical_order_and_offsets() {
|
fn canonical_order_and_offsets() {
|
||||||
let layout = Layout::default().with_writes(0, (8, 8), &[sized_field("tint", 4, 4), f64_field("opacity"), sized_field("flag", 1, 1)]);
|
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[sized_field("tint", 4, 4), f64_field("opacity"), sized_field("flag", 1, 1)]);
|
||||||
assert_eq!(layout.offset_of("opacity", 0), Some(8));
|
assert_eq!(layout.offset_of("opacity", 0), Some(8));
|
||||||
assert_eq!(layout.offset_of("tint", 0), Some(16));
|
assert_eq!(layout.offset_of("tint", 0), Some(16));
|
||||||
assert_eq!(layout.offset_of("flag", 0), Some(20));
|
assert_eq!(layout.offset_of("flag", 0), Some(20));
|
||||||
@@ -950,22 +1140,22 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "two different sizes")]
|
#[should_panic(expected = "two different sizes")]
|
||||||
fn size_conflicts_panic() {
|
fn size_conflicts_panic() {
|
||||||
let layout = Layout::default().with_writes(0, (8, 8), &[f64_field("opacity")]);
|
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("opacity")]);
|
||||||
layout.with_writes(0, (8, 8), &[sized_field("opacity", 4, 4)]);
|
layout.with_writes(0, element_write::<f64>(), &[sized_field("opacity", 4, 4)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn union_is_order_independent() {
|
fn union_is_order_independent() {
|
||||||
let a = Layout::default().with_writes(0, (8, 8), &[f64_field("opacity")]);
|
let a = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("opacity")]);
|
||||||
let b = Layout::default().with_writes(0, (8, 8), &[f64_field("length")]);
|
let b = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("length")]);
|
||||||
assert_eq!(Layout::union(&[&a, &b]), Layout::union(&[&b, &a]));
|
assert_eq!(Layout::union(&[&a, &b]), Layout::union(&[&b, &a]));
|
||||||
assert!(Layout::union(&[&a, &b]).offset_of("length", 0).is_some());
|
assert!(Layout::union(&[&a, &b]).offset_of("length", 0).is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn translation_moves_fields_and_fills_census_defaults() {
|
fn translation_moves_fields_and_fills_census_defaults() {
|
||||||
let source = Layout::default().with_writes(0, (8, 8), &[f64_field("length")]);
|
let source = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("length")]);
|
||||||
let union = Layout::union(&[&source, &Layout::default().with_writes(0, (8, 8), &[f64_field("opacity")])]);
|
let union = Layout::union(&[&source, &Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("opacity")])]);
|
||||||
|
|
||||||
let plan = SourcePlan::new(&source, &union).unwrap();
|
let plan = SourcePlan::new(&source, &union).unwrap();
|
||||||
let record = [5f64, 7f64];
|
let record = [5f64, 7f64];
|
||||||
@@ -978,7 +1168,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn identity_layouts_forward() {
|
fn identity_layouts_forward() {
|
||||||
let layout = Layout::default().with_writes(0, (8, 8), &[f64_field("opacity")]);
|
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("opacity")]);
|
||||||
assert!(SourcePlan::new(&layout, &layout.clone()).is_none());
|
assert!(SourcePlan::new(&layout, &layout.clone()).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1009,6 +1199,29 @@ mod tests {
|
|||||||
assert_eq!(unsafe { read_element::<String>(rec) }, "moved once");
|
assert_eq!(unsafe { read_element::<String>(rec) }, "moved once");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn owned_records_replay_re_parked_payloads_after_the_source_dies() {
|
||||||
|
let layout = Layout::default().with_writes(0, element_write::<String>(), &[FieldWrite::of::<crate::attribute::Name>(0)]);
|
||||||
|
let mut buffer = vec![0u64; layout.size.div_ceil(8)];
|
||||||
|
let base: *mut u8 = buffer.as_mut_ptr().cast();
|
||||||
|
|
||||||
|
let copy = {
|
||||||
|
let arena = crate::arena::Arena::new(1024).unwrap();
|
||||||
|
unsafe { write_element(base, String::from("element"), &arena) }.unwrap();
|
||||||
|
let (name, _) = arena.alloc(String::from("field")).unwrap();
|
||||||
|
unsafe { write_field::<&str>(base, layout.offset_of("name", 0).unwrap(), name.as_str()) };
|
||||||
|
unsafe { OwnedRecord::copy_out(&layout, Rec::new(base)) }
|
||||||
|
};
|
||||||
|
buffer.fill(u64::MAX);
|
||||||
|
|
||||||
|
let replay_arena = crate::arena::Arena::new(1024).unwrap();
|
||||||
|
stack::reserve(layout.frame_bytes());
|
||||||
|
let value = copy.replay(&layout, &replay_arena).unwrap();
|
||||||
|
let rec = layout.rec(&value);
|
||||||
|
assert_eq!(unsafe { read_element::<String>(rec) }, "element");
|
||||||
|
assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, "field");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn record_values_are_two_words() {
|
fn record_values_are_two_words() {
|
||||||
assert_eq!(size_of::<RecordValue>(), 16);
|
assert_eq!(size_of::<RecordValue>(), 16);
|
||||||
@@ -1017,7 +1230,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn layouts_resolve_inline_and_spilled_values() {
|
fn layouts_resolve_inline_and_spilled_values() {
|
||||||
let inline = Layout::default().with_writes(0, (8, 8), &[f64_field("opacity")]);
|
let inline = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("opacity")]);
|
||||||
assert!(inline.is_inline());
|
assert!(inline.is_inline());
|
||||||
assert_eq!(inline.frame_bytes(), 0);
|
assert_eq!(inline.frame_bytes(), 0);
|
||||||
let mut value = RecordValue::zeroed();
|
let mut value = RecordValue::zeroed();
|
||||||
@@ -1029,7 +1242,7 @@ mod tests {
|
|||||||
assert_eq!(unsafe { rec.element::<f64>() }, 4.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 4.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(inline.offset_of("opacity", 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { rec.read::<f64>(inline.offset_of("opacity", 0).unwrap()) }, 0.5);
|
||||||
|
|
||||||
let spilled = Layout::default().with_writes(0, (8, 8), &[f64_field("opacity"), f64_field("length")]);
|
let spilled = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("opacity"), f64_field("length")]);
|
||||||
assert!(!spilled.is_inline());
|
assert!(!spilled.is_inline());
|
||||||
assert_eq!(spilled.frame_bytes(), 24);
|
assert_eq!(spilled.frame_bytes(), 24);
|
||||||
let record = [1f64, 2., 3.];
|
let record = [1f64, 2., 3.];
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ mod tests {
|
|||||||
use crate::context::{ContextImpl, Ctx, CtxSnapshot, EvalScope, ExtractFootprint, ExtractVarArgs, VarArgLink, VarArgSlots};
|
use crate::context::{ContextImpl, Ctx, CtxSnapshot, EvalScope, ExtractFootprint, ExtractVarArgs, VarArgLink, VarArgSlots};
|
||||||
use crate::gpoll::GPoll;
|
use crate::gpoll::GPoll;
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
use crate::record::{Layout, RecordExtract, RecordLift, element_dims};
|
use crate::record::{Layout, RecordExtract, RecordLift, element_write};
|
||||||
use crate::transform::Footprint;
|
use crate::transform::Footprint;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
@@ -258,15 +258,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_layout<T: 'static>() -> Layout {
|
fn element_layout<T: Clone + Send + Sync + 'static>() -> Layout {
|
||||||
Layout::default().with_writes(0, element_dims::<T>(), &[])
|
Layout::default().with_writes(0, element_write::<T>(), &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lifted<T: Send + Sync + 'static>(value: T) -> RecordLift<T, SourceNode<T>> {
|
fn lifted<T: Clone + Send + Sync + 'static>(value: T) -> RecordLift<T, SourceNode<T>> {
|
||||||
RecordLift::new(SourceNode(value))
|
RecordLift::new(SourceNode(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract<El: Clone + 'static, N>(graph: N) -> RecordExtract<El, N> {
|
fn extract<El: Clone + Send + Sync + 'static, N>(graph: N) -> RecordExtract<El, N> {
|
||||||
RecordExtract::new(graph, &element_layout::<El>())
|
RecordExtract::new(graph, &element_layout::<El>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let flip_prelude = flip
|
let flip_prelude = flip
|
||||||
.then(|| {
|
.then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
let __layout = gcore::record::Layout::default().with_writes(0, gcore::record::element_dims::<#slot_value_type>(), &[]);
|
let __layout = gcore::record::Layout::default().with_writes(0, gcore::record::element_write::<#slot_value_type>(), &[]);
|
||||||
let __frame_bytes = __layout.frame_bytes();
|
let __frame_bytes = __layout.frame_bytes();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -365,8 +365,14 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
false => quote!(__layout, __frame_bytes, __marker: ::core::marker::PhantomData,),
|
false => quote!(__layout, __frame_bytes, __marker: ::core::marker::PhantomData,),
|
||||||
})
|
})
|
||||||
.into_iter();
|
.into_iter();
|
||||||
let fn_where = &parsed.where_clause;
|
// The flip prelude's `element_write` instantiates the erased glue at the
|
||||||
let new_where = flip.then(|| quote!(#fn_where)).into_iter();
|
// output type, so `new` carries the bounds the glue needs.
|
||||||
|
let new_where = flip
|
||||||
|
.then(|| {
|
||||||
|
let existing = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter());
|
||||||
|
quote!(where #(#existing,)* #slot_value_type: ::core::clone::Clone + ::core::marker::Send + ::core::marker::Sync + 'static)
|
||||||
|
})
|
||||||
|
.into_iter();
|
||||||
let new_impl = match record.is_none() {
|
let new_impl = match record.is_none() {
|
||||||
true => quote! {
|
true => quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
@@ -1547,19 +1553,19 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|marker| quote!(#core_types::record::FieldWrite::of::<#marker>(0)))
|
.map(|marker| quote!(#core_types::record::FieldWrite::of::<#marker>(0)))
|
||||||
.collect();
|
.collect();
|
||||||
let element_dims = match &shape.element_write {
|
let element = match &shape.element_write {
|
||||||
Some(ty) => quote!((::core::mem::size_of::<#ty>(), ::core::mem::align_of::<#ty>())),
|
Some(ty) => quote!(#core_types::record::element_write::<#ty>()),
|
||||||
None => quote!((__carrier.element_size, __carrier.element_align)),
|
None => quote!(__carrier.element),
|
||||||
};
|
};
|
||||||
let layout_def = match shape.skips_carrier() {
|
let layout_def = match shape.skips_carrier() {
|
||||||
true => quote! {
|
true => quote! {
|
||||||
#vis fn #layout_fn() -> #core_types::record::Layout {
|
#vis fn #layout_fn() -> #core_types::record::Layout {
|
||||||
#core_types::record::Layout::default().with_writes(0, #element_dims, &[#(#write_descs),*])
|
#core_types::record::Layout::default().with_writes(0, #element, &[#(#write_descs),*])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false => quote! {
|
false => quote! {
|
||||||
#vis fn #layout_fn(__carrier: &#core_types::record::Layout) -> #core_types::record::Layout {
|
#vis fn #layout_fn(__carrier: &#core_types::record::Layout) -> #core_types::record::Layout {
|
||||||
__carrier.with_writes(__carrier.depth, #element_dims, &[#(#write_descs),*])
|
__carrier.with_writes(__carrier.depth, #element, &[#(#write_descs),*])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -2302,7 +2308,7 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
|
|||||||
let output_alias = format_ident!("__{}_output", fn_name);
|
let output_alias = format_ident!("__{}_output", fn_name);
|
||||||
let alias_def = match alias_param_tokens.is_empty() {
|
let alias_def = match alias_param_tokens.is_empty() {
|
||||||
true => quote!(#[allow(non_camel_case_types)] type #output_alias = #output;),
|
true => quote!(#[allow(non_camel_case_types)] type #output_alias = #output;),
|
||||||
false => quote!(#[allow(non_camel_case_types)] type #output_alias<#(#alias_param_tokens,)*> = #output;),
|
false => quote!(#[allow(non_camel_case_types, type_alias_bounds)] type #output_alias<#(#alias_param_tokens,)*> = #output;),
|
||||||
};
|
};
|
||||||
|
|
||||||
let entries = rows.iter().filter_map(|row| {
|
let entries = rows.iter().filter_map(|row| {
|
||||||
|
|||||||
@@ -149,9 +149,10 @@ mod tests {
|
|||||||
size: 8,
|
size: 8,
|
||||||
align: 8,
|
align: 8,
|
||||||
read_erased: <Opacity as AttributeMarker>::read_erased,
|
read_erased: <Opacity as AttributeMarker>::read_erased,
|
||||||
|
repark: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Layout::default().with_writes(0, (8, 8), &writes)
|
Layout::default().with_writes(0, core_types::record::element_write::<f64>(), &writes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserve_for(layouts: &[&Layout]) {
|
fn reserve_for(layouts: &[&Layout]) {
|
||||||
@@ -270,7 +271,7 @@ mod tests {
|
|||||||
|
|
||||||
let f64_source = f64_layout(&[]);
|
let f64_source = f64_layout(&[]);
|
||||||
let f64_faded = fade_layout(&f64_source);
|
let f64_faded = fade_layout(&f64_source);
|
||||||
let u32_source = Layout::default().with_writes(0, (4, 4), &[]);
|
let u32_source = Layout::default().with_writes(0, core_types::record::element_write::<u32>(), &[]);
|
||||||
let u32_faded = fade_layout(&u32_source);
|
let u32_faded = fade_layout(&u32_source);
|
||||||
reserve_for(&[&f64_source, &f64_faded, &u32_source, &u32_faded]);
|
reserve_for(&[&f64_source, &f64_faded, &u32_source, &u32_faded]);
|
||||||
|
|
||||||
@@ -436,7 +437,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn census_fills_reference_defaults_from_static_data() {
|
fn census_fills_reference_defaults_from_static_data() {
|
||||||
let source = f64_layout(&[]);
|
let source = f64_layout(&[]);
|
||||||
let labeled = Layout::default().with_writes(0, (8, 8), &[core_types::record::FieldWrite::of::<Label>(0)]);
|
let labeled = Layout::default().with_writes(0, core_types::record::element_write::<f64>(), &[core_types::record::FieldWrite::of::<Label>(0)]);
|
||||||
|
|
||||||
let plan = core_types::record::SourcePlan::new(&source, &labeled).unwrap();
|
let plan = core_types::record::SourcePlan::new(&source, &labeled).unwrap();
|
||||||
let record = [5f64];
|
let record = [5f64];
|
||||||
@@ -729,4 +730,96 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(unsafe { layout.rec(&value).element::<f64>() }, 4.);
|
assert_eq!(unsafe { layout.rec(&value).element::<f64>() }, 4.);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CountingValue(std::sync::Arc<std::sync::atomic::AtomicU32>);
|
||||||
|
|
||||||
|
impl<Input> Node<Input> for CountingValue {
|
||||||
|
type Output = f64;
|
||||||
|
|
||||||
|
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||||
|
self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
GPoll::Final(21.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_memo_replays_the_deep_copy_on_a_context_hit() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let evals = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
|
||||||
|
let lift = core_types::record::RecordLift::<f64, _>::new(CountingValue(evals.clone()));
|
||||||
|
let layout = Node::<ContextImpl>::layout(&lift).unwrap().clone();
|
||||||
|
let memo = core_types::record::RecordMemo::new(lift, &layout);
|
||||||
|
|
||||||
|
let GPoll::Final(value) = memo.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
assert_eq!(unsafe { layout.rec(&value).element::<f64>() }, 21.);
|
||||||
|
let GPoll::Final(value) = memo.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
assert_eq!(unsafe { layout.rec(&value).element::<f64>() }, 21.);
|
||||||
|
assert_eq!(evals.load(std::sync::atomic::Ordering::Relaxed), 1, "a context hit must not re-evaluate the edge");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_memo_caches_partial_finality() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let layout = f64_layout(&["opacity"]);
|
||||||
|
reserve_for(&[&layout, &layout]);
|
||||||
|
|
||||||
|
let source = RecordSourceNode {
|
||||||
|
layout: layout.clone(),
|
||||||
|
element: 4.,
|
||||||
|
fields: vec![(layout.offset_of("opacity", 0).unwrap(), 0.5)],
|
||||||
|
partial: true,
|
||||||
|
};
|
||||||
|
let memo = core_types::record::RecordMemo::new(source, &layout);
|
||||||
|
|
||||||
|
let GPoll::Partial(_) = memo.eval(&ctx) else {
|
||||||
|
panic!("expected a partial record");
|
||||||
|
};
|
||||||
|
let GPoll::Partial(value) = memo.eval(&ctx) else {
|
||||||
|
panic!("expected the replay to keep the partial finality");
|
||||||
|
};
|
||||||
|
assert_eq!(unsafe { layout.rec(&value).read::<f64>(layout.offset_of("opacity", 0).unwrap()) }, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_memo_re_parks_droppable_payloads_on_replay() {
|
||||||
|
let generations = [];
|
||||||
|
|
||||||
|
let source_layout = f64_layout(&[]);
|
||||||
|
let labeled = label_layout(&source_layout);
|
||||||
|
reserve_for(&[&labeled, &labeled]);
|
||||||
|
|
||||||
|
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 first_arena = Arena::new(1024).unwrap();
|
||||||
|
{
|
||||||
|
let scope = scope_fixture(&generations, &first_arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
let GPoll::Final(_) = memo.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let replay_arena = Arena::new(1024).unwrap();
|
||||||
|
let scope = scope_fixture(&generations, &replay_arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
let GPoll::Final(value) = memo.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
let rec = labeled.rec(&value);
|
||||||
|
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
||||||
|
assert_eq!(unsafe { rec.read::<&str>(labeled.offset_of(Label::NAME, 0).unwrap()) }, "a");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user