# Map Renderer Rewrite Plan **Date:** 2026-07-26 **Status:** Draft **Scope:** `makepad/widgets/src/map/` (8 files, ~9,000 lines) **Constraint:** Preserve Makepad Widget/Script API, all 184+ existing tests, downstream book.rs DSL --- ## Problem Statement The renderer is a monolithic `MapView` struct (1,882 lines) that owns viewport, scheduling, caching, rendering, labels, interaction, and style in one object. The review identified: 1. **Modular architecture** — 8.5/10: Good ideas, but everything lives in one struct 2. **Performance** — 8/10: CPU-bound draw, unnecessary HashMap lookups, per-frame recomputation 3. **Responsibility boundaries** — Scheduler knows about rendering, rendering knows about workers 4. **Hidden state machine** — `TileLoadState` scattered as `if loading/if ready/if cached/if dirty` 5. **Cache ownership** — Who evicts? Who marks stale? Who retries? Not obvious 6. **Mutable state** — Dozens of fields mutated from multiple code paths 7. **No render graph** — Everything funnels into one draw path 8. **Long functions** — 200-500 line methods with deep nesting --- ## Target Architecture ### Current Module Structure ``` map/ mod.rs (13 lines) — pub exports view.rs (1882 lines) — MapView widget: EVERYTHING tile.rs (2163 lines) — Tile types + loading + tessellation + disk cache geometry.rs (1968 lines) — Projection, DP, tessellation (well-factored) label.rs (1098 lines) — Label extraction, scoring, collision grid style.rs (496 lines) — Theme system style_json.rs(3227 lines) — JSON parser + tests (behind map_style feature) asset_loader.rs (124 lines) — Sprite/glyph caches sprite.rs (433 lines) — POI icon classification ``` ### Proposed Module Structure ``` map/ mod.rs — pub exports (MapView + subsystems for testing) view.rs — THIN Widget shell (~400 lines): DSL, #[live] props, delegates to subsystems viewport.rs — NEW: ViewportState (center, zoom, screen<->world, wrap/clamp) scheduler.rs — NEW: TileScheduler (request queue, retry, prioritization, cancellation) cache.rs — NEW: TileCache (HashMap, eviction, dedup, generation, GC) renderer.rs — NEW: RenderPass (fill, stroke, POI, label passes with explicit ordering) label_state.rs — NEW: LabelState (all scratch buffers, collision grid, placement) tile.rs — SLIMMED: Tile types only (TileKey, TileEntry, TileLoadState enum, message types) tile_decode.rs — NEW: MVT parsing, tessellation, Overpass body processing (extracted from tile.rs) tile_disk.rs — NEW: Disk cache read/write, mbtiles batch loading (extracted from tile.rs) geometry.rs — UNCHANGED label.rs — UNCHANGED (scoring/extraction only) style.rs — UNCHANGED style_json.rs — UNCHANGED asset_loader.rs — UNCHANGED sprite.rs — UNCHANGED ``` **Total new files:** 6 **Total modified files:** 3 (view.rs, tile.rs, mod.rs) **Total unchanged files:** 6 (geometry, label, style, style_json, asset_loader, sprite) --- ## New Types — Detailed Design ### 1. `ViewportState` (viewport.rs, ~150 lines) **Addresses:** Review points #1, #4 (mutable state, coupling) Extracts all viewport/coordinate logic from `view.rs`. No dependency on tiles, workers, or rendering. ```rust pub struct ViewportState { center_norm: Vec2d, // [0,1] Web Mercator zoom: f64, min_zoom: f64, max_zoom: f64, view_rect: Rect, // pixel rect of widget area } // Pure functions, no Cx dependency impl ViewportState { pub fn view_zoom(&self) -> f64; pub fn request_zoom_level(&self, use_local: bool) -> u32; pub fn world_size(&self) -> f64; // 256 * 2^zoom pub fn center_world(&self) -> Vec2d; // center_norm * world_size pub fn map_offset(&self) -> Vec2f; // screen offset for GPU uniforms pub fn wrap_and_clamp(&mut self); pub fn set_rect(&mut self, rect: Rect); // Interaction pub fn apply_drag(&mut self, delta_pixels: Vec2d); pub fn apply_pinch(&mut self, initial_zoom: f64, initial_center: Vec2d, initial_distance: f64, new_distance: f64, center: Vec2d); pub fn apply_scroll(&mut self, scroll: f64, anchor_abs: Vec2d); // Coordinate transforms pub fn screen_to_world(&self, screen_pos: Vec2d) -> Vec2d; pub fn world_to_screen(&self, world_pos: Vec2d) -> Vec2d; pub fn tile_world_size_zoom(zoom: f64) -> f64; // static // Visible tile computation pub fn visible_tile_keys(&self) -> Vec; // Property sync pub fn sync_from_properties(&mut self, center_lon: f64, center_lat: f64, zoom: f64); } ``` **Current code moving here:** - `view_zoom()`, `request_zoom_level()` (view.rs:1851-1863) - `wrap_and_clamp_center()` (view.rs:1064-1067) - `zoom_with_anchor()` (view.rs:1069-1100) - `visible_tile_keys()` (view.rs:1185-1226) - Part of `on_after_apply` that sets center_norm, zoom - Part of `draw_walk` that computes map_offset - Part of `handle_event` FingerMove/FingerDown/FingerUp interaction --- ### 2. `TileCache` (cache.rs, ~200 lines) **Addresses:** Review points #3, #4, #19 (cache ownership, mutable state, memory growth) Single owner of tile lifetime. Every mutation goes through `TileCache` methods. ```rust pub struct TileCache { tiles: HashMap, frame_counter: u64, style_epoch: u64, max_tiles: usize, // default 640 stale_frame_threshold: u64, // 240 frames } ``` **Explicit `TileLoadState` enum** (review point #5 — replace hidden state machine): ```rust pub enum TileLoadState { Missing, LoadingLocal, LoadingNetwork, Ready { fill_geometry: Option, stroke_geometry: Option, feature_count: usize, labels: Vec