pax-runtime 0.39.1

Core shared runtime and rendering engine for Pax
Documentation
use super::*;
use kurbo::{Affine, BezPath, Rect};
use pax_runtime_api::{Paint, ResolvedMaterial, ResolvedStroke, SceneLighting};

#[derive(Default)]
struct RecordingRenderer {
    layer_count: usize,
    pending: bool,
    removals: Vec<(usize, u32)>,
    clears: Vec<usize>,
    lighting_updates: Vec<usize>,
    flushes: Vec<usize>,
}

impl RenderContext for RecordingRenderer {
    fn layers(&self) -> usize {
        self.layer_count
    }
    fn resize_layers_to(&mut self, count: usize, _: Rc<RefCell<Vec<bool>>>) {
        self.layer_count = count;
    }
    fn clear(&mut self, layer: usize) {
        assert!(layer < self.layer_count);
        self.clears.push(layer);
    }
    fn flush(&mut self, layer: usize, _: Rc<RefCell<Vec<bool>>>) {
        assert!(layer < self.layer_count);
        self.flushes.push(layer);
    }
    fn set_scene_lighting(&mut self, layer: usize, _: &SceneLighting) {
        assert!(layer < self.layer_count);
        self.lighting_updates.push(layer);
    }
    fn remove_node(&mut self, layer: usize, node: u32) -> bool {
        assert!(layer < self.layer_count);
        self.removals.push((layer, node));
        !self.pending
    }
    fn fill_with_opacity(&mut self, _: usize, _: BezPath, _: &Paint, _: f64) {
        unreachable!()
    }
    fn stroke_with_opacity(&mut self, _: usize, _: BezPath, _: &ResolvedStroke, _: f64) {
        unreachable!()
    }
    fn stroke_with_draw_range_and_material_and_opacity(
        &mut self,
        _: usize,
        _: BezPath,
        _: &ResolvedStroke,
        _: &ResolvedMaterial,
        _: f64,
        _: f64,
        _: f64,
    ) {
        unreachable!()
    }
    fn save(&mut self, _: usize) {
        unreachable!()
    }
    fn restore(&mut self, _: usize) {
        unreachable!()
    }
    fn clip(&mut self, _: usize, _: BezPath) {
        unreachable!()
    }
    fn transform(&mut self, _: usize, _: Affine) {
        unreachable!()
    }
    fn load_image(&mut self, _: &str, _: &[u8], _: usize, _: usize) {
        unreachable!()
    }
    fn draw_image(&mut self, _: usize, _: &str, _: Rect) {
        unreachable!()
    }
    fn draw_image_with_opacity(&mut self, _: usize, _: &str, _: Rect, _: f64) {
        unreachable!()
    }
    fn get_image_size(&mut self, _: &str) -> Option<(usize, usize)> {
        unreachable!()
    }
    fn image_loaded(&self, _: &str) -> bool {
        unreachable!()
    }
    fn resize(&mut self, _: usize, _: usize) {
        unreachable!()
    }
    fn refresh_layers(&mut self, _: &[usize]) {
        unreachable!()
    }
}

fn engine() -> PaxEngine {
    crate::test_support::empty_engine(
        (320.0, 240.0),
        Platform::Web,
        OS::Mac,
        Box::new(|| 0),
        Default::default(),
    )
}

#[test]
fn lighting_only_work_resolves_and_flushes_without_clearing_canvases() {
    let mut engine = engine();
    let mut renderer = RecordingRenderer::default();
    engine.runtime_context.layer_count.set(3);
    engine.render(&mut renderer);
    renderer.clears.clear();
    renderer.lighting_updates.clear();
    renderer.flushes.clear();
    engine.runtime_context.mark_scene_lighting_dirty();
    assert!(engine.runtime_context.has_canvas_render_work());
    assert!(!engine.runtime_context.has_dirty_canvas_layers());
    assert!(!engine.runtime_context.has_dirty_canvas_nodes());
    engine.render(&mut renderer);
    assert_eq!(renderer.lighting_updates, vec![0, 1, 2]);
    assert_eq!(renderer.flushes, vec![0, 1, 2]);
    assert!(renderer.clears.is_empty());
    assert!(!engine.runtime_context.has_canvas_render_work());

    engine.runtime_context.mark_scene_lighting_dirty();
    engine.runtime_context.set_canvas_dirty(1);
    engine.render(&mut renderer);
    assert_eq!(renderer.clears, vec![1], "independent redraw survives");
}

#[test]
fn removed_layers_discard_queued_node_removals() {
    let mut engine = engine();
    let mut renderer = RecordingRenderer::default();
    engine.runtime_context.layer_count.set(7);
    engine.render(&mut renderer);
    engine.runtime_context.enqueue_canvas_node_removal(6, 42);
    engine.runtime_context.enqueue_canvas_node_removal(0, 43);
    engine.runtime_context.layer_count.set(6);
    engine.render(&mut renderer);
    assert_eq!(renderer.removals, vec![(0, 43)]);
    assert!(!engine.runtime_context.has_canvas_render_work());
    engine.render(&mut renderer);
    assert_eq!(renderer.removals, vec![(0, 43)]);
}

#[test]
fn layer_synchronization_is_local_to_each_renderer_and_engine() {
    let mut first = engine();
    let mut second = engine();
    let mut first_renderer = RecordingRenderer::default();
    let mut second_renderer = RecordingRenderer::default();
    first.runtime_context.layer_count.set(2);
    second.runtime_context.layer_count.set(2);
    first.render(&mut first_renderer);
    second.render(&mut second_renderer);
    assert_eq!((first_renderer.layers(), second_renderer.layers()), (2, 2));
    assert_eq!(second.runtime_context.dirty_canvases.borrow().len(), 2);
    let mut replacement = RecordingRenderer::default();
    second.render(&mut replacement);
    assert_eq!(replacement.layers(), 2);
}

#[test]
fn pending_layer_removals_retry_until_the_backend_is_ready() {
    let mut engine = engine();
    let mut renderer = RecordingRenderer {
        pending: true,
        ..Default::default()
    };
    engine.runtime_context.layer_count.set(1);
    engine.runtime_context.enqueue_canvas_node_removal(0, 42);
    engine.render(&mut renderer);
    assert!(engine.runtime_context.has_canvas_node_removals());
    renderer.pending = false;
    engine.render(&mut renderer);
    assert_eq!(renderer.removals, vec![(0, 42), (0, 42)]);
    assert!(!engine.runtime_context.has_canvas_render_work());
}