Skip to main content

blockworx/script/
session.rs

1//! The shared demo engine: a complete editing session — its own [`Repo`],
2//! the scope being edited, and the active real `Tool` — driven by
3//! synthetic [`SimFrame`]s through a [`SimDriver`] instead of a pointer. The
4//! headless test harness (`headless`) hands the driver a throwaway painter
5//! and gets the golden-replay tests; the tutorial player hands in its video
6//! pane's painter and every frame *is* a video frame.
7//!
8//! The session is the level's, not the user's: a level lowers to its own
9//! commit log and opens a [`Repo`] on it, so a demo's edits reach nothing
10//! outside this struct and a restart re-folds the same log.
11
12use crate::gesture::Gesture;
13use blockworx_doc::document::{DocIndex, Document as DocDocument};
14
15use crate::gesture;
16use crate::path::BlockPath;
17use crate::schema::lower::{SourceIds, lower};
18use crate::schema::model as schema;
19use crate::theme::Theme;
20#[cfg(test)]
21use crate::tools::RenameTitle;
22use crate::tools::SelectTool;
23use crate::tools::tool::{Tool, ToolTrait};
24use blockworx_doc::repo::Repo;
25
26use super::driver::{SimDriver, SimTarget};
27use super::lowering::SimFrame;
28use super::step::CueScope;
29
30pub struct Session {
31    repo: Repo,
32    index: DocIndex,
33    path: BlockPath,
34    presentation: crate::presentation::Presentation,
35    gesture: Gesture,
36    tool: Tool,
37    driver: SimDriver,
38    /// The level file's own `"b1"` / `"b1:p1"` spellings, which its cues
39    /// resolve through.
40    ids: SourceIds,
41}
42
43impl Session {
44    /// A session at a level's starting point: the level's own repo, seeded
45    /// with the log its initial document lowers to (or an empty one), with
46    /// the Select tool armed — exactly how the app loads a level. An initial
47    /// that fails to parse starts the session empty (the level parse test
48    /// catches broken embedded KDL long before this).
49    pub fn new(initial_kdl: Option<&str>, name: &str) -> Self {
50        let lowered = match initial_kdl {
51            None => crate::schema::lower::Lowered::default(),
52            Some(src) => match schema::Document::parse_kdl(src, name) {
53                Ok(doc) => lower(&doc, name),
54                Err(e) => {
55                    tracing::error!("session initial for {name} failed to parse: {e:?}");
56                    crate::schema::lower::Lowered::default()
57                }
58            },
59        };
60        let repo = Repo::folding(&lowered.commits).unwrap_or_else(|e| {
61            tracing::error!("session initial for {name} will not seed a repo: {e}");
62            Repo::default()
63        });
64        let path = BlockPath::opening(repo.document());
65        Self {
66            repo,
67            index: DocIndex::default(),
68            path,
69            presentation: crate::presentation::Presentation::default(),
70            gesture: gesture::Gesture::idle(),
71            tool: Tool::Select(SelectTool),
72            driver: SimDriver::default(),
73            ids: lowered.ids,
74        }
75    }
76
77    pub fn idle_frame(&self) -> SimFrame {
78        self.driver.idle_frame()
79    }
80
81    pub fn step(&mut self, frame: SimFrame, theme: &Theme, egui_painter: &egui::Painter) {
82        let Self {
83            repo,
84            index,
85            path,
86            presentation,
87            gesture,
88            tool,
89            driver,
90            ..
91        } = self;
92        driver.apply(
93            frame,
94            SimTarget {
95                repo,
96                index,
97                path,
98                presentation,
99                gesture,
100                tool,
101            },
102            theme,
103            egui_painter,
104        );
105    }
106
107    /// What this session's cues resolve against.
108    pub fn cue_scope(&mut self) -> CueScope<'_> {
109        let doc: &DocDocument = self.repo.document();
110        CueScope::new(self.index.view(doc), &self.ids)
111    }
112
113    /// The armed tool, for the video's presentational toolbar.
114    pub fn tool_name(&self) -> crate::tools::names::ToolName {
115        self.tool.name()
116    }
117
118    /// The document the demo has built.
119    #[cfg(test)]
120    pub fn document(&self) -> &blockworx_doc::document::Document {
121        self.repo.document()
122    }
123
124    /// The commits behind that document: the level's own lowered initial,
125    /// then one per edit the demo made.
126    #[cfg(test)]
127    pub fn log(&self) -> &[blockworx_doc::commit::Commit] {
128        self.repo.log()
129    }
130
131    /// Whether the session is mid-rename (used by tests to sanity-check the
132    /// editor seam engaged).
133    #[cfg(test)]
134    pub fn renaming(&self) -> bool {
135        matches!(self.tool, Tool::RenameTitle(RenameTitle::Renaming { .. }))
136    }
137}