DIRECTOR: Freeze LingoState instead of using callstack

Previously, the Lingo state would be frozen by persisting the frames on
the bottom of the callstack with a special flag. This required extra
logic to determine whether or not the callstack was intended to be empty
after execution finished, with some complex edge cases.

The new approach is to swap out the LingoState object on the window,
meaning that an empty callstack always signifies execution has completed.
It remains to be seen if we'll need to track more than one frozen
context; for now it seems to be okay.

Fixes driving to locations in DEVO Presents: Adventures of the Smart Patrol.
This commit is contained in:
Scott Percival 2022-11-05 10:31:07 +08:00 committed by Eugene Sandulenko
parent 873095c87e
commit 3ea7f10467
7 changed files with 58 additions and 38 deletions

View File

@ -233,7 +233,6 @@ void Lingo::pushContext(const Symbol funcSym, bool allowRetVal, Datum defaultRet
fp->retPC = _state->pc;
fp->retScript = _state->script;
fp->retContext = _state->context;
fp->retFreezeContext = _freezeContext;
fp->retLocalVars = _state->localVars;
fp->retMe = _state->me;
fp->sp = funcSym;
@ -249,7 +248,6 @@ void Lingo::pushContext(const Symbol funcSym, bool allowRetVal, Datum defaultRet
_state->context = funcSym.ctx;
*_state->context->_refCount += 1;
}
_freezeContext = false;
DatumHash *localvars = _state->localVars;
if (!funcSym.anonymous) {
@ -345,7 +343,6 @@ void Lingo::popContext(bool aborting) {
_state->script = fp->retScript;
_state->context = fp->retContext;
_freezeContext = fp->retFreezeContext;
_state->pc = fp->retPC;
_state->me = fp->retMe;
@ -364,17 +361,15 @@ void Lingo::popContext(bool aborting) {
g_debugger->popContextHook();
}
bool Lingo::hasFrozenContext() {
if (_freezeContext)
return true;
bool Lingo::hasFrozenState() {
Window *window = _vm->getCurrentWindow();
return window->hasFrozenLingoState();
}
Common::Array<CFrame *> &callstack = _state->callstack;
for (uint i = 0; i < callstack.size(); i++) {
if (callstack[i]->retFreezeContext)
return true;
}
return false;
void Lingo::freezeState() {
Window *window = _vm->getCurrentWindow();
window->freezeLingoState();
switchStateFromWindow();
}
void LC::c_constpush() {

View File

@ -197,8 +197,8 @@ void Lingo::func_goto(Datum &frame, Datum &movie) {
// If there isn't already frozen Lingo (e.g. from a previous func_goto we haven't yet unfrozen),
// freeze this script context. We'll return to it after entering the next frame.
if (!g_lingo->hasFrozenContext()) {
g_lingo->_freezeContext = true;
if (!g_lingo->hasFrozenState()) {
g_lingo->_freezeState = true;
}
if (movie.type != VOID) {

View File

@ -155,7 +155,7 @@ Lingo::Lingo(DirectorEngine *vm) : _vm(vm) {
_state = nullptr;
_currentChannelId = -1;
_globalCounter = 0;
_freezeContext = false;
_freezeState = false;
_abort = false;
_expectError = false;
_caughtError = false;
@ -541,7 +541,7 @@ Common::String Lingo::formatFunctionBody(Symbol &sym) {
void Lingo::execute() {
uint localCounter = 0;
while (!_abort && !_freezeContext && _state->script && (*_state->script)[_state->pc] != STOP) {
while (!_abort && !_freezeState && _state->script && (*_state->script)[_state->pc] != STOP) {
if (_globalCounter > 1000 && debugChannelSet(-1, kDebugFewFramesOnly)) {
warning("Lingo::execute(): Stopping due to debug few frames only");
_vm->getCurrentMovie()->getScore()->_playState = kPlayStopped;
@ -595,17 +595,18 @@ void Lingo::execute() {
}
}
if (_abort || _vm->getCurrentMovie()->getScore()->_playState == kPlayStopped) {
if (_freezeState) {
debugC(5, kDebugLingoExec, "Lingo::execute(): Context is frozen, pausing execution");
freezeState();
} else if (_abort || _vm->getCurrentMovie()->getScore()->_playState == kPlayStopped) {
// Clean up call stack
while (_state->callstack.size()) {
popContext(true);
}
}
_abort = false;
_freezeState = false;
if (_freezeContext) {
debugC(1, kDebugLingoExec, "Lingo::execute(): Context is frozen, pausing execution");
}
g_debugger->stepHook();
}

View File

@ -241,7 +241,6 @@ struct CFrame { /* proc/func call stack frame */
int retPC; /* where to resume after return */
ScriptData *retScript; /* which script to resume after return */
ScriptContext *retContext; /* which script context to use after return */
bool retFreezeContext; /* whether the context should be frozen after return */
DatumHash *retLocalVars;
Datum retMe; /* which me obj to use after return */
uint stackSizeBefore;
@ -358,9 +357,10 @@ public:
public:
void execute();
void switchStateFromWindow();
void freezeState();
bool hasFrozenState();
void pushContext(const Symbol funcSym, bool allowRetVal, Datum defaultRetVal);
void popContext(bool aborting = false);
bool hasFrozenContext();
void cleanLocalVars();
void varAssign(const Datum &var, const Datum &value);
Datum varFetch(const Datum &var, bool silent = false);
@ -448,7 +448,7 @@ public:
int _currentChannelId;
bool _freezeContext;
bool _freezeState;
bool _abort;
bool _expectError;
bool _caughtError;

View File

@ -471,7 +471,11 @@ void Score::update() {
debugC(1, kDebugLoading, "****************************** Current frame: %d, time: %d", _currentFrame, g_system->getMillis(false));
g_debugger->frameHook();
uint initialCallStackSize = g_lingo->_state->callstack.size();
if (_window->hasFrozenLingoState()) {
_window->thawLingoState();
g_lingo->switchStateFromWindow();
g_lingo->execute();
}
_lingo->executeImmediateScripts(_frames[_currentFrame]);
@ -506,18 +510,6 @@ void Score::update() {
_movie->_lastTimeOut = _vm->getMacTicks();
}
// If we have more call stack frames than we started with, then we have a newly
// added frozen context. We'll deal with that later.
if (g_lingo->_state->callstack.size() == initialCallStackSize) {
// We may have a frozen Lingo context from func_goto.
// Now that we've entered a new frame, let's unfreeze that context.
if (g_lingo->_freezeContext) {
debugC(1, kDebugLingoExec, "Score::update(): Unfreezing Lingo context");
g_lingo->_freezeContext = false;
g_lingo->execute();
}
}
}
void Score::renderFrame(uint16 frameId, RenderMode mode) {

View File

@ -49,6 +49,7 @@ Window::Window(int id, bool scrollable, bool resizable, bool editable, Graphics:
_puppetTransition = nullptr;
_soundManager = new DirectorSound(this);
_lingoState = new LingoState;
_frozenLingoState = nullptr;
_currentMovie = nullptr;
_mainArchive = nullptr;
@ -428,4 +429,29 @@ Common::String Window::getSharedCastPath() {
return Common::String();
}
void Window::freezeLingoState() {
if (_frozenLingoState) {
warning("State already frozen! Ditching callstack with %d frames", _frozenLingoState->callstack.size());
delete _frozenLingoState;
}
_frozenLingoState = _lingoState;
_lingoState = new LingoState;
debugC(kDebugLingoExec, 5, "Freezing Lingo state");
}
void Window::thawLingoState() {
if (!_frozenLingoState) {
warning("Tried to thaw when there's no frozen state, ignoring");
return;
}
if (!_lingoState->callstack.empty()) {
warning("Can't thaw a Lingo state in mid-execution, ignoring");
return;
}
delete _lingoState;
_lingoState = _frozenLingoState;
_frozenLingoState = nullptr;
debugC(kDebugLingoExec, 5, "Thawing Lingo state");
}
} // End of namespace Director

View File

@ -125,6 +125,7 @@ public:
void transMultiPass(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
void transZoom(TransParams &t, Common::Rect &clipRect, Graphics::ManagedSurface *tmpSurface);
// window.cpp
Common::Point getMousePos();
DirectorEngine *getVM() const { return _vm; }
@ -140,7 +141,6 @@ public:
int getWindowType() const { return _windowType; }
void setTitleVisible(bool titleVisible) { _titleVisible = titleVisible; updateBorderType(); };
bool isTitleVisible() { return _titleVisible; };
LingoState *getLingoState() { return _lingoState; };
Datum getStageRect();
void updateBorderType();
@ -151,6 +151,11 @@ public:
Common::String getSharedCastPath();
LingoState *getLingoState() { return _lingoState; };
bool hasFrozenLingoState() { return _frozenLingoState != nullptr; };
void freezeLingoState();
void thawLingoState();
// events.cpp
bool processEvent(Common::Event &event) override;
@ -199,6 +204,7 @@ private:
DirectorEngine *_vm;
DirectorSound *_soundManager;
LingoState *_lingoState;
LingoState *_frozenLingoState;
bool _isStage;
Archive *_mainArchive;
Movie *_currentMovie;