[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 &params, 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 &params, const CoroutineReturnValueRef<ReturnValue_t> &rvRef)\
+			: CoroutineStackFrame2(compiledCoro), _params(params), _rvRef(rvRef) {\
+		}\
+		static CoroutineStackFrame2 *constructFrame(void *ptr, const CompiledCoroutine *compiledCoro, const CoroutineParamsBase &params, 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 &params, 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 &params, 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 &params, 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 &params, 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