step B.2: elimina fiber — Director posseeix l'escena, JD8_Flip sense yield, fiber.hpp/cpp esborrats
This commit is contained in:
@@ -39,7 +39,6 @@ set(APP_SOURCES
|
|||||||
|
|
||||||
# Core - System (nova capa)
|
# Core - System (nova capa)
|
||||||
source/core/system/director.cpp
|
source/core/system/director.cpp
|
||||||
source/core/system/fiber.cpp
|
|
||||||
|
|
||||||
# Scenes (cinemàtiques i menús reescrits)
|
# Scenes (cinemàtiques i menús reescrits)
|
||||||
source/scenes/timeline.cpp
|
source/scenes/timeline.cpp
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include "core/jail/jfile.hpp"
|
#include "core/jail/jfile.hpp"
|
||||||
#include "core/system/fiber.hpp"
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
|
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
|
||||||
@@ -151,16 +150,16 @@ void JD8_BlitCKToSurface(int x, int y, JD8_Surface surface, int sx, int sy, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
void JD8_Flip() {
|
void JD8_Flip() {
|
||||||
|
// Converteix el framebuffer indexat (paletted) a ARGB (pixel_data).
|
||||||
|
// El Director crida aquesta funció després del tick de cada escena
|
||||||
|
// per preparar el frame abans de presentar-lo. Ja no fa yield —
|
||||||
|
// tot corre en un sol thread sense fibers des de Phase B.2.
|
||||||
for (int x = 0; x < 320; x++) {
|
for (int x = 0; x < 320; x++) {
|
||||||
for (int y = 0; y < 200; y++) {
|
for (int y = 0; y < 200; y++) {
|
||||||
Uint32 color = 0xFF000000 + main_palette[screen[x + (y * 320)]].r + (main_palette[screen[x + (y * 320)]].g << 8) + (main_palette[screen[x + (y * 320)]].b << 16);
|
Uint32 color = 0xFF000000 + main_palette[screen[x + (y * 320)]].r + (main_palette[screen[x + (y * 320)]].g << 8) + (main_palette[screen[x + (y * 320)]].b << 16);
|
||||||
pixel_data[x + (y * 320)] = color;
|
pixel_data[x + (y * 320)] = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cedeix el control al Director. Quan Director::run() ens torne a fer
|
|
||||||
// resume(), continuarem just ací i el joc continuarà amb la següent
|
|
||||||
// iteració del seu loop sense bloquejos de mutex/cv.
|
|
||||||
GameFiber::yield();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32* JD8_GetFramebuffer() {
|
Uint32* JD8_GetFramebuffer() {
|
||||||
@@ -254,20 +253,9 @@ bool JD8_FadeTickStep() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JD8_FadeOut() {
|
// Els shims bloquejants `JD8_FadeOut` i `JD8_FadeToPal` han estat
|
||||||
JD8_FadeStartOut();
|
// eliminats a Phase B.2: feien un bucle de 32 iteracions amb `JD8_Flip`
|
||||||
while (true) {
|
// entre cada una que només funcionava mentre l'entorn tenia fibers i
|
||||||
const bool done = JD8_FadeTickStep();
|
// `JD8_Flip` cedia el control al Director. Ara tot fade es fa tick a
|
||||||
JD8_Flip();
|
// tick via `scenes::PaletteFade` (que encapsula `JD8_FadeStartOut` /
|
||||||
if (done) break;
|
// `JD8_FadeStartToPal` + `JD8_FadeTickStep`).
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void JD8_FadeToPal(JD8_Palette pal) {
|
|
||||||
JD8_FadeStartToPal(pal);
|
|
||||||
while (true) {
|
|
||||||
const bool done = JD8_FadeTickStep();
|
|
||||||
JD8_Flip();
|
|
||||||
if (done) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -40,10 +40,9 @@ void JD8_BlitCKScroll(int y, JD8_Surface surface, int sx, int sy, int sh, Uint8
|
|||||||
|
|
||||||
void JD8_BlitCKToSurface(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, JD8_Surface dest, Uint8 colorkey);
|
void JD8_BlitCKToSurface(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, JD8_Surface dest, Uint8 colorkey);
|
||||||
|
|
||||||
// Converteix la pantalla indexada a ARGB i cedeix el control al Director
|
// Converteix la pantalla indexada a ARGB. El Director crida aquesta
|
||||||
// (GameFiber::yield). El Director llegirà el framebuffer convertit via
|
// funció al final de cada tick i després llegeix el framebuffer via
|
||||||
// JD8_GetFramebuffer() i tornarà a cridar Fiber::resume() quan toque el
|
// JD8_GetFramebuffer() per presentar-lo.
|
||||||
// pròxim frame.
|
|
||||||
void JD8_Flip();
|
void JD8_Flip();
|
||||||
|
|
||||||
// Accés al framebuffer ARGB de 320x200 actualitzat per l'última crida a
|
// Accés al framebuffer ARGB de 320x200 actualitzat per l'última crida a
|
||||||
@@ -58,16 +57,13 @@ void JD8_PutPixel(JD8_Surface surface, int x, int y, Uint8 pixel);
|
|||||||
|
|
||||||
void JD8_SetPaletteColor(Uint8 index, Uint8 r, Uint8 g, Uint8 b);
|
void JD8_SetPaletteColor(Uint8 index, Uint8 r, Uint8 g, Uint8 b);
|
||||||
|
|
||||||
// Fades legacy bloquejants (shim damunt la màquina d'estats de sota).
|
|
||||||
void JD8_FadeOut();
|
|
||||||
void JD8_FadeToPal(JD8_Palette pal);
|
|
||||||
|
|
||||||
// API de fade no bloquejant (màquina d'estats). `FadeStart*` inicia el
|
// API de fade no bloquejant (màquina d'estats). `FadeStart*` inicia el
|
||||||
// fade; `FadeTickStep` aplica un pas i retorna `true` quan el fade ha
|
// fade; `FadeTickStep` aplica un pas i retorna `true` quan el fade ha
|
||||||
// acabat. Un pas correspon visualment a una iteració del fade original
|
// acabat. Un pas correspon visualment a una iteració del fade original
|
||||||
// (32 passos en total). El caller és responsable de fer el Flip entre
|
// (32 passos en total). El caller és responsable de fer el Flip entre
|
||||||
// passos si el vol veure animat. `FadeIsActive` permet saber si hi ha
|
// passos si el vol veure animat. `FadeIsActive` permet saber si hi ha
|
||||||
// un fade en curs per a enllaçar-lo amb un altre subsistema.
|
// un fade en curs per a enllaçar-lo amb un altre subsistema.
|
||||||
|
// L'embolcall `scenes::PaletteFade` ho fa més idiomàtic per a escenes.
|
||||||
void JD8_FadeStartOut();
|
void JD8_FadeStartOut();
|
||||||
void JD8_FadeStartToPal(JD8_Palette pal);
|
void JD8_FadeStartToPal(JD8_Palette pal);
|
||||||
bool JD8_FadeTickStep();
|
bool JD8_FadeTickStep();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "core/jail/jgame.hpp"
|
#include "core/jail/jgame.hpp"
|
||||||
|
|
||||||
#include "core/system/fiber.hpp"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool quitting = false;
|
bool quitting = false;
|
||||||
@@ -41,12 +39,9 @@ bool JG_ShouldUpdate() {
|
|||||||
cycle_counter++;
|
cycle_counter++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Encara no toca update: cedim el control al Director per a que puga
|
// No toca update — retornem false sense més. Des de Phase B.2 ja no
|
||||||
// processar events, animar l'overlay i mantindre l'àudio viu. Sense
|
// hi ha fibers: cap caller fa spin-waits (`while (!JG_ShouldUpdate())`)
|
||||||
// aquest yield, els spin-waits típics de les cinemàtiques
|
// i el Director pren el control del main loop frame a frame.
|
||||||
// (`while (!JG_ShouldUpdate()) { JI_Update(); ... }`) congelarien
|
|
||||||
// tot el main loop — el fiber no cediria mai.
|
|
||||||
GameFiber::yield();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include "core/rendering/menu.hpp"
|
#include "core/rendering/menu.hpp"
|
||||||
#include "core/rendering/overlay.hpp"
|
#include "core/rendering/overlay.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/system/fiber.hpp"
|
|
||||||
#include "game/info.hpp"
|
#include "game/info.hpp"
|
||||||
#include "game/modulegame.hpp"
|
#include "game/modulegame.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
@@ -35,14 +34,9 @@ extern void JI_moveCheats(Uint8 new_key);
|
|||||||
|
|
||||||
Director* Director::instance_ = nullptr;
|
Director* Director::instance_ = nullptr;
|
||||||
|
|
||||||
namespace {
|
Director::~Director() = default;
|
||||||
|
|
||||||
// Entry del fiber del joc. Dispatcha a una escena segons l'estat actual:
|
void Director::initGameContext() {
|
||||||
// `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() {
|
|
||||||
info::ctx.num_habitacio = Options::game.habitacio_inicial;
|
info::ctx.num_habitacio = Options::game.habitacio_inicial;
|
||||||
info::ctx.num_piramide = Options::game.piramide_inicial;
|
info::ctx.num_piramide = Options::game.piramide_inicial;
|
||||||
info::ctx.diners = Options::game.diners_inicial;
|
info::ctx.diners = Options::game.diners_inicial;
|
||||||
@@ -57,55 +51,31 @@ void gameFiberEntry() {
|
|||||||
info::ctx.nou_personatge = true;
|
info::ctx.nou_personatge = true;
|
||||||
fclose(ini);
|
fclose(ini);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int gameState = 1;
|
std::unique_ptr<scenes::Scene> Director::createNextScene() {
|
||||||
while (gameState != -1 && !JG_Quitting()) {
|
if (game_state_ == 0) {
|
||||||
std::unique_ptr<scenes::Scene> scene;
|
// Gameplay. ModuleGame és una scenes::Scene des de la Phase A.
|
||||||
|
return std::make_unique<ModuleGame>();
|
||||||
if (gameState == 0) {
|
}
|
||||||
// Gameplay. ModuleGame és una scenes::Scene des de Phase A de
|
// game_state_ == 1: dispatch al registry per num_piramide. Replica
|
||||||
// la migració — mateix mini-loop tick+flip que la resta.
|
// del redirect que el vell ModuleSequence::Go() feia: si el jugador
|
||||||
scene = std::make_unique<ModuleGame>();
|
// arriba a la Secreta (6) sense prou diners, salta als slides de
|
||||||
} else {
|
// fracàs (7) abans de buscar l'escena al registry.
|
||||||
// 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) {
|
if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) {
|
||||||
info::ctx.num_piramide = 7;
|
info::ctx.num_piramide = 7;
|
||||||
}
|
}
|
||||||
scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide);
|
return 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
|
|
||||||
|
|
||||||
void Director::init() {
|
void Director::init() {
|
||||||
instance_ = new Director();
|
instance_ = new Director();
|
||||||
Gamepad::init();
|
Gamepad::init();
|
||||||
|
|
||||||
// Registre d'escenes. Cada entrada = un state_key (`num_piramide`)
|
// Registre d'escenes. Cada entrada = un state_key (`num_piramide`)
|
||||||
// amb una factory de `scenes::Scene`. El gameFiberEntry consulta
|
// amb una factory de `scenes::Scene`. iterate() consulta aquest
|
||||||
// aquest registry per a tots els states de seqüència; si una clau
|
// registry per a tots els states de seqüència (game_state_ == 1); si
|
||||||
// no apareix ací, el fiber eixirà del loop.
|
// una clau no apareix ací, Director surt ordenadament.
|
||||||
auto& registry = scenes::SceneRegistry::instance();
|
auto& registry = scenes::SceneRegistry::instance();
|
||||||
registry.registerScene(0, [] { return std::make_unique<scenes::MenuScene>(); });
|
registry.registerScene(0, [] { return std::make_unique<scenes::MenuScene>(); });
|
||||||
registry.registerScene(100, [] { return std::make_unique<scenes::MortScene>(); });
|
registry.registerScene(100, [] { return std::make_unique<scenes::MortScene>(); });
|
||||||
@@ -132,12 +102,9 @@ void Director::init() {
|
|||||||
}
|
}
|
||||||
return std::make_unique<scenes::IntroScene>();
|
return std::make_unique<scenes::IntroScene>();
|
||||||
});
|
});
|
||||||
|
|
||||||
GameFiber::init(gameFiberEntry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Director::destroy() {
|
void Director::destroy() {
|
||||||
GameFiber::destroy();
|
|
||||||
Gamepad::destroy();
|
Gamepad::destroy();
|
||||||
delete instance_;
|
delete instance_;
|
||||||
instance_ = nullptr;
|
instance_ = nullptr;
|
||||||
@@ -164,17 +131,17 @@ void Director::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Director::iterate() {
|
bool Director::iterate() {
|
||||||
if (GameFiber::is_done() || quit_requested_) {
|
if (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.
|
|
||||||
JG_QuitSignal();
|
JG_QuitSignal();
|
||||||
while (!GameFiber::is_done()) {
|
current_scene_.reset(); // destrueix l'escena actual ordenadament
|
||||||
GameFiber::resume();
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context_initialized_) {
|
||||||
|
initGameContext();
|
||||||
|
context_initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr Uint32 FRAME_MS_VSYNC = 16; // ~60 FPS amb VSync
|
constexpr Uint32 FRAME_MS_VSYNC = 16; // ~60 FPS amb VSync
|
||||||
constexpr Uint32 FRAME_MS_NO_VSYNC = 4; // ~250 FPS sense VSync (límit superior)
|
constexpr Uint32 FRAME_MS_NO_VSYNC = 4; // ~250 FPS sense VSync (límit superior)
|
||||||
|
|
||||||
@@ -205,15 +172,41 @@ bool Director::iterate() {
|
|||||||
esc_blocked_ = false;
|
esc_blocked_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cedeix el control al fiber del joc. Quan retorne (per un JD8_Flip()
|
// Avança l'escena (si no estem pausats). En pausa, es manté l'escena
|
||||||
// dins del joc) tindrem un nou frame a pixel_data. Si estem en pausa,
|
// congelada i re-presentem l'últim frame amb l'overlay fresc per
|
||||||
// no executem el fiber: es queda congelat al seu últim yield i
|
// damunt.
|
||||||
// continuem presentant l'últim frame conegut.
|
|
||||||
if (!paused_) {
|
if (!paused_) {
|
||||||
GameFiber::resume();
|
// Transicions: si l'escena actual ha acabat (o s'ha senyalat
|
||||||
if (GameFiber::is_done()) {
|
// quit), llegim el seu next state i la destruïm per crear la
|
||||||
return false;
|
// 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_));
|
std::memcpy(game_frame_, JD8_GetFramebuffer(), sizeof(game_frame_));
|
||||||
has_frame_ = true;
|
has_frame_ = true;
|
||||||
}
|
}
|
||||||
@@ -238,12 +231,11 @@ bool Director::iterate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Director::teardown() {
|
void Director::teardown() {
|
||||||
// Si el joc encara no ha acabat (p.ex. eixida per SDL_QUIT des del
|
// Senyal de quit i descàrrega ordenada de l'escena en curs. Els
|
||||||
// sistema), li donem l'oportunitat de tornar net.
|
// destructors de cada escena són no-bloquejants — ja no fan fades
|
||||||
|
// bloquejants. La resta de cleanup la gestiona `destroy()`.
|
||||||
JG_QuitSignal();
|
JG_QuitSignal();
|
||||||
while (!GameFiber::is_done()) {
|
current_scene_.reset();
|
||||||
GameFiber::resume();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Director::run() {
|
void Director::run() {
|
||||||
|
|||||||
@@ -4,13 +4,15 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
// El Director és el thread principal que controla la presentació i els inputs.
|
#include "scenes/scene.hpp"
|
||||||
// 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
|
// El Director és l'únic thread del runtime. Cada iterate() fa input →
|
||||||
// Director, i el Director el presenta abans de tornar-lo a reprendre amb
|
// tick de l'escena actual → JD8_Flip → overlay → present → sleep al frame
|
||||||
// GameFiber::resume(). Tot ocorre en un únic thread — sense mutex, sense
|
// target. Totes les escenes (`scenes::Scene` i `ModuleGame`) són
|
||||||
// condition_variable, compatible amb el futur port a SDL_AppIterate.
|
// 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 {
|
class Director {
|
||||||
public:
|
public:
|
||||||
static void init();
|
static void init();
|
||||||
@@ -40,19 +42,26 @@ class Director {
|
|||||||
// Indica si ESC està bloquejada (el joc no l'ha de veure)
|
// Indica si ESC està bloquejada (el joc no l'ha de veure)
|
||||||
auto isEscBlocked() const -> bool { return esc_blocked_ || esc_swallow_until_release_; }
|
auto isEscBlocked() const -> bool { return esc_blocked_ || esc_swallow_until_release_; }
|
||||||
|
|
||||||
// Pausa: mentre està activa, Director no fa resume() del fiber del joc,
|
// Pausa: mentre està activa, iterate() no avança l'escena — es
|
||||||
// així que el joc queda congelat al seu últim JD8_Flip.
|
// continua presentant el darrer frame amb overlay fresc.
|
||||||
void togglePause();
|
void togglePause();
|
||||||
auto isPaused() const -> bool { return paused_; }
|
auto isPaused() const -> bool { return paused_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Director() = default;
|
Director() = default;
|
||||||
~Director() = default;
|
~Director();
|
||||||
|
|
||||||
static Director* instance_;
|
static Director* instance_;
|
||||||
|
|
||||||
void pollAllEvents(); // drenatge amb SDL_PollEvent, només per al bucle natiu
|
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(),
|
// Buffers persistents entre iteracions. Abans eren locals a run(),
|
||||||
// ara són membres perquè iterate() els pot reutilitzar sense tornar-los
|
// ara són membres perquè iterate() els pot reutilitzar sense tornar-los
|
||||||
// a reservar en cada crida del callback.
|
// a reservar en cada crida del callback.
|
||||||
@@ -60,6 +69,13 @@ class Director {
|
|||||||
Uint32 presentation_buffer_[320 * 200]{};
|
Uint32 presentation_buffer_[320 * 200]{};
|
||||||
bool has_frame_{false};
|
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> quit_requested_{false};
|
||||||
std::atomic<bool> key_pressed_{false};
|
std::atomic<bool> key_pressed_{false};
|
||||||
std::atomic<bool> esc_blocked_{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