Recreate introspection data from the monitor's context snapshot

This commit is contained in:
Dennis Kobert
2026-08-27 13:53:25 +00:00
parent 79f3571b02
commit 50911a6a25
10 changed files with 302 additions and 253 deletions

View File

@@ -1004,6 +1004,54 @@ impl CtxSnapshot {
pub fn generations(&self) -> &[(SourceId, u64)] {
&self.generations
}
/// Rebuilds the captured context against `scope`, allocating the borrowed
/// chains in the scope's arena; `None` reports arena exhaustion. The
/// scope carries the serving generation, so the recreated evaluation reads
/// current source data under the captured addressing.
pub fn rehydrate<'e>(&'e self, scope: &'e EvalScope<'e>) -> Option<ContextImpl<'e>> {
let arena = scope.arena();
let mut ctx = ContextImpl::root(scope);
if let Some(footprint) = self.footprint {
let (footprint, _) = arena.alloc(footprint)?;
ctx = ctx.with_footprint(footprint);
}
if let Some(levels) = &self.index {
let mut outer = None;
for &index in levels.iter().skip(1).rev() {
let (link, _) = arena.alloc(IndexLink { index: index as u64, outer })?;
outer = Some(link);
}
ctx.index = IndexLink {
index: levels.first().copied().unwrap_or_default() as u64,
outer,
};
}
if let Some(positions) = &self.positions {
let mut head = None;
for &position in positions.iter().rev() {
let (link, _) = arena.alloc(PositionLink { position, outer: head })?;
head = Some(link);
}
if let Some(head) = head {
ctx = ctx.with_position(head);
}
}
let mut varargs = None;
for args in self.varargs.iter().rev() {
let slots: Vec<DynSlot<'e>> = args.iter().map(|slot| &**slot as DynSlot<'e>).collect();
let slots = arena.alloc_slice_copy(&slots)?;
let (link, _) = arena.alloc(VarArgLink {
args: VarArgSlots::Slice(slots),
outer: varargs,
})?;
varargs = Some(link);
}
if let Some(varargs) = varargs {
ctx = ctx.with_varargs(varargs);
}
Some(ctx)
}
}
impl ExtractFootprint for CtxSnapshot {

View File

@@ -1554,96 +1554,6 @@ pub unsafe fn interrupt_frame(entry: usize, layout: &Layout) {
claim_frame(layout);
}
/// A captured record: the layout plus a generation-checked handle to the
/// arena copy, materialized by the introspection holder, which owns the
/// arena. A dead generation materializes to `None`, never to a stale read.
#[derive(Clone)]
pub struct RecordCapture {
layout: Layout,
lanes: usize,
bytes: crate::arena::ArenaWeak<Box<[u8]>>,
}
impl std::fmt::Debug for RecordCapture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("RecordCapture(..)")
}
}
impl RecordCapture {
/// # Safety
/// `rec` must be a live record of `layout`.
pub unsafe fn capture(layout: &Layout, rec: Rec, arena: &crate::arena::Arena) -> Option<RecordCapture> {
let bytes = unsafe { copy_record_bytes(layout, rec) };
arena.alloc(bytes).map(|(_, weak)| RecordCapture {
layout: layout.clone(),
lanes: 1,
bytes: weak,
})
}
/// Captures every lane of a leveled wire's batch.
///
/// # Safety
/// `batch` must hold live records of `layout`.
pub unsafe fn capture_level(layout: &Layout, batch: crate::node::RecordBatch<'_>, arena: &crate::arena::Arena) -> Option<RecordCapture> {
let stride = layout.lane_stride();
let mut bytes = vec![0u8; batch.len() * stride].into_boxed_slice();
for lane in 0..batch.len() {
// SAFETY: both sides hold `len` lanes at the shared layout's stride.
unsafe { std::ptr::copy_nonoverlapping(batch.get(lane).rec().ptr(), bytes.as_mut_ptr().add(lane * stride), stride) };
}
arena.alloc(bytes).map(|(_, weak)| RecordCapture {
layout: layout.clone(),
lanes: batch.len(),
bytes: weak,
})
}
pub fn layout(&self) -> &Layout {
&self.layout
}
/// The captured lane count: one for a rank-0 capture, the whole extent
/// for a level capture.
pub fn lanes(&self) -> usize {
self.lanes
}
/// A batch view over the captured records, alive while the arena holds
/// the capture's generation.
pub fn batch<'a>(&'a self, arena: &'a crate::arena::Arena) -> Option<crate::node::RecordBatch<'a>> {
let bytes = self.bytes.upgrade(arena)?;
// SAFETY: the constructors store `lanes` records of `layout`.
Some(unsafe { crate::node::RecordBatch::new(bytes.as_ptr(), self.lanes, &self.layout) })
}
/// The captured element of the first lane, cloned out through the
/// layout's erased glue.
pub fn materialize_element(&self, arena: &crate::arena::Arena) -> Option<Box<dyn std::any::Any + Send + Sync>> {
let bytes = self.bytes.upgrade(arena)?;
match self.lanes {
0 => None,
_ => Some(unsafe { (self.layout.element.clone_out)(bytes.as_ptr()) }),
}
}
/// The first lane's attributes, read out through the layout's erased glue.
pub fn materialize(&self, arena: &crate::arena::Arena) -> Option<Vec<(&'static str, Box<dyn crate::list::AnyAttributeValue>)>> {
let bytes = self.bytes.upgrade(arena)?;
if self.lanes == 0 {
return Some(Vec::new());
}
Some(
self.layout
.fields
.iter()
.map(|field| (field.name, unsafe { (field.read_erased)(bytes.as_ptr().add(field.offset)) }))
.collect(),
)
}
}
/// 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
@@ -2088,6 +1998,13 @@ impl<'a, T: 'static> crate::lane::LaneSource for RunView<'a, T> {
}
}
impl<T: crate::render_complexity::RenderComplexity + 'static> crate::render_complexity::RenderComplexity for RunView<'_, T> {
fn render_complexity(&self) -> usize {
use crate::lane::LaneSource;
(0..self.lane_count()).filter_map(|lane| self.element(lane)).map(crate::render_complexity::RenderComplexity::render_complexity).sum()
}
}
impl<T: crate::bounds::BoundingBox + 'static> crate::bounds::BoundingBox for RunView<'_, T> {
fn bounding_box(&self, transform: glam::DAffine2, include_stroke: bool) -> crate::bounds::RenderBoundingBox {
crate::bounds::lane_bounding_box(self, transform, include_stroke)

View File

@@ -303,6 +303,12 @@ impl EdgeHandle {
self.downcast_erased(record_edge_type::<T>())
}
/// The erased record edge, for callers that dispatch on the layout rather
/// than a static element type. `None` for a plain (non-record) edge.
pub fn record_edge(self) -> Option<SharedEdge<ErasedRecordNode>> {
self.node.downcast::<SharedEdge<ErasedRecordNode>>().ok().map(|edge| *edge)
}
pub fn downcast_erased<N: ?Sized + 'static>(self, expected: Type) -> Result<SharedEdge<N>, ConstructionError> {
let found = self.ty;
self.node.downcast::<SharedEdge<N>>().map(|edge| *edge).map_err(|_| ConstructionError::Type {

View File

@@ -10,7 +10,7 @@ use core_types::arena::Arena;
use core_types::context::InjectIndex;
use core_types::gpoll::{Finality, GraphError};
use core_types::node::Node;
use core_types::record::{Group, GroupContent, GroupItem, LevelStatus, RecordCapture, RecordValue, materialize_level};
use core_types::record::{Group, GroupContent, GroupItem, LevelStatus, RecordValue, materialize_level};
use core_types::uuid::NodeId;
use glam::{DAffine2, DVec2};
use vector_types::GradientStops;
@@ -46,36 +46,35 @@ where
}
}
/// The captured wire as the legacy value the editor's downcasts expect: a
/// rank-0 capture is its element, a leveled `Graphic` capture becomes its
/// legacy list through the group bridge, and another element type becomes a
/// legacy list of that element. `None` for an element type outside the
/// legacy vocabulary or a capture whose arena generation has passed.
pub fn capture_to_legacy(capture: &RecordCapture, arena: &Arena) -> Option<Box<dyn std::any::Any + Send + Sync>> {
if capture.layout().depth == 0 {
// The group-carrying types legacy-convert while the capture is still
/// The resident batch as the legacy value the editor's downcasts expect: a
/// rank-0 wire is its element, a leveled `Graphic` wire becomes its legacy
/// list through the group bridge, and another element type becomes a legacy
/// list of that element. `None` for an element type outside the legacy
/// vocabulary or an empty rank-0 batch.
pub fn batch_to_legacy(layout: &core_types::record::Layout, batch: core_types::node::RecordBatch<'_>, _arena: &Arena) -> Option<Box<dyn std::any::Any + Send + Sync>> {
if layout.depth == 0 {
// The group-carrying types legacy-convert while the batch is
// resident; the deep clone-out would hand back the unreadable owned
// form.
if capture.lanes() > 0 {
let element = &capture.layout().element;
if element.type_id == std::any::TypeId::of::<Graphic>() {
let batch = capture.batch(arena)?;
// SAFETY: the layout records the element type, and a parked
// element stores its reference at offset 0.
let graphic = unsafe { core_types::record::borrow_element::<Graphic>(batch.get(0).rec()) };
return Some(Box::new(crate::graphic::map_groups_to_legacy(graphic)));
}
if element.type_id == std::any::TypeId::of::<Artboard>() {
let batch = capture.batch(arena)?;
// SAFETY: as for the graphic arm.
let artboard = unsafe { core_types::record::borrow_element::<Artboard>(batch.get(0).rec()) };
return Some(Box::new(artboard.with_legacy_groups()));
}
if batch.is_empty() {
return None;
}
return capture.materialize_element(arena);
let element = &layout.element;
if element.type_id == std::any::TypeId::of::<Graphic>() {
// SAFETY: the layout records the element type, and a parked
// element stores its reference at offset 0.
let graphic = unsafe { core_types::record::borrow_element::<Graphic>(batch.get(0).rec()) };
return Some(Box::new(crate::graphic::map_groups_to_legacy(graphic)));
}
if element.type_id == std::any::TypeId::of::<Artboard>() {
// SAFETY: as for the graphic arm.
let artboard = unsafe { core_types::record::borrow_element::<Artboard>(batch.get(0).rec()) };
return Some(Box::new(artboard.with_legacy_groups()));
}
// SAFETY: lane 0 is a live record of `layout`.
return Some(unsafe { (element.clone_out)(batch.get(0).rec().ptr()) });
}
let batch = capture.batch(arena)?;
// SAFETY: the captured bytes live in the arena for the capture's generation.
// SAFETY: the caller's batch is resident for the read.
let item = unsafe { GroupItem::from_resident(batch) };
fn typed<T: Clone + Send + Sync + 'static>(item: &GroupItem) -> Option<Box<dyn std::any::Any + Send + Sync>> {
run_to_legacy_list::<T>(item).map(|list| Box::new(list) as Box<dyn std::any::Any + Send + Sync>)

View File

@@ -2966,6 +2966,46 @@ impl Render for List<String> {
}
}
impl Render for RunView<'_, Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
render_artboard_svg(self, render, render_params)
}
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
render_artboard_vello(self, scene, transform, context, render_params)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
collect_artboard_metadata(self, metadata, footprint)
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
add_artboard_upstream_click_targets(self, click_targets)
}
fn contains_artboard(&self) -> bool {
self.lane_count() > 0
}
}
impl Render for RunView<'_, String> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
render_text_svg(self, render, render_params)
}
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
render_text_vello(self, scene, transform, render_params)
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
collect_text_metadata(self, metadata, footprint, caller_element_id)
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
add_text_upstream_click_targets(self, click_targets)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SvgSegment {
Slice(&'static str),