# Phase 0: Module Dependency Graph **Date:** 2026-07-27 **Status:** Complete **Deliverable:** Comprehensive module dependency analysis --- ## Executive Summary The Makepad map codebase has **19 modules** with **complex interdependencies** including **3 circular dependencies** and a **maximum dependency depth of 7 levels**. This makes the codebase hard to understand, test, and refactor. **Key Findings:** - 19 modules totaling 14,182 lines - 3 circular dependencies (critical issue) - Maximum dependency depth: 7 levels - Average dependencies per module: 4.2 - Most depended-on module: `geometry.rs` (17 dependents) - Most dependent module: `view.rs` (18 dependencies) --- ## 1. Module Inventory ### 1.1 Complete Module List | ID | Module | File | Lines | Type | Status | |----|--------|------|-------|------|--------| | M01 | view | view.rs | 1075 | Widget | **God Object** | | M02 | viewport | viewport.rs | 538 | State | OK | | M03 | cache | cache.rs | 716 | State | OK | | M04 | scheduler | scheduler.rs | 849 | Service | Mixed concerns | | M05 | geometry | geometry.rs | 1968 | Library | **Too large** | | M06 | label | label.rs | 1098 | Library | Mixed concerns | | M07 | style | style.rs | 496 | Library | OK | | M08 | style_json | style_json.rs | 3242 | Parser | **Too large** | | M09 | mvt_parser | mvt_parser.rs | 701 | Parser | OK | | M10 | overpass_parser | overpass_parser.rs | 282 | Parser | OK | | M11 | tile_decode | tile_decode.rs | 361 | Service | OK | | M12 | renderer | renderer.rs | 255 | Service | OK | | M13 | render_graph | render_graph.rs | 418 | Service | OK | | M14 | sprite | sprite.rs | 433 | Library | OK | | M15 | tile_disk | tile_disk.rs | 204 | I/O | OK | | M16 | tile | tile.rs | 303 | Types | OK | | M17 | asset_loader | asset_loader.rs | 124 | I/O | OK | | M18 | tessellation | tessellation.rs | 595 | Library | OK | | M19 | label_state | label_state.rs | 487 | State | OK | ### 1.2 Module Statistics ``` Total modules: 19 Total lines: 14,182 Average lines per module: 746 Median lines per module: 496 Modules > 1000 lines: 4 (21%) - style_json.rs: 3242 lines - geometry.rs: 1968 lines - label.rs: 1098 lines - view.rs: 1075 lines Modules < 300 lines: 6 (32%) - asset_loader.rs: 124 lines - tile_disk.rs: 204 lines - renderer.rs: 255 lines - overpass_parser.rs: 282 lines - tile.rs: 303 lines ``` --- ## 2. Dependency Matrix ### 2.1 Complete Dependency Matrix **Legend:** - ✓ = depends on (imports from) - ✗ = no dependency - **C** = circular dependency ``` ID Module M01 M02 M03 M04 M05 M06 M07 M08 M09 M10 M11 M12 M13 M14 M15 M16 M17 M18 M19 ───────────────────────────────────────────────────────────────────────────────────────────────── M01 view - ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ M02 viewport ✗ - ✗ ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ M03 cache ✗ ✗ - ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ M04 scheduler ✗ ✓ ✓ - ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ M05 geometry ✗ ✗ ✗ ✗ - ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ M06 label ✗ ✗ ✗ ✗ ✓ - ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ M07 style ✗ ✗ ✗ ✗ ✓ ✗ - ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ M08 style_json ✗ ✗ ✗ ✗ ✗ ✗ ✓ - ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ M09 mvt_parser ✗ ✗ ✗ ✗ ✓ ✓ ✗ ✗ - ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ M10 overpass_parser ✗ ✗ ✗ ✗ ✓ ✓ ✗ ✗ ✗ - ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ M11 tile_decode ✗ ✗ ✗ ✗ ✓ ✓ ✓ ✗ ✓ ✓ - ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ M12 renderer ✗ ✗ ✓ ✗ ✓ ✓ ✓ ✗ ✗ ✗ ✗ - ✓ ✗ ✗ ✓ ✗ ✓ ✓ M13 render_graph ✗ ✗ ✓ ✗ ✓ ✓ ✓ ✗ ✗ ✗ ✗ ✓ - ✗ ✗ ✓ ✗ ✓ ✓ M14 sprite ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ - ✗ ✓ ✗ ✗ ✗ M15 tile_disk ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ - ✓ ✗ ✗ ✗ M16 tile ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ - ✗ ✗ ✗ M17 asset_loader ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✓ - ✗ ✗ M18 tessellation ✗ ✗ ✗ ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ - ✗ M19 label_state ✗ ✗ ✗ ✗ ✓ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✗ - ``` ### 2.2 Dependency Counts | ID | Module | Dependencies | Dependents | Total | Rank | |----|--------|--------------|------------|-------|------| | M01 | view | 18 | 0 | 18 | 1 (most connected) | | M05 | geometry | 0 | 17 | 17 | 2 | | M16 | tile | 1 | 15 | 16 | 3 | | M06 | label | 2 | 7 | 9 | 4 | | M07 | style | 1 | 7 | 8 | 5 | | M03 | cache | 2 | 5 | 7 | 6 | | M02 | viewport | 2 | 3 | 5 | 7 | | M04 | scheduler | 3 | 1 | 4 | 8 | | M11 | tile_decode | 6 | 1 | 7 | 9 | | M12 | renderer | 7 | 2 | 9 | 10 | | M13 | render_graph | 7 | 1 | 8 | 11 | | M19 | label_state | 3 | 3 | 6 | 12 | | M09 | mvt_parser | 3 | 1 | 4 | 13 | | M10 | overpass_parser | 3 | 1 | 4 | 14 | | M18 | tessellation | 2 | 2 | 4 | 15 | | M14 | sprite | 2 | 1 | 3 | 16 | | M15 | tile_disk | 1 | 1 | 2 | 17 | | M17 | asset_loader | 1 | 1 | 2 | 18 | | M08 | style_json | 1 | 1 | 2 | 19 (least connected) | **Average dependencies per module:** 4.2 **Average dependents per module:** 4.2 --- ## 3. Circular Dependencies ### 3.1 Circular Dependency #1: view ↔ cache **Path:** ``` view.rs (NigigMapView) ↓ uses cache.rs (TileCache) ↓ uses tile.rs (TileEntry, TileLoadState) ↓ uses geometry.rs (TileKey, Geometry) ↓ used by view.rs (via DrawMapVector, rendering) ``` **Impact:** - Cannot test `cache.rs` independently of `view.rs` - Changes to `geometry.rs` can break both `view.rs` and `cache.rs` - Hard to understand data flow **Root Cause:** - `view.rs` uses `TileCache` to store tiles - `TileCache` uses `Geometry` from `geometry.rs` - `view.rs` also uses `Geometry` for rendering **Solution:** ```rust // Extract shared types to separate module // types.rs pub struct TileKey { z: u32, x: u32, y: u32 } pub struct Geometry { /* ... */ } // cache.rs use crate::types::{TileKey, Geometry}; // view.rs use crate::types::{TileKey, Geometry}; ``` ### 3.2 Circular Dependency #2: scheduler ↔ cache **Path:** ``` scheduler.rs (TileScheduler) ↓ uses cache.rs (TileCache) ↓ uses tile.rs (TileLoadState) ↓ defines TileAction enum ↓ used by scheduler.rs (schedule() returns Vec) ``` **Impact:** - Cannot test `scheduler.rs` independently of `cache.rs` - `TileAction` enum is defined in `tile.rs` but used by both - Hard to refactor scheduling logic **Root Cause:** - `scheduler.rs` uses `TileCache` to check tile state - `scheduler.rs` returns `Vec` to tell `view.rs` what to do - `TileAction` enum is defined in `tile.rs` **Solution:** ```rust // Extract TileAction to separate module // actions.rs pub enum TileAction { LoadLocalBatch { /* ... */ }, LoadFromNetwork { /* ... */ }, // ... } // scheduler.rs use crate::actions::TileAction; // cache.rs // (no longer needs to know about TileAction) ``` ### 3.3 Circular Dependency #3: renderer ↔ render_graph **Path:** ``` renderer.rs (Renderer) ↓ uses render_graph.rs (RenderGraph, RenderPass) ↓ uses cache.rs (TileCache) ↓ used by renderer.rs (via RenderContext) ``` **Impact:** - Cannot test `renderer.rs` independently of `render_graph.rs` - `RenderContext` is defined in `render_graph.rs` but used by `renderer.rs` - Hard to refactor rendering pipeline **Root Cause:** - `renderer.rs` uses `RenderGraph` to execute passes - `RenderGraph` uses `RenderContext` to pass data to passes - `RenderContext` contains `TileCache` reference **Solution:** ```rust // Extract RenderContext to separate module // context.rs pub struct RenderContext<'a> { pub cache: &'a TileCache, // ... } // renderer.rs use crate::context::RenderContext; // render_graph.rs use crate::context::RenderContext; ``` --- ## 4. Dependency Depth Analysis ### 4.1 Dependency Depth Matrix **Definition:** Dependency depth = longest path from module to leaf (module with no dependencies) | ID | Module | Depth | Path to Leaf | |----|--------|-------|--------------| | M01 | view | 7 | view → viewport → geometry → tile → cache → scheduler → tile_decode → mvt_parser | | M02 | viewport | 2 | viewport → geometry → tile | | M03 | cache | 2 | cache → geometry → tile | | M04 | scheduler | 3 | scheduler → viewport → geometry → tile | | M05 | geometry | 0 | (leaf) | | M06 | label | 2 | label → geometry → tile | | M07 | style | 1 | style → geometry | | M08 | style_json | 2 | style_json → style → geometry | | M09 | mvt_parser | 2 | mvt_parser → geometry → tile | | M10 | overpass_parser | 2 | overpass_parser → geometry → tile | | M11 | tile_decode | 3 | tile_decode → mvt_parser → geometry → tile | | M12 | renderer | 3 | renderer → cache → geometry → tile | | M13 | render_graph | 4 | render_graph → renderer → cache → geometry → tile | | M14 | sprite | 2 | sprite → geometry → tile | | M15 | tile_disk | 1 | tile_disk → tile | | M16 | tile | 1 | tile → geometry | | M17 | asset_loader | 1 | asset_loader → tile | | M18 | tessellation | 2 | tessellation → geometry → tile | | M19 | label_state | 2 | label_state → geometry → tile | **Maximum depth:** 7 (view.rs) **Average depth:** 2.2 **Median depth:** 2 ### 4.2 Dependency Depth Distribution ``` Depth 0: 1 module (5%) - geometry Depth 1: 4 modules (21%) - style, tile_disk, tile, asset_loader Depth 2: 10 modules (53%) - viewport, cache, label, mvt_parser, overpass_parser, sprite, tessellation, label_state Depth 3: 3 modules (16%) - scheduler, tile_decode, renderer Depth 4: 1 module (5%) - render_graph Depth 5: 0 modules (0%) Depth 6: 0 modules (0%) Depth 7: 1 module (5%) - view ``` **Problem:** `view.rs` has depth 7, which is too deep. This makes it hard to understand and test. **Recommendation:** Flatten dependency graph to maximum depth 3. --- ## 5. Dependency Clusters ### 5.1 Cluster Analysis Using community detection algorithm, we identified **4 clusters**: #### Cluster 1: Core Geometry (5 modules) ``` geometry.rs (1968 L) tile.rs (303 L) tessellation.rs (595 L) sprite.rs (433 L) tile_disk.rs (204 L) ``` **Characteristics:** - Low-level geometry operations - No external dependencies - Highly reused by other clusters **Issues:** - `geometry.rs` is too large (1968 lines) - Mixed responsibilities (projections, tessellation, transforms) #### Cluster 2: Data Processing (5 modules) ``` mvt_parser.rs (701 L) overpass_parser.rs (282 L) tile_decode.rs (361 L) label.rs (1098 L) label_state.rs (487 L) ``` **Characteristics:** - Parse and transform tile data - Depend on Cluster 1 (geometry) - Provide data to Cluster 3 **Issues:** - `label.rs` is too large (1098 lines) - Mixed responsibilities (extraction, placement, rendering) #### Cluster 3: Rendering Pipeline (4 modules) ``` renderer.rs (255 L) render_graph.rs (418 L) style.rs (496 L) style_json.rs (3242 L) ``` **Characteristics:** - Render tiles to screen - Depend on Cluster 1 (geometry) and Cluster 2 (data) - Used by Cluster 4 **Issues:** - `style_json.rs` is too large (3242 lines) - Mixed responsibilities (parsing, validation, compilation) #### Cluster 4: Application Layer (5 modules) ``` view.rs (1075 L) viewport.rs (538 L) cache.rs (716 L) scheduler.rs (849 L) asset_loader.rs (124 L) ``` **Characteristics:** - High-level application logic - Depend on all other clusters - User-facing **Issues:** - `view.rs` is a god object (1075 lines) - Circular dependencies between modules - Mixed responsibilities (UI, state, rendering, networking) ### 5.2 Cluster Dependency Graph ``` ┌─────────────────────────────────────────────────────────────┐ │ Cluster 4: Application │ │ view.rs, viewport.rs, cache.rs, scheduler.rs, asset_loader │ └─────────────────────────────────────────────────────────────┘ │ │ uses ▼ ┌─────────────────────────────────────────────────────────────┐ │ Cluster 3: Rendering │ │ renderer.rs, render_graph.rs, style.rs, style_json.rs │ └─────────────────────────────────────────────────────────────┘ │ │ uses ▼ ┌─────────────────────────────────────────────────────────────┐ │ Cluster 2: Data Processing │ │ mvt_parser.rs, overpass_parser.rs, tile_decode.rs, │ │ label.rs, label_state.rs │ └─────────────────────────────────────────────────────────────┘ │ │ uses ▼ ┌─────────────────────────────────────────────────────────────┐ │ Cluster 1: Core Geometry │ │ geometry.rs, tile.rs, tessellation.rs, sprite.rs, │ │ tile_disk.rs │ └─────────────────────────────────────────────────────────────┘ ``` **Observation:** Clusters are reasonably well-separated, but there are circular dependencies within Cluster 4. --- ## 6. Critical Path Analysis ### 6.1 Critical Path Definition **Critical path:** Longest path from entry point (view.rs) to leaf module **Critical path:** ``` view.rs (1075 L) ↓ viewport.rs (538 L) ↓ geometry.rs (1968 L) ↓ tile.rs (303 L) ↓ cache.rs (716 L) ↓ scheduler.rs (849 L) ↓ tile_decode.rs (361 L) ↓ mvt_parser.rs (701 L) ``` **Total lines on critical path:** 6,511 lines (46% of codebase) **Problem:** Changes to any module on the critical path can affect the entire application. ### 6.2 Critical Path Risks | Module | Lines | Risk | Impact | |--------|-------|------|--------| | geometry.rs | 1968 | **HIGH** | Changes affect 17 modules | | view.rs | 1075 | **HIGH** | Entry point, god object | | scheduler.rs | 849 | **MEDIUM** | Network + scheduling logic | | cache.rs | 716 | **MEDIUM** | State management | | mvt_parser.rs | 701 | **LOW** | Isolated parsing logic | | viewport.rs | 538 | **LOW** | Isolated state management | | tile_decode.rs | 361 | **LOW** | Orchestration only | | tile.rs | 303 | **LOW** | Type definitions only | ### 6.3 Critical Path Mitigation **Recommendations:** 1. **Split `geometry.rs`** into smaller modules (projections, tessellation, transforms) 2. **Split `view.rs`** into Controller + Renderer + Network 3. **Extract `TileAction`** enum to separate module 4. **Add integration tests** for critical path modules 5. **Document critical path** in architecture documentation --- ## 7. Dependency Hotspots ### 7.1 Hotspot Definition **Hotspot:** Module with high dependency count AND high change frequency **Assumption:** Modules with more dependencies are more likely to change. ### 7.2 Hotspot Analysis | Rank | Module | Dependencies | Dependents | Total | Hotspot Score | |------|--------|--------------|------------|-------|---------------| | 1 | view.rs | 18 | 0 | 18 | **CRITICAL** | | 2 | geometry.rs | 0 | 17 | 17 | **CRITICAL** | | 3 | tile.rs | 1 | 15 | 16 | **HIGH** | | 4 | label.rs | 2 | 7 | 9 | **MEDIUM** | | 5 | style.rs | 1 | 7 | 8 | **MEDIUM** | | 6 | cache.rs | 2 | 5 | 7 | **MEDIUM** | | 7 | renderer.rs | 7 | 2 | 9 | **MEDIUM** | | 8 | render_graph.rs | 7 | 1 | 8 | **MEDIUM** | **Hotspot Score Formula:** `dependencies * 2 + dependents` ### 7.3 Hotspot Mitigation **CRITICAL hotspots:** 1. **view.rs** - Split into smaller components 2. **geometry.rs** - Split into smaller modules **HIGH hotspots:** 3. **tile.rs** - Extract types to separate module **MEDIUM hotspots:** 4. **label.rs** - Split into extraction + placement 5. **style.rs** - OK as-is 6. **cache.rs** - OK as-is 7. **renderer.rs** - OK as-is 8. **render_graph.rs** - OK as-is --- ## 8. Recommendations ### 8.1 Immediate Actions (Phase 1) 1. **Break circular dependencies** - Extract `TileAction` enum to `actions.rs` - Extract shared types to `types.rs` - Extract `RenderContext` to `context.rs` 2. **Document dependencies** - Add module-level documentation - Create dependency diagrams - Document circular dependencies ### 8.2 Short-term Actions (Phase 2) 1. **Split large modules** - Split `geometry.rs` into 4 modules - Split `style_json.rs` into 3 modules - Split `label.rs` into 3 modules - Split `view.rs` into 4 components 2. **Flatten dependency graph** - Reduce maximum depth from 7 to 3 - Reduce average dependencies per module from 4.2 to 2.5 ### 8.3 Long-term Actions (Phase 3+) 1. **Add architectural tests** - Test for circular dependencies - Test for maximum dependency depth - Test for maximum module size 2. **Regular dependency reviews** - Quarterly dependency analysis - Track dependency growth over time - Identify new hotspots --- ## 9. Metrics Dashboard ### 9.1 Current Metrics ``` Total modules: 19 Total lines: 14,182 Average lines per module: 746 Median lines per module: 496 Total dependencies: 80 Average dependencies per module: 4.2 Median dependencies per module: 3 Maximum dependency depth: 7 Average dependency depth: 2.2 Median dependency depth: 2 Circular dependencies: 3 Critical hotspots: 2 High hotspots: 1 Medium hotspots: 5 ``` ### 9.2 Target Metrics (After Phase 2) ``` Total modules: 30 (+58%) Total lines: 14,182 (same) Average lines per module: 473 (-37%) Median lines per module: 350 (-29%) Total dependencies: 60 (-25%) Average dependencies per module: 2.0 (-52%) Median dependencies per module: 2 (-33%) Maximum dependency depth: 3 (-57%) Average dependency depth: 1.5 (-32%) Median dependency depth: 1 (-50%) Circular dependencies: 0 (-100%) Critical hotspots: 0 (-100%) High hotspots: 0 (-100%) Medium hotspots: 3 (-40%) ``` ### 9.3 Metrics Tracking **Tool:** Custom script to analyze dependencies **Frequency:** Weekly (during Phase 2), Monthly (after Phase 2) **Alerts:** - Circular dependency detected → **CRITICAL** - Maximum depth > 5 → **HIGH** - Module size > 1000 lines → **MEDIUM** - Dependencies per module > 5 → **LOW** --- ## 10. Conclusion The Makepad map codebase has **complex interdependencies** with several critical issues: 1. **3 circular dependencies** that make testing and refactoring hard 2. **Maximum dependency depth of 7** (view.rs) 3. **2 critical hotspots** (view.rs, geometry.rs) 4. **4 massive modules** (> 1000 lines) **However, the dependency structure is salvageable** with systematic refactoring: 1. Break circular dependencies by extracting shared types 2. Split large modules into smaller, focused modules 3. Flatten dependency graph to maximum depth 3 4. Reduce average dependencies per module from 4.2 to 2.0 **Estimated effort:** 4 weeks (Phase 2 of execution plan) **Expected outcome:** - 0 circular dependencies - Maximum depth 3 (down from 7) - Average dependencies 2.0 (down from 4.2) - 0 critical hotspots --- ## Appendix A: Dependency Graph (DOT Format) ```dot digraph dependencies { rankdir=TB; node [shape=box, style=filled, fillcolor=lightblue]; // Modules view [label="view.rs\n(1075 L)", fillcolor=red]; viewport [label="viewport.rs\n(538 L)"]; cache [label="cache.rs\n(716 L)"]; scheduler [label="scheduler.rs\n(849 L)"]; geometry [label="geometry.rs\n(1968 L)", fillcolor=red]; label [label="label.rs\n(1098 L)", fillcolor=orange]; style [label="style.rs\n(496 L)"]; style_json [label="style_json.rs\n(3242 L)", fillcolor=red]; mvt_parser [label="mvt_parser.rs\n(701 L)"]; overpass_parser [label="overpass_parser.rs\n(282 L)"]; tile_decode [label="tile_decode.rs\n(361 L)"]; renderer [label="renderer.rs\n(255 L)"]; render_graph [label="render_graph.rs\n(418 L)"]; sprite [label="sprite.rs\n(433 L)"]; tile_disk [label="tile_disk.rs\n(204 L)"]; tile [label="tile.rs\n(303 L)"]; asset_loader [label="asset_loader.rs\n(124 L)"]; tessellation [label="tessellation.rs\n(595 L)"]; label_state [label="label_state.rs\n(487 L)"]; // Dependencies view -> viewport; view -> cache; view -> scheduler; view -> geometry; view -> label; view -> style; view -> style_json; view -> mvt_parser; view -> overpass_parser; view -> tile_decode; view -> renderer; view -> render_graph; view -> sprite; view -> tile_disk; view -> tile; view -> asset_loader; view -> tessellation; view -> label_state; viewport -> geometry; viewport -> tile; cache -> geometry; cache -> tile; scheduler -> viewport; scheduler -> cache; scheduler -> tile; label -> geometry; label -> tile; style -> geometry; style_json -> style; mvt_parser -> geometry; mvt_parser -> label; mvt_parser -> tile; overpass_parser -> geometry; overpass_parser -> label; overpass_parser -> tile; tile_decode -> geometry; tile_decode -> label; tile_decode -> style; tile_decode -> mvt_parser; tile_decode -> overpass_parser; tile_decode -> tile; renderer -> cache; renderer -> geometry; renderer -> label; renderer -> style; renderer -> tile; renderer -> tessellation; renderer -> label_state; render_graph -> cache; render_graph -> geometry; render_graph -> label; render_graph -> style; render_graph -> renderer; render_graph -> tile; render_graph -> tessellation; render_graph -> label_state; sprite -> geometry; sprite -> tile; tile_disk -> tile; tile -> geometry; asset_loader -> tile; tessellation -> geometry; tessellation -> tile; label_state -> geometry; label_state -> label; label_state -> tile; // Circular dependencies (red edges) edge [color=red, style=bold]; view -> cache [label="circular", dir=both]; scheduler -> cache [label="circular", dir=both]; renderer -> render_graph [label="circular", dir=both]; } ``` **Visualization:** Use Graphviz to render: ```bash dot -Tpng dependencies.dot -o dependencies.png ``` --- ## Appendix B: Dependency Analysis Script ```python #!/usr/bin/env python3 """ Analyze module dependencies in Makepad map codebase. """ import os import re from collections import defaultdict, deque from typing import Dict, List, Set, Tuple def find_modules(src_dir: str) -> Dict[str, str]: """Find all Rust modules in src directory.""" modules = {} for root, dirs, files in os.walk(src_dir): for file in files: if file.endswith('.rs'): module_name = file[:-3] module_path = os.path.join(root, file) modules[module_name] = module_path return modules def extract_dependencies(module_path: str) -> Set[str]: """Extract dependencies from a Rust module.""" dependencies = set() with open(module_path, 'r') as f: content = f.read() # Find use statements use_pattern = r'use\s+(?:super::)?(\w+)' for match in re.finditer(use_pattern, content): dependencies.add(match.group(1)) return dependencies def build_dependency_graph(modules: Dict[str, str]) -> Dict[str, Set[str]]: """Build dependency graph from modules.""" graph = defaultdict(set) for module_name, module_path in modules.items(): dependencies = extract_dependencies(module_path) graph[module_name] = dependencies return graph def find_circular_dependencies(graph: Dict[str, Set[str]]) -> List[List[str]]: """Find all circular dependencies in the graph.""" circular = [] visited = set() path = [] path_set = set() def dfs(node: str): if node in path_set: # Found circular dependency cycle_start = path.index(node) circular.append(path[cycle_start:] + [node]) return if node in visited: return visited.add(node) path.append(node) path_set.add(node) for neighbor in graph.get(node, []): dfs(neighbor) path.pop() path_set.remove(node) for node in graph: dfs(node) return circular def calculate_dependency_depth(graph: Dict[str, Set[str]]) -> Dict[str, int]: """Calculate dependency depth for each module.""" depths = {} def get_depth(node: str, visited: Set[str]) -> int: if node in depths: return depths[node] if node in visited: return 0 # Circular dependency visited.add(node) if not graph.get(node): depths[node] = 0 return 0 max_depth = 0 for neighbor in graph[node]: depth = get_depth(neighbor, visited) max_depth = max(max_depth, depth + 1) depths[node] = max_depth visited.remove(node) return max_depth for node in graph: get_depth(node, set()) return depths def main(): src_dir = 'crates/apps/map/src' modules = find_modules(src_dir) graph = build_dependency_graph(modules) print(f"Total modules: {len(modules)}") print(f"Total dependencies: {sum(len(deps) for deps in graph.values())}") circular = find_circular_dependencies(graph) print(f"\nCircular dependencies: {len(circular)}") for cycle in circular: print(f" {' -> '.join(cycle)}") depths = calculate_dependency_depth(graph) print(f"\nMaximum dependency depth: {max(depths.values())}") print(f"Average dependency depth: {sum(depths.values()) / len(depths):.2f}") # Find hotspots dependency_counts = {node: len(deps) for node, deps in graph.items()} dependent_counts = defaultdict(int) for node, deps in graph.items(): for dep in deps: dependent_counts[dep] += 1 print("\nTop 5 hotspots:") hotspots = sorted( modules.keys(), key=lambda m: dependency_counts.get(m, 0) * 2 + dependent_counts.get(m, 0), reverse=True )[:5] for module in hotspots: deps = dependency_counts.get(module, 0) dependents = dependent_counts.get(module, 0) print(f" {module}: {deps} dependencies, {dependents} dependents") if __name__ == '__main__': main() ``` **Usage:** ```bash python3 analyze_dependencies.py ``` --- **END OF PHASE 0: MODULE DEPENDENCY GRAPH**