Skip to main content

blockworx_kernel/
driving.rs

1//! Driving the kernel the way a front end does — batches of events on a
2//! 60 Hz clock over a fixed viewport — for the suites and benches above it.
3//!
4//! The camera rests at unity with the viewport at the origin, so screen and
5//! world agree until something moves the camera.
6
7use core::time::Duration;
8
9use blockworx_doc::{document::Document, id::BlockId};
10use blockworx_geom::{Pos2, Rect};
11use blockworx_paint::{Button, Raw, Tick};
12use blockworx_store::{
13    doc::Doc,
14    handle::{Clock, Store},
15    record::Identity,
16    storage::{Any, Memory},
17};
18
19use crate::{Event, Session};
20
21/// The canvas every batch reports.
22pub const VIEWPORT: Rect = Rect {
23    min: Pos2::ZERO,
24    max: Pos2 { x: 800.0, y: 600.0 },
25};
26
27/// One 60 Hz frame, so the easing table advances the way it does live.
28pub const FRAME: Duration = Duration::from_millis(16);
29
30/// The tick for `millis` into the session.
31#[must_use]
32pub fn at(millis: u64) -> Tick {
33    Tick::predicting(Duration::from_millis(millis), FRAME)
34}
35
36/// A batch as a front end sends one: what time it is and how big the canvas
37/// is, in front of whatever happened.
38#[must_use]
39pub fn batch(millis: u64, events: Vec<Event>) -> Vec<Event> {
40    batch_in(VIEWPORT, millis, events)
41}
42
43/// The same, from a canvas of another size.
44#[must_use]
45pub fn batch_in(viewport: Rect, millis: u64, events: Vec<Event>) -> Vec<Event> {
46    let mut batch = vec![Event::Tick(at(millis)), Event::Viewport(viewport)];
47    batch.extend(events);
48    batch
49}
50
51/// The pointer over the canvas, here.
52#[must_use]
53pub fn moved(pos: Pos2) -> Event {
54    Event::Pointer(Raw::Moved(pos))
55}
56
57/// The primary button pressed at `pos`.
58#[must_use]
59pub fn down(pos: Pos2) -> Event {
60    Event::Pointer(Raw::Down {
61        pos,
62        button: Button::Primary,
63    })
64}
65
66/// The primary button released at `pos`.
67#[must_use]
68pub fn up(pos: Pos2) -> Event {
69    Event::Pointer(Raw::Up {
70        pos,
71        button: Button::Primary,
72    })
73}
74
75/// Every block of the viewed document, in id order.
76#[must_use]
77pub fn blocks(session: &Session) -> Vec<BlockId> {
78    let document: &Document = session.viewed_document();
79    let mut ids: Vec<_> = document.blocks().map(|(id, _)| id).collect();
80    ids.sort();
81    ids
82}
83
84/// The `n`×`n` autogen grid, settled — every wire routed and its corners
85/// written. Settling is the slow part, so it is done once and sessions are
86/// seeded from it.
87pub struct SettledGrid(blockworx_doc::commit::Commit);
88
89impl SettledGrid {
90    /// # Panics
91    ///
92    /// If the generated grid fails to settle, which is a fixture bug.
93    #[must_use]
94    pub fn new(n: usize) -> Self {
95        #[expect(clippy::expect_used, reason = "a fixture that cannot build is a bug")]
96        let commit = blockworx_editor::widget::drawing::settle_corners(
97            blockworx_doc::fixtures::scale::build_scale(n),
98        )
99        .expect("the grid settles")
100        .creating_commit("Generated")
101        .expect("a grid to create");
102        Self(commit)
103    }
104
105    /// A session over the grid, attached to a container over memory, so a
106    /// commit pays for the rev it writes as the browser does. Not yet framed:
107    /// its first kernel call is the open.
108    ///
109    /// # Panics
110    ///
111    /// If the container cannot be seeded, which is a fixture bug.
112    #[must_use]
113    pub fn session(&self) -> Session {
114        #[expect(clippy::expect_used, reason = "a fixture that cannot build is a bug")]
115        let store = Store::seeded(
116            Any::new(Memory::new("scale.bwx")),
117            Clock::System,
118            std::slice::from_ref(&self.0),
119            &Identity::from_environment(),
120        )
121        .expect("the container");
122        Session::opening(Doc::attached(store), Identity::from_environment())
123    }
124}