[Scummvm-git-logs] scummvm master -> ddad333857dc579f19fdefa28571d0c3b194212a
elasota
noreply at scummvm.org
Thu Aug 29 03:02:43 UTC 2024
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
ddad333857 MTROPOLIS: Coroutines
Commit: ddad333857dc579f19fdefa28571d0c3b194212a
https://github.com/scummvm/scummvm/commit/ddad333857dc579f19fdefa28571d0c3b194212a
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2024-08-28T22:58:25-04:00
Commit Message:
MTROPOLIS: Coroutines
Changed paths:
A engines/mtropolis/coroutine_compiler.h
A engines/mtropolis/coroutine_exec.cpp
A engines/mtropolis/coroutine_exec.h
A engines/mtropolis/coroutine_manager.cpp
A engines/mtropolis/coroutine_manager.h
A engines/mtropolis/coroutine_protos.h
A engines/mtropolis/coroutine_return_value.h
A engines/mtropolis/coroutines.cpp
A engines/mtropolis/coroutines.h
A engines/mtropolis/miniscript_protos.h
engines/mtropolis/elements.cpp
engines/mtropolis/elements.h
engines/mtropolis/module.mk
engines/mtropolis/runtime.cpp
engines/mtropolis/runtime.h
engines/mtropolis/vthread.cpp
engines/mtropolis/vthread.h
diff --git a/engines/mtropolis/coroutine_compiler.h b/engines/mtropolis/coroutine_compiler.h
new file mode 100644
index 00000000000..6f70f09beec
--- /dev/null
+++ b/engines/mtropolis/coroutine_compiler.h
@@ -0,0 +1 @@
+#pragma once
diff --git a/engines/mtropolis/coroutine_exec.cpp b/engines/mtropolis/coroutine_exec.cpp
new file mode 100644
index 00000000000..eff85063b7c
--- /dev/null
+++ b/engines/mtropolis/coroutine_exec.cpp
@@ -0,0 +1,100 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mtropolis/coroutine_exec.h"
+#include "mtropolis/coroutines.h"
+
+namespace MTropolis {
+
+CoroutineRuntimeState::CoroutineRuntimeState(VThread *vthread, CoroutineStackFrame2 *frame)
+: _vthread(vthread), _frame(frame), _condition(false) {
+}
+
+CompiledCoroutine::CompiledCoroutine()
+ : _frameConstructor(nullptr), _getFrameParameters(nullptr), _isVoidReturn(false), _instructions(nullptr), _numInstructions(0) {
+}
+
+CompiledCoroutine::~CompiledCoroutine() {
+ delete[] _instructions;
+}
+
+CoroutineStackFrame2::CoroutineStackFrame2(const CompiledCoroutine *compiledCoro)
+ : _compiledCoro(compiledCoro), _nextInstr(0) {
+}
+
+CoroutineStackFrame2::~CoroutineStackFrame2() {
+}
+
+VThreadState CoroutineStackFrame2::execute(VThread *thread) {
+ const CoroExecInstr *instrs = _compiledCoro->_instructions;
+
+ uint ip = _nextInstr;
+
+ CoroutineRuntimeState runtimeState(thread, this);
+
+ for (;;) {
+ assert(ip < _compiledCoro->_numInstructions);
+
+ const CoroExecInstr &instr = instrs[ip++];
+
+ switch (instr._opcode) {
+ case CoroExecOp::Code:
+ instr._func(runtimeState);
+ break;
+ case CoroExecOp::Jump:
+ ip = instr._arg;
+ break;
+ case CoroExecOp::JumpIfFalse:
+ if (!runtimeState._condition)
+ ip = instr._arg;
+ break;
+ case CoroExecOp::EnterFunction:
+ _nextInstr = ip;
+ return kVThreadReturn;
+ case CoroExecOp::ExitFunction:
+ thread->popFrame();
+ return kVThreadReturn;
+ case CoroExecOp::Error:
+ return kVThreadError;
+ case CoroExecOp::CheckMiniscript:
+ if (runtimeState._miniscriptOutcome == kMiniscriptInstructionOutcomeFailed)
+ return kVThreadError;
+ else if (runtimeState._miniscriptOutcome == kMiniscriptInstructionOutcomeContinue)
+ break;
+ else if (runtimeState._miniscriptOutcome == kMiniscriptInstructionOutcomeYieldToVThreadNoRetry) {
+ _nextInstr = ip;
+ return kVThreadReturn;
+ } else {
+ error("Unhandled miniscript result in coro runtime");
+ }
+
+ break;
+ default:
+ error("Internal error: Unhandled coro opcode");
+ }
+ }
+}
+
+const CompiledCoroutine *CoroutineStackFrame2::getCompiledCoroutine() const {
+ return _compiledCoro;
+}
+
+}
diff --git a/engines/mtropolis/coroutine_exec.h b/engines/mtropolis/coroutine_exec.h
new file mode 100644
index 00000000000..bdd7c0c060c
--- /dev/null
+++ b/engines/mtropolis/coroutine_exec.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_COROUTINE_EXEC_H
+#define MTROPOLIS_COROUTINE_EXEC_H
+
+#include "mtropolis/coroutines.h"
+
+namespace MTropolis {
+
+enum class CoroExecOp {
+ Invalid = 0,
+
+ Code,
+ Jump,
+ JumpIfFalse,
+ EnterFunction,
+ ExitFunction,
+
+ Error,
+ CheckMiniscript,
+};
+
+struct CoroExecInstr {
+ CoroExecOp _opcode;
+
+ union {
+ uint _arg;
+ CoroutineFragmentFunction_t _func;
+ };
+};
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/coroutine_manager.cpp b/engines/mtropolis/coroutine_manager.cpp
new file mode 100644
index 00000000000..ef7e2f2a80a
--- /dev/null
+++ b/engines/mtropolis/coroutine_manager.cpp
@@ -0,0 +1,767 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mtropolis/coroutine_manager.h"
+#include "mtropolis/coroutines.h"
+#include "mtropolis/coroutine_exec.h"
+
+namespace MTropolis {
+
+class CoroutineManager : public ICoroutineManager {
+public:
+ CoroutineManager();
+ ~CoroutineManager();
+
+private:
+ void registerCoroutine(CompiledCoroutine **compiledCoroPtr) override;
+ void compileCoroutine(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn) override;
+
+ Common::Array<CompiledCoroutine **> _compiledCoroutineRefs;
+};
+
+class CoroutineCompiler : public ICoroutineCompiler {
+public:
+ explicit CoroutineCompiler(ICoroutineManager *coroManager);
+
+ void addFunctionToCompile(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn);
+
+ void compileAll();
+
+ void defineFunction(CoroutineFrameConstructor_t frameConstructor, CoroutineGetFrameParametersFunction_t frameGetParams) override;
+ void addOp(CoroOps op, CoroutineFragmentFunction_t fragmentFunc) override;
+
+private:
+ struct PendingCompile {
+ CompiledCoroutine *_compiledCoro;
+ CoroutineCompileFunction_t _compileFunction;
+ };
+
+ enum class ControlFlowType {
+ Invalid = 0,
+
+ Function,
+ If, // Label 1 = Else label, Label 2 = End label
+ While, // Label 1 = Loop label, Label 2 = End label
+ DoWhile, // Label 1 = Loop label, Label 2 = End label
+ For, // Label 1 = Loop label, Label 2 = End label, Label 3 = Iterate label
+ };
+
+ enum class ControlFlowState {
+ Default = 0,
+
+ NoBody,
+ NoElse,
+ HasElse,
+ };
+
+ struct ControlFlowStack {
+ ControlFlowStack()
+ : _type(ControlFlowType::Invalid), _state(ControlFlowState::Default), _endLabel(0), _loopOrElseLabel(0), _iterateLabel(0) {
+ }
+
+ ControlFlowType _type;
+ ControlFlowState _state;
+ uint _endLabel;
+ uint _loopOrElseLabel;
+ uint _iterateLabel;
+ };
+
+ enum class ProtoOp {
+ Invalid = 0,
+
+ Code,
+
+ NoOp,
+
+ Jump,
+ JumpIfFalse,
+ Label,
+
+ YieldToFunction,
+ CheckMiniscript,
+
+ Return,
+ Error,
+
+ InfiniteLoop,
+ };
+
+ struct ProtoInstr {
+ ProtoOp _op;
+ uint _value;
+ CoroutineFragmentFunction_t _func;
+ };
+
+ void compileOne(CompiledCoroutine *compiledCoro, CoroutineCompileFunction_t compileFunction);
+ void reportError(const char *str);
+
+ void addProtoInstr(ProtoOp op, CoroutineFragmentFunction_t func);
+ void addProtoInstr(ProtoOp op, uint value, CoroutineFragmentFunction_t func);
+ void addProtoInstr(ProtoOp op, uint value);
+ void addProtoInstr(ProtoOp op);
+
+ static bool isSimpleTerminalOp(ProtoOp op);
+
+ uint allocLabel();
+
+ ICoroutineManager *_coroManager;
+ Common::Array<PendingCompile> _pendingCompiles;
+
+ Common::Array<ControlFlowStack> _funcControlFlowStack;
+ Common::Array<ProtoInstr> _funcProtoInstrs;
+ uint _funcNumLabels;
+ bool _funcIsVoidReturn;
+
+ CoroutineFrameConstructor_t _funcFrameCtor;
+ CoroutineGetFrameParametersFunction_t _funcFrameGetParams;
+};
+
+CoroutineManager::CoroutineManager() {
+}
+
+CoroutineManager::~CoroutineManager() {
+ for (CompiledCoroutine **compiledCoroRef : _compiledCoroutineRefs) {
+ delete (*compiledCoroRef);
+ *compiledCoroRef = nullptr;
+ }
+}
+
+
+void CoroutineManager::registerCoroutine(CompiledCoroutine **compiledCoroPtr) {
+ _compiledCoroutineRefs.push_back(compiledCoroPtr);
+}
+
+void CoroutineManager::compileCoroutine(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn) {
+ CoroutineCompiler coroCompiler(this);
+
+ coroCompiler.addFunctionToCompile(compiledCoroPtr, compileFunction, isVoidReturn);
+ coroCompiler.compileAll();
+}
+
+CoroutineCompiler::CoroutineCompiler(ICoroutineManager *coroManager)
+ : _coroManager(coroManager), _funcFrameCtor(nullptr), _funcFrameGetParams(nullptr), _funcNumLabels(0), _funcIsVoidReturn(false) {
+}
+
+void CoroutineCompiler::addFunctionToCompile(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn) {
+ if (*compiledCoroPtr)
+ return;
+
+ CompiledCoroutine *compiledCoro = new CompiledCoroutine();
+
+ _coroManager->registerCoroutine(compiledCoroPtr);
+
+ compiledCoro->_isVoidReturn = isVoidReturn;
+
+ PendingCompile pendingCompile;
+ pendingCompile._compiledCoro = compiledCoro;
+ pendingCompile._compileFunction = compileFunction;
+
+ *compiledCoroPtr = pendingCompile._compiledCoro;
+
+ _pendingCompiles.push_back(pendingCompile);
+}
+
+void CoroutineCompiler::compileAll() {
+ // pendingCompiles may grow during this
+ for (uint i = 0; i < _pendingCompiles.size(); i++) {
+ const PendingCompile &pendingCompile = _pendingCompiles[i];
+ compileOne(pendingCompile._compiledCoro, pendingCompile._compileFunction);
+ }
+}
+
+void CoroutineCompiler::defineFunction(CoroutineFrameConstructor_t frameConstructor, CoroutineGetFrameParametersFunction_t frameGetParams) {
+ _funcFrameCtor = frameConstructor;
+ _funcFrameGetParams = frameGetParams;
+}
+
+void CoroutineCompiler::addOp(CoroOps op, CoroutineFragmentFunction_t fragmentFunc) {
+ if (op == CoroOps::BeginFunction && _funcProtoInstrs.size() != 0)
+ reportError("Begin function came after the start of the function");
+
+ if (op != CoroOps::BeginFunction && _funcProtoInstrs.size() == 0)
+ reportError("First op wasn't begin function");
+
+ if (op != CoroOps::BeginFunction && _funcControlFlowStack.size() == 0)
+ reportError("Op after end of function");
+
+ switch (op) {
+ case CoroOps::BeginFunction: {
+ ControlFlowStack cf;
+ cf._type = ControlFlowType::Function;
+
+ _funcControlFlowStack.push_back(cf);
+
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+
+ case CoroOps::EndFunction: {
+ if (_funcControlFlowStack.size() != 1)
+ reportError("End function doesn't close function scope");
+
+ _funcControlFlowStack.pop_back();
+
+ if (_funcIsVoidReturn) {
+ addProtoInstr(ProtoOp::Return);
+ } else {
+ if (_funcProtoInstrs.back()._op != ProtoOp::Return)
+ reportError("Value-returning function didn't return a value");
+ }
+ } break;
+
+ case CoroOps::IfCond: {
+ ControlFlowStack cf;
+ cf._type = ControlFlowType::If;
+ cf._state = ControlFlowState::NoBody;
+ cf._endLabel = allocLabel();
+ cf._loopOrElseLabel = allocLabel();
+
+ _funcControlFlowStack.push_back(cf);
+
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+
+ case CoroOps::IfBody: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::If || cf._state != ControlFlowState::NoBody)
+ reportError("If body in wrong location");
+
+ cf._state = ControlFlowState::NoElse;
+
+ addProtoInstr(ProtoOp::JumpIfFalse, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+ case CoroOps::Else: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::If)
+ reportError("Unexpected 'else'");
+
+ if (cf._state != ControlFlowState::NoElse)
+ reportError("If block has an 'else' already");
+
+ cf._state = ControlFlowState::HasElse;
+
+ addProtoInstr(ProtoOp::Jump, cf._endLabel);
+ addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+ case CoroOps::ElseIfCond: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::If)
+ reportError("Unexpected 'else if'");
+
+ if (cf._state != ControlFlowState::NoElse)
+ reportError("If block has an 'else' already");
+
+ addProtoInstr(ProtoOp::Jump, cf._endLabel);
+ addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+
+ cf._loopOrElseLabel = allocLabel();
+ } break;
+ case CoroOps::ElseIfBody: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::If)
+ reportError("Else if body in the wrong place");
+
+ addProtoInstr(ProtoOp::JumpIfFalse, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+ case CoroOps::EndIf: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::If)
+ reportError("Else if body in the wrong place");
+
+ if (cf._state != ControlFlowState::HasElse)
+ addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
+
+ addProtoInstr(ProtoOp::Label, cf._endLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+
+ _funcControlFlowStack.pop_back();
+ } break;
+
+ case CoroOps::WhileCond: {
+ ControlFlowStack cf;
+ cf._type = ControlFlowType::While;
+ cf._loopOrElseLabel = allocLabel();
+ cf._endLabel = allocLabel();
+
+ _funcControlFlowStack.push_back(cf);
+
+ addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+ case CoroOps::WhileBody: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::While)
+ reportError("While body in the wrong place");
+
+ addProtoInstr(ProtoOp::JumpIfFalse, cf._endLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+ case CoroOps::EndWhile: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::While)
+ reportError("'end while' didn't close while block");
+
+ addProtoInstr(ProtoOp::Jump, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Label, cf._endLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+
+ _funcControlFlowStack.pop_back();
+ } break;
+
+ // Order of for loops is Next->Cond->Body
+ case CoroOps::ForNext: {
+ ControlFlowStack cf;
+ cf._type = ControlFlowType::For;
+ cf._iterateLabel = allocLabel();
+ cf._loopOrElseLabel = allocLabel();
+ cf._endLabel = allocLabel();
+
+ _funcControlFlowStack.push_back(cf);
+
+ addProtoInstr(ProtoOp::Jump, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Label, cf._iterateLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+
+ case CoroOps::ForCond: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::For)
+ reportError("'for' condition in the wrong place");
+
+ addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+
+ case CoroOps::ForBody: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::For)
+ reportError("'for' body in the wrong place");
+
+ addProtoInstr(ProtoOp::JumpIfFalse, cf._endLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+
+ case CoroOps::EndFor: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::For)
+ reportError("'end for' didn't close a for loop");
+
+ addProtoInstr(ProtoOp::Jump, cf._iterateLabel);
+ addProtoInstr(ProtoOp::Label, cf._endLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+
+ _funcControlFlowStack.pop_back();
+ } break;
+
+ case CoroOps::Do: {
+ ControlFlowStack cf;
+ cf._type = ControlFlowType::DoWhile;
+ cf._loopOrElseLabel = allocLabel();
+ cf._endLabel = allocLabel();
+
+ _funcControlFlowStack.push_back(cf);
+
+ addProtoInstr(ProtoOp::Label, cf._loopOrElseLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+
+ case CoroOps::DoWhileCond: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::DoWhile)
+ reportError("'do/while' condition didn't close a 'do' block");
+
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ } break;
+
+ case CoroOps::DoWhile: {
+ ControlFlowStack &cf = _funcControlFlowStack.back();
+ if (cf._type != ControlFlowType::DoWhile)
+ reportError("'do while' in the wrong place");
+
+ addProtoInstr(ProtoOp::JumpIfFalse, fragmentFunc);
+ addProtoInstr(ProtoOp::Label, cf._endLabel);
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+
+ _funcControlFlowStack.pop_back();
+ } break;
+
+ case CoroOps::Return:
+ addProtoInstr(ProtoOp::Return);
+ break;
+
+ case CoroOps::Error:
+ addProtoInstr(ProtoOp::Error);
+ break;
+
+ case CoroOps::Code:
+ addProtoInstr(ProtoOp::Code, fragmentFunc);
+ break;
+
+ case CoroOps::YieldToFunction:
+ addProtoInstr(ProtoOp::YieldToFunction);
+ break;
+
+ case CoroOps::CheckMiniscript:
+ addProtoInstr(ProtoOp::CheckMiniscript);
+ break;
+
+ default:
+ reportError("Unimplemented coro opcode");
+ }
+}
+
+
+void CoroutineCompiler::compileOne(CompiledCoroutine *compiledCoro, CoroutineCompileFunction_t compileFunction) {
+ _funcNumLabels = 0;
+ _funcProtoInstrs.clear();
+
+ _funcIsVoidReturn = compiledCoro->_isVoidReturn;
+
+ compileFunction(this);
+
+#if defined(_M_X64) || defined(__x86_64__)
+ for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
+ ProtoInstr &instr = _funcProtoInstrs[i];
+ if (instr._op == ProtoOp::Code) {
+ // Empty cdecl function:
+ // 33 c0 xor eax,eax
+ // c3 ret
+ const byte emptyFunctionSignature[] = {0x33u, 0xc0u, 0xc3u};
+
+ if (!memcmp(reinterpret_cast<const void *>(instr._func), emptyFunctionSignature, sizeof(emptyFunctionSignature)))
+ instr._op = ProtoOp::NoOp;
+ }
+ }
+#endif
+
+ // Renumber label to instructions
+ {
+ Common::Array<uint> labelToInstr;
+ labelToInstr.resize(_funcNumLabels, (uint)-1);
+
+ for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
+ ProtoInstr &instr = _funcProtoInstrs[i];
+
+ if (instr._op == ProtoOp::Label) {
+ labelToInstr[instr._value] = i;
+ instr._op = ProtoOp::NoOp;
+ }
+ }
+
+ for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
+ ProtoInstr &instr = _funcProtoInstrs[i];
+
+ if (instr._op == ProtoOp::JumpIfFalse || instr._op == ProtoOp::Jump) {
+ assert(labelToInstr[instr._value] != (uint)-1);
+ instr._value = labelToInstr[instr._value];
+ }
+ }
+ }
+
+ bool haveWork = true;
+ while (haveWork) {
+ haveWork = false;
+
+ // Locate infinite loops and thread jumps
+ {
+ Common::Array<uint> chainEndInstr;
+ Common::Array<uint> instrJumpRoot;
+ Common::Array<uint> rootToChain;
+
+ uint initialNumInstrs = _funcProtoInstrs.size();
+
+ chainEndInstr.push_back(0);
+ rootToChain.push_back(0);
+
+ instrJumpRoot.resize(initialNumInstrs, 0);
+
+ for (uint i = 0; i < initialNumInstrs; i++) {
+ const ProtoInstr &baseInstr = _funcProtoInstrs[i];
+
+ if (baseInstr._op == ProtoOp::Jump && instrJumpRoot[i] == 0) {
+ uint jumpRootID = rootToChain.size();
+ uint chainID = chainEndInstr.size();
+
+ chainEndInstr.push_back(0);
+ rootToChain.push_back(chainID);
+
+ uint traceInstr = i;
+ for (;;) {
+ const ProtoInstr &instr = _funcProtoInstrs[traceInstr];
+
+ // Ended as a new chain
+ if (instr._op != ProtoOp::Jump) {
+ chainEndInstr[chainID] = traceInstr;
+ break;
+ }
+
+ if (instr._op == ProtoOp::InfiniteLoop || instrJumpRoot[traceInstr] == jumpRootID) {
+ // Ended in an infinite loop.
+ chainEndInstr[chainID] = (uint)-1;
+ break;
+ } else if (instrJumpRoot[traceInstr] == 0) {
+ // Propgate jump chain
+ instrJumpRoot[traceInstr] = jumpRootID;
+ traceInstr = instr._value;
+ } else {
+ // Converge into existing chain
+ rootToChain[jumpRootID] = rootToChain[instrJumpRoot[traceInstr]];
+ break;
+ }
+ }
+ }
+ }
+
+ for (uint i = 0; i < initialNumInstrs; i++) {
+ ProtoInstr &instr = _funcProtoInstrs[i];
+
+ if (instr._op == ProtoOp::Jump) {
+ uint endInstr = chainEndInstr[rootToChain[instrJumpRoot[i]]];
+ if (endInstr == (uint)-1)
+ instr._op = ProtoOp::InfiniteLoop;
+ else
+ instr._value = endInstr;
+ }
+ }
+ }
+
+ for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
+ ProtoInstr &instr = _funcProtoInstrs[i];
+
+ if (instr._op == ProtoOp::JumpIfFalse || instr._op == ProtoOp::Jump) {
+ // Remove jumps that jump to the next instruction
+ if (instr._value == i + 1) {
+ instr._op = ProtoOp::NoOp;
+ continue;
+ }
+
+ const ProtoInstr &targetInstr = _funcProtoInstrs[instr._value];
+
+ if (instr._op == ProtoOp::JumpIfFalse) {
+ // Thread conditional jumps to jumps
+ if (targetInstr._op == ProtoOp::Jump) {
+ instr._value = targetInstr._value;
+ haveWork = true;
+ }
+
+ // Remove conditional jumps that jump to the same target as the next instruction
+ const ProtoInstr &nextInstr = _funcProtoInstrs[i + 1];
+ if (nextInstr._op == ProtoOp::Jump && instr._value == nextInstr._value) {
+ instr._op = ProtoOp::NoOp;
+ continue;
+ }
+
+ // Remove conditional jumps to simple terminal ops with the terminal op
+ if (isSimpleTerminalOp(nextInstr._op) && targetInstr._op == nextInstr._op) {
+ instr._op = ProtoOp::NoOp;
+ continue;
+ }
+ } else if (instr._op == ProtoOp::Jump) {
+ // Replace jumps to simple terminal ops
+ if (isSimpleTerminalOp(targetInstr._op)) {
+ instr._op = targetInstr._op;
+ haveWork = true;
+ }
+ }
+ }
+ }
+
+ // Remove dead instructions
+ {
+ Common::Array<bool> instrIsAlive;
+ Common::Array<uint> pendingExecRoots;
+ pendingExecRoots.push_back(0);
+
+ instrIsAlive.resize(_funcProtoInstrs.size(), false);
+ instrIsAlive[0] = true;
+
+ while (pendingExecRoots.size() > 0) {
+ uint fillLocation = pendingExecRoots.back();
+ pendingExecRoots.pop_back();
+
+ for (;;) {
+ const ProtoInstr &instr = _funcProtoInstrs[fillLocation];
+
+ if (instr._op == ProtoOp::Jump || instr._op == ProtoOp::JumpIfFalse) {
+ if (!instrIsAlive[instr._value]) {
+ pendingExecRoots.push_back(instr._value);
+ instrIsAlive[instr._value] = true;
+ }
+ }
+
+ instrIsAlive[fillLocation] = true;
+
+ if (instr._op == ProtoOp::Jump || instr._op == ProtoOp::Return || instr._op == ProtoOp::InfiniteLoop)
+ break;
+
+ fillLocation++;
+ }
+ }
+
+ for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
+ if (_funcProtoInstrs[i]._op == ProtoOp::NoOp)
+ instrIsAlive[i] = false;
+ }
+
+ uint numDeadInstructions = 0;
+ for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
+ if (!instrIsAlive[i])
+ numDeadInstructions++;
+ }
+
+ if (numDeadInstructions > 0) {
+ haveWork = true;
+
+ Common::Array<ProtoInstr> newInstrs;
+
+ newInstrs.resize(_funcProtoInstrs.size() - numDeadInstructions);
+ uint newInstrWritePos = 0;
+
+ for (uint i = 0; i < _funcProtoInstrs.size(); i++) {
+ if (instrIsAlive[i])
+ newInstrs[newInstrWritePos++] = _funcProtoInstrs[i];
+ }
+
+ assert(newInstrWritePos == newInstrs.size());
+
+ Common::Array<uint> oldInstrToNewInstr;
+ oldInstrToNewInstr.resize(_funcProtoInstrs.size());
+
+ uint newInstrIndex = newInstrs.size();
+ uint oldInstrIndex = _funcProtoInstrs.size();
+
+ for (;;) {
+ oldInstrIndex--;
+
+ if (instrIsAlive[oldInstrIndex]) {
+ assert(newInstrIndex > 0);
+ newInstrIndex--;
+ }
+
+ oldInstrToNewInstr[oldInstrIndex] = newInstrIndex;
+
+ if (oldInstrIndex == 0)
+ break;
+ }
+
+ for (ProtoInstr &instr : newInstrs) {
+ if (instr._op == ProtoOp::Jump || instr._op == ProtoOp::JumpIfFalse)
+ instr._value = oldInstrToNewInstr[instr._value];
+ }
+
+ _funcProtoInstrs = Common::move(newInstrs);
+ }
+ }
+ }
+
+ compiledCoro->_frameConstructor = _funcFrameCtor;
+ compiledCoro->_getFrameParameters = _funcFrameGetParams;
+ compiledCoro->_numInstructions = _funcProtoInstrs.size();
+ compiledCoro->_instructions = new CoroExecInstr[compiledCoro->_numInstructions];
+
+ CoroExecInstr *outInstrs = compiledCoro->_instructions;
+
+ for (uint i = 0; i < compiledCoro->_numInstructions; i++) {
+ const ProtoInstr &instr = _funcProtoInstrs[i];
+ CoroExecInstr *outInstr = outInstrs + i;
+
+ switch (instr._op) {
+ case ProtoOp::Code:
+ outInstr->_opcode = CoroExecOp::Code;
+ outInstr->_func = instr._func;
+ break;
+ case ProtoOp::Jump:
+ outInstr->_opcode = CoroExecOp::Jump;
+ outInstr->_arg = instr._value;
+ break;
+ case ProtoOp::JumpIfFalse:
+ outInstr->_opcode = CoroExecOp::JumpIfFalse;
+ outInstr->_arg = instr._value;
+ break;
+ case ProtoOp::Return:
+ outInstr->_opcode = CoroExecOp::ExitFunction;
+ outInstr->_arg = 0;
+ break;
+ case ProtoOp::YieldToFunction:
+ outInstr->_opcode = CoroExecOp::EnterFunction;
+ outInstr->_arg = 0;
+ break;
+ case ProtoOp::Error:
+ outInstr->_opcode = CoroExecOp::Error;
+ outInstr->_arg = 0;
+ break;
+ case ProtoOp::CheckMiniscript:
+ outInstr->_opcode = CoroExecOp::CheckMiniscript;
+ outInstr->_arg = 0;
+ break;
+ default:
+ error("Internal error: Unhandled coro op");
+ }
+ }
+}
+
+void CoroutineCompiler::reportError(const char *str) {
+ error("%s", str);
+}
+
+void CoroutineCompiler::addProtoInstr(ProtoOp op, CoroutineFragmentFunction_t func) {
+ addProtoInstr(op, 0, func);
+}
+
+void CoroutineCompiler::addProtoInstr(ProtoOp op, uint value) {
+ addProtoInstr(op, value, nullptr);
+}
+
+void CoroutineCompiler::addProtoInstr(ProtoOp op) {
+ addProtoInstr(op, 0, nullptr);
+}
+
+void CoroutineCompiler::addProtoInstr(ProtoOp op, uint value, CoroutineFragmentFunction_t func) {
+ ProtoInstr instr;
+ instr._func = func;
+ instr._op = op;
+ instr._value = value;
+
+ _funcProtoInstrs.push_back(instr);
+}
+
+bool CoroutineCompiler::isSimpleTerminalOp(ProtoOp op) {
+ return op == ProtoOp::InfiniteLoop || op == ProtoOp::Error || op == ProtoOp::Return;
+}
+
+uint CoroutineCompiler::allocLabel() {
+ return _funcNumLabels++;
+}
+
+ICoroutineManager::~ICoroutineManager() {
+}
+
+ICoroutineManager *ICoroutineManager::create() {
+ return new CoroutineManager();
+}
+
+ICoroutineCompiler::~ICoroutineCompiler() {
+}
+
+} // End of namespace MTropolis
diff --git a/engines/mtropolis/coroutine_manager.h b/engines/mtropolis/coroutine_manager.h
new file mode 100644
index 00000000000..868bc0c82d7
--- /dev/null
+++ b/engines/mtropolis/coroutine_manager.h
@@ -0,0 +1,45 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_COROUTINE_MANAGER_H
+#define MTROPOLIS_COROUTINE_MANAGER_H
+
+#include "mtropolis/coroutine_protos.h"
+
+namespace MTropolis {
+
+struct CoroutineStackFrame2;
+struct CompiledCoroutine;
+struct ICoroutineCompiler;
+struct CoroutineParamsBase;
+
+struct ICoroutineManager {
+ virtual ~ICoroutineManager();
+
+ virtual void registerCoroutine(CompiledCoroutine **compiledCoroPtr) = 0;
+ virtual void compileCoroutine(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn) = 0;
+
+ static ICoroutineManager *create();
+};
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/coroutine_protos.h b/engines/mtropolis/coroutine_protos.h
new file mode 100644
index 00000000000..087b5f7abb3
--- /dev/null
+++ b/engines/mtropolis/coroutine_protos.h
@@ -0,0 +1,110 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_COROUTINE_PROTOS_H
+#define MTROPOLIS_COROUTINE_PROTOS_H
+
+#include <stddef.h>
+
+namespace MTropolis {
+
+struct ICoroutineCompiler;
+struct CompiledCoroutine;
+
+struct CoroutineStackFrame2;
+struct CoroutineParamsBase;
+struct CoroutineReturnValueRefBase;
+class CoroutineManager;
+
+typedef CoroutineStackFrame2 *(*CoroutineFrameConstructor_t)(void *ptr, const CompiledCoroutine *compiledCoro, const CoroutineParamsBase ¶ms, const CoroutineReturnValueRefBase &returnValueRef);
+typedef void (*CoroutineGetFrameParametersFunction_t)(size_t &outSize, size_t &outAlignment);
+typedef void (*CoroutineCompileFunction_t)(ICoroutineCompiler *compiler);
+
+
+struct CoroutineParamsBase {
+};
+
+} // End of namespace MTropolis
+
+#define CORO_STUB \
+ static void compileCoroutine(ICoroutineCompiler *compiler); \
+ static CompiledCoroutine *ms_compiledCoro;
+
+#define CORO_DEFINE_PARAMS_0() \
+ CORO_STUB \
+ struct Params : public CoroutineParamsBase { \
+ }
+
+#define CORO_DEFINE_PARAMS_1(type1, name1) \
+ CORO_STUB \
+ struct Params : public CoroutineParamsBase { \
+ typedef type1 ParamType1_t; \
+ \
+ ParamType1_t name1; \
+ \
+ inline explicit Params(const ParamType1_t &p_##name1) \
+ : name1(p_##name1) { \
+ } \
+ \
+ private: \
+ Params() = delete; \
+ }
+
+#define CORO_DEFINE_PARAMS_2(type1, name1, type2, name2) \
+ CORO_STUB \
+ struct Params : public CoroutineParamsBase { \
+ typedef type1 ParamType1_t; \
+ typedef type2 ParamType2_t; \
+ \
+ ParamType1_t name1; \
+ ParamType2_t name2; \
+ \
+ explicit Params(const ParamType1_t &p_##name1, const ParamType2_t &p_##name2) \
+ : name1(p_##name1), name2(p_##name2) { \
+ } \
+ \
+ private: \
+ Params() = delete; \
+ }
+
+#define CORO_DEFINE_PARAMS_3(type1, name1, type2, name2, type3, name3) \
+ CORO_STUB \
+ struct Params : public CoroutineParamsBase { \
+ typedef type1 ParamType1_t; \
+ typedef type2 ParamType2_t; \
+ typedef type3 ParamType3_t; \
+ \
+ ParamType1_t name1; \
+ ParamType2_t name2; \
+ ParamType3_t name3; \
+ \
+ explicit Params(const ParamType1_t &p_##name1, const ParamType2_t &p_##name2, const ParamType3_t &p_##name3) \
+ : name1(p_##name1), name2(p_##name2), name3(p_##name3) { \
+ } \
+ \
+ private: \
+ Params() = delete; \
+ }
+
+#define CORO_DEFINE_RETURN_TYPE(type) \
+ typedef type ReturnValue_t
+
+#endif
diff --git a/engines/mtropolis/coroutine_return_value.h b/engines/mtropolis/coroutine_return_value.h
new file mode 100644
index 00000000000..8c15a1629d7
--- /dev/null
+++ b/engines/mtropolis/coroutine_return_value.h
@@ -0,0 +1,70 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_COROUTINE_RETURN_VALUE_H
+#define MTROPOLIS_COROUTINE_RETURN_VALUE_H
+
+namespace MTropolis {
+
+struct CoroutineReturnValueRefBase {
+};
+
+template<class T>
+struct CoroutineReturnValueRef : public CoroutineReturnValueRefBase {
+ T *_returnValue;
+
+ CoroutineReturnValueRef() : _returnValue(nullptr) {
+ }
+
+ explicit CoroutineReturnValueRef(T *returnValue) : _returnValue(returnValue) {
+ }
+
+ inline void set(const T &value) const {
+ if (_returnValue != nullptr)
+ *_returnValue = value;
+ }
+
+ inline void set(T &&value) const {
+ if (_returnValue != nullptr)
+ *_returnValue = static_cast<T &&>(value);
+ }
+
+ inline static bool isVoid() {
+ return false;
+ }
+};
+
+template<>
+struct CoroutineReturnValueRef<void> : public CoroutineReturnValueRefBase {
+ CoroutineReturnValueRef() {
+ }
+
+ inline void voidSet() const {
+ }
+
+ inline static bool isVoid() {
+ return true;
+ }
+};
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/coroutines.cpp b/engines/mtropolis/coroutines.cpp
new file mode 100644
index 00000000000..c6751086305
--- /dev/null
+++ b/engines/mtropolis/coroutines.cpp
@@ -0,0 +1,41 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mtropolis/coroutines.h"
+#include "common/debug.h"
+
+namespace MTropolis {
+
+struct MyCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_0();
+};
+
+CORO_BEGIN_DEFINITION(MyCoroutine)
+ struct Locals {
+ };
+
+ CORO_BEGIN_FUNCTION
+ debug(1, "Hello");
+ CORO_END_FUNCTION
+CORO_END_DEFINITION
+
+} // namespace MTropolis
diff --git a/engines/mtropolis/coroutines.h b/engines/mtropolis/coroutines.h
new file mode 100644
index 00000000000..7b0c179d9e7
--- /dev/null
+++ b/engines/mtropolis/coroutines.h
@@ -0,0 +1,276 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_COROUTINES_H
+#define MTROPOLIS_COROUTINES_H
+
+#include "mtropolis/coroutine_protos.h"
+#include "mtropolis/miniscript_protos.h"
+#include "mtropolis/vthread.h"
+
+namespace MTropolis {
+
+struct CoroExecInstr;
+struct CoroutineRuntimeState;
+
+typedef VThreadState (*CoroutineFragmentFunction_t)(CoroutineRuntimeState &coroState);
+
+struct CoroutineRuntimeState {
+ CoroutineRuntimeState(VThread *vthread, CoroutineStackFrame2 *frame);
+
+ VThread *_vthread;
+ CoroutineStackFrame2 *_frame;
+ bool _condition;
+ MiniscriptInstructionOutcome _miniscriptOutcome;
+
+private:
+ CoroutineRuntimeState() = delete;
+ CoroutineRuntimeState(const CoroutineRuntimeState &) = delete;
+ CoroutineRuntimeState &operator=(const CoroutineRuntimeState&) = delete;
+};
+
+struct CompiledCoroutine {
+ CompiledCoroutine();
+ ~CompiledCoroutine();
+
+ CoroutineFrameConstructor_t _frameConstructor;
+ CoroutineGetFrameParametersFunction_t _getFrameParameters;
+ bool _isVoidReturn;
+
+ CoroExecInstr *_instructions;
+ uint _numInstructions;
+
+private:
+ CompiledCoroutine(const CompiledCoroutine &) = delete;
+ CompiledCoroutine &operator =(const CompiledCoroutine &) = delete;
+};
+
+struct CoroutineStackFrame2 : public VThreadTaskData {
+ explicit CoroutineStackFrame2(const CompiledCoroutine *compiledCoro);
+ virtual ~CoroutineStackFrame2();
+
+ VThreadState execute(VThread *thread) override;
+
+ const CompiledCoroutine *getCompiledCoroutine() const;
+
+private:
+ CoroutineStackFrame2() = delete;
+ CoroutineStackFrame2(const CoroutineStackFrame2&) = delete;
+
+ const CompiledCoroutine *_compiledCoro;
+ uint _nextInstr;
+};
+
+enum class CoroOps {
+ Invalid = 0,
+
+ BeginFunction,
+ EndFunction,
+
+ IfCond,
+ IfBody,
+ Else,
+ ElseIfCond,
+ ElseIfBody,
+ EndIf,
+
+ WhileCond,
+ WhileBody,
+ EndWhile,
+
+ ForNext,
+ ForCond,
+ ForBody,
+ EndFor,
+
+ Do,
+ DoWhile,
+ DoWhileCond,
+
+ Return,
+
+ Error,
+
+ YieldToFunction,
+
+ CheckMiniscript,
+
+ Code,
+};
+
+struct ICoroutineCompiler {
+ virtual ~ICoroutineCompiler();
+
+ virtual void defineFunction(CoroutineFrameConstructor_t frameConstructor, CoroutineGetFrameParametersFunction_t frameGetParams) = 0;
+ virtual void addOp(CoroOps op, CoroutineFragmentFunction_t fragmentFunc) = 0;
+};
+
+#define CORO_START_CODE_BLOCK(op) \
+ compiler->addOp(op, [](CoroutineRuntimeState &coroRuntime) -> VThreadState { \
+ Params *params = &static_cast<CoroStackFrame *>(coroRuntime._frame)->_params; \
+ Locals *locals = &static_cast<CoroStackFrame *>(coroRuntime._frame)->_locals; \
+ CoroutineReturnValueRef<ReturnValue_t> coroReturnValueRef = (static_cast<CoroStackFrame *>(coroRuntime._frame)->_rvRef);
+
+#define CORO_DISUSE_CODE_BLOCK_VARS \
+ (void)params; \
+ (void)locals; \
+ (void)coroReturnValueRef;
+
+#define CORO_END_CODE_BLOCK \
+ CORO_DISUSE_CODE_BLOCK_VARS \
+ return kVThreadReturn; \
+ });
+
+#define CORO_BEGIN_DEFINITION(type) \
+CompiledCoroutine *type::ms_compiledCoro = nullptr;\
+void type::compileCoroutine(ICoroutineCompiler *compiler) {
+
+#define CORO_END_DEFINITION \
+ }
+
+#define CORO_BEGIN_FUNCTION \
+ struct CoroStackFrame : public CoroutineStackFrame2 {\
+ Params _params;\
+ Locals _locals;\
+ CoroutineReturnValueRef<ReturnValue_t> _rvRef;\
+ explicit CoroStackFrame(const CompiledCoroutine *compiledCoro, const Params ¶ms, const CoroutineReturnValueRef<ReturnValue_t> &rvRef)\
+ : CoroutineStackFrame2(compiledCoro), _params(params), _rvRef(rvRef) {\
+ }\
+ static CoroutineStackFrame2 *constructFrame(void *ptr, const CompiledCoroutine *compiledCoro, const CoroutineParamsBase ¶ms, const CoroutineReturnValueRefBase &returnValueRef) {\
+ return new (ptr) CoroStackFrame(compiledCoro, static_cast<const Params &>(params), static_cast<const CoroutineReturnValueRef<ReturnValue_t>&>(returnValueRef));\
+ }\
+ static void getFrameParameters(size_t &outSize, size_t &outAlignment) { \
+ outSize = sizeof(CoroStackFrame); \
+ outAlignment = alignof(CoroStackFrame); \
+ }\
+ };\
+ compiler->defineFunction(CoroStackFrame::constructFrame, CoroStackFrame::getFrameParameters); \
+ CORO_START_CODE_BLOCK(CoroOps::BeginFunction)
+
+#define CORO_END_FUNCTION \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::EndFunction) \
+ CORO_END_CODE_BLOCK
+
+#define CORO_AWAIT(expr) \
+ (expr); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::YieldToFunction) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Code)
+
+#define CORO_AWAIT_MINISCRIPT(expr) \
+ coroRuntime._miniscriptOutcome = ((expr)); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::CheckMiniscript) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Code)
+
+#define CORO_IF(expr) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::IfCond) \
+ coroRuntime._condition = !!(expr); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::IfBody)
+
+#define CORO_ELSE \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Else)
+
+#define CORO_ELSE_IF(expr) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::ElseIfCond) \
+ coroRuntime._condition = !!(expr); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::ElseIfBody)
+
+#define CORO_END_IF \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::EndIf)
+
+#define CORO_WHILE(expr) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::WhileCond) \
+ coroCondition = !!(expr); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::WhileBody)
+
+#define CORO_END_WHILE(expr) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::EndWhile)
+
+#define CORO_DO \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Do)
+
+#define CORO_DO_WHILE(expr) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::DoWhileCond) \
+ coroCondition = !(expr); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::DoWhile)
+
+#define CORO_FOR(initExpr, condExpr, nextExpr) \
+ (initExpr); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::ForNext) \
+ (nextExpr); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::ForCond) \
+ coroRuntime._condition = !!(condExpr)); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::ForBody)
+
+#define CORO_END_FOR(expr) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::EndFor)
+
+#define CORO_RETURN_VALUE(expr) \
+ coroReturnValueRef.set((expr)); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Return)
+
+#define CORO_ERROR \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Error)
+
+
+#define CORO_RETURN \
+ coroReturnValueRef.voidSet(); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Return)
+
+#define CORO_RETURN_CALL(func, ...) \
+ coroFrame->pushFrame<CoroAutoFrame<func> >(coroReturnValueRef._returnValue, __VA_ARGS__); \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::YieldToFunction) \
+ CORO_END_CODE_BLOCK \
+ CORO_START_CODE_BLOCK(CoroOps::Return)
+
+#define CORO_SET_CALL(dest, func, ...) \
+ CORO_AWAIT(coroRuntime._vthread->pushCoroutineWithReturn<func>(&(dest), __VA_ARGS__))
+
+#define CORO_CALL(func, ...) \
+ CORO_AWAIT(coroRuntime._vthread->pushCoroutine<func>(__VA_ARGS__))
+
+} // Namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/elements.cpp b/engines/mtropolis/elements.cpp
index 9098000450f..d8b64437382 100644
--- a/engines/mtropolis/elements.cpp
+++ b/engines/mtropolis/elements.cpp
@@ -33,6 +33,7 @@
#include "mtropolis/assets.h"
#include "mtropolis/audio_player.h"
+#include "mtropolis/coroutines.h"
#include "mtropolis/elements.h"
#include "mtropolis/element_factory.h"
#include "mtropolis/miniscript.h"
@@ -574,21 +575,82 @@ MiniscriptInstructionOutcome MovieElement::writeRefAttribute(MiniscriptThread *t
return VisualElement::writeRefAttribute(thread, result, attrib);
}
-VThreadState MovieElement::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
- // The reaction to the Play command should be to fire Unpaused and then fire Played.
+CORO_BEGIN_DEFINITION(MovieElement::MovieElementConsumeCommandCoroutine)
+ // The reaction to the Play command should be Shown -> Unpaused -> Played
// At First Cel is NOT fired by Play commands for some reason.
+ // The reaction to the Stop command should be Paused -> Hidden -> Stopped
+ struct Locals {
+ bool wasPaused;
+ };
+
+ CORO_BEGIN_FUNCTION
+ CORO_IF(Event(EventIDs::kPlay, 0).respondsTo(params->msg->getEvent()))
+ locals->wasPaused = params->self->_paused;
+
+ CORO_CALL(ChangeVisibilityCoroutine, params->self, params->runtime, true);
+
+ CORO_CALL(StartPlayingCoroutine, params->self, params->runtime);
+
+ CORO_IF(locals->wasPaused)
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kUnpause, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
+
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+ CORO_END_IF
+
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPlay, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+
+ CORO_RETURN;
+ CORO_ELSE_IF(Event(EventIDs::kStop, 0).respondsTo(params->msg->getEvent()))
+ CORO_IF(!params->self->_paused)
+ params->self->stopSubtitles();
+
+ params->self->_paused = true;
+
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPause, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
+
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+ CORO_END_IF
+
+ CORO_CALL(ChangeVisibilityCoroutine, params->self, params->runtime, false);
+
+ params->self->_paused = true;
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPause, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
+
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+
+ CORO_RETURN;
+ CORO_END_IF
+
+ CORO_CALL(VisualElement::VisualElementConsumeCommandCoroutine, params->self, params->runtime, params->msg);
+ CORO_END_FUNCTION
+
+CORO_END_DEFINITION
+
+VThreadState MovieElement::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+ // The reaction to the Play command should be Shown -> Unpaused -> Played
+ // At First Cel is NOT fired by Play commands for some reason.
+ // The reaction to the Stop command should be Paused -> Hidden -> Stopped
+
+ if (true) {
+ runtime->getVThread().pushCoroutine<MovieElement::MovieElementConsumeCommandCoroutine>(this, runtime, msg);
+ return kVThreadReturn;
+ }
if (Event(EventIDs::kPlay, 0).respondsTo(msg->getEvent())) {
- if (_paused)
{
- _paused = false;
- Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kUnpause, 0), DynamicValue(), getSelfReference()));
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->sendMessageOnVThread(dispatch);
}
- {
- Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
+ if (_paused) {
+ _paused = false;
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kUnpause, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->sendMessageOnVThread(dispatch);
}
@@ -603,15 +665,6 @@ VThreadState MovieElement::consumeCommand(Runtime *runtime, const Common::Shared
return kVThreadReturn;
}
if (Event(EventIDs::kStop, 0).respondsTo(msg->getEvent())) {
- if (!_paused) {
- stopSubtitles();
-
- _paused = true;
- Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPause, 0), DynamicValue(), getSelfReference()));
- Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
- runtime->sendMessageOnVThread(dispatch);
- }
-
{
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kStop, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
@@ -622,10 +675,19 @@ VThreadState MovieElement::consumeCommand(Runtime *runtime, const Common::Shared
becomeVisibleTaskData->desiredFlag = false;
becomeVisibleTaskData->runtime = runtime;
+ if (!_paused) {
+ stopSubtitles();
+
+ _paused = true;
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPause, 0), DynamicValue(), getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
+ runtime->sendMessageOnVThread(dispatch);
+ }
+
return kVThreadReturn;
}
- return VisualElement::consumeCommand(runtime, msg);
+ return VisualElement::asyncConsumeCommand(runtime, msg);
}
void MovieElement::activate() {
@@ -1132,6 +1194,28 @@ MiniscriptInstructionOutcome MovieElement::scriptSetRangeTyped(MiniscriptThread
return kMiniscriptInstructionOutcomeContinue;
}
+CORO_BEGIN_DEFINITION(MovieElement::StartPlayingCoroutine)
+ struct Locals {
+ };
+
+ CORO_BEGIN_FUNCTION
+ MovieElement *self = params->self;
+
+ if (self->_videoDecoder) {
+ self->_videoDecoder->stop();
+ self->_currentPlayState = kMediaStateStopped;
+ self->_needsReset = true;
+ self->_contentsDirty = true;
+ self->_currentTimestamp = self->_reversed ? self->_playRange.max : self->_playRange.min;
+
+ self->_shouldPlayIfNotPaused = true;
+ self->_paused = false;
+
+ self->stopSubtitles();
+ }
+ CORO_END_FUNCTION
+CORO_END_DEFINITION
+
VThreadState MovieElement::startPlayingTask(const StartPlayingTaskData &taskData) {
if (_videoDecoder) {
_videoDecoder->stop();
@@ -1407,7 +1491,7 @@ MiniscriptInstructionOutcome MToonElement::writeRefAttribute(MiniscriptThread *t
return VisualElement::writeRefAttribute(thread, result, attrib);
}
-VThreadState MToonElement::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+VThreadState MToonElement::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (Event(EventIDs::kPlay, 0).respondsTo(msg->getEvent())) {
// If the range set fails, then the mToon should play anyway, so ignore the result
(void)scriptSetRange(nullptr, msg->getValue());
@@ -1443,7 +1527,7 @@ VThreadState MToonElement::consumeCommand(Runtime *runtime, const Common::Shared
return kVThreadReturn;
}
- return VisualElement::consumeCommand(runtime, msg);
+ return VisualElement::asyncConsumeCommand(runtime, msg);
}
void MToonElement::activate() {
@@ -2441,7 +2525,7 @@ MiniscriptInstructionOutcome SoundElement::writeRefAttribute(MiniscriptThread *t
return NonVisualElement::writeRefAttribute(thread, writeProxy, attrib);
}
-VThreadState SoundElement::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+VThreadState SoundElement::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (Event(EventIDs::kPlay, 0).respondsTo(msg->getEvent())) {
StartPlayingTaskData *startPlayingTaskData = runtime->getVThread().pushTask("SoundElement::startPlayingTask", this, &SoundElement::startPlayingTask);
startPlayingTaskData->runtime = runtime;
@@ -2455,7 +2539,7 @@ VThreadState SoundElement::consumeCommand(Runtime *runtime, const Common::Shared
return kVThreadReturn;
}
- return NonVisualElement::consumeCommand(runtime, msg);
+ return NonVisualElement::asyncConsumeCommand(runtime, msg);
}
void SoundElement::initSubtitles() {
diff --git a/engines/mtropolis/elements.h b/engines/mtropolis/elements.h
index 7699af0f3d4..bd87cac1c7b 100644
--- a/engines/mtropolis/elements.h
+++ b/engines/mtropolis/elements.h
@@ -28,6 +28,7 @@
#include "mtropolis/data.h"
#include "mtropolis/runtime.h"
#include "mtropolis/render.h"
+#include "mtropolis/coroutine_protos.h"
namespace Video {
@@ -97,7 +98,7 @@ public:
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) override;
- VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
+ VThreadState asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
void activate() override;
void deactivate() override;
@@ -125,6 +126,11 @@ protected:
void onPauseStateChanged() override;
void onSegmentUnloaded(int segmentIndex) override;
+ struct MovieElementConsumeCommandCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_3(MovieElement *, self, Runtime *, runtime, Common::SharedPtr<MessageProperties>, msg);
+ };
+
private:
IntRange computeRealRange() const;
@@ -154,6 +160,16 @@ private:
uint32 timestamp;
};
+ struct StartPlayingCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_2(MovieElement *, self, Runtime *, runtime);
+ };
+
+ struct SeekToTimeCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_2(Runtime *, runtime, uint32, timestamp);
+ };
+
VThreadState startPlayingTask(const StartPlayingTaskData &taskData);
VThreadState seekToTimeTask(const SeekToTimeTaskData &taskData);
@@ -236,7 +252,7 @@ public:
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) override;
- VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
+ VThreadState asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
void activate() override;
void deactivate() override;
@@ -411,7 +427,7 @@ public:
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) override;
- VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
+ VThreadState asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
void activate() override;
void deactivate() override;
diff --git a/engines/mtropolis/miniscript_protos.h b/engines/mtropolis/miniscript_protos.h
new file mode 100644
index 00000000000..df445fdd9bc
--- /dev/null
+++ b/engines/mtropolis/miniscript_protos.h
@@ -0,0 +1,36 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MTROPOLIS_MINISCRIPT_PROTOS_H
+#define MTROPOLIS_MINISCRIPT_PROTOS_H
+
+namespace MTropolis {
+
+enum MiniscriptInstructionOutcome {
+ kMiniscriptInstructionOutcomeContinue, // Continue executing next instruction
+ kMiniscriptInstructionOutcomeYieldToVThreadNoRetry, // Instruction pushed a VThread task and should be retried when the task completes
+ kMiniscriptInstructionOutcomeYieldToVThreadAndRetry, // Instruction pushed a VThread task and completed
+ kMiniscriptInstructionOutcomeFailed, // Instruction errored
+};
+
+} // End of namespace MTropolis
+
+#endif
diff --git a/engines/mtropolis/module.mk b/engines/mtropolis/module.mk
index 3266383c95d..474401e20bc 100644
--- a/engines/mtropolis/module.mk
+++ b/engines/mtropolis/module.mk
@@ -6,6 +6,9 @@ MODULE_OBJS = \
audio_player.o \
boot.o \
core.o \
+ coroutine_exec.o \
+ coroutine_manager.o \
+ coroutines.o \
data.o \
debug.o \
element_factory.o \
diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp
index 890f46c02ce..54c4173392e 100644
--- a/engines/mtropolis/runtime.cpp
+++ b/engines/mtropolis/runtime.cpp
@@ -38,6 +38,8 @@
#include "audio/mixer.h"
#include "mtropolis/runtime.h"
+#include "mtropolis/coroutine_manager.h"
+#include "mtropolis/coroutines.h"
#include "mtropolis/data.h"
#include "mtropolis/vthread.h"
#include "mtropolis/asset_factory.h"
@@ -3818,93 +3820,109 @@ void Structural::materializeDescendents(Runtime *runtime, ObjectLinkingScope *ou
}
}
-VThreadState Structural::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
- if (Event(EventIDs::kUnpause, 0).respondsTo(msg->getEvent())) {
- if (_paused) {
- _paused = false;
- onPauseStateChanged();
- }
+CORO_BEGIN_DEFINITION(Structural::StructuralConsumeCommandCoroutine)
+ struct Locals {
+ Common::ScopedPtr<MiniscriptThread> miniscriptThread;
+ uint32 attribID;
+ const Common::String *attribName;
+ DynamicValueWriteProxy writeProxy;
+ DynamicValue attribGetResult;
+ bool quietlyDiscard;
+ };
- Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kUnpause, 0), DynamicValue(), getSelfReference()));
- Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
- runtime->sendMessageOnVThread(dispatch);
+ CORO_BEGIN_FUNCTION
+ CORO_IF(Event(EventIDs::kUnpause, 0).respondsTo(params->msg->getEvent()))
+ if (params->self->_paused) {
+ params->self->_paused = false;
+ params->self->onPauseStateChanged();
+ }
- return kVThreadReturn;
- }
- if (Event(EventIDs::kPause, 0).respondsTo(msg->getEvent())) {
- if (!_paused) {
- _paused = true;
- onPauseStateChanged();
- }
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kUnpause, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
- Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPause, 0), DynamicValue(), getSelfReference()));
- Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
- runtime->sendMessageOnVThread(dispatch);
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
- return kVThreadReturn;
- }
- if (msg->getEvent().eventType == EventIDs::kAttribSet) {
- const uint32 attribID = msg->getEvent().eventInfo;
+ CORO_RETURN;
+ CORO_ELSE_IF(Event(EventIDs::kPause, 0).respondsTo(params->msg->getEvent()))
+ if (params->self->_paused) {
+ params->self->_paused = false;
+ params->self->onPauseStateChanged();
+ }
+
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPause, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
+
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+
+ CORO_RETURN;
+ CORO_ELSE_IF(params->msg->getEvent().eventType == EventIDs::kAttribSet)
+ locals->attribID = params->msg->getEvent().eventInfo;
- const Common::String *attribName = runtime->resolveAttributeIDName(attribID);
- if (attribName == nullptr) {
+ locals->attribName = params->runtime->resolveAttributeIDName(locals->attribID);
+
+ CORO_IF(locals->attribName == nullptr)
#ifdef MTROPOLIS_DEBUG_ENABLE
- if (Debugger *debugger = runtime->debugGetDebugger())
- debugger->notifyFmt(kDebugSeverityError, "Attribute ID '%i' couldn't be resolved for Set Attribute message", static_cast<int>(attribID));
+ if (Debugger *debugger = params->runtime->debugGetDebugger())
+ debugger->notifyFmt(kDebugSeverityError, "Attribute ID '%i' couldn't be resolved for Set Attribute message", static_cast<int>(locals->attribID));
#endif
- return kVThreadError;
- }
+ CORO_ERROR;
+ CORO_END_IF
- MiniscriptThread miniscriptThread(runtime, msg, nullptr, nullptr, nullptr);
+ locals->miniscriptThread.reset(new MiniscriptThread(params->runtime, params->msg, nullptr, nullptr, nullptr));
- DynamicValueWriteProxy writeProxy;
- MiniscriptInstructionOutcome outcome = this->writeRefAttribute(&miniscriptThread, writeProxy, *attribName);
- if (outcome == kMiniscriptInstructionOutcomeFailed)
- return kVThreadError;
+ CORO_AWAIT_MINISCRIPT(params->self->writeRefAttribute(locals->miniscriptThread.get(), locals->writeProxy, *locals->attribName));
- outcome = writeProxy.pod.ifc->write(&miniscriptThread, msg->getValue(), writeProxy.pod.objectRef, writeProxy.pod.ptrOrOffset);
- if (outcome == kMiniscriptInstructionOutcomeFailed)
- return kVThreadError;
+ CORO_AWAIT_MINISCRIPT(locals->writeProxy.pod.ifc->write(locals->miniscriptThread.get(), params->msg->getValue(), locals->writeProxy.pod.objectRef, locals->writeProxy.pod.ptrOrOffset));
- return kVThreadReturn;
- }
- if (msg->getEvent().eventType == EventIDs::kAttribGet) {
- const uint32 attribID = msg->getEvent().eventInfo;
+ CORO_RETURN;
+ CORO_ELSE_IF(params->msg->getEvent().eventType == EventIDs::kAttribGet)
+ locals->attribID = params->msg->getEvent().eventInfo;
- const Common::String *attribName = runtime->resolveAttributeIDName(attribID);
- if (attribName == nullptr) {
+ locals->attribName = params->runtime->resolveAttributeIDName(locals->attribID);
+ CORO_IF(locals->attribName == nullptr)
#ifdef MTROPOLIS_DEBUG_ENABLE
- if (Debugger *debugger = runtime->debugGetDebugger())
- debugger->notifyFmt(kDebugSeverityError, "Attribute ID '%i' couldn't be resolved for Get Attribute message", static_cast<int>(attribID));
+ if (Debugger *debugger = params->runtime->debugGetDebugger())
+ debugger->notifyFmt(kDebugSeverityError, "Attribute ID '%i' couldn't be resolved for Set Attribute message", static_cast<int>(locals->attribID));
#endif
- return kVThreadError;
- }
+ CORO_ERROR;
+ CORO_END_IF
+
+ locals->miniscriptThread.reset(new MiniscriptThread(params->runtime, params->msg, nullptr, nullptr, nullptr));
- MiniscriptThread miniscriptThread(runtime, msg, nullptr, nullptr, nullptr);
+ CORO_IF(!params->self->readAttribute(locals->miniscriptThread.get(), locals->attribGetResult, *locals->attribName))
+ CORO_ERROR;
+ CORO_END_IF
- DynamicValue result;
- if (!readAttribute(&miniscriptThread, result, *attribName))
- return kVThreadError;
+ params->msg->setValue(locals->attribGetResult);
- msg->setValue(result);
+ CORO_RETURN;
+ CORO_END_IF
- return kVThreadReturn;
- }
+ locals->quietlyDiscard = false;
- // Just ignore these
- const EventIDs::EventID ignoredIDs[] = {
- EventIDs::kPreloadMedia,
- EventIDs::kFlushMedia,
- EventIDs::kFlushAllMedia,
- EventIDs::kPrerollMedia
- };
+ // Just ignore these
+ const EventIDs::EventID ignoredIDs[] = {
+ EventIDs::kPreloadMedia,
+ EventIDs::kFlushMedia,
+ EventIDs::kFlushAllMedia,
+ EventIDs::kPrerollMedia
+ };
- for (EventIDs::EventID evtID : ignoredIDs) {
- if (Event(evtID, 0).respondsTo(msg->getEvent()))
- return kVThreadReturn;
- }
+ for (EventIDs::EventID evtID : ignoredIDs) {
+ if (Event(evtID, 0).respondsTo(params->msg->getEvent())) {
+ locals->quietlyDiscard = true;
+ break;
+ }
+ }
- warning("Command type %i was ignored", msg->getEvent().eventType);
+ if (!locals->quietlyDiscard)
+ warning("Command type %i was ignored", params->msg->getEvent().eventType);
+ CORO_END_FUNCTION
+
+CORO_END_DEFINITION
+
+VThreadState Structural::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+ runtime->getVThread().pushCoroutine<StructuralConsumeCommandCoroutine>(this, runtime, msg);
return kVThreadReturn;
}
@@ -4720,7 +4738,8 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, ISaveUIProvider *saveProv
_pendingSceneReturnCount(0) {
_random.reset(new Common::RandomSource("mtropolis"));
- _vthread.reset(new VThread());
+ _coroManager.reset(ICoroutineManager::create());
+ _vthread.reset(new VThread(_coroManager.get()));
for (int i = 0; i < kColorDepthModeCount; i++) {
_displayModeSupported[i] = false;
@@ -6031,6 +6050,15 @@ void Runtime::loadScene(const Common::SharedPtr<Structural> &scene) {
}
}
+CORO_BEGIN_DEFINITION(Runtime::SendMessageOnVThreadCoroutine)
+ struct Locals {
+ };
+
+ CORO_BEGIN_FUNCTION
+ CORO_AWAIT(params->runtime->sendMessageOnVThread(params->dispatch));
+ CORO_END_FUNCTION
+CORO_END_DEFINITION
+
void Runtime::sendMessageOnVThread(const Common::SharedPtr<MessageDispatch> &dispatch) {
EventIDs::EventID eventID = dispatch->getMsg()->getEvent().eventType;
@@ -6378,7 +6406,7 @@ VThreadState Runtime::consumeMessageTask(const ConsumeMessageTaskData &data) {
VThreadState Runtime::consumeCommandTask(const ConsumeCommandTaskData &data) {
Structural *structural = data.structural;
- return structural->consumeCommand(this, data.message);
+ return structural->asyncConsumeCommand(this, data.message);
}
VThreadState Runtime::updateMouseStateTask(const UpdateMouseStateTaskData &data) {
@@ -7339,7 +7367,7 @@ void Runtime::unloadProject() {
_pendingSceneTransitions.clear();
_pendingTeardowns.clear();
_messageQueue.clear();
- _vthread.reset(new VThread());
+ _vthread.reset(new VThread(_coroManager.get()));
if (!_mainWindow.expired()) {
removeWindow(_mainWindow.lock().get());
@@ -7770,13 +7798,13 @@ Project::~Project() {
_resources.reset();
}
-VThreadState Project::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+VThreadState Project::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (Event(EventIDs::kCloseProject, 0).respondsTo(msg->getEvent())) {
runtime->closeProject();
return kVThreadReturn;
}
- return Structural::consumeCommand(runtime, msg);
+ return Structural::asyncConsumeCommand(runtime, msg);
}
MiniscriptInstructionOutcome Project::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
@@ -9088,38 +9116,48 @@ void VisualElement::setLayer(uint16 layer) {
}
}
-VThreadState VisualElement::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
- if (Event(EventIDs::kElementShow, 0).respondsTo(msg->getEvent())) {
- if (!_visible) {
- _visible = true;
- runtime->setSceneGraphDirty();
- }
+CORO_BEGIN_DEFINITION(VisualElement::VisualElementConsumeCommandCoroutine)
+ struct Locals {
+ };
- Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementShow, 0), DynamicValue(), getSelfReference()));
- Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
- runtime->sendMessageOnVThread(dispatch);
+ CORO_BEGIN_FUNCTION
+ CORO_IF(Event(EventIDs::kElementShow, 0).respondsTo(params->msg->getEvent()))
+ if (!params->self->_visible) {
+ params->self->_visible = true;
+ params->runtime->setSceneGraphDirty();
+ }
- return kVThreadReturn;
- }
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementShow, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
- if (Event(EventIDs::kElementHide, 0).respondsTo(msg->getEvent())) {
- if (_visible) {
- _visible = false;
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+
+ CORO_RETURN;
+ CORO_ELSE_IF(Event(EventIDs::kElementHide, 0).respondsTo(params->msg->getEvent()))
+ if (params->self->_visible) {
+ params->self->_visible = false;
- if (_hooks)
- _hooks->onHidden(this, _visible);
+ if (params->self->_hooks)
+ params->self->_hooks->onHidden(params->self, params->self->_visible);
- runtime->setSceneGraphDirty();
- }
+ params->runtime->setSceneGraphDirty();
+ }
- Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementHide, 0), DynamicValue(), getSelfReference()));
- Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
- runtime->sendMessageOnVThread(dispatch);
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementHide, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
- return kVThreadReturn;
- }
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+
+ CORO_RETURN;
+ CORO_END_IF
- return Element::consumeCommand(runtime, msg);
+ CORO_CALL(Element::ElementConsumeCommandCoroutine, params->self, params->runtime, params->msg);
+ CORO_END_FUNCTION
+CORO_END_DEFINITION
+
+VThreadState VisualElement::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
+ runtime->getVThread().pushCoroutine<VisualElementConsumeCommandCoroutine>(this, runtime, msg);
+ return kVThreadReturn;
}
bool VisualElement::respondsToEvent(const Event &evt) const {
@@ -9787,6 +9825,22 @@ Common::Point VisualElement::getCenterPosition() const {
return Common::Point((_rect.left + _rect.right) / 2, (_rect.top + _rect.bottom) / 2);
}
+CORO_BEGIN_DEFINITION(VisualElement::ChangeVisibilityCoroutine)
+ struct Locals {
+ };
+
+ CORO_BEGIN_FUNCTION
+ CORO_IF(params->self->_visible != params->desiredFlag)
+ params->self->setVisible(params->runtime, params->desiredFlag);
+
+ Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(params->desiredFlag ? EventIDs::kElementShow : EventIDs::kElementHide, 0), DynamicValue(), params->self->getSelfReference()));
+ Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
+
+ CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
+ CORO_END_IF
+ CORO_END_FUNCTION
+CORO_END_DEFINITION
+
VThreadState VisualElement::changeVisibilityTask(const ChangeFlagTaskData &taskData) {
if (_visible != taskData.desiredFlag) {
setVisible(taskData.runtime, taskData.desiredFlag);
diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h
index 14a0ae3a5b4..213ba8e8aa4 100644
--- a/engines/mtropolis/runtime.h
+++ b/engines/mtropolis/runtime.h
@@ -36,9 +36,11 @@
#include "mtropolis/actions.h"
#include "mtropolis/core.h"
+#include "mtropolis/coroutine_protos.h"
#include "mtropolis/data.h"
#include "mtropolis/debug.h"
#include "mtropolis/hacks.h"
+#include "mtropolis/miniscript_protos.h"
#include "mtropolis/subtitles.h"
#include "mtropolis/vthread.h"
@@ -72,6 +74,7 @@ namespace MTropolis {
class Asset;
class AssetManagerInterface;
+class CoroutineManager;
class CursorGraphic;
class CursorGraphicCollection;
class Element;
@@ -110,13 +113,6 @@ struct PlugInModifierLoaderContext;
struct SIModifierFactory;
template<typename TElement, typename TElementData> class ElementFactory;
-enum MiniscriptInstructionOutcome {
- kMiniscriptInstructionOutcomeContinue, // Continue executing next instruction
- kMiniscriptInstructionOutcomeYieldToVThreadNoRetry, // Instruction pushed a VThread task and should be retried when the task completes
- kMiniscriptInstructionOutcomeYieldToVThreadAndRetry, // Instruction pushed a VThread task and completed
- kMiniscriptInstructionOutcomeFailed, // Instruction errored
-};
-
#ifdef MTROPOLIS_DEBUG_ENABLE
class DebugPrimaryTaskList;
#endif
@@ -1656,6 +1652,12 @@ public:
// Sending a message on the VThread means "immediately"
void sendMessageOnVThread(const Common::SharedPtr<MessageDispatch> &dispatch);
+
+ struct SendMessageOnVThreadCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_2(Runtime *, runtime, Common::SharedPtr<MessageDispatch>, dispatch);
+ };
+
void queueMessage(const Common::SharedPtr<MessageDispatch> &dispatch);
void queueOSEvent(const Common::SharedPtr<OSEvent> &osEvent);
@@ -1893,6 +1895,8 @@ private:
static void recursiveFindColliders(Structural *structural, size_t sceneStackDepth, Common::Array<ColliderInfo> &colliders, int32 parentOriginX, int32 parentOriginY, bool isRoot);
static bool sortColliderPredicate(const ColliderInfo &a, const ColliderInfo &b);
+ Common::ScopedPtr<ICoroutineManager> _coroManager;
+
Common::Array<VolumeState> _volumes;
Common::SharedPtr<ProjectDescription> _queuedProjectDesc;
Common::SharedPtr<Project> _project;
@@ -2237,7 +2241,12 @@ public:
void materializeSelfAndDescendents(Runtime *runtime, ObjectLinkingScope *outerScope);
void materializeDescendents(Runtime *runtime, ObjectLinkingScope *outerScope);
- virtual VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg);
+ struct StructuralConsumeCommandCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_3(Structural *, self, Runtime *, runtime, Common::SharedPtr<MessageProperties>, msg);
+ };
+
+ virtual VThreadState asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg);
virtual void activate();
virtual void deactivate();
@@ -2465,7 +2474,7 @@ public:
explicit Project(Runtime *runtime);
~Project();
- VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
+ VThreadState asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) override;
@@ -2703,6 +2712,8 @@ public:
void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
+ typedef StructuralConsumeCommandCoroutine ElementConsumeCommandCoroutine;
+
protected:
Element(const Element &other);
@@ -2820,7 +2831,12 @@ public:
bool isVisual() const override;
virtual bool isTextLabel() const;
- VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
+ struct VisualElementConsumeCommandCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_3(VisualElement *, self, Runtime *, runtime, Common::SharedPtr<MessageProperties>, msg);
+ };
+
+ VThreadState asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
bool respondsToEvent(const Event &evt) const override;
VThreadState consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
@@ -2920,6 +2936,11 @@ protected:
Common::Point getCenterPosition() const;
+ struct ChangeVisibilityCoroutine {
+ CORO_DEFINE_RETURN_TYPE(void);
+ CORO_DEFINE_PARAMS_3(VisualElement *, self, Runtime *, runtime, bool, desiredFlag);
+ };
+
struct ChangeFlagTaskData {
ChangeFlagTaskData() : desiredFlag(false), runtime(nullptr) {}
diff --git a/engines/mtropolis/vthread.cpp b/engines/mtropolis/vthread.cpp
index aa477d0cdf3..5beb2668004 100644
--- a/engines/mtropolis/vthread.cpp
+++ b/engines/mtropolis/vthread.cpp
@@ -19,6 +19,8 @@
*
*/
+#include "mtropolis/coroutines.h"
+#include "mtropolis/coroutine_manager.h"
#include "mtropolis/vthread.h"
namespace MTropolis {
@@ -58,9 +60,8 @@ void VThreadTaskData::debugInspect(IDebugInspectionReport *report) const {
}
#endif
-VThread::VThread()
- : _numActiveStackChunks(0)
-{
+VThread::VThread(ICoroutineManager *coroManager)
+ : _numActiveStackChunks(0), _coroManager(coroManager) {
}
VThread::~VThread() {
@@ -176,6 +177,43 @@ void VThread::reserveFrame(size_t frameAlignment, size_t frameSize, VThreadStack
outIsNewChunk = true;
}
+void VThread::pushCoroutineInternal(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunction, bool isVoidReturn, const CoroutineParamsBase ¶ms, const CoroutineReturnValueRefBase &returnValueRef) {
+ const CompiledCoroutine *compiledCoro = *compiledCoroPtr;
+ if (compiledCoro == nullptr) {
+ _coroManager->compileCoroutine(compiledCoroPtr, compileFunction, isVoidReturn);
+ compiledCoro = *compiledCoroPtr;
+ assert(compiledCoro);
+ }
+
+ pushCoroutineFrame(compiledCoro, params, returnValueRef);
+}
+
+VThreadTaskData *VThread::pushCoroutineFrame(const CompiledCoroutine *compiledCoro, const CoroutineParamsBase ¶ms, const CoroutineReturnValueRefBase &returnValueRef) {
+ const size_t frameAlignment = alignof(VThreadStackFrame);
+ size_t dataAlignment = 0;
+ size_t dataSize = 0;
+
+ compiledCoro->_getFrameParameters(dataSize, dataAlignment);
+
+ VThreadStackFrame *prevFrame = nullptr;
+ if (_numActiveStackChunks > 0)
+ prevFrame = _stackChunks[_numActiveStackChunks - 1]._topFrame;
+
+ VThreadStackFrame *framePtr = nullptr;
+ void *dataPtr = nullptr;
+ bool isNewChunk = false;
+ reserveFrame(frameAlignment, sizeof(VThreadStackFrame), framePtr, dataAlignment, dataSize, dataPtr, isNewChunk);
+
+ VThreadStackFrame *frame = new (framePtr) VThreadStackFrame();
+ VThreadTaskData *frameData = compiledCoro->_frameConstructor(dataPtr, compiledCoro, params, returnValueRef);
+
+ frame->data = frameData;
+ frame->prevFrame = prevFrame;
+ frame->isLastInChunk = isNewChunk;
+
+ return frameData;
+}
+
bool VThread::popFrame() {
if (_numActiveStackChunks == 0)
return false;
diff --git a/engines/mtropolis/vthread.h b/engines/mtropolis/vthread.h
index 96e99154b27..fc0c3b9fb3e 100644
--- a/engines/mtropolis/vthread.h
+++ b/engines/mtropolis/vthread.h
@@ -22,10 +22,13 @@
#ifndef MTROPOLIS_VTHREAD_H
#define MTROPOLIS_VTHREAD_H
+#include "mtropolis/coroutine_protos.h"
+#include "mtropolis/coroutine_return_value.h"
#include "mtropolis/debug.h"
namespace MTropolis {
+struct ICoroutineManager;
class VThread;
// Virtual thread, really a task stack
@@ -129,7 +132,7 @@ private:
class VThread {
public:
- VThread();
+ explicit VThread(ICoroutineManager *coroManager);
~VThread();
template<typename TClass, typename TData>
@@ -144,10 +147,27 @@ public:
bool popFrame();
+ VThreadTaskData *pushCoroutineFrame(const CompiledCoroutine *compiledCoro, const CoroutineParamsBase ¶ms, const CoroutineReturnValueRefBase &returnValueRef);
+
+ template<typename TCoroutine, typename TReturnValue, typename ...TParams>
+ void pushCoroutineWithReturn(TReturnValue *returnValuePtr, TParams &&...args);
+
+ template<typename TCoroutine, typename TReturnValue>
+ void pushCoroutineWithReturn(TReturnValue *returnValuePtr);
+
+ template<typename TCoroutine, typename... TParams>
+ void pushCoroutine(TParams &&...args);
+
+ template<typename TCoroutine>
+ void pushCoroutine();
+
+
private:
void reserveFrame(size_t frameAlignment, size_t frameSize, VThreadStackFrame *&outFramePtr, size_t dataAlignment, size_t dataSize, void *&outDataPtr, bool &outIsNewChunk);
static bool reserveFrameInChunk(VThreadStackChunk *chunk, size_t frameAlignment, size_t frameSize, VThreadStackFrame *&outFramePtr, size_t dataAlignment, size_t dataSize, void *&outDataPtr);
+ void pushCoroutineInternal(CompiledCoroutine **compiledCoroPtr, CoroutineCompileFunction_t compileFunc, bool isVoidReturn, const CoroutineParamsBase ¶ms, const CoroutineReturnValueRefBase &returnValueRef);
+
template<typename TClass, typename TData>
TData *pushTaskWithFaultHandler(const VThreadFaultIdentifier *faultID, const char *name, TClass *obj, VThreadState (TClass::*method)(const TData &data));
@@ -155,6 +175,7 @@ private:
TData *pushTaskWithFaultHandler(const VThreadFaultIdentifier *faultID, const char *name, VThreadState (*func)(const TData &data));
Common::Array<VThreadStackChunk> _stackChunks;
+ ICoroutineManager *_coroManager;
uint _numActiveStackChunks;
};
@@ -221,6 +242,28 @@ TData &VThreadFunctionData<TData>::getData() {
return _data;
}
+template<typename TCoroutine, typename TReturnValue, typename... TParams>
+void VThread::pushCoroutineWithReturn(TReturnValue *returnValuePtr, TParams &&...args) {
+ assert(returnValuePtr != nullptr);
+ this->pushCoroutineInternal(&TCoroutine::ms_compiledCoro, TCoroutine::compileCoroutine, CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>::isVoid(), typename TCoroutine::Params(Common::forward<TParams>(args)...), CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>(returnValuePtr));
+}
+
+template<typename TCoroutine, typename TReturnValue>
+void VThread::pushCoroutineWithReturn(TReturnValue *returnValuePtr) {
+ assert(returnValuePtr != nullptr);
+ this->pushCoroutineInternal(&TCoroutine::ms_compiledCoro, TCoroutine::compileCoroutine, CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>::isVoid(), typename TCoroutine::Params(), CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>(returnValuePtr));
+}
+
+template<typename TCoroutine, typename... TParams>
+void VThread::pushCoroutine(TParams &&...args) {
+ this->pushCoroutineInternal(&TCoroutine::ms_compiledCoro, TCoroutine::compileCoroutine, CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>::isVoid(), typename TCoroutine::Params(Common::forward<TParams>(args)...), CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>());
+}
+
+template<typename TCoroutine>
+void VThread::pushCoroutine() {
+ this->pushCoroutineInternal(&TCoroutine::ms_compiledCoro, TCoroutine::compileCoroutine, CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>::isVoid(), typename TCoroutine::Params(), CoroutineReturnValueRef<typename TCoroutine::ReturnValue_t>());
+}
+
template<typename TClass, typename TData>
TData *VThread::pushTask(const char *name, TClass *obj, VThreadState (TClass::*method)(const TData &data)) {
return this->pushTaskWithFaultHandler(nullptr, name, obj, method);
More information about the Scummvm-git-logs
mailing list