threads
This commit is contained in:
69
CLAUDE.md
69
CLAUDE.md
@@ -51,6 +51,10 @@ Flat C-style APIs (no classes), prefixed by subsystem. **Do not touch gameplay l
|
||||
- **JI** (`jinput`) — Input: keyboard state polling, key debouncing, cheat code detection. Filters GUI keys from game, calls `GlobalInputs::handle()` and `Mouse::updateCursorVisibility()` each update
|
||||
- **JF** (`jfile`) — File I/O: filesystem folder mode (`data/`) or packed resource file (`.jrf`). Config folder at `~/.config/jailgames/aee/`
|
||||
|
||||
### System Layer (`source/core/system/`)
|
||||
|
||||
- **Director** (`director.hpp/cpp`) — **Orchestrator singleton**. Owns main thread. Launches game thread that runs `ModuleGame`/`ModuleSequence::Go()`. Emulator-style architecture: Director runs at ~60 FPS independently, polls SDL events, updates overlay, presents frames. Game thread blocks at `JD8_Flip()` → `Director::publishFrame()` until Director consumes the frame. Director is **non-blocking**: if no new frame is available, it re-presents the last known game frame with fresh overlay on top
|
||||
|
||||
### Presentation Layer (`source/core/rendering/`)
|
||||
|
||||
- **Screen** (`screen.hpp/cpp`) — Singleton. Manages SDL_Window, SDL_Renderer, SDL_Texture. Dual rendering path: SDL3GPU with shaders (primary) or SDL_Renderer fallback. Handles fullscreen, zoom, aspect ratio 4:3, integer scaling, VSync. Counts FPS and updates render info text
|
||||
@@ -93,23 +97,45 @@ Follows the pattern from `jaildoctors_dilemma`, persists to YAML:
|
||||
|
||||
All key bindings are configurable via `Options::keys_gui` and stored in `config.yaml`.
|
||||
|
||||
### Rendering Pipeline
|
||||
### Threading Model (Emulator Architecture)
|
||||
|
||||
```
|
||||
JD8_Flip():
|
||||
1. palette→ARGB in pixel_data[320×200] (original engine)
|
||||
2. Screen::present(pixel_data):
|
||||
a. FPS count + render info text update
|
||||
b. Overlay::render(pixel_data) (notifications, render info, directly on ARGB)
|
||||
c. IF GPU + shaders enabled:
|
||||
- uploadPixels → scene_texture (320×200)
|
||||
- [IF 4:3] stretch pass fused with upscale: scene → scaled_texture (W×factor, H×factor×1.2)
|
||||
- [IF SS] upscale pass: scene → scaled_texture (W×factor, H×factor)
|
||||
- PostFX or CRT-Pi shader → swapchain (with viewport letterboxing)
|
||||
d. ELSE IF GPU without shaders:
|
||||
- uploadPixels → clean render → swapchain
|
||||
e. ELSE (fallback):
|
||||
- SDL_UpdateTexture → SDL_RenderPresent
|
||||
Main thread (Director) Game thread (ModuleGame/Sequence::Go())
|
||||
──────────────────── ────────────────────────────────────
|
||||
loop at ~60 FPS { loop {
|
||||
SDL_PollEvent() ... game logic ...
|
||||
GlobalInputs, Mouse JD8_Flip():
|
||||
if new_frame_available: palette→ARGB in pixel_data
|
||||
copy to game_frame publishFrame(pixel_data) ⏸
|
||||
signal → ────────────────────→ (blocks until Director consumes)
|
||||
copy game_frame → present_buffer ←──── signal_consumed
|
||||
Overlay::render(present_buffer) continue game loop
|
||||
Screen::present(present_buffer) }
|
||||
SDL_Delay to hit 60fps
|
||||
}
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Director is NON-BLOCKING: if no new frame, re-presents the last one with fresh overlay
|
||||
- Double buffer: `game_frame` (untouched copy from game) + `presentation_buffer` (regenerated with overlay each frame)
|
||||
- Game thread pauses at `JD8_Flip()` waiting for Director — natural sync point
|
||||
- SDL events processed ONLY on main thread (SDL requirement)
|
||||
- `JI_Update()` no longer polls events — reads Director's state
|
||||
|
||||
### Rendering Pipeline (inside Screen::present)
|
||||
|
||||
```
|
||||
Screen::present(pixel_data):
|
||||
1. FPS count + render info text update
|
||||
2. IF GPU + shaders enabled:
|
||||
- uploadPixels → scene_texture (320×200)
|
||||
- [IF 4:3] stretch pass fused with upscale: scene → scaled_texture (W×factor, H×factor×1.2)
|
||||
- [IF SS] upscale pass: scene → scaled_texture (W×factor, H×factor)
|
||||
- PostFX or CRT-Pi shader → swapchain (with viewport letterboxing)
|
||||
3. ELSE IF GPU without shaders:
|
||||
- uploadPixels → clean render → swapchain
|
||||
4. ELSE (fallback):
|
||||
- SDL_UpdateTexture → SDL_RenderPresent
|
||||
```
|
||||
|
||||
### Pixel Format
|
||||
@@ -139,12 +165,15 @@ JD8_Flip produces ABGR byte order: `0xFF000000 + R + (G<<8) + (B<<16)`. SDL text
|
||||
|
||||
### Known Issues & Technical Debt
|
||||
|
||||
1. **ESC double-press does not work in ModuleGame**: `modulegame.cpp:136` polls `JI_KeyPressed(ESCAPE)` each frame and calls `JG_QuitSignal()` immediately. The overlay intercepts the KEY_UP event and blocks polling via `esc_blocked_`, but there is a race condition — the game's polling can fire before the block takes effect. Needs deeper integration (possibly intercepting at `JI_KeyPressed` level with state tracking across frames, or modifying the game module to use the overlay's quit flow)
|
||||
1. **gif.h cannot be included twice**: Functions are not `static` or `inline`, causing multiple definition errors. Text class uses `extern` forward declarations as workaround
|
||||
|
||||
2. **Overlay freezes during intro sequences**: ModuleSequence does blocking loops with delays that don't call `JI_Update()`, so `Overlay::render()` doesn't execute and notifications appear frozen. Would require refactoring the original modules to use non-blocking animation — conflicts with golden rule
|
||||
### Previously Fixed (kept for reference)
|
||||
|
||||
3. **gif.h cannot be included twice**: Functions are not `static` or `inline`, causing multiple definition errors. Text class uses `extern` forward declarations as workaround
|
||||
- **ESC double-press**: Fixed by intercepting KEY_DOWN in Director, setting atomic `esc_blocked_` immediately. `JI_KeyPressed(ESCAPE)` consults this flag. No race condition possible because Director's flag wins before game polls
|
||||
- **Overlay freeze during intros**: Fixed by threading model. Director runs independently at 60 FPS regardless of game delays. Double buffer avoids overlay smearing on re-presented frames
|
||||
|
||||
### Main Loop (`source/main.cpp`)
|
||||
### Main Entry (`source/main.cpp`)
|
||||
|
||||
Init order: `file_setconfigfolder` → `Options::load` → `Options::loadPostFX/CrtPi` → `JG_Init` → `Screen::init` → `JD8_Init` → `JA_Init` → `Overlay::init`. Shutdown reverse. A state machine alternates between `ModuleSequence` (state 1) and `ModuleGame` (state 0). Each module's `Go()` returns the next state (-1 to quit).
|
||||
Init order: `file_setconfigfolder` → `Options::load` → `Options::loadPostFX/CrtPi` → `JG_Init` → `Screen::init` → `JD8_Init` → `JA_Init` → `Overlay::init` → `Director::init` → `Director::run()` (blocks until quit). Shutdown: `Options::save` → `Director::destroy` → `Overlay::destroy` → `JA_Quit` → `JD8_Quit` → `Screen::destroy` → `JG_Finalize`.
|
||||
|
||||
The state machine (alternating `ModuleSequence` state=1 and `ModuleGame` state=0) now lives inside `Director::gameThreadFunc()`, running on the game thread.
|
||||
|
||||
Reference in New Issue
Block a user