step B.2: elimina fiber — Director posseeix l'escena, JD8_Flip sense yield, fiber.hpp/cpp esborrats
This commit is contained in:
@@ -15,7 +15,6 @@
|
||||
#include "core/rendering/menu.hpp"
|
||||
#include "core/rendering/overlay.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/system/fiber.hpp"
|
||||
#include "game/info.hpp"
|
||||
#include "game/modulegame.hpp"
|
||||
#include "game/options.hpp"
|
||||
@@ -35,14 +34,9 @@ extern void JI_moveCheats(Uint8 new_key);
|
||||
|
||||
Director* Director::instance_ = nullptr;
|
||||
|
||||
namespace {
|
||||
Director::~Director() = default;
|
||||
|
||||
// Entry del fiber del joc. Dispatcha a una escena segons l'estat actual:
|
||||
// `gameState == 0` → ModuleGame (gameplay), `gameState == 1` → una
|
||||
// `scenes::Scene` del registry triada per `info::ctx.num_piramide`. Cada
|
||||
// escena és tick-based; el JD8_Flip() entre ticks cedeix al Director
|
||||
// via `GameFiber::yield()`.
|
||||
void gameFiberEntry() {
|
||||
void Director::initGameContext() {
|
||||
info::ctx.num_habitacio = Options::game.habitacio_inicial;
|
||||
info::ctx.num_piramide = Options::game.piramide_inicial;
|
||||
info::ctx.diners = Options::game.diners_inicial;
|
||||
@@ -57,55 +51,31 @@ void gameFiberEntry() {
|
||||
info::ctx.nou_personatge = true;
|
||||
fclose(ini);
|
||||
}
|
||||
|
||||
int gameState = 1;
|
||||
while (gameState != -1 && !JG_Quitting()) {
|
||||
std::unique_ptr<scenes::Scene> scene;
|
||||
|
||||
if (gameState == 0) {
|
||||
// Gameplay. ModuleGame és una scenes::Scene des de Phase A de
|
||||
// la migració — mateix mini-loop tick+flip que la resta.
|
||||
scene = std::make_unique<ModuleGame>();
|
||||
} else {
|
||||
// gameState == 1: dispatch al registry per num_piramide. El
|
||||
// vell ModuleSequence::Go() feia aquest redirect al principi:
|
||||
// si el jugador arriba a la Secreta (6) sense prou diners,
|
||||
// salta als slides de fracàs (7) abans de buscar l'escena.
|
||||
if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) {
|
||||
info::ctx.num_piramide = 7;
|
||||
}
|
||||
scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide);
|
||||
}
|
||||
|
||||
if (!scene) {
|
||||
// State no registrat — indica un bug del dispatcher o del
|
||||
// registre d'escenes. Eixim ordenadament en lloc de cremar CPU.
|
||||
break;
|
||||
}
|
||||
|
||||
scene->onEnter();
|
||||
Uint32 last = SDL_GetTicks();
|
||||
while (!scene->done() && !JG_Quitting()) {
|
||||
JI_Update(); // refresca key_pressed/any_key per a les escenes
|
||||
const Uint32 now = SDL_GetTicks();
|
||||
scene->tick(static_cast<int>(now - last));
|
||||
last = now;
|
||||
JD8_Flip(); // presenta i cedix al Director
|
||||
}
|
||||
gameState = scene->nextState();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
std::unique_ptr<scenes::Scene> Director::createNextScene() {
|
||||
if (game_state_ == 0) {
|
||||
// Gameplay. ModuleGame és una scenes::Scene des de la Phase A.
|
||||
return std::make_unique<ModuleGame>();
|
||||
}
|
||||
// game_state_ == 1: dispatch al registry per num_piramide. Replica
|
||||
// del redirect que el vell ModuleSequence::Go() feia: si el jugador
|
||||
// arriba a la Secreta (6) sense prou diners, salta als slides de
|
||||
// fracàs (7) abans de buscar l'escena al registry.
|
||||
if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) {
|
||||
info::ctx.num_piramide = 7;
|
||||
}
|
||||
return scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide);
|
||||
}
|
||||
|
||||
void Director::init() {
|
||||
instance_ = new Director();
|
||||
Gamepad::init();
|
||||
|
||||
// Registre d'escenes. Cada entrada = un state_key (`num_piramide`)
|
||||
// amb una factory de `scenes::Scene`. El gameFiberEntry consulta
|
||||
// aquest registry per a tots els states de seqüència; si una clau
|
||||
// no apareix ací, el fiber eixirà del loop.
|
||||
// amb una factory de `scenes::Scene`. iterate() consulta aquest
|
||||
// registry per a tots els states de seqüència (game_state_ == 1); si
|
||||
// una clau no apareix ací, Director surt ordenadament.
|
||||
auto& registry = scenes::SceneRegistry::instance();
|
||||
registry.registerScene(0, [] { return std::make_unique<scenes::MenuScene>(); });
|
||||
registry.registerScene(100, [] { return std::make_unique<scenes::MortScene>(); });
|
||||
@@ -132,12 +102,9 @@ void Director::init() {
|
||||
}
|
||||
return std::make_unique<scenes::IntroScene>();
|
||||
});
|
||||
|
||||
GameFiber::init(gameFiberEntry);
|
||||
}
|
||||
|
||||
void Director::destroy() {
|
||||
GameFiber::destroy();
|
||||
Gamepad::destroy();
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
@@ -164,17 +131,17 @@ void Director::setup() {
|
||||
}
|
||||
|
||||
bool Director::iterate() {
|
||||
if (GameFiber::is_done() || quit_requested_) {
|
||||
// Si el joc encara no ha acabat (p.ex. eixida per ESC doble-press),
|
||||
// li donem l'oportunitat de tornar net: marquem quit i reprenem el
|
||||
// fiber fins que detecte JG_Quitting() i retorne de forma natural.
|
||||
if (quit_requested_) {
|
||||
JG_QuitSignal();
|
||||
while (!GameFiber::is_done()) {
|
||||
GameFiber::resume();
|
||||
}
|
||||
current_scene_.reset(); // destrueix l'escena actual ordenadament
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!context_initialized_) {
|
||||
initGameContext();
|
||||
context_initialized_ = true;
|
||||
}
|
||||
|
||||
constexpr Uint32 FRAME_MS_VSYNC = 16; // ~60 FPS amb VSync
|
||||
constexpr Uint32 FRAME_MS_NO_VSYNC = 4; // ~250 FPS sense VSync (límit superior)
|
||||
|
||||
@@ -205,15 +172,41 @@ bool Director::iterate() {
|
||||
esc_blocked_ = false;
|
||||
}
|
||||
|
||||
// Cedeix el control al fiber del joc. Quan retorne (per un JD8_Flip()
|
||||
// dins del joc) tindrem un nou frame a pixel_data. Si estem en pausa,
|
||||
// no executem el fiber: es queda congelat al seu últim yield i
|
||||
// continuem presentant l'últim frame conegut.
|
||||
// Avança l'escena (si no estem pausats). En pausa, es manté l'escena
|
||||
// congelada i re-presentem l'últim frame amb l'overlay fresc per
|
||||
// damunt.
|
||||
if (!paused_) {
|
||||
GameFiber::resume();
|
||||
if (GameFiber::is_done()) {
|
||||
return false;
|
||||
// Transicions: si l'escena actual ha acabat (o s'ha senyalat
|
||||
// quit), llegim el seu next state i la destruïm per crear la
|
||||
// següent a continuació.
|
||||
if (current_scene_ && (current_scene_->done() || JG_Quitting())) {
|
||||
game_state_ = current_scene_->nextState();
|
||||
current_scene_.reset();
|
||||
}
|
||||
|
||||
// Si no hi ha escena activa, construeix la pròxima segons
|
||||
// game_state_ i info::ctx. Si és impossible (game_state_ == -1,
|
||||
// quit, o state no registrat), eixim del loop.
|
||||
if (!current_scene_) {
|
||||
if (game_state_ == -1 || JG_Quitting()) return false;
|
||||
current_scene_ = createNextScene();
|
||||
if (!current_scene_) return false;
|
||||
current_scene_->onEnter();
|
||||
last_tick_ms_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
// Tick de l'escena. JI_Update refresca key_pressed/any_key; el
|
||||
// delta_ms és el temps real transcorregut des de l'últim tick.
|
||||
JI_Update();
|
||||
const Uint32 now = SDL_GetTicks();
|
||||
const int delta_ms = static_cast<int>(now - last_tick_ms_);
|
||||
last_tick_ms_ = now;
|
||||
current_scene_->tick(delta_ms);
|
||||
|
||||
// Converteix `screen` indexat → `pixel_data` ARGB amb la paleta
|
||||
// actual. JD8_Flip ja no fa yield (Phase B.2 eliminà els fibers);
|
||||
// ara només omple el framebuffer perquè el Director l'aprofite.
|
||||
JD8_Flip();
|
||||
std::memcpy(game_frame_, JD8_GetFramebuffer(), sizeof(game_frame_));
|
||||
has_frame_ = true;
|
||||
}
|
||||
@@ -238,12 +231,11 @@ bool Director::iterate() {
|
||||
}
|
||||
|
||||
void Director::teardown() {
|
||||
// Si el joc encara no ha acabat (p.ex. eixida per SDL_QUIT des del
|
||||
// sistema), li donem l'oportunitat de tornar net.
|
||||
// Senyal de quit i descàrrega ordenada de l'escena en curs. Els
|
||||
// destructors de cada escena són no-bloquejants — ja no fan fades
|
||||
// bloquejants. La resta de cleanup la gestiona `destroy()`.
|
||||
JG_QuitSignal();
|
||||
while (!GameFiber::is_done()) {
|
||||
GameFiber::resume();
|
||||
}
|
||||
current_scene_.reset();
|
||||
}
|
||||
|
||||
void Director::run() {
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
// El Director és el thread principal que controla la presentació i els inputs.
|
||||
// El codi del joc s'executa dins d'un *fiber* cooperatiu (veure fiber.hpp):
|
||||
// el joc produeix un frame, crida JD8_Flip() que internament fa yield al
|
||||
// Director, i el Director el presenta abans de tornar-lo a reprendre amb
|
||||
// GameFiber::resume(). Tot ocorre en un únic thread — sense mutex, sense
|
||||
// condition_variable, compatible amb el futur port a SDL_AppIterate.
|
||||
#include "scenes/scene.hpp"
|
||||
|
||||
// El Director és l'únic thread del runtime. Cada iterate() fa input →
|
||||
// tick de l'escena actual → JD8_Flip → overlay → present → sleep al frame
|
||||
// target. Totes les escenes (`scenes::Scene` i `ModuleGame`) són
|
||||
// tick-based i no bloquegen — no hi ha fibers, mutex ni condition_variable.
|
||||
// Compatible amb SDL_AppIterate i amb el futur port a emscripten.
|
||||
class Director {
|
||||
public:
|
||||
static void init();
|
||||
@@ -40,19 +42,26 @@ class Director {
|
||||
// Indica si ESC està bloquejada (el joc no l'ha de veure)
|
||||
auto isEscBlocked() const -> bool { return esc_blocked_ || esc_swallow_until_release_; }
|
||||
|
||||
// Pausa: mentre està activa, Director no fa resume() del fiber del joc,
|
||||
// així que el joc queda congelat al seu últim JD8_Flip.
|
||||
// Pausa: mentre està activa, iterate() no avança l'escena — es
|
||||
// continua presentant el darrer frame amb overlay fresc.
|
||||
void togglePause();
|
||||
auto isPaused() const -> bool { return paused_; }
|
||||
|
||||
private:
|
||||
Director() = default;
|
||||
~Director() = default;
|
||||
~Director();
|
||||
|
||||
static Director* instance_;
|
||||
|
||||
void pollAllEvents(); // drenatge amb SDL_PollEvent, només per al bucle natiu
|
||||
|
||||
// Inicialitza info::ctx a partir de Options::game.* i comprova trick.ini.
|
||||
// Es crida una sola vegada des d'iterate() a la primera invocació.
|
||||
void initGameContext();
|
||||
// Construeix l'escena apropiada segons game_state_ i info::ctx.
|
||||
// Retorna nullptr si l'state actual no té escena registrada (bug).
|
||||
std::unique_ptr<scenes::Scene> createNextScene();
|
||||
|
||||
// Buffers persistents entre iteracions. Abans eren locals a run(),
|
||||
// ara són membres perquè iterate() els pot reutilitzar sense tornar-los
|
||||
// a reservar en cada crida del callback.
|
||||
@@ -60,6 +69,13 @@ class Director {
|
||||
Uint32 presentation_buffer_[320 * 200]{};
|
||||
bool has_frame_{false};
|
||||
|
||||
// Estat de l'escena actual. Abans vivia al stack del GameFiber; des
|
||||
// de la Phase B.2 de la migració viu directament al Director.
|
||||
std::unique_ptr<scenes::Scene> current_scene_;
|
||||
int game_state_{1}; // 0 = gameplay (ModuleGame), 1 = via SceneRegistry, -1 = quit
|
||||
Uint32 last_tick_ms_{0};
|
||||
bool context_initialized_{false};
|
||||
|
||||
std::atomic<bool> quit_requested_{false};
|
||||
std::atomic<bool> key_pressed_{false};
|
||||
std::atomic<bool> esc_blocked_{false};
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
#include "core/system/fiber.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
// ucontext_t està marcat com a obsolet a POSIX.1-2008 però continua
|
||||
// funcional a glibc Linux i macOS. Si en el futur migrem a una alternativa
|
||||
// (boost::context, makecontext personalitzat) només cal tocar aquest fitxer.
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
|
||||
namespace GameFiber {
|
||||
|
||||
namespace {
|
||||
|
||||
bool initialized_ = false;
|
||||
bool done_ = false;
|
||||
EntryFn entry_fn_ = nullptr;
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
LPVOID main_fiber_ = nullptr;
|
||||
LPVOID game_fiber_ = nullptr;
|
||||
|
||||
void __stdcall trampoline(void* /*param*/) {
|
||||
if (entry_fn_) entry_fn_();
|
||||
done_ = true;
|
||||
SwitchToFiber(main_fiber_);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
ucontext_t main_ctx_{};
|
||||
ucontext_t fiber_ctx_{};
|
||||
void* fiber_stack_ = nullptr;
|
||||
|
||||
void trampoline() {
|
||||
if (entry_fn_) entry_fn_();
|
||||
done_ = true;
|
||||
// Retornar al main: uc_link apunta a main_ctx_ en init().
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
void init(EntryFn entry, std::size_t stack_size) {
|
||||
if (initialized_) destroy();
|
||||
entry_fn_ = entry;
|
||||
done_ = false;
|
||||
|
||||
#if defined(_WIN32)
|
||||
main_fiber_ = ConvertThreadToFiber(nullptr);
|
||||
if (!main_fiber_) {
|
||||
// Ja era un fiber (no sol passar en el main thread d'una app SDL).
|
||||
main_fiber_ = GetCurrentFiber();
|
||||
}
|
||||
game_fiber_ = CreateFiber(stack_size, trampoline, nullptr);
|
||||
if (!game_fiber_) {
|
||||
std::cerr << "GameFiber::init: CreateFiber failed\n";
|
||||
return;
|
||||
}
|
||||
#else
|
||||
fiber_stack_ = std::malloc(stack_size);
|
||||
if (!fiber_stack_) {
|
||||
std::cerr << "GameFiber::init: malloc failed\n";
|
||||
return;
|
||||
}
|
||||
getcontext(&fiber_ctx_);
|
||||
fiber_ctx_.uc_stack.ss_sp = fiber_stack_;
|
||||
fiber_ctx_.uc_stack.ss_size = stack_size;
|
||||
fiber_ctx_.uc_link = &main_ctx_;
|
||||
makecontext(&fiber_ctx_, trampoline, 0);
|
||||
#endif
|
||||
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (!initialized_) return;
|
||||
#if defined(_WIN32)
|
||||
if (game_fiber_) {
|
||||
DeleteFiber(game_fiber_);
|
||||
game_fiber_ = nullptr;
|
||||
}
|
||||
// No desconvertim el main thread: SDL pot estar-ne pendent i ja no
|
||||
// tornem a crear fibers en aquesta execució. ConvertFiberToThread()
|
||||
// només cal si volguerem reutilitzar el main com a thread normal.
|
||||
#else
|
||||
if (fiber_stack_) {
|
||||
std::free(fiber_stack_);
|
||||
fiber_stack_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
initialized_ = false;
|
||||
done_ = false;
|
||||
entry_fn_ = nullptr;
|
||||
}
|
||||
|
||||
void resume() {
|
||||
if (!initialized_ || done_) return;
|
||||
#if defined(_WIN32)
|
||||
SwitchToFiber(game_fiber_);
|
||||
#else
|
||||
swapcontext(&main_ctx_, &fiber_ctx_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void yield() {
|
||||
if (!initialized_) return;
|
||||
#if defined(_WIN32)
|
||||
SwitchToFiber(main_fiber_);
|
||||
#else
|
||||
swapcontext(&fiber_ctx_, &main_ctx_);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_done() { return done_; }
|
||||
bool is_initialized() { return initialized_; }
|
||||
|
||||
} // namespace GameFiber
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
// Fiber minimalista sobre el suport natiu del SO (ucontext_t en POSIX,
|
||||
// Fibers API en Windows). Serveix per a implementar un yield/resume
|
||||
// cooperatiu entre el Director i el codi del joc sense un std::thread
|
||||
// ni mutex/condition_variable. Substituïx el bloqueig de publishFrame/
|
||||
// consumeFrame amb un mecanisme de control explícit.
|
||||
//
|
||||
// Contracte:
|
||||
// - GameFiber::init(entry) prepara un fiber que executarà `entry`
|
||||
// en un stack dedicat. No el comença a executar encara.
|
||||
// - GameFiber::resume() cedeix el control al fiber. Retorna quan el
|
||||
// fiber crida GameFiber::yield() o quan la funció entry retorna.
|
||||
// - GameFiber::yield() es crida des de dins del fiber per a tornar
|
||||
// el control al main (al punt just després de resume()).
|
||||
// - GameFiber::is_done() indica si la funció entry ha retornat.
|
||||
// - GameFiber::destroy() allibera el stack i reinicia l'estat.
|
||||
//
|
||||
// Per al port a emscripten (Fase 7) caldrà substituir aquesta capa per
|
||||
// Asyncify, però el contracte públic pot romandre idèntic.
|
||||
namespace GameFiber {
|
||||
|
||||
using EntryFn = void (*)();
|
||||
|
||||
void init(EntryFn entry, std::size_t stack_size = 256 * 1024);
|
||||
void destroy();
|
||||
|
||||
void resume();
|
||||
void yield();
|
||||
|
||||
bool is_done();
|
||||
bool is_initialized();
|
||||
|
||||
} // namespace GameFiber
|
||||
Reference in New Issue
Block a user