MTROPOLIS: Convert Miniscript program runs from tasks to coroutines

This commit is contained in:
elasota 2024-08-29 21:22:32 -04:00
parent 590844d4d4
commit fdaa96daba
10 changed files with 112 additions and 96 deletions

View File

@ -79,7 +79,7 @@ VThreadState CoroutineStackFrame2::execute(VThread *thread) {
return kVThreadError;
else if (runtimeState._miniscriptOutcome == kMiniscriptInstructionOutcomeContinue)
break;
else if (runtimeState._miniscriptOutcome == kMiniscriptInstructionOutcomeYieldToVThreadNoRetry) {
else if (runtimeState._miniscriptOutcome == kMiniscriptInstructionOutcomeYieldToVThread) {
_nextInstr = ip;
return kVThreadReturn;
} else {

View File

@ -104,6 +104,27 @@ struct CoroutineParamsBase {
Params() = delete; \
}
#define CORO_DEFINE_PARAMS_4(type1, name1, type2, name2, type3, name3, type4, name4) \
CORO_STUB \
struct Params : public CoroutineParamsBase { \
typedef type1 ParamType1_t; \
typedef type2 ParamType2_t; \
typedef type3 ParamType3_t; \
typedef type4 ParamType4_t; \
\
ParamType1_t name1; \
ParamType2_t name2; \
ParamType3_t name3; \
ParamType4_t name4; \
\
explicit Params(const ParamType1_t &p_##name1, const ParamType2_t &p_##name2, const ParamType3_t &p_##name3, const ParamType4_t &p_##name4) \
: name1(p_##name1), name2(p_##name2), name3(p_##name3), name4(p_##name4) { \
} \
\
private: \
Params() = delete; \
}
#define CORO_DEFINE_RETURN_TYPE(type) \
typedef type ReturnValue_t

View File

@ -129,7 +129,10 @@ struct ICoroutineCompiler {
Locals *locals = &static_cast<CoroStackFrame *>(coroRuntime._frame)->_locals; \
CoroutineReturnValueRef<ReturnValue_t> coroReturnValueRef = (static_cast<CoroStackFrame *>(coroRuntime._frame)->_rvRef);
#define CORO_DISUSE_CODE_BLOCK_VARS \
#define CORO_AWAIT_PUSHED_TASK \
return kVThreadReturn
#define CORO_DISUSE_CODE_BLOCK_VARS \
(void)params; \
(void)locals; \
(void)coroReturnValueRef;
@ -209,11 +212,11 @@ void type::compileCoroutine(ICoroutineCompiler *compiler) {
#define CORO_WHILE(expr) \
CORO_END_CODE_BLOCK \
CORO_START_CODE_BLOCK(CoroOps::WhileCond) \
coroCondition = !!(expr); \
coroRuntime._condition = !!(expr); \
CORO_END_CODE_BLOCK \
CORO_START_CODE_BLOCK(CoroOps::WhileBody)
#define CORO_END_WHILE(expr) \
#define CORO_END_WHILE \
CORO_END_CODE_BLOCK \
CORO_START_CODE_BLOCK(CoroOps::EndWhile)

View File

@ -1057,7 +1057,7 @@ MiniscriptInstructionOutcome MovieElement::scriptSetTimestamp(MiniscriptThread *
if (asInteger != (int32)_currentTimestamp) {
thread->getRuntime()->getVThread().pushCoroutine<MovieElement::SeekToTimeCoroutine>(this, getRuntime(), asInteger);
return kMiniscriptInstructionOutcomeYieldToVThreadNoRetry;
return kMiniscriptInstructionOutcomeYieldToVThread;
}
return kMiniscriptInstructionOutcomeContinue;
@ -1128,7 +1128,7 @@ MiniscriptInstructionOutcome MovieElement::scriptSetRangeTyped(MiniscriptThread
if (targetTS != _currentTimestamp) {
thread->getRuntime()->getVThread().pushCoroutine<MovieElement::SeekToTimeCoroutine>(this, getRuntime(), targetTS);
return kMiniscriptInstructionOutcomeYieldToVThreadNoRetry;
return kMiniscriptInstructionOutcomeYieldToVThread;
}
return kMiniscriptInstructionOutcomeContinue;

View File

@ -24,6 +24,7 @@
#include "common/random.h"
#include "common/memstream.h"
#include "mtropolis/coroutines.h"
#include "mtropolis/miniscript.h"
namespace MTropolis {
@ -630,7 +631,7 @@ MiniscriptInstructionOutcome Send::execute(MiniscriptThread *thread) const {
if (_messageFlags.immediate) {
thread->getRuntime()->sendMessageOnVThread(dispatch);
return kMiniscriptInstructionOutcomeYieldToVThreadNoRetry;
return kMiniscriptInstructionOutcomeYieldToVThread;
} else {
thread->getRuntime()->queueMessage(dispatch);
return kMiniscriptInstructionOutcomeContinue;
@ -1907,12 +1908,7 @@ MiniscriptInstructionOutcome Jump::execute(MiniscriptThread *thread) const {
} // End of namespace MiniscriptInstructions
MiniscriptThread::MiniscriptThread(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msgProps, const Common::SharedPtr<MiniscriptProgram> &program, const Common::SharedPtr<MiniscriptReferences> &refs, Modifier *modifier)
: _runtime(runtime), _msgProps(msgProps), _program(program), _refs(refs), _modifier(modifier), _currentInstruction(0), _failed(false) {
}
void MiniscriptThread::runOnVThread(VThread &vthread, const Common::SharedPtr<MiniscriptThread> &thread) {
ResumeTaskData *taskData = vthread.pushTask("MiniscriptThread::resumeTask", resumeTask);
taskData->thread = thread;
: _runtime(runtime), _msgProps(msgProps), _program(program), _instructions(program->getInstructions()), _refs(refs), _modifier(modifier), _currentInstruction(0), _failed(false) {
}
void MiniscriptThread::error(const Common::String &message) {
@ -2026,6 +2022,10 @@ void MiniscriptThread::createWriteIncomingDataProxy(DynamicValueWriteProxy &prox
proxy.pod.ptrOrOffset = 0;
}
void MiniscriptThread::retryInstruction() {
_currentInstruction--;
}
MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
thread->_msgProps->setValue(value);
@ -2042,50 +2042,40 @@ MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::refAt
return kMiniscriptInstructionOutcomeFailed;
}
CORO_BEGIN_DEFINITION(MiniscriptThread::ResumeThreadCoroutine)
struct Locals {
MiniscriptThread *self = nullptr;
size_t numInstrs = 0;
size_t instrNum = 0;
};
VThreadState MiniscriptThread::resumeTask(const ResumeTaskData &data) {
return data.thread->resume(data);
}
CORO_BEGIN_FUNCTION
locals->self = params->thread.get();
locals->numInstrs = locals->self->_program->getInstructions().size();
VThreadState MiniscriptThread::resume(const ResumeTaskData &taskData) {
const Common::Array<MiniscriptInstruction *> &instrsArray = _program->getInstructions();
CORO_IF (locals->numInstrs == 0)
CORO_RETURN;
CORO_END_IF
if (instrsArray.size() == 0)
return kVThreadReturn;
CORO_WHILE (locals->self->_currentInstruction < locals->numInstrs && !locals->self->_failed)
CORO_AWAIT_MINISCRIPT(locals->self->runNextInstruction());
CORO_END_WHILE
CORO_END_FUNCTION
CORO_END_DEFINITION
MiniscriptInstruction *const *instrs = &instrsArray[0];
size_t numInstrs = instrsArray.size();
MiniscriptInstructionOutcome MiniscriptThread::runNextInstruction() {
const MiniscriptInstruction *instr = _instructions[_currentInstruction++];
if (_currentInstruction >= numInstrs || _failed)
return kVThreadReturn;
MiniscriptInstructionOutcome outcome = instr->execute(this);
// Requeue now so that any VThread tasks queued by instructions run in front of the resume
{
ResumeTaskData *requeueData = _runtime->getVThread().pushTask("MiniscriptThread::resumeTask", resumeTask);
requeueData->thread = taskData.thread;
if (outcome == kMiniscriptInstructionOutcomeFailed) {
// Treat this as non-fatal but bail out of the execution loop
_failed = true;
return kMiniscriptInstructionOutcomeContinue;
}
while (_currentInstruction < numInstrs && !_failed) {
size_t instrNum = _currentInstruction++;
MiniscriptInstruction *instr = instrs[instrNum];
MiniscriptInstructionOutcome outcome = instr->execute(this);
if (outcome == kMiniscriptInstructionOutcomeFailed) {
// Should this also interrupt the message dispatch?
_failed = true;
return kVThreadReturn;
}
if (outcome == kMiniscriptInstructionOutcomeYieldToVThreadAndRetry) {
_currentInstruction = instrNum;
return kVThreadReturn;
}
if (outcome == kMiniscriptInstructionOutcomeYieldToVThreadNoRetry)
return kVThreadReturn;
}
return kVThreadReturn;
// Otherwise continue
return outcome;
}
MiniscriptInstructionOutcome MiniscriptThread::tryLoadVariable(MiniscriptStackValue &stackValue) {

View File

@ -422,8 +422,6 @@ class MiniscriptThread {
public:
MiniscriptThread(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msgProps, const Common::SharedPtr<MiniscriptProgram> &program, const Common::SharedPtr<MiniscriptReferences> &refs, Modifier *modifier);
static void runOnVThread(VThread &vthread, const Common::SharedPtr<MiniscriptThread> &thread);
void error(const Common::String &message);
const Common::SharedPtr<MiniscriptProgram> &getProgram() const;
@ -444,6 +442,13 @@ public:
void createWriteIncomingDataProxy(DynamicValueWriteProxy &proxy);
void retryInstruction();
struct ResumeThreadCoroutine {
CORO_DEFINE_RETURN_TYPE(void);
CORO_DEFINE_PARAMS_1(Common::SharedPtr<MiniscriptThread>, thread);
};
private:
struct IncomingDataWriteInterface {
static MiniscriptInstructionOutcome write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset);
@ -451,18 +456,17 @@ private:
static MiniscriptInstructionOutcome refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index);
};
struct ResumeTaskData {
Common::SharedPtr<MiniscriptThread> thread;
};
MiniscriptInstructionOutcome runNextInstruction();
static VThreadState resumeTask(const ResumeTaskData &data);
VThreadState resume(const ResumeTaskData &data);
VThreadState resume(MiniscriptThread *thread);
MiniscriptInstructionOutcome tryLoadVariable(MiniscriptStackValue &stackValue);
Common::SharedPtr<MiniscriptProgram> _program;
Common::SharedPtr<MiniscriptReferences> _refs;
Common::SharedPtr<MessageProperties> _msgProps;
const Common::Array<MiniscriptInstruction *> &_instructions;
Modifier *_modifier;
Runtime *_runtime;
Common::Array<MiniscriptStackValue> _stack;

View File

@ -25,10 +25,9 @@
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
kMiniscriptInstructionOutcomeContinue, // Continue executing next instruction
kMiniscriptInstructionOutcomeYieldToVThread, // Instruction pushed a VThread task
kMiniscriptInstructionOutcomeFailed, // Instruction errored
};
} // End of namespace MTropolis

View File

@ -26,6 +26,7 @@
#include "mtropolis/assets.h"
#include "mtropolis/audio_player.h"
#include "mtropolis/coroutines.h"
#include "mtropolis/miniscript.h"
#include "mtropolis/modifiers.h"
#include "mtropolis/modifier_factory.h"
@ -252,7 +253,7 @@ bool MiniscriptModifier::respondsToEvent(const Event &evt) const {
VThreadState MiniscriptModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (_enableWhen.respondsTo(msg->getEvent())) {
Common::SharedPtr<MiniscriptThread> thread(new MiniscriptThread(runtime, msg, _program, _references, this));
MiniscriptThread::runOnVThread(runtime->getVThread(), thread);
runtime->getVThread().pushCoroutine<MiniscriptThread::ResumeThreadCoroutine>(thread);
}
return kVThreadReturn;
@ -1855,18 +1856,36 @@ bool IfMessengerModifier::respondsToEvent(const Event &evt) const {
return _when.respondsTo(evt);
}
CORO_BEGIN_DEFINITION(IfMessengerModifier::RunEvaluateAndSendCoroutine)
struct Locals {
Common::WeakPtr<RuntimeObject> triggerSource;
DynamicValue incomingData;
bool isTrue = false;
Common::SharedPtr<MiniscriptThread> thread;
};
CORO_BEGIN_FUNCTION
// Is this the right place for this? Not sure if Miniscript can change incomingData
locals->triggerSource = params->msg->getSource();
locals->incomingData = params->msg->getValue();
locals->thread.reset(new MiniscriptThread(params->runtime, params->msg, params->self->_program, params->self->_references, params->self));
CORO_CALL(MiniscriptThread::ResumeThreadCoroutine, locals->thread);
CORO_IF (!locals->thread->evaluateTruthOfResult(locals->isTrue))
CORO_ERROR;
CORO_END_IF
CORO_IF(locals->isTrue)
CORO_AWAIT(params->self->_sendSpec.sendFromMessenger(params->runtime, params->self, locals->triggerSource.lock().get(), locals->incomingData, nullptr));
CORO_END_IF
CORO_END_FUNCTION
CORO_END_DEFINITION
VThreadState IfMessengerModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (_when.respondsTo(msg->getEvent())) {
Common::SharedPtr<MiniscriptThread> thread(new MiniscriptThread(runtime, msg, _program, _references, this));
EvaluateAndSendTaskData *evalAndSendData = runtime->getVThread().pushTask("IfMessengerModifier::evaluateAndSendTask", this, &IfMessengerModifier::evaluateAndSendTask);
evalAndSendData->thread = thread;
evalAndSendData->runtime = runtime;
evalAndSendData->incomingData = msg->getValue();
evalAndSendData->triggerSource = msg->getSource();
MiniscriptThread::runOnVThread(runtime->getVThread(), thread);
}
if (_when.respondsTo(msg->getEvent()))
runtime->getVThread().pushCoroutine<IfMessengerModifier::RunEvaluateAndSendCoroutine>(this, runtime, msg);
return kVThreadReturn;
}
@ -1895,20 +1914,6 @@ void IfMessengerModifier::visitInternalReferences(IStructuralReferenceVisitor *v
_references->visitInternalReferences(visitor);
}
VThreadState IfMessengerModifier::evaluateAndSendTask(const EvaluateAndSendTaskData &taskData) {
MiniscriptThread *thread = taskData.thread.get();
bool isTrue = false;
if (!thread->evaluateTruthOfResult(isTrue))
return kVThreadError;
if (isTrue)
_sendSpec.sendFromMessenger(taskData.runtime, this, taskData.triggerSource.lock().get(), taskData.incomingData, nullptr);
return kVThreadReturn;
}
TimerMessengerModifier::TimerMessengerModifier() : _milliseconds(0), _looping(false) {
}

View File

@ -707,13 +707,9 @@ public:
#endif
private:
struct EvaluateAndSendTaskData {
EvaluateAndSendTaskData() : runtime(nullptr) {}
Common::SharedPtr<MiniscriptThread> thread;
Common::WeakPtr<RuntimeObject> triggerSource;
Runtime *runtime;
DynamicValue incomingData;
struct RunEvaluateAndSendCoroutine {
CORO_DEFINE_RETURN_TYPE(void);
CORO_DEFINE_PARAMS_3(IfMessengerModifier *, self, Runtime *, runtime, Common::SharedPtr<MessageProperties>, msg);
};
Common::SharedPtr<Modifier> shallowClone() const override;
@ -721,8 +717,6 @@ private:
void linkInternalReferences(ObjectLinkingScope *scope) override;
void visitInternalReferences(IStructuralReferenceVisitor *visitor) override;
VThreadState evaluateAndSendTask(const EvaluateAndSendTaskData &taskData);
Event _when;
MessengerSendSpec _sendSpec;

View File

@ -4006,7 +4006,7 @@ MiniscriptInstructionOutcome Structural::scriptSetPaused(MiniscriptThread *threa
thread->getRuntime()->sendMessageOnVThread(dispatch);
}
return kMiniscriptInstructionOutcomeYieldToVThreadNoRetry;
return kMiniscriptInstructionOutcomeYieldToVThread;
}
MiniscriptInstructionOutcome Structural::scriptSetLoop(MiniscriptThread *thread, const DynamicValue &value) {