diff options
| author | skal <pascal.massimino@gmail.com> | 2026-01-31 15:57:00 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-01-31 15:57:00 +0100 |
| commit | 9a6cd87164995df93cf3df410ce37721910ce240 (patch) | |
| tree | 04bc3f733ca916ad13ea6304069f63cdacec5399 | |
| parent | 1016d65d4b5bf7cbd05bba4cf79dc2ce172c9fad (diff) | |
feat: Implement Sequence Compiler for data-driven choreography
Adds a 'seq_compiler' tool that converts a text-based timeline (assets/demo.seq) into a generated C++ file. This allows editing effect sequences and timing without modifying engine code. Replaces manual sequence creation with a generated 'LoadTimeline' function.
| -rw-r--r-- | CMakeLists.txt | 22 | ||||
| -rw-r--r-- | PROJECT_CONTEXT.md | 25 | ||||
| -rw-r--r-- | assets/demo.seq | 8 | ||||
| -rw-r--r-- | src/gpu/demo_effects.cc | 12 | ||||
| -rw-r--r-- | src/gpu/demo_effects.h | 7 | ||||
| -rw-r--r-- | src/gpu/gpu.cc | 3 | ||||
| -rw-r--r-- | tools/seq_compiler.cc | 130 |
7 files changed, 184 insertions, 23 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c7b7508..3812fb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,25 @@ else() set(ASSET_PACKER_DEPENDS asset_packer) endif() +# Sequence Compiler Tool +add_executable(seq_compiler tools/seq_compiler.cc) + +# Configure Timeline Generation +set(DEMO_SEQ_PATH ${CMAKE_CURRENT_SOURCE_DIR}/assets/demo.seq) +set(GENERATED_TIMELINE_CC ${CMAKE_CURRENT_BINARY_DIR}/src/timeline.cc) + +add_custom_command( + OUTPUT ${GENERATED_TIMELINE_CC} + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/src + COMMAND $<TARGET_FILE:seq_compiler> ${DEMO_SEQ_PATH} ${GENERATED_TIMELINE_CC} + DEPENDS seq_compiler ${DEMO_SEQ_PATH} + COMMENT "Compiling demo sequence..." +) + +add_custom_target(generate_timeline ALL + DEPENDS ${GENERATED_TIMELINE_CC} +) + # Configure DEMO asset generation set(DEMO_ASSETS_TXT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/assets/final/demo_assets.txt) set(GENERATED_DEMO_ASSETS_H ${CMAKE_CURRENT_BINARY_DIR}/src/assets.h) @@ -170,9 +189,10 @@ add_executable(demo64k src/util/asset_manager.cc third_party/glfw3webgpu/glfw3webgpu.c ${GENERATED_DEMO_ASSETS_DATA_CC} + ${GENERATED_TIMELINE_CC} ) -add_dependencies(demo64k generate_demo_assets) +add_dependencies(demo64k generate_demo_assets generate_timeline) target_link_libraries(demo64k PRIVATE ${DEMO_LIBS}) target_include_directories(demo64k PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src) diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index 24b1517..5afc7a7 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -37,13 +37,30 @@ Incoming tasks in no particular order: - [x] 9. work on the compact in-line and off-line asset system (@ASSET_SYSTEM.md) - 10. optimize spectool to remove trailing zeroes from a spec file before saving it - [x] 11. implement a general [time / timer / beat] system in the demo, for effects timing -- 12. Implement 'Sequence' and 'Effect' system: - - `Effect` interface: `start()`, `draw(time, beat, intensity)`, `end()`. - - `Sequence` manager: Handles a list of potentially overlapping effects. - - `time` is relative to sequence start. `beat` is a [0..1] pulse. `intensity` is signal strength (e.g. music level). +- [x] 12. Implement 'Sequence' and 'Effect' system: + - `Effect` interface: `init`, `start`, `compute`, `render`, `end`, `priority`. + - `Sequence` manager: Handles a list of temporally sorted `SequenceItem`s. + - `MainSequence`: Manages global resources (`device`, `queue`) and orchestrates the frame render. + - Refactored `gpu.cc` to use `MainSequence`. + - Moved concrete effects to `src/gpu/demo_effects.cc`. ## Session Decisions and Current State +### Sequence & Effect System +- **Architecture**: Implemented a hierarchical sequencing system. + - **Effect**: Abstract base for visual elements. Supports `compute` (physics) and `render` (draw) phases. Idempotent `init` for shared asset loading. + - **Sequence**: Manages a timeline of effects with `start_time` and `end_time`. Handles activation/deactivation and sorting by priority. + - **MainSequence**: The top-level coordinator. Holds the WebGPU device/queue/surface. Manages multiple overlapping `Sequence` layers (sorted by priority). +- **Implementation**: + - `src/gpu/gpu.cc`: Simplified to be a thin wrapper initializing and driving `MainSequence`. + - `src/gpu/demo_effects.h/cc`: Concrete implementations of `HeptagonEffect` and `ParticlesEffect`. +- **Integration**: The main loop now calculates fractional beats and passes them along with `time` and `aspect_ratio` to the rendering system. + +### Debugging Features +- **Fast Forward / Seek**: Implemented `--seek <seconds>` CLI option. + - **Mechanism**: Simulates logic, audio (silent render), and GPU compute (physics) frame-by-frame from `t=0` to `target_time` before starting real-time playback. + - **Audio Refactor**: Split `audio_init()` (resource allocation) from `audio_start()` (device callback start) to allow manual `audio_render_silent()` during the seek phase. + ### Asset Management System: - **Architecture**: Implemented a C++ tool (`asset_packer`) that bundles external files into hex-encoded C arrays. - **Lookup**: Uses a highly efficient array-based lookup (AssetRecord) for O(1) retrieval of raw byte data at runtime. diff --git a/assets/demo.seq b/assets/demo.seq new file mode 100644 index 0000000..12d70b1 --- /dev/null +++ b/assets/demo.seq @@ -0,0 +1,8 @@ +# Main demo timeline +# Format: +# SEQUENCE <start_time> <priority> +# EFFECT <ClassName> <start> <end> <priority> [extra_args...] + +SEQUENCE 0.0 0 + EFFECT HeptagonEffect 0.0 1000.0 0 + EFFECT ParticlesEffect 0.0 1000.0 1 diff --git a/src/gpu/demo_effects.cc b/src/gpu/demo_effects.cc index 5fc7c15..869cd12 100644 --- a/src/gpu/demo_effects.cc +++ b/src/gpu/demo_effects.cc @@ -300,15 +300,3 @@ void ParticlesEffect::render(WGPURenderPassEncoder pass, float time, float beat, wgpuRenderPassEncoderDraw(pass, render_pass_.vertex_count, render_pass_.instance_count, 0, 0); } - -std::shared_ptr<Sequence> create_demo_sequence(WGPUDevice device, - WGPUQueue queue, - WGPUTextureFormat format) { - auto seq = std::make_shared<Sequence>(); - // Overlap them for now to replicate original behavior - seq->add_effect(std::make_shared<HeptagonEffect>(device, queue, format), 0.0f, - 1000.0f); - seq->add_effect(std::make_shared<ParticlesEffect>(device, queue, format), - 0.0f, 1000.0f); - return seq; -} diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h index 81321d9..befb1fe 100644 --- a/src/gpu/demo_effects.h +++ b/src/gpu/demo_effects.h @@ -34,7 +34,6 @@ private: GpuBuffer uniforms_; }; -// Factory -std::shared_ptr<Sequence> create_demo_sequence(WGPUDevice device, - WGPUQueue queue, - WGPUTextureFormat format); +// Auto-generated function to populate the timeline +void LoadTimeline(MainSequence &main_seq, WGPUDevice device, WGPUQueue queue, + WGPUTextureFormat format); diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc index d119e60..e76aecc 100644 --- a/src/gpu/gpu.cc +++ b/src/gpu/gpu.cc @@ -417,8 +417,7 @@ void gpu_init(GLFWwindow *window) { g_main_sequence.init(g_device, g_queue, g_config.format); - auto seq = create_demo_sequence(g_device, g_queue, g_config.format); - g_main_sequence.add_sequence(seq, 0.0f, 0); + LoadTimeline(g_main_sequence, g_device, g_queue, g_config.format); } void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat) { diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc new file mode 100644 index 0000000..8da4e4e --- /dev/null +++ b/tools/seq_compiler.cc @@ -0,0 +1,130 @@ +// This file is part of the 64k demo project. +// It implements the sequence compiler tool. +// Converts a text-based timeline description into C++ code. + +#include <fstream> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +struct EffectEntry { + std::string class_name; + std::string start; + std::string end; + std::string priority; + std::string extra_args; +}; + +struct SequenceEntry { + std::string start_time; + std::string priority; + std::vector<EffectEntry> effects; +}; + +std::string trim(const std::string &str) { + size_t first = str.find_first_not_of(" "); + if (std::string::npos == first) + return str; + size_t last = str.find_last_not_of(" "); + return str.substr(first, (last - first + 1)); +} + +int main(int argc, char *argv[]) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " <input.seq> <output.cc>\n"; + return 1; + } + + std::ifstream in_file(argv[1]); + if (!in_file.is_open()) { + std::cerr << "Error: Could not open input file " << argv[1] << "\n"; + return 1; + } + + std::vector<SequenceEntry> sequences; + SequenceEntry *current_seq = nullptr; + + std::string line; + int line_num = 0; + while (std::getline(in_file, line)) { + line_num++; + std::string trimmed = trim(line); + if (trimmed.empty() || trimmed[0] == '#') + continue; + + std::stringstream ss(trimmed); + std::string command; + ss >> command; + + if (command == "SEQUENCE") { + std::string start, priority; + if (!(ss >> start >> priority)) { + std::cerr << "Error line " << line_num + << ": SEQUENCE requires <start> <priority>\n"; + return 1; + } + sequences.push_back({start, priority, {}}); + current_seq = &sequences.back(); + } else if (command == "EFFECT") { + if (!current_seq) { + std::cerr << "Error line " << line_num + << ": EFFECT found outside of SEQUENCE\n"; + return 1; + } + std::string class_name, start, end, priority; + if (!(ss >> class_name >> start >> end >> priority)) { + std::cerr << "Error line " << line_num + << ": EFFECT requires <Class> <start> <end> <priority>\n"; + return 1; + } + + // Capture remaining args + std::string extra_args; + std::getline(ss, extra_args); // Read rest of line + // Remove leading whitespace from getline if any (getline reads the space after priority) + extra_args = trim(extra_args); + if (!extra_args.empty()) { + extra_args = ", " + extra_args; + } + + current_seq->effects.push_back( + {class_name, start, end, priority, extra_args}); + } else { + std::cerr << "Error line " << line_num << ": Unknown command '" << command + << "'\n"; + return 1; + } + } + + std::ofstream out_file(argv[2]); + if (!out_file.is_open()) { + std::cerr << "Error: Could not open output file " << argv[2] << "\n"; + return 1; + } + + out_file << "// Auto-generated by seq_compiler. Do not edit.\n"; + out_file << "#include \"gpu/demo_effects.h\"\n"; + out_file << "#include \"gpu/effect.h\"\n\n"; + out_file << "void LoadTimeline(MainSequence& main_seq, WGPUDevice device, " + "WGPUQueue queue, WGPUTextureFormat format) {\n"; + + for (const auto &seq : sequences) { + out_file << " {\n"; + out_file << " auto seq = std::make_shared<Sequence>();\n"; + for (const auto &eff : seq.effects) { + out_file << " seq->add_effect(std::make_shared<" << eff.class_name + << ">(device, queue, format" << eff.extra_args << "), " + << eff.start << "f, " << eff.end << "f, " << eff.priority << ");\n"; + } + out_file << " main_seq.add_sequence(seq, " << seq.start_time << "f, " + << seq.priority << ");\n"; + out_file << " }\n"; + } + + out_file << "}\n"; + + std::cout << "Successfully generated timeline with " << sequences.size() << " sequences.\n"; + + return 0; +} |
