From 31b9995406421d68eca9b01c6feb7a66d207947b Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 19 Jul 2020 11:57:00 +0200 Subject: [PATCH] - rewrote the ScreenJob player as a class that can be called by an asynchronous dispatcher. Works, except for timing issues with ANMs. --- source/core/screenjob.cpp | 321 ++++++++++++++++++++-------- source/core/screenjob.h | 3 + source/games/duke/src/2d_d.cpp | 10 +- source/games/duke/src/2d_r.cpp | 4 +- source/games/duke/src/dispatch.cpp | 4 +- source/games/duke/src/duke3d.h | 2 +- source/games/duke/src/funct.h | 13 +- source/games/duke/src/game_misc.cpp | 13 +- source/games/duke/src/premap.cpp | 7 +- 9 files changed, 263 insertions(+), 114 deletions(-) diff --git a/source/core/screenjob.cpp b/source/core/screenjob.cpp index b0ce84dd6..0672d82d6 100644 --- a/source/core/screenjob.cpp +++ b/source/core/screenjob.cpp @@ -67,95 +67,6 @@ int DImageScreen::Frame(uint64_t clock, bool skiprequest) return skiprequest ? -1 : 1; } - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -void RunScreenJob(JobDesc *jobs, int count, CompletionFunc completion, bool clearbefore) -{ - // Release all jobs from the garbage collector - the code as it is cannot deal with them getting collected. - for (int i = 0; i < count; i++) - { - jobs[i].job->Release(); - } - bool skipped = false; - for (int i = 0; i < count; i++) - { - auto job = jobs[i]; - if (job.job != nullptr && (!skipped || !job.ignoreifskipped)) - { - skipped = false; - if (clearbefore) - { - twod->ClearScreen(); - videoNextPage(); - } - - auto startTime = I_nsTime(); - - // Input later needs to be event based so that these screens can do more than be skipped. - inputState.ClearAllInput(); - - float screenfade = job.job->fadestyle & DScreenJob::fadein ? 0.f : 1.f; - - while (true) - { - auto now = I_nsTime(); - handleevents(); - bool skiprequest = inputState.CheckAllInput(); - auto clock = now - startTime; - if (screenfade < 1.f) - { - float ms = (clock / 1'000'000) / job.job->fadetime; - screenfade = clamp(ms, 0.f, 1.f); - twod->SetScreenFade(screenfade); - job.job->fadestate = DScreenJob::fadein; - } - else job.job->fadestate = DScreenJob::visible; - job.job->SetClock(clock); - int state = job.job->Frame(clock, skiprequest); - startTime -= job.job->GetClock() - clock; - // Must lock before displaying. - if (state < 1 && job.job->fadestyle & DScreenJob::fadeout) - twod->Lock(); - - videoNextPage(); - if (state < 1) - { - if (job.job->fadestyle & DScreenJob::fadeout) - { - job.job->fadestate = DScreenJob::fadeout; - startTime = now; - float screenfade2; - do - { - now = I_nsTime(); - auto clock = now - startTime; - float ms = (clock / 1'000'000) / job.job->fadetime; - screenfade2 = clamp(screenfade - ms, 0.f, 1.f); - twod->SetScreenFade(screenfade2); - if (screenfade2 <= 0.f) twod->Unlock(); // must unlock before displaying. - videoNextPage(); - - } while (screenfade2 > 0.f); - } - skipped = state < 0; - job.job->Destroy(); - job.job->ObjectFlags |= OF_YesReallyDelete; - delete job.job; - twod->SetScreenFade(1); - break; - } - } - } - if (job.postAction) job.postAction(); - } - if (completion) completion(false); -} - //--------------------------------------------------------------------------- // // @@ -305,3 +216,235 @@ DScreenJob *PlayVideo(const char* filename, const AnimSound* ans, const int* fra return nothing(); } + +//--------------------------------------------------------------------------- +// +// +// +//--------------------------------------------------------------------------- + +class ScreenJobRunner +{ + enum + { + State_Clear, + State_Run, + State_Fadeout + }; + TArray jobs; + CompletionFunc completion; + int index = -1; + float screenfade; + bool clearbefore; + bool skipped = false; + uint64_t startTime; + int actionState; + int terminateState; + +public: + ScreenJobRunner(JobDesc* jobs_, int count, CompletionFunc completion_, bool clearbefore_) + : completion(std::move(completion_)), clearbefore(clearbefore_) + { + jobs.Resize(count); + memcpy(jobs.Data(), jobs_, count * sizeof(JobDesc)); + // Release all jobs from the garbage collector - the code as it is cannot deal with them getting collected. This should be removed later once the GC is working. + for (int i = 0; i < count; i++) + { + jobs[i].job->Release(); + } + AdvanceJob(false); + } + + void AdvanceJob(bool skip) + { + index++; + while (index < jobs.Size() && (jobs[index].job == nullptr || (skip && jobs[index].ignoreifskipped))) index++; + actionState = clearbefore ? State_Clear : State_Run; + if (index < jobs.Size()) screenfade = jobs[index].job->fadestyle & DScreenJob::fadein ? 0.f : 1.f; + } + + int DisplayFrame() + { + auto& job = jobs[index]; + auto now = I_nsTime(); + bool skiprequest = inputState.CheckAllInput(); + auto clock = now - startTime; + if (screenfade < 1.f) + { + float ms = (clock / 1'000'000) / job.job->fadetime; + screenfade = clamp(ms, 0.f, 1.f); + twod->SetScreenFade(screenfade); + job.job->fadestate = DScreenJob::fadein; + } + else job.job->fadestate = DScreenJob::visible; + job.job->SetClock(clock); + int state = job.job->Frame(clock, skiprequest); + startTime -= job.job->GetClock() - clock; + return state; + } + + int FadeoutFrame() + { + auto now = I_nsTime(); + auto clock = now - startTime; + float ms = (clock / 1'000'000) / jobs[index].job->fadetime; + float screenfade2 = clamp(screenfade - ms, 0.f, 1.f); + twod->SetScreenFade(screenfade2); + if (screenfade2 <= 0.f) + { + twod->Unlock(); // must unlock before displaying. + return 0; + } + return 1; + } + + bool RunFrame() + { + if (index >= jobs.Size()) + { + for (auto& job : jobs) + { + job.job->Destroy(); + job.job->ObjectFlags |= OF_YesReallyDelete; + delete job.job; + } + twod->SetScreenFade(1); + if (completion) completion(false); + return false; + } + handleevents(); + if (actionState == State_Clear) + { + actionState = State_Run; + twod->ClearScreen(); + } + else if (actionState == State_Run) + { + terminateState = DisplayFrame(); + if (terminateState < 1) + { + // Must lock before displaying. + if (jobs[index].job->fadestyle & DScreenJob::fadeout) + { + twod->Lock(); + startTime = I_nsTime(); + jobs[index].job->fadestate = DScreenJob::fadeout; + actionState = State_Fadeout; + } + else + { + AdvanceJob(terminateState < 0); + } + } + } + else if (actionState == State_Fadeout) + { + int ended = FadeoutFrame(); + if (ended < 1) + { + AdvanceJob(terminateState < 0); + } + } + return true; + } +}; + +void RunScreenJob(JobDesc* jobs, int count, CompletionFunc completion, bool clearbefore) +{ + ScreenJobRunner runner(jobs, count, completion, clearbefore); + + while (runner.RunFrame()) + { + videoNextPage(); + } +} +#if 0 +//--------------------------------------------------------------------------- +// +// +// +//--------------------------------------------------------------------------- + +void RunScreenJobSync(JobDesc* jobs, int count, CompletionFunc completion, bool clearbefore) +{ + // Release all jobs from the garbage collector - the code as it is cannot deal with them getting collected. + for (int i = 0; i < count; i++) + { + jobs[i].job->Release(); + } + bool skipped = false; + for (int i = 0; i < count; i++) + { + auto job = jobs[i]; + if (job.job != nullptr && (!skipped || !job.ignoreifskipped)) + { + skipped = false; + if (clearbefore) + { + twod->ClearScreen(); + videoNextPage(); + } + + auto startTime = I_nsTime(); + + // Input later needs to be event based so that these screens can do more than be skipped. + inputState.ClearAllInput(); + + float screenfade = job.job->fadestyle & DScreenJob::fadein ? 0.f : 1.f; + + while (true) + { + auto now = I_nsTime(); + handleevents(); + bool skiprequest = inputState.CheckAllInput(); + auto clock = now - startTime; + if (screenfade < 1.f) + { + float ms = (clock / 1'000'000) / job.job->fadetime; + screenfade = clamp(ms, 0.f, 1.f); + twod->SetScreenFade(screenfade); + job.job->fadestate = DScreenJob::fadein; + } + else job.job->fadestate = DScreenJob::visible; + job.job->SetClock(clock); + int state = job.job->Frame(clock, skiprequest); + startTime -= job.job->GetClock() - clock; + // Must lock before displaying. + if (state < 1 && job.job->fadestyle & DScreenJob::fadeout) + twod->Lock(); + + videoNextPage(); + if (state < 1) + { + if (job.job->fadestyle & DScreenJob::fadeout) + { + job.job->fadestate = DScreenJob::fadeout; + startTime = now; + float screenfade2; + do + { + now = I_nsTime(); + auto clock = now - startTime; + float ms = (clock / 1'000'000) / job.job->fadetime; + screenfade2 = clamp(screenfade - ms, 0.f, 1.f); + twod->SetScreenFade(screenfade2); + if (screenfade2 <= 0.f) twod->Unlock(); // must unlock before displaying. + videoNextPage(); + + } while (screenfade2 > 0.f); + } + skipped = state < 0; + job.job->Destroy(); + job.job->ObjectFlags |= OF_YesReallyDelete; + delete job.job; + twod->SetScreenFade(1); + break; + } + } + } + if (job.postAction) job.postAction(); + } + if (completion) completion(false); +} +#endif + diff --git a/source/core/screenjob.h b/source/core/screenjob.h index d587d73ed..f8fb89422 100644 --- a/source/core/screenjob.h +++ b/source/core/screenjob.h @@ -4,6 +4,7 @@ using CompletionFunc = std::function; struct JobDesc; +class ScreenJobRunner; class DScreenJob : public DObject { @@ -14,6 +15,7 @@ class DScreenJob : public DObject int fadestate = fadein; friend void RunScreenJob(JobDesc* jobs, int count, CompletionFunc completion, bool clearbefore); + friend class ScreenJobRunner; public: enum @@ -79,6 +81,7 @@ struct JobDesc void RunScreenJob(JobDesc *jobs, int count, CompletionFunc completion, bool clearbefore = true); +void RunScreenJobSync(JobDesc* jobs, int count, CompletionFunc completion, bool clearbefore = true); struct AnimSound { diff --git a/source/games/duke/src/2d_d.cpp b/source/games/duke/src/2d_d.cpp index 714fe26a4..55df398d9 100644 --- a/source/games/duke/src/2d_d.cpp +++ b/source/games/duke/src/2d_d.cpp @@ -253,7 +253,7 @@ public: // //--------------------------------------------------------------------------- -void Logo_d(CompletionFunc completion) +void Logo_d(const CompletionFunc &completion) { Mus_Stop(); FX_StopAllSounds(); // JBF 20031228 @@ -636,7 +636,7 @@ static void bonussequence_d(int num, JobDesc *jobs, int &job) // //--------------------------------------------------------------------------- -void showtwoscreens(CompletionFunc completion) +void showtwoscreens(const CompletionFunc& completion) { JobDesc jobs[2]; int job = 0; @@ -646,7 +646,7 @@ void showtwoscreens(CompletionFunc completion) RunScreenJob(jobs, job, completion); } -void doorders(CompletionFunc completion) +void doorders(const CompletionFunc& completion) { JobDesc jobs[4]; int job = 0; @@ -956,7 +956,7 @@ public: // //--------------------------------------------------------------------------- -void dobonus_d(bool bonusonly, CompletionFunc completion) +void dobonus_d(bool bonusonly, const CompletionFunc& completion) { JobDesc jobs[20]; int job = 0; @@ -988,7 +988,7 @@ void dobonus_d(bool bonusonly, CompletionFunc completion) // //--------------------------------------------------------------------------- -void e4intro(CompletionFunc completion) +void e4intro(const CompletionFunc& completion) { JobDesc jobs[5]; int job = 0; diff --git a/source/games/duke/src/2d_r.cpp b/source/games/duke/src/2d_r.cpp index 98641bb00..b9304fc5b 100644 --- a/source/games/duke/src/2d_r.cpp +++ b/source/games/duke/src/2d_r.cpp @@ -164,7 +164,7 @@ static void MiniText(double x, double y, const char* t, int shade, int align = - // //--------------------------------------------------------------------------- -void Logo_r(CompletionFunc completion) +void Logo_r(const CompletionFunc& completion) { Mus_Stop(); FX_StopAllSounds(); // JBF 20031228 @@ -552,7 +552,7 @@ public: // //--------------------------------------------------------------------------- -void dobonus_r(bool bonusonly, CompletionFunc completion) +void dobonus_r(bool bonusonly, const CompletionFunc& completion) { JobDesc jobs[20]; int job = 0; diff --git a/source/games/duke/src/dispatch.cpp b/source/games/duke/src/dispatch.cpp index 03c4d0c3f..40eabf691 100644 --- a/source/games/duke/src/dispatch.cpp +++ b/source/games/duke/src/dispatch.cpp @@ -114,8 +114,8 @@ void think_r(); void animatesprites_d(int x, int y, int a, int smoothratio); void animatesprites_r(int x, int y, int a, int smoothratio); -void Logo_d(CompletionFunc); -void Logo_r(CompletionFunc); +void Logo_d(const CompletionFunc&); +void Logo_r(const CompletionFunc&); void InitFonts_d(); void InitFonts_r(); void PrintPaused_d(); diff --git a/source/games/duke/src/duke3d.h b/source/games/duke/src/duke3d.h index 20ac92728..75722865e 100644 --- a/source/games/duke/src/duke3d.h +++ b/source/games/duke/src/duke3d.h @@ -69,7 +69,7 @@ struct GameInterface : ::GameInterface struct Dispatcher { // global stuff - void (*ShowLogo)(CompletionFunc completion); + void (*ShowLogo)(const CompletionFunc& completion); void (*InitFonts)(); void (*PrintPaused)(); diff --git a/source/games/duke/src/funct.h b/source/games/duke/src/funct.h index 60b9ca7f1..0cc3e0031 100644 --- a/source/games/duke/src/funct.h +++ b/source/games/duke/src/funct.h @@ -156,8 +156,8 @@ void setsectinterpolate(int sprnum); int LocateTheLocator(int const tag, int const sectnum); void clearcamera(player_struct* ps); -void showtwoscreens(CompletionFunc func); -void doorders(CompletionFunc func); +void showtwoscreens(const CompletionFunc& func); +void doorders(const CompletionFunc& func); void execute(int s, int p, int d); void makeitfall(int s); @@ -208,9 +208,9 @@ void drawstatusbar_d(int snum); void drawstatusbar_r(int snum); void drawoverheadmap(int cposx, int cposy, int czoom, int cang); void cameratext(int i); -void dobonus(int bonusonly, CompletionFunc completion); -void dobonus_d(bool bonusonly, CompletionFunc completion); -void dobonus_r(bool bonusonly, CompletionFunc completion); +void dobonus(int bonusonly, const CompletionFunc& completion); +void dobonus_d(bool bonusonly, const CompletionFunc& completion); +void dobonus_r(bool bonusonly, const CompletionFunc& completion); void displayrest(int32_t smoothratio); void drawbackground(void); @@ -221,11 +221,10 @@ void resettimevars(); bool setnextmap(bool checksecretexit); void prelevel_d(int g); void prelevel_r(int g); -void e4intro(CompletionFunc completion); +void e4intro(const CompletionFunc& completion); void clearfrags(void); void exitlevel(); int enterlevel(MapRecord* mi, int gm); -void newgame(MapRecord* mi, int sk, CompletionFunc completion); void donewgame(MapRecord* map, int sk); void startnewgame(MapRecord* map, int skill); void setlocalplayerinput(player_struct *pp); diff --git a/source/games/duke/src/game_misc.cpp b/source/games/duke/src/game_misc.cpp index f329dad58..3592f49dd 100644 --- a/source/games/duke/src/game_misc.cpp +++ b/source/games/duke/src/game_misc.cpp @@ -109,22 +109,25 @@ void setmapfog(int fogtype) // //--------------------------------------------------------------------------- -static void runbonus(CompletionFunc completion) +template +void runbonus(func completion) { // MP scoreboard if (playerswhenstarted > 1 && ps[myconnectindex].gm & MODE_GAME && !ud.coop) { dobonus(1, completion); } - else if (completion) completion(false); + else completion(false); } -static void runtwoscreens(CompletionFunc completion) + +template +void runtwoscreens(func completion) { // shareware and TEN screens if (!VOLUMEALL && !isRR()) showtwoscreens(completion); - else if (completion) completion(false); + else completion(false); } static void endthegame(bool) @@ -722,7 +725,7 @@ void cameratext(int i) // //--------------------------------------------------------------------------- -void dobonus(int bonusonly, CompletionFunc completion) +void dobonus(int bonusonly, const CompletionFunc& completion) { if (isRRRA()) { if (completion) completion(false); } else if (isRR()) dobonus_r(bonusonly, completion); diff --git a/source/games/duke/src/premap.cpp b/source/games/duke/src/premap.cpp index c3801d642..d06469b96 100644 --- a/source/games/duke/src/premap.cpp +++ b/source/games/duke/src/premap.cpp @@ -837,7 +837,8 @@ void donewgame(MapRecord* map, int sk) } } -void newgame(MapRecord* map, int sk, CompletionFunc completion) +template +void newgame(MapRecord* map, int sk, func completion) { handleevents(); ready2send = 0; @@ -846,12 +847,12 @@ void newgame(MapRecord* map, int sk, CompletionFunc completion) { if (!isRR() && map->levelNumber == levelnum(3, 0) && (ud.multimode < 2)) { - e4intro([=](bool) { donewgame(map, sk); if (completion) completion(res); }); + e4intro([=](bool) { donewgame(map, sk); completion(res); }); } else { donewgame(map, sk); - if (completion) completion(res); + completion(res); } };