From 4bc6a95ef6f15d091cbcab7e51be1af6439f2ad3 Mon Sep 17 00:00:00 2001 From: natepayn3 Date: Sat, 7 Nov 2026 21:27:41 -0700 Subject: [PATCH] A named surface beside the copies reads the copy on its monitor In a scene copied per monitor (`screens: each`), a named surface with its own `screens:` read the plain `screen.index`, `.width` and `.height`, which are copy 0's. A panel sent to the second monitor (`screens: some_text`) measured the first one, and every loose `let` it shared with the copies ("the panel is on this copy") was false there. So a shell could not draw a panel once, on its own surface, instead of in every copy, where it costs properties per monitor until the scene no longer fits in 65 536. Now such a surface reads `NAME.screen.*`, which the render keeps as the values of the copy on the monitor it is shown on (index -1 where there is no copy), and a loose `let` that reads per-copy values is read once more for it, as it is for each copy. Co-Authored-By: Claude Opus 5.5 --- docs/11-language-reference.md | 2 ++ src/language/compiler.rs | 63 ++++++++++++++++++++++++++++++++++- src/render.rs | 62 ++++++++++++++++++++++++++++++++++ src/scenes/from_file.rs | 24 +++++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) diff --git a/docs/11-language-reference.md b/docs/11-language-reference.md index 3c96ddc..fd81dc5 100644 --- a/docs/11-language-reference.md +++ b/docs/11-language-reference.md @@ -449,6 +449,8 @@ With the scene's surface (the one with no name), what is repeated is the loose d Its loose `prop`s and `fact`s, though, are **one for all the copies**: they are not drawing. So what a `follow` follows must not depend on which copy it is —`follow a = screen.index` ends up as the last copy's in every one—; say which monitor shows something in its `show:` instead, which is drawing and is read per copy. +**A named surface beside the copies** (one with its own `screens:`, such as a text naming the monitor a panel was opened on) reads `screen.index`, `screen.width` and `screen.height` from **the copy on its monitor**, and so do the loose `let`s it names: `let mine = if(panel_on == screen.index, 1, 0)` is 1 there when the copy it sits over is the panel's, as it is in that copy. On a monitor without a copy, `screen.index` is -1. So a panel the copies all open can be drawn once, on a surface of its own that follows the monitor, instead of once in every copy. + ``` surface { size: full, 44; anchor: top; screens: each } model mon max 4 { active: number = 1; title: text } // one record per monitor, from the logic diff --git a/src/language/compiler.rs b/src/language/compiler.rs index d70e09c..27a8f79 100644 --- a/src/language/compiler.rs +++ b/src/language/compiler.rs @@ -232,6 +232,9 @@ struct Scope { /// The strings with holes, already resolved where they were written: `Chip("{n.app}")`. contents: HashMap, alias: HashMap, + /// Inside a named surface beside the copies of `screens: each`: the mark its own + /// reading of a loose `let` is stored with (`crosses#on.panel`), as a copy's is with its suffix. + let_mark: String, /// Of those names, the ones that have parts: `label.width` belongs to the measure `label`. with_parts: std::collections::HashSet, suffix: String, @@ -1683,7 +1686,13 @@ impl<'a> Compiler<'a> { let n = &self.global(n); // Inside a screen copy, a `let` that was read once per // copy is stored with its mark: that is its own. - let marked = self.scopes.iter().rev().find(|e| e.suffix.starts_with("#screen")).map(|e| format!("{n}{}", e.suffix)).filter(|m| self.lets.contains_key(m)); + let marked = self + .scopes + .iter() + .rev() + .find_map(|e| if e.suffix.starts_with("#screen") { Some(&e.suffix) } else if !e.let_mark.is_empty() { Some(&e.let_mark) } else { None }) + .map(|mark| format!("{n}{mark}")) + .filter(|m| self.lets.contains_key(m)); let n = match &marked { Some(m) => m, None => n, @@ -2844,6 +2853,19 @@ impl<'a> Compiler<'a> { self.lets.insert(format!("{name}#screen{k}"), own); } } + // And once for each named surface beside them, with what its monitor's copy holds. + for surface in self.beside_copies() { + let env = self.own_screen_scope(&surface); + let mark = env.let_mark.clone(); + self.scopes.push(Rc::new(env)); + let mut c2 = Cur::new(&n.head[3..], n.line, n.col); + let own = self.expr(&mut c2); + self.scopes.pop(); + if let Ok(own) = own { + let own = if own.node_count() > 8 { self.computed_once(&name, own).e() } else { own }; + self.lets.insert(format!("{name}{mark}"), own); + } + } } } "zone" => { @@ -4679,6 +4701,12 @@ impl<'a> Compiler<'a> { env.alias.insert(format!("screen.{part}"), format!("{name}.{part}")); } Some(env) + } else if self.beside_copies().contains(&name) { + // Beside the copies of `screens: each`, a named surface reads its + // `screen.index`, `.width` and `.height` from the copy on ITS monitor + // (see `own_screen_scope`). + let env = self.own_screen_scope(&name); + Some(env) } else { None }; @@ -5073,6 +5101,39 @@ impl<'a> Compiler<'a> { props.iter().any(|p| self.copy_props.contains(p)) } + /// The named surfaces of a scene copied per monitor (not a lock, not a window): + /// each is one surface that goes to the monitors its `screens:` says. + fn beside_copies(&self) -> Vec { + if !self.e.surfaces.iter().any(|s| s.name.is_empty() && matches!(s.screens, Screens::Number(_))) { + return Vec::new(); + } + let mut out: Vec = Vec::new(); + for s in &self.e.surfaces { + if !s.name.is_empty() && !matches!(s.screens, Screens::Number(_)) && !s.lock_screen && s.window.is_none() && !out.contains(&s.name) { + out.push(s.name.clone()); + } + } + out + } + + /// Inside one of those: `screen.index`, `.width` and `.height` are the copy's on + /// its monitor, which the render keeps in `NAME.screen.*` (index -1 on a monitor + /// without a copy). They were the plain `screen.*`, which is copy 0's: a panel sent + /// to the second monitor measured the first, and every `if(panel_on == screen.index, …)` + /// it shared with the copies said no. + fn own_screen_scope(&mut self, name: &str) -> Scope { + let mut env = Scope { let_mark: format!("#on.{name}"), ..Default::default() }; + for part in ["index", "width", "height"] { + let full_name = format!("{name}.screen.{part}"); + if !self.facts.contains_key(&full_name) { + let h = self.e.fact(interned(&full_name), if part == "index" { -1.0 } else { 0.0 }); + self.facts.insert(full_name.clone(), h); + } + env.alias.insert(format!("screen.{part}"), full_name); + } + env + } + /// Inside a `screens: each` surface: what holds for that copy. fn screen_scope(&self, k: usize) -> Scope { let mut env = Scope { suffix: format!("#screen{k}"), ..Default::default() }; diff --git a/src/render.rs b/src/render.rs index eea0b7d..b6d58b2 100644 --- a/src/render.rs +++ b/src/render.rs @@ -550,6 +550,7 @@ pub fn run( // ── state ──────────────────────────────────────────────────── let mut scene = Scene::default(); + let mut own_screens = OwnScreens::default(); let mut props: Vec = Vec::new(); let mut facts: Vec = Vec::new(); let mut inside: Vec = Vec::new(); @@ -995,6 +996,7 @@ pub fn run( fresh.gestures.len(), fresh.rules.len(), fresh.zones.len() ); scene = fresh; + own_screens = OwnScreens::of(&scene); z_memo = None; window_instrs = None; draw.hidden.clear(); @@ -2873,6 +2875,7 @@ pub fn run( // And the copies of a `screens: each` surface for monitors that are not // there: `each max 4` with one monitor was four times the scene's rules // every frame, for three copies nobody sees. The first copy always runs. + own_screens.update(&sheets, &texts, &mut facts); let asleep: Vec<&crate::scene::Span> = { let c = Ctx { props: &props, facts: &facts, seen: None }; let shown = |k: usize| sheets.iter().any(|l| l.view.surface == k); @@ -5812,6 +5815,65 @@ fn error_banner(message: &str, width: f32) -> Vec { /// not one for each surface: the scene's named ones sit on the same monitor as its main /// one and are not another monitor. The lock's are left out: they go without a /// `SheetGone`, and their name is the monitor's with " (lock)" after it. +/// A named surface beside the copies of `screens: each` reads `NAME.screen.index`, +/// `.width` and `.height` for its `screen.*` (see the compiler): those of the copy on +/// the monitor it is shown on, which moves when its `screens:` text does. On a monitor +/// without a copy, index -1 (it is none of them) and the scene's `screen.width` and +/// `.height`. Where those names are is found once per scene, not every round. +#[derive(Default)] +struct OwnScreens { + /// Each such surface: its number, and its three facts (index, width, height). + surfaces: Vec<(usize, [usize; 3])>, + /// Each copy: its `screen.K.name` text and its width and height facts. + copies: Vec<(usize, Option, Option)>, + /// The scene's own `screen.width` and `screen.height`. + plain: (Option, Option), +} + +impl OwnScreens { + fn of(scene: &Scene) -> Self { + let fact = |n: &str| scene.facts.iter().position(|h| h.0 == n); + let surfaces = scene + .surfaces + .iter() + .enumerate() + .filter(|(_, s)| !s.name.is_empty() && !matches!(s.screens, Screens::Number(_)) && !s.lock_screen && s.window.is_none()) + .filter_map(|(k, s)| { + let [i, w, h] = ["index", "width", "height"].map(|part| fact(&format!("{}.screen.{part}", s.name))); + Some((k, [i?, w?, h?])) + }) + .collect::>(); + if surfaces.is_empty() { + return Self::default(); + } + let copies = (0..8) + .map_while(|c| { + let t = scene.texts.iter().position(|t| t.0 == format!("screen.{c}.name"))?; + Some((t, fact(&format!("screen.{c}.width")), fact(&format!("screen.{c}.height")))) + }) + .collect(); + Self { surfaces, copies, plain: (fact("screen.width"), fact("screen.height")) } + } + + fn update(&self, sheets: &[Sheet], texts: &[String], facts: &mut [f32]) { + for &(k, [fi, fw, fh]) in &self.surfaces { + let Some(monitor) = sheets.iter().find(|l| l.view.surface == k && l.view.popup.is_none()).map(|l| l.name.as_str()) else { continue }; + let copy = self.copies.iter().position(|(t, _, _)| texts.get(*t).is_some_and(|v| v == monitor)); + let (index, (w, h)) = match copy { + Some(c) => (c as f32, (self.copies[c].1, self.copies[c].2)), + None => (-1.0, self.plain), + }; + facts[fi] = index; + if let Some(v) = w.and_then(|i| facts.get(i).copied()) { + facts[fw] = v; + } + if let Some(v) = h.and_then(|i| facts.get(i).copied()) { + facts[fh] = v; + } + } + } +} + fn screens_showing(sheets: &[Sheet], surfaces: &[Surface]) -> usize { sheets .iter() diff --git a/src/scenes/from_file.rs b/src/scenes/from_file.rs index c9198f3..33b166c 100644 --- a/src/scenes/from_file.rs +++ b/src/scenes/from_file.rs @@ -559,6 +559,30 @@ mod tests { assert_eq!(b, vec![1050.0, 1150.0]); } + #[test] + fn a_named_surface_beside_the_copies_reads_the_copy_on_its_monitor() { + // A panel of its own (`screens: on`) sent to the second monitor said copy 0's + // `screen.index` and `screen.width`, so a `let` it shared with the copies + // —"the panel is on this copy"— was false there. + let set = set_by_rules("scene Beside { + surface { size: full, 40; anchor: top; screens: each max 2 } + text on = \"B\" + fact panel_on = 1 + let mine = if(panel_on == screen.index, 1, 0) + surface sheet { + size: 200, 200; anchor: top; screens: on + on demo { a = mine; b = screen.width; c = screen.index } + } + fact a = 0 + fact b = 0 + fact c = 0 +} +", &[("screen.0.width", 100.0), ("screen.1.width", 300.0), ("sheet.screen.index", 1.0), ("sheet.screen.width", 300.0)]); + assert_eq!(set["a"], vec![1.0]); + assert_eq!(set["b"], vec![300.0]); + assert_eq!(set["c"], vec![1.0]); + } + #[test] fn each_copy_of_a_surface_measures_its_own_monitor() { // A phone's monitor of 442 came last and said `bar.width` for every copy: the bar on