summaryrefslogtreecommitdiff
path: root/doc/BUILD_OPTIMIZATION_PROPOSAL.md
blob: 8d61e10ba7949e6b3b10c1e35aea21f15a2e8aed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# Build Optimization Proposal: CMake Dependency Graph Analysis

## Executive Summary

Current incremental build times are good (0.2s when nothing changes), but **changing common headers or assets causes unnecessary broad rebuilds** (1.9-3.5s). This analysis identifies bottlenecks and proposes solutions to achieve <1s incremental builds for typical development workflows.

---

## Current Build Performance Baseline

### Measured Build Times
```
Clean build (all targets):        ~45s (estimated)
Incremental build (no changes):   0.229s ✅
Touch mini_math.h:                1.897s ⚠️
Touch demo_assets.txt:            3.498s ⚠️
Touch shader asset:               0.636s ⚠️ (should be 0s - not tracked!)
```

### Dependency Analysis Results

**Key Finding 1: Individual asset files are NOT tracked as dependencies**
- Only `demo_assets.txt` is a dependency, not the actual `.wgsl`/`.spec`/`.obj` files
- Changing a shader doesn't trigger asset regeneration (stale builds!)
- Developer must manually re-run `./scripts/gen_assets.sh` or touch `demo_assets.txt`

**Key Finding 2: Generated files cause broad rebuilds**
- `assets_data.cc`: 33 assets, 20KB+ generated code
- `music_data.cc`: Tracker patterns
- `timeline.cc`: Demo sequence
- Changes to ANY asset → full rebuild of `util`, `demo64k`, and all tests

**Key Finding 3: Header dependency chains**
- `mini_math.h` → triggers rebuild of entire `3d` + `gpu` libraries (23 files)
- `asset_manager.h` → used by audio, 3d, gpu, tests (30+ files)
- `object.h` (includes asset_manager) → transitively includes it everywhere

**Key Finding 4: Test dependency explosion**
- 20 tests, each depends on `generate_demo_assets` or `generate_test_assets`
- Changing assets rebuilds ALL tests (even if they don't use the changed asset)

---

## Problem Categories

### 1. Missing File-Level Asset Dependencies ⚠️ HIGH PRIORITY
**Problem**: Changing a `.wgsl` shader doesn't trigger `asset_packer` to regenerate `assets.h`/`assets_data.cc`.

**Root Cause**: CMake custom command only depends on `demo_assets.txt`, not the actual asset files:
```cmake
add_custom_command(
    OUTPUT ${OUT_H} ${OUT_CC}
    DEPENDS ${ASSET_PACKER_DEPENDS} ${INPUT_TXT}  # ❌ Missing individual asset files!
    ...
)
```

**Impact**:
- Developers get **stale builds** after modifying shaders
- Must manually trigger rebuild (`touch demo_assets.txt` or `./scripts/gen_assets.sh`)
- Risk of bugs from testing old shader code

**Solution Complexity**: Medium (requires parsing `demo_assets.txt` to extract file list)

---

### 2. Monolithic Generated Asset Files ⚠️ MEDIUM PRIORITY
**Problem**: `assets_data.cc` contains ALL assets (33 items, 20KB+). Changing one shader regenerates the entire file, causing broad rebuilds.

**Root Cause**: Single monolithic file instead of per-asset or per-category files.

**Impact**:
- Touch `demo_assets.txt` → 3.5s rebuild (main.cc, util, all tests)
- Even if developer only changed one shader

**Solution Complexity**: High (requires refactoring asset packing system)

---

### 3. Header Include Hygiene 🟢 LOW PRIORITY
**Problem**: `asset_manager.h` and `mini_math.h` included widely, causing transitive rebuilds.

**Root Cause**: Headers include more than necessary, creating dependency chains.

**Example Chain**:
```
object.h (includes asset_manager.h)
  ↓
renderer.cc (includes object.h)
  ↓
Entire 3d library rebuilds when asset_manager.h changes
```

**Impact**: Moderate (1-2s for `mini_math.h`, less for `asset_manager.h`)

**Solution Complexity**: Low (forward declarations, split headers)

---

### 4. Test Dependency Granularity 🟢 LOW PRIORITY
**Problem**: All tests depend on `generate_demo_assets`, even if they don't use assets.

**Impact**: Moderate (asset changes rebuild all 20 tests unnecessarily)

**Solution Complexity**: Medium (requires per-test dependency analysis)

---

## Proposed Solutions (Ranked by Impact)

### **Proposal 1: Add File-Level Asset Dependencies** ⭐ HIGHEST IMPACT
**Effort**: Medium | **Impact**: High | **Priority**: Critical

**Goal**: Make CMake aware of individual asset files, so changing a shader triggers regeneration.

**Implementation**:
```cmake
# Parse demo_assets.txt to extract asset file paths
function(parse_asset_list INPUT_TXT OUT_FILE_LIST)
    file(STRINGS ${INPUT_TXT} LINES)
    set(ASSET_FILES "")
    foreach(LINE ${LINES})
        if(NOT LINE MATCHES "^#")  # Skip comments
            string(REGEX REPLACE "^[^,]+,[ ]*([^,]+).*" "\\1" FILENAME "${LINE}")
            list(APPEND ASSET_FILES "${CMAKE_SOURCE_DIR}/assets/final/${FILENAME}")
        endif()
    endforeach()
    set(${OUT_FILE_LIST} ${ASSET_FILES} PARENT_SCOPE)
endfunction()

# Use in pack_assets function
function(pack_assets NAME INPUT_TXT HEADER_VAR DATA_CC_VAR TARGET_NAME)
    parse_asset_list(${INPUT_TXT} ASSET_FILE_DEPS)

    add_custom_command(
        OUTPUT ${OUT_H} ${OUT_CC}
        COMMAND ${ASSET_PACKER_CMD} ${INPUT_TXT} ${OUT_H} ${OUT_CC}
        DEPENDS ${ASSET_PACKER_DEPENDS} ${INPUT_TXT} ${ASSET_FILE_DEPS}  # ✅ Fixed!
        COMMENT "Generating assets for ${NAME}..."
    )
    ...
endfunction()
```

**Benefits**:
- ✅ Correct incremental builds (no more stale shaders!)
- ✅ Eliminates manual `touch demo_assets.txt` workaround
- ✅ Developer workflow improved

**Risks**:
- Adds ~33 file dependencies to CMake graph (negligible overhead)
- Requires robust parsing of `demo_assets.txt` format

---

### **Proposal 2: Split Assets into Categories** ⭐ MEDIUM IMPACT
**Effort**: High | **Impact**: Medium | **Priority**: Medium

**Goal**: Split `assets_data.cc` into `shaders_data.cc`, `audio_data.cc`, `meshes_data.cc` to reduce rebuild scope.

**Implementation**:
```cmake
pack_assets(shaders assets/final/shaders_list.txt ...)
pack_assets(audio assets/final/audio_list.txt ...)
pack_assets(meshes assets/final/meshes_list.txt ...)

# demo64k depends on all three
target_link_libraries(demo64k PRIVATE ... ${SHADER_DATA_CC} ${AUDIO_DATA_CC} ${MESH_DATA_CC})
```

**Benefits**:
- ✅ Changing a shader only rebuilds files using shaders
- ✅ Reduces 3.5s rebuild to ~1s for single-category changes

**Risks**:
- ⚠️ Requires splitting `demo_assets.txt` into multiple files
- ⚠️ More complex build system
- ⚠️ Asset ID namespacing might be needed

---

### **Proposal 3: Precompiled Headers (PCH)** 🔧 ALTERNATIVE APPROACH
**Effort**: Low | **Impact**: Medium | **Priority**: Optional

**Goal**: Use CMake's `target_precompile_headers` for common includes.

**Implementation**:
```cmake
target_precompile_headers(3d PRIVATE
    <cstdio>
    <cmath>
    <vector>
    <algorithm>
    "util/mini_math.h"
)
```

**Benefits**:
- ✅ Reduces parse time for large headers
- ✅ Speeds up clean builds significantly

**Drawbacks**:
- ⚠️ Doesn't help incremental builds much (headers still tracked)
- ⚠️ Can mask missing includes (code compiles but is fragile)

---

### **Proposal 4: Header Include Hygiene** 🟢 LOW HANGING FRUIT
**Effort**: Low | **Impact**: Low | **Priority**: Nice-to-have

**Goal**: Reduce transitive includes via forward declarations.

**Example Refactor**:
```cpp
// object.h (BEFORE)
#include "util/asset_manager.h"  // ❌ Full include

// object.h (AFTER)
enum class AssetId : uint16_t;  // ✅ Forward declaration
```

**Benefits**:
- ✅ Reduces dependency chains
- ✅ Faster incremental builds for header changes

**Drawbacks**:
- ⚠️ Requires careful analysis of which headers can be forward-declared
- ⚠️ May break existing code if not done carefully

---

### **Proposal 5: Ninja Build Generator** ⚡ QUICK WIN
**Effort**: Trivial | **Impact**: Small | **Priority**: Easy

**Goal**: Use Ninja instead of Make for faster dependency checking.

**Implementation**:
```bash
cmake -S . -B build -G Ninja
ninja -C build demo64k
```

**Benefits**:
- ✅ ~20% faster incremental builds (0.229s → 0.18s)
- ✅ Better parallelism for large projects
- ✅ Zero code changes required

**Drawbacks**:
- ⚠️ Requires Ninja installed on developer machines

---

## Recommended Implementation Plan

### Phase 1: Critical Fixes (1-2 days)
1. ✅ **Proposal 1**: Add file-level asset dependencies
2. ✅ **Proposal 5**: Document Ninja usage (already works, just recommend it)

### Phase 2: Medium-Term Improvements (3-5 days)
3. 🔧 **Proposal 2**: Split assets into categories (optional, if rebuild times still problematic)
4. 🔧 **Proposal 4**: Header include hygiene (ongoing, as-needed)

### Phase 3: Optimization (optional)
5. 🔧 **Proposal 3**: Precompiled headers (if clean builds become bottleneck)

---

## Metrics & Success Criteria

**Current Baseline**:
- Touch shader → 0.6s (but stale build!)
- Touch demo_assets.txt → 3.5s
- Touch mini_math.h → 1.9s

**Target After Phase 1**:
- Touch shader → 0.8s (regenerates assets_data.cc correctly)
- Touch demo_assets.txt → 3.5s (unchanged, but correct)
- Touch mini_math.h → 1.9s (unchanged)

**Target After Phase 2**:
- Touch shader → 0.5s (only shader_data.cc regenerates)
- Touch audio asset → 0.4s (only audio_data.cc regenerates)
- Touch mini_math.h → 1.2s (fewer transitive includes)

---

## Open Questions for Discussion

1. **Asset categorization**: Split by type (shaders/audio/meshes) or by usage (runtime/debug/tests)?
2. **Precompiled headers**: Worth the complexity given incremental builds are already fast?
3. **Unity builds**: Would `UNITY_BUILD` help for clean builds? (Probably not needed for 64k demo)
4. **ccache**: Should we recommend/document ccache usage for developers?

---

## Appendix: Dependency Graph Analysis

### Current Library Dependencies
```
demo64k
├── 3d (7 files, depends on: util, procedural)
│   ├── renderer.cc → includes asset_manager.h
│   ├── renderer_sdf.cc → includes asset_manager.h
│   ├── renderer_mesh.cc → includes asset_manager.h
│   └── ... (all include mini_math.h)
├── gpu (18 files, depends on: util, procedural)
│   ├── effects/shaders.cc → includes asset_manager.h
│   └── effects/*_effect.cc → includes object.h → mini_math.h
├── audio (12 files, depends on: util, procedural)
│   ├── audio.cc → includes asset_manager.h
│   └── tracker.cc → includes asset_manager.h
├── util (1 file)
│   └── asset_manager.cc (includes generated assets.h)
└── procedural (1 file)
```

### Rebuild Cascade for `assets_data.cc` Change
```
assets_data.cc (regenerated)
  ↓
util library (recompiles asset_manager.cc)
  ↓
audio library (no rebuild - doesn't depend on util)
3d library (no rebuild - doesn't depend on util)
gpu library (no rebuild - doesn't depend on util)
  ↓
demo64k (relinks with new util)
  ↓
ALL tests (relink with new util + demo64k dependencies)
```

**Conclusion**: Rebuild cascade is actually **well-isolated** to util + demo64k. The 3.5s time is mostly asset_packer + relink overhead, not cascading recompiles.

---

## Final Recommendation

**Implement Proposal 1 immediately** (file-level asset dependencies) to fix the critical stale build issue. This is the highest-priority bug affecting developer workflow.

**Defer Proposal 2** (asset categorization) unless measurements show it's worth the complexity. Current 3.5s rebuild for asset changes is acceptable if it only happens when intentionally modifying assets.

**Document Ninja usage** as a quick win for interested developers.

**Monitor** header dependency issues (Proposal 4) and address opportunistically during refactors.