TINSEL: Fix token function calls from non-coroutines

Add extra bools to track token status. This allows setting
the owner to nullptr when a token is acquired from a
non-coroutine context / nullContext without marking it as free.

Fixes the following assert in Discworld 1 (see #13220):

After using the matches on the drainpipe to get the golden chimney
brush, the game switches to non-interactive scenes twice.
When changing back to the original scene, player control should be
disabled while a short animation plays, but this is not the case.
A mouse click by the player during this animation will then initiate
a walk action and cause a failed assert:

engines/tinsel/events.cpp:280:
 void Tinsel::WalkProcess(Common::CoroBaseContext*&, const void*):
 Assertion `_ctx->pMover->hCpath != NOPOLY' failed.

The game attempts to disable user control via the following call chain:
Tinsel::TinselEngine::NextGameCycle()
  Tinsel::ChangeScene()
    Tinsel::DoRestoreSceneFrame()
      Tinsel::Background::StartupBackground()
        Tinsel::ControlStartOff()
	  Tinsel::Control()
            Tinsel::GetControlToken()

This will set the owner of the control token to the current coroutine:
g_tokens[TOKEN_CONTROL].proc = CoroScheduler.getCurrentProcess()

However, since StartupBackground() is called from a non-coroutine
with a nullContext, getCurrentProcess() will return nullptr,
thereby marking the control token as free and allowing player actions.
This commit is contained in:
PushmePullyu 2023-06-25 02:54:14 +02:00 committed by Eugene Sandulenko
parent b351758701
commit 7b2924b83e

View File

@ -31,6 +31,7 @@ namespace Tinsel {
struct Token {
Common::PROCESS *proc;
bool isFree;
};
// These vars are reset upon engine destruction
@ -44,13 +45,15 @@ static void TerminateProcess(Common::PROCESS *tProc) {
// Release tokens held by the process
for (int i = 0; i < NUMTOKENS; i++) {
if (g_tokens[i].proc == tProc) {
if (!g_tokens[i].isFree && g_tokens[i].proc == tProc) {
g_tokens[i].proc = nullptr;
g_tokens[i].isFree = true;
}
}
// Kill the process
CoroScheduler.killProcess(tProc);
if (tProc != nullptr)
CoroScheduler.killProcess(tProc);
}
/**
@ -59,8 +62,9 @@ static void TerminateProcess(Common::PROCESS *tProc) {
void GetControlToken() {
const int which = TOKEN_CONTROL;
if (g_tokens[which].proc == NULL) {
if (g_tokens[which].isFree) {
g_tokens[which].proc = CoroScheduler.getCurrentProcess();
g_tokens[which].isFree = false;
}
}
@ -70,6 +74,7 @@ void GetControlToken() {
void FreeControlToken() {
// Allow anyone to free TOKEN_CONTROL
g_tokens[TOKEN_CONTROL].proc = nullptr;
g_tokens[TOKEN_CONTROL].isFree = true;
}
@ -84,12 +89,13 @@ void FreeControlToken() {
void GetToken(int which) {
assert(TOKEN_LEAD <= which && which < NUMTOKENS);
if (g_tokens[which].proc != NULL) {
if (!g_tokens[which].isFree) {
assert(g_tokens[which].proc != CoroScheduler.getCurrentProcess());
TerminateProcess(g_tokens[which].proc);
}
g_tokens[which].proc = CoroScheduler.getCurrentProcess();
g_tokens[which].isFree = false;
}
/**
@ -102,6 +108,7 @@ void FreeToken(int which) {
assert(g_tokens[which].proc == CoroScheduler.getCurrentProcess()); // we'd have been killed if some other proc had taken this token
g_tokens[which].proc = nullptr;
g_tokens[which].isFree = true;
}
/**
@ -111,7 +118,7 @@ bool TestToken(int which) {
if (which < 0 || which >= NUMTOKENS)
return false;
return (g_tokens[which].proc == NULL);
return (g_tokens[which].isFree);
}
/**
@ -120,6 +127,7 @@ bool TestToken(int which) {
void FreeAllTokens() {
for (int i = 0; i < NUMTOKENS; i++) {
g_tokens[i].proc = nullptr;
g_tokens[i].isFree = true;
}
}