Files
scummvm-cursorfix/engines/m4/wscript/ws_machine.cpp
2026-02-02 04:50:13 +01:00

1288 lines
34 KiB
C++

/* 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 "m4/wscript/ws_machine.h"
#include "m4/wscript/ws_cruncher.h"
#include "m4/wscript/ws_hal.h"
#include "m4/wscript/wst_regs.h"
#include "m4/wscript/wscript.h"
#include "m4/core/errors.h"
#include "m4/core/imath.h"
#include "m4/dbg/debug.h"
#include "m4/mem/mem.h"
#include "m4/platform/timer.h"
#include "m4/vars.h"
#include "m4/detection.h"
namespace M4 {
#define COND_FLAG 0x80000000
#define OP_COUNT 0x00007fff
#define OP_JUMP 3
#define OP_KILL 4
static void clear_msg_list(machine *m);
static void clear_persistent_msg_list(machine *m);
bool ws_Initialize(frac16 *theGlobals) {
_GWS(machineIDCount) = 0;
_GWS(dataFormats) = ws_GetDataFormats();
if (!theGlobals) {
ws_LogErrorMsg(FL, "ws_Initialize() called without a valid global register array.");
return false;
}
_GWS(ws_globals) = theGlobals;
for (int32 i = 0; i < GLB_SHARED_VARS; i++) {
_GWS(ws_globals)[i] = 0;
}
_GWS(firstMachine) = nullptr;
_GWS(nextXM) = nullptr;
_GWS(myGlobalMessages) = nullptr;
if (!ws_InitWSTimer()) {
return false;
}
if (!ws_InitCruncher()) {
return false;
}
if (!ws_InitHAL()) {
return false;
}
_GWS(oldTime) = timer_read_60();
_GWS(pauseTime) = 0;
_GWS(enginesPaused) = false;
return true;
}
void ws_Shutdown() {
ws_KillTime();
ws_KillCruncher();
ws_KillMachines();
ws_KillHAL();
}
static void dispose_msgRequest(msgRequest *msg) {
if (msg)
mem_free(msg);
}
static void clear_msg_list(machine *m) {
msgRequest *nextMsg = m->myMsgs;
while (nextMsg) {
msgRequest *freeMsg = nextMsg;
nextMsg = nextMsg->nextMsg;
dispose_msgRequest(freeMsg);
}
m->myMsgs = nullptr;
}
static void clear_persistent_msg_list(machine *m) {
msgRequest *freeMsg;
// Clear the active persistent msgs
msgRequest *nextMsg = m->myPersistentMsgs;
while (nextMsg) {
freeMsg = nextMsg;
nextMsg = nextMsg->nextMsg;
dispose_msgRequest(freeMsg);
}
m->myPersistentMsgs = nullptr;
// Clear the used persistent msgs
nextMsg = m->usedPersistentMsgs;
while (nextMsg) {
freeMsg = nextMsg;
nextMsg = nextMsg->nextMsg;
dispose_msgRequest(freeMsg);
}
m->usedPersistentMsgs = nullptr;
}
static msgRequest *new_msgRequest() {
msgRequest *newMsg = (msgRequest *)mem_alloc(sizeof(msgRequest), "msgRequest");
if (newMsg == nullptr) {
ws_LogErrorMsg(FL, "Failed to mem_alloc() %d bytes.", sizeof(msgRequest));
}
return newMsg;
}
static void restore_persistent_msgs(machine *m) {
// Check params...
if ((!m) || (!m->usedPersistentMsgs)) {
return;
}
// Loop to find the last used persistent msg
msgRequest *lastMsg = m->usedPersistentMsgs;
while (lastMsg->nextMsg) {
lastMsg = lastMsg->nextMsg;
}
// Place the entire usedPersistentMsgs linked list at the front of the persistentMsgs list
lastMsg->nextMsg = m->myPersistentMsgs;
m->myPersistentMsgs = m->usedPersistentMsgs;
m->usedPersistentMsgs = nullptr;
}
// CONDITIONAL OPs
static void op_AFTER(machine *m, int32 *pcOffset) {
int32 myElapsedTime;
if (!_GWS(myArg2)) {
ws_Error(m, ERR_MACH, 0x0261, "functionality: after arg1 {...}");
}
if (_GWS(myArg3)) {
myElapsedTime = (int32)imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3)) >> 16;
} else {
myElapsedTime = (int32)(*_GWS(myArg2)) >> 16;
}
ws_MakeOnTimeReq(_GWS(ws_globals)[GLB_TIME] + myElapsedTime, m, *pcOffset, (int32)*_GWS(myArg1) >> 14);
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
static void op_ON_END_SEQ(machine *m, int32 *pcOffset) {
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0260, "on_seq_end() failed.");
}
ws_OnEndSeqRequest(m->myAnim8, *pcOffset, *_GWS(myArg1) >> 14);
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
/**
* Message Requests are stored in a linked list, directly in the machine
* A message is never actually received, but when another machine wishes to
* send a message to this one, it checks this list to see if the message is
* expected, and if this machine knows what to do.
*/
static void op_ON_MSG(machine *m, int32 *pcOffset) {
msgRequest *myMsg;
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0260, "on_msg() failed.");
}
if ((myMsg = new_msgRequest()) == nullptr) {
ws_Error(m, ERR_MACH, 0x02fe, "on_msg() failed.");
return;
}
if (_GWS(myArg2)) {
myMsg->msgHash = *_GWS(myArg2);
} else {
myMsg->msgHash = 0;
}
if (_GWS(myArg3)) {
myMsg->msgValue = *_GWS(myArg3);
} else {
myMsg->msgValue = 0;
}
myMsg->pcOffset = *pcOffset;
myMsg->pcCount = (int32)*_GWS(myArg1) >> 14;
myMsg->nextMsg = m->myMsgs;
m->myMsgs = myMsg;
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
/**
* Same as op_ON_MSG() except these messages do not get cleared between states
*/
static void op_ON_P_MSG(machine *m, int32 *pcOffset) {
frac16 msgValue;
if (!_GWS(myArg2)) {
ws_Error(m, ERR_MACH, 0x0261, "functionality: on_p_msg arg1 {...}");
}
// Get the values for msgHash and msgValue from the args...
uint32 msgHash = *_GWS(myArg2);
if (_GWS(myArg3)) {
msgValue = *_GWS(myArg3);
} else {
msgValue = 0;
}
// Since the message is persistent, it may have been satisfied earlier, check the used list
msgRequest *prevMsg = nullptr;
msgRequest *myMsg = m->usedPersistentMsgs;
// Loop through all the used msgs, see if there is already a struct in place
while (myMsg && ((myMsg->msgHash != msgHash) || (myMsg->msgValue != msgValue))) {
prevMsg = myMsg;
myMsg = myMsg->nextMsg;
}
// If a previous identical msg has already been requested, restore it
if (myMsg) {
// Remove it from the used msgs linked list
// if myMsg is first in the list
if (!prevMsg) {
m->usedPersistentMsgs = myMsg->nextMsg;
} else {
// Else myMsg is in the middle of the list (after prevMsg)
prevMsg->nextMsg = myMsg->nextMsg;
}
} else {
// Else a new msg has to be created
if ((myMsg = new_msgRequest()) == nullptr) {
ws_Error(m, ERR_MACH, 0x02fe, "on_p_msg() failed.");
return;
}
// Set the msg request values
myMsg->msgHash = msgHash;
myMsg->msgValue = msgValue;
}
// Since it may be a replacement msg, a new pcOffset may be set
myMsg->pcOffset = *pcOffset;
myMsg->pcCount = (int32)*_GWS(myArg1) >> 14;
// Link it into the list
myMsg->nextMsg = m->myPersistentMsgs;
m->myPersistentMsgs = myMsg;
// Update the pcOffset
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
static void op_SWITCH_LT(machine *m, int32 *pcOffset) {
if (!_GWS(myArg3)) {
ws_Error(m, ERR_MACH, 0x0262, "functionality: switch (arg1 < arg2) {...}");
}
if (*_GWS(myArg2) >= *_GWS(myArg3)) {
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
}
static void op_SWITCH_LE(machine *m, int32 *pcOffset) {
if (!_GWS(myArg3)) {
ws_Error(m, ERR_MACH, 0x0262, "functionality: switch (arg1 <= arg2) {...}");
}
if (*_GWS(myArg2) > *_GWS(myArg3)) {
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
}
static void op_SWITCH_EQ(machine *m, int32 *pcOffset) {
if (!_GWS(myArg3)) {
ws_Error(m, ERR_MACH, 0x0262, "functionality: switch (arg1 == arg2) {...}");
}
if (*_GWS(myArg2) != *_GWS(myArg3)) {
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
}
static void op_SWITCH_NE(machine *m, int32 *pcOffset) {
if (!_GWS(myArg3)) {
ws_Error(m, ERR_MACH, 0x0262, "functionality: switch (arg1 != arg2) {...}");
}
if (*_GWS(myArg2) == *_GWS(myArg3)) {
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
}
static void op_SWITCH_GE(machine *m, int32 *pcOffset) {
if (!_GWS(myArg3)) {
ws_Error(m, ERR_MACH, 0x0262, "functionality: switch (arg1 >= arg2) {...}");
}
if (*_GWS(myArg2) < *_GWS(myArg3)) {
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
}
static void op_SWITCH_GT(machine *m, int32 *pcOffset) {
if (!_GWS(myArg3)) {
ws_Error(m, ERR_MACH, 0x0262, "functionality: switch (arg1 > arg2) {...}");
}
if (*_GWS(myArg2) <= *_GWS(myArg3)) {
*pcOffset += (int32)*_GWS(myArg1) >> 14;
}
}
// IMMEDIATE OPs
static bool op_DO_NOTHING(machine *m, int32 *pcOffset) {
return true;
}
static bool op_GOTO(machine *m, int32 *pcOffset) {
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "functionality: goto arg1");
}
m->curState = (*_GWS(myArg1)) >> 16;
m->recurseLevel = 0;
return false;
}
static bool op_JUMP(machine *m, int32 *pcOffset) {
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "functionality: jump arg1");
}
*pcOffset += (int32)*_GWS(myArg1) >> 16;
return true;
}
static bool op_TERMINATE(machine *m, int32 *pcOffset) {
m->curState = -1;
m->recurseLevel = 0;
return false;
}
static bool op_START_SEQ(machine *m, int32 *pcOffset) {
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "functionality: start_seq arg1");
}
// Here we check whether a program was previously running
if (!m->myAnim8) {
if ((m->myAnim8 = ws_AddAnim8ToCruncher(m, *_GWS(myArg1) >> 16)) == nullptr) {
ws_Error(m, ERR_MACH, 0x02ff, "start_seq() failed.");
}
} else {
if (!ws_ChangeAnim8Program(m, *_GWS(myArg1) >> 16)) {
ws_Error(m, ERR_MACH, 0x02ff, "start_seq() failed.");
}
}
// Inform the ws debugger of the new sequence
dbg_LaunchSequence(m->myAnim8);
return true;
}
static bool op_PAUSE_SEQ(machine *m, int32 *pcOffset) {
ws_PauseAnim8(m->myAnim8);
return true;
}
static bool op_STORE_VAL(machine *m, int32 *pcOffset) {
if (!_GWS(myArg2)) {
ws_Error(m, ERR_MACH, 0x0264, "functionality: arg1 = arg2 or arg1 = rand(arg2, arg3)");
}
if (_GWS(myArg3)) {
*_GWS(myArg1) = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
} else {
*_GWS(myArg1) = *_GWS(myArg2);
}
return true;
}
static bool op_SEND_MSG(machine *m, int32 *pcOffset) {
frac16 msgValue;
if (!_GWS(myArg2)) {
ws_Error(m, ERR_MACH, 0x0264, "functionality: send to machine arg1, message arg2");
}
if (_GWS(myArg3)) {
msgValue = *_GWS(myArg3);
} else {
msgValue = 0;
}
sendWSMessage(*_GWS(myArg2), msgValue, nullptr, *_GWS(myArg1) >> 16, m, 1);
return true;
}
static bool op_SEND_GMSG(machine *m, int32 *pcOffset) {
frac16 msgValue;
if (!_GWS(myArg2)) {
ws_Error(m, ERR_MACH, 0x0264, "functionality: send to to all machines of type arg1, message arg2");
}
if (_GWS(myArg3)) {
msgValue = *_GWS(myArg3);
} else {
msgValue = 0;
}
sendWSMessage(*_GWS(myArg2), msgValue, nullptr, *_GWS(myArg1) >> 16, m, 0);
return true;
}
static bool op_REPLY_MSG(machine *m, int32 *pcOffset) {
frac16 msgValue;
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "functionality: reply to sender with msg arg1");
}
if (_GWS(myArg2)) {
msgValue = *_GWS(myArg2);
} else {
msgValue = 0;
}
sendWSMessage(*_GWS(myArg1), msgValue, m->msgReplyXM, 0, m, 1);
return true;
}
static bool op_SYSTEM_MSG(machine *m, int32 *pcOffset) {
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "functionality: send to 'C' callback function with msg arg1");
}
if (m->CintrMsg) {
(m->CintrMsg)(*_GWS(myArg1), m);
}
return true;
}
static bool op_TRIG(machine *m, int32 *pcOffset) {
int32 myCount;
char tempStr[80];
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "functionality: trigger mach arg1, arg2 instances");
}
if (_GWS(myArg2)) {
if (_GWS(myArg3)) {
myCount = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3)) >> 16;
} else {
myCount = (*_GWS(myArg2)) >> 16;
}
} else {
myCount = 1;
}
Common::sprintf_s(tempStr, "*** TRIGGERED BY MACHINE: %d", m->myHash);
for (int32 i = 0; i < myCount; i++) {
if (!TriggerMachineByHash(*_GWS(myArg1) >> 16, m->myAnim8, -1, -1, m->CintrMsg, false, tempStr)) {
ws_Error(m, ERR_MACH, 0x0267, "trig() failed");
}
}
return true;
}
static bool op_TRIG_W(machine *m, int32 *pcOffset) {
int32 myCount = 0, minCount = 0, maxCount = 0, i, myInstruction;
int32 myIndex, minIndex, maxIndex;
bool randFlag = false;
char tempStr[80];
uint32 *myPC;
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "functionality: trigger mach arg1, arg2 instances");
}
const int32 myHash = (*_GWS(myArg1)) >> 16;
if (_GWS(myArg2)) {
if (_GWS(myArg3)) {
randFlag = true;
minCount = (*_GWS(myArg2)) >> 16;
maxCount = (*_GWS(myArg3)) >> 16;
} else {
myCount = (*_GWS(myArg2)) >> 16;
}
} else {
myCount = 1;
}
// This is a double length instruction - up to 128 bits, we must read in the next pCode
uint32 *machInstr = (uint32 *)((intptr)(*(m->machHandle)) + (uint32)m->machInstrOffset);
myPC = (uint32 *)((intptr)machInstr + *pcOffset);
uint32 *oldPC = myPC;
dbg_SetCurrMachInstr(m, *pcOffset, false);
if ((myInstruction = ws_PreProcessPcode(&myPC, m->myAnim8)) < 0) {
ws_Error(m, ERR_MACH, 0x0266, "trig_w() failed.");
}
dbg_EndCurrMachInstr();
// Now find the new pcOffset
*pcOffset += (byte *)myPC - (byte *)oldPC;
if (!_GWS(myArg1)) {
ws_Error(m, ERR_MACH, 0x0263, "trig_w instruction requires a data hash specified by a second pCode.");
}
const int32 myDataHash = (int32)(*_GWS(myArg1)) >> 16;
const int32 myDataCount = ws_GetDATACount(myDataHash);
if (_GWS(myArg2)) {
if (_GWS(myArg3)) {
minIndex = (*_GWS(myArg2)) >> 16;
maxIndex = (*_GWS(myArg3)) >> 16;
} else {
minIndex = (*_GWS(myArg2)) >> 16;
maxIndex = (*_GWS(myArg2)) >> 16;
}
} else {
minIndex = 0;
maxIndex = myDataCount;
}
if (myInstruction) {
for (myIndex = minIndex; myIndex <= maxIndex; myIndex++) {
if (randFlag) {
myCount = imath_ranged_rand(minCount, maxCount);
}
for (i = 0; i < myCount; i++) {
Common::sprintf_s(tempStr, "*** TRIGGERED BY MACHINE: %d", m->myHash);
if (!TriggerMachineByHash(myHash, m->myAnim8, myDataHash, myIndex, m->CintrMsg, false, tempStr)) {
ws_Error(m, ERR_MACH, 0x0267, "trig_w() failed");
}
}
}
} else {
myIndex = imath_ranged_rand(minIndex, maxIndex);
if (randFlag) {
myCount = imath_ranged_rand(minCount, maxCount);
}
for (i = 0; i < myCount; i++) {
Common::sprintf_s(tempStr, "*** TRIGGERED BY MACHINE: %d", m->myHash);
if (!TriggerMachineByHash(myHash, m->myAnim8, myDataHash, myIndex, m->CintrMsg, false, tempStr)) {
ws_Error(m, ERR_MACH, 0x0267, "trig_w() failed");
}
}
}
return true;
}
static bool op_CLEAR_REGS(machine *m, int32 *pcOffset) {
if (!m->myAnim8) {
ws_Error(m, ERR_INTERNAL, 0x02f3, "clear_regs() failed.");
}
Anim8 *myAnim8 = m->myAnim8;
for (int32 i = 0; i < IDX_COUNT + myAnim8->numLocalVars; i++) {
myAnim8->myRegs[i] = 0;
}
myAnim8->myRegs[IDX_S] = 0x10000;
return true;
}
static bool op_RESUME_SEQ(machine *m, int32 *pcOffset) {
if (!m->myAnim8) {
ws_Error(m, ERR_INTERNAL, 0x02f3, "resume_seq() failed.");
}
ws_ResumeAnim8(m->myAnim8);
return true;
}
bool (*immOpTable[])(machine *m, int32 *pcOffset) = {
nullptr, //0 ***END
&op_DO_NOTHING, //1
&op_GOTO, //2
&op_JUMP, //3 don't forget the op_jump #define
&op_TERMINATE, //4
&op_START_SEQ, //5
&op_PAUSE_SEQ, //6
&op_RESUME_SEQ, //7
&op_STORE_VAL, //8
&op_SEND_MSG, //9
&op_SEND_GMSG, //10
&op_REPLY_MSG, //11
&op_SYSTEM_MSG, //12
&op_TRIG, //13
&op_TRIG_W, //14
&op_CLEAR_REGS //15
};
void (*condOpTable[])(machine *m, int32 *pcOffset) = {
&op_AFTER, //0
&op_ON_END_SEQ, //1
&op_ON_MSG, //2
&op_ON_P_MSG, //3
&op_SWITCH_LT, //4
&op_SWITCH_LE, //5
&op_SWITCH_EQ, //6
&op_SWITCH_NE, //7
&op_SWITCH_GE, //8
&op_SWITCH_GT //9
};
void pauseEngines(void) {
_GWS(enginesPaused) = true;
}
void unpauseEngines(void) {
_GWS(enginesPaused) = false;
}
void addPauseTime(int32 myTime) {
_GWS(pauseTime) += myTime;
}
void cycleEngines(Buffer *cleanBackground, int16 *depth_table, Buffer *screenCodes,
uint8 *myPalette, uint8 *ICT, bool updateVideo) {
dbg_DebugNextCycle();
const int32 clockTime = timer_read_60();
if (_GWS(enginesPaused)) {
_GWS(pauseTime) += clockTime - _GWS(oldTime);
_GWS(oldTime) = clockTime;
} else {
_GWS(ws_globals)[GLB_WATCH_DOG] = clockTime - _GWS(pauseTime) - _GWS(ws_globals)[GLB_TIME];
_GWS(ws_globals)[GLB_TIME] += _GWS(ws_globals)[GLB_WATCH_DOG];
ws_CrunchAnim8s(depth_table);
if (cleanBackground) {
ws_DoDisplay(cleanBackground, depth_table, screenCodes, myPalette, ICT, updateVideo);
}
ws_CrunchEOSreqs();
ws_CheckTimeReqs(_GWS(ws_globals)[GLB_TIME]);
}
}
void ws_RefreshWoodscriptBuffer(Buffer *cleanBackground, int16 *depth_table,
Buffer *screenCodes, uint8 *myPalette, uint8 *ICT) {
ws_hal_RefreshWoodscriptBuffer(_GWS(myCruncher), cleanBackground, depth_table,
screenCodes, myPalette, ICT);
}
static void cancelAllEngineReqs(machine *m) {
if (m->machID == DEAD_MACHINE_ID) {
return;
}
//---- CANCEL CRUNCHER REQS
if (m->myAnim8) {
ws_CancelOnEndSeq(m->myAnim8);
}
//---- Free all pending state message requests in this machine
clear_msg_list(m);
//---- Restore all persistent message requests in this machine
restore_persistent_msgs(m);
//---- Free all pending global messages requests in this machine
if (_GWS(myGlobalMessages)) {
globalMsgReq *myGMsg = _GWS(myGlobalMessages);
while (myGMsg->next) {
if (myGMsg->next->sendM == m) {
globalMsgReq *tempGMsg = myGMsg->next;
myGMsg->next = myGMsg->next->next;
mem_free((void *)tempGMsg);
} else myGMsg = myGMsg->next;
}
}
//---- CANCEL TIMER REQS
ws_CancelOnTimeReqs(m);
}
static void shutdownMachine(machine *m) {
if (m->machID == DEAD_MACHINE_ID) {
return;
}
dbg_RemoveWSMach(m);
if (m->myAnim8) {
ws_RemoveAnim8FromCruncher(m->myAnim8);
}
//---- Free all pending message requests in this machine
clear_msg_list(m);
clear_persistent_msg_list(m);
// Fix nextXM so SendWSMessage doesn't break
if (m == _GWS(nextXM)) {
_GWS(nextXM) = _GWS(nextXM)->next;
}
// Clear any existing walk path
DisposePath(m->walkPath);
m->machID = DEAD_MACHINE_ID;
if (m->machName) {
m->machName[0] = '\0';
mem_free((void *)m->machName);
m->machName = nullptr;
}
}
static machine *getValidNext(machine *currMachine) {
machine *iterMachine = currMachine;
if (iterMachine) {
while ((iterMachine = iterMachine->next) != nullptr) {
if (iterMachine->machID != DEAD_MACHINE_ID) {
return iterMachine;
}
}
}
return nullptr;
}
void terminateMachinesByHash(uint32 machHash) {
machine *curr = _GWS(firstMachine); // Start at beginning of machine chain
while (curr) {
machine *next = curr->next; // Preserve next pointer against curr's dealloc
if (curr->myHash == machHash) { // is this one to delete?
if (curr == _GWS(firstMachine)) { // maintain the beginning of machine chain
_GWS(firstMachine) = next;
}
cancelAllEngineReqs(curr); // cancel its requests
shutdownMachine(curr); // deallocate the whole ball'o'wax
}
curr = next; // and pop aint32 the chain
}
}
void terminateMachine(machine *myMachine) {
if ((!myMachine) || (!verifyMachineExists(myMachine))) {
return;
}
cancelAllEngineReqs(myMachine);
shutdownMachine(myMachine);
}
void terminateMachineAndNull(machine *&m) {
if (m)
terminateMachine(m);
m = nullptr;
}
bool verifyMachineExists(machine *m) {
// Parameter verification
if (!m) {
return false;
}
// Loop through the active machine list, looking for m
machine *tempM = _GWS(firstMachine);
while (tempM && (tempM != m)) {
tempM = getValidNext(tempM);
}
// If the end of the list was reached, and m was not found, false
if (!tempM) {
return false;
}
// Otherwise m was found, therefore machine exists
return true;
}
int32 ws_KillMachines() {
int32 myBytes = 0;
// Deallocate all machines
machine *myMachine = _GWS(firstMachine);
while (myMachine) {
// get any next Machine here, not validNext
_GWS(firstMachine) = _GWS(firstMachine)->next;
if (myMachine->machID != DEAD_MACHINE_ID) {
cancelAllEngineReqs(myMachine);
shutdownMachine(myMachine);
}
mem_free((void *)myMachine);
myBytes += sizeof(machine);
myMachine = _GWS(firstMachine);
}
// Deallocate global messages
globalMsgReq *tempGlobalMsg = _GWS(myGlobalMessages);
while (tempGlobalMsg) {
_GWS(myGlobalMessages) = _GWS(myGlobalMessages)->next;
mem_free((void *)tempGlobalMsg);
tempGlobalMsg = _GWS(myGlobalMessages);
}
return myBytes;
}
void ws_KillDeadMachines() {
machine *myMachine;
machine **priorNext = &_GWS(firstMachine);
// Deallocate all machines that are dead
while ((myMachine = *priorNext) != nullptr) {
if (myMachine->machID == DEAD_MACHINE_ID) {
// Shutdown the dead machine, and unlink it from the machine chain
*priorNext = myMachine->next;
mem_free(myMachine);
} else {
// Valid machine, skip over
priorNext = &myMachine->next;
}
}
}
// This is the proc designed to evaluate the instructions of the state machine
static int32 StepAt(int32 *pcOffset, machine *m) {
int32 myInstruction;
uint32 *myPC;
const uint32 machID = m->machID;
Anim8 *myAnim8 = m->myAnim8;
// Find the current PC and process it to get the current instruction
uint32 *machInstr = (uint32 *)((intptr)(*(m->machHandle)) + m->machInstrOffset);
myPC = (uint32 *)((intptr)(machInstr) + *pcOffset);
uint32 *oldPC = myPC;
_GWS(pcOffsetOld) = *pcOffset;
dbg_SetCurrMachInstr(m, *pcOffset, false);
if ((myInstruction = ws_PreProcessPcode(&myPC, myAnim8)) < 0) {
ws_Error(m, ERR_MACH, 0x0266, nullptr);
}
dbg_EndCurrMachInstr();
// Now find the new pcOffset
*pcOffset += (byte *)myPC - (byte *)oldPC;
if (myInstruction >= 64) {
if (myInstruction >= 74)
error("Unexpected instruction %d", myInstruction);
condOpTable[myInstruction - 64](m, pcOffset);
} else if (myInstruction > 0) {
if (myInstruction > 15)
error("Unexpected instruction %d", myInstruction);
const bool keepProcessing = immOpTable[myInstruction](m, pcOffset);
if (!keepProcessing) {
// Does the machine still exist
if (m->machID == machID) {
cancelAllEngineReqs(m);
if (m->curState == -1) {
shutdownMachine(m);
} else { // If machine hasn't terminated
IntoTheState(m); // recurse to kickstart next state
}
}
}
}
return myInstruction;
}
void ws_StepWhile(machine *m, int32 pcOffset, int32 pcCount) {
// We are executing machine instructions after a conditional has been satisfied.
// Mark where we started
const int32 oldPC = pcOffset;
// Increment and remember the recurseLevel and the machine ID
m->recurseLevel++;
const uint32 recurseLevel = m->recurseLevel;
const uint32 machID = m->machID;
// Execute instructions until the conditional count has been reached.
int32 myInstruction = -1;
while (myInstruction && (myInstruction != OP_KILL) &&
(pcOffset >= oldPC) && (pcOffset - oldPC < pcCount) &&
(m->machID == machID) && (m->recurseLevel == recurseLevel)) {
myInstruction = StepAt(&pcOffset, m);
}
// The last instruction might have been a JUMP instruction. This should be
// a JUMP to reissue the conditional, Therefore, reexecute the conditional.
if (myInstruction == OP_JUMP) {
StepAt(&pcOffset, m);
}
// If the above loop executed without being modified (ie terminated) by a call to StepAt()
if (myInstruction != OP_KILL) {
if ((m->machID == machID) && (m->recurseLevel == recurseLevel)) {
m->recurseLevel--;
}
}
}
// When a state machine enters a new state, every request and command is
// evaluated immediately.
void IntoTheState(machine *m) {
if ((m->curState >= m->numOfStates) || (m->curState < 0)) {
ws_Error(m, ERR_INTERNAL, 0x2f2, "IntoTheState() failed.");
}
uint32 *stateTable = (uint32 *)((intptr)(*(m->machHandle)) + (intptr)m->stateTableOffset);
int32 pcOffset = FROM_LE_32(stateTable[m->curState]);
// Increment and remember the recurseLevel and the machine ID
m->recurseLevel++;
const uint32 recurseLevel = m->recurseLevel;
const uint32 machID = m->machID;
// Execute all instruction until an instruction (ie. OP_END) signals execution to stop
// by returning 0, or something has reset the recurseLevel (ie. op_GOTO)
int32 myInstruction = -1;
while (myInstruction && (myInstruction != OP_KILL) &&
((m->machID == machID) && (m->recurseLevel == recurseLevel))) {
myInstruction = StepAt(&pcOffset, m);
}
if (myInstruction != OP_KILL) {
// If the above loop executed without being modified (ie terminated) by a call to StepAt()
if ((m->machID == machID) && (m->recurseLevel == recurseLevel)) {
// Decrement the recurse counter
m->recurseLevel--;
}
}
}
// This proc creates an instance of a machine based on the machine chunk
machine *TriggerMachineByHash(int32 myHash, Anim8 *parentAnim8, int32 dataHash, int32 dataRow, MessageCB CintrMsg, bool debug, const char *machName) {
machine *m = (machine *)mem_alloc(sizeof(machine), "machine");
if (m == nullptr) {
ws_LogErrorMsg(FL, "Out of memory - mem requested: %d.", sizeof(machine));
ws_LogErrorMsg(FL, "Trying to trigger hash: %d, name: %s", myHash, machName);
ws_Error(m, ERR_INTERNAL, 0x2fe, "TriggerMachineByHash() failed.");
return nullptr;
}
// Initialize the identification fields
_GWS(machineIDCount)++;
if (_GWS(machineIDCount) == DEAD_MACHINE_ID) {
_GWS(machineIDCount)++;
}
m->myHash = myHash;
m->machID = _GWS(machineIDCount);
m->machName = mem_strdup(machName);
if ((m->machHandle = ws_GetMACH(myHash, &m->numOfStates, &m->stateTableOffset, &m->machInstrOffset)) == nullptr) {
ws_LogErrorMsg(FL, "Trying to trigger hash: %d, name: %s", myHash, machName);
return nullptr;
}
// Get the data handle and offset if requested
if (dataHash >= 0) {
m->dataHash = dataHash;
if ((m->dataHandle = ws_GetDATA(dataHash, (uint32)dataRow, &m->dataOffset)) == nullptr) {
ws_LogErrorMsg(FL, "Trying to trigger hash: %d, name: %s", myHash, machName);
return nullptr;
}
} else {
m->dataHash = -1;
m->dataHandle = nullptr;
}
// Insert m into the list...
m->next = _GWS(firstMachine);
m->prev = nullptr;
if (_GWS(firstMachine)) {
_GWS(firstMachine)->prev = m;
}
_GWS(firstMachine) = m;
m->recurseLevel = 0;
m->curState = 0;
m->myAnim8 = nullptr;
m->parentAnim8 = parentAnim8;
m->targetCount = 0;
m->msgReplyXM = nullptr;
m->CintrMsg = CintrMsg;
m->myMsgs = nullptr;
m->myPersistentMsgs = nullptr;
m->usedPersistentMsgs = nullptr;
m->walkPath = nullptr;
dbg_DebugWSMach(m, debug);
IntoTheState(m);
return m;
}
machine *TriggerMachineByHash(MessageCB intrMsg, const char *machName) {
return TriggerMachineByHash(1, 1, 0, 0, 0, 0, 0, 0, 100, 0x400, false,
intrMsg, machName);
}
machine *TriggerMachineByHash(int32 val1, int32 val2, int32 val3, int32 val4, int32 val5, int32 val6,
int32 x, int32 y, int32 scale, int32 layer, bool flag,
MessageCB intrMsg, const char *machName) {
_G(globals)[GLB_TEMP_1] = val1 << 24;
_G(globals)[GLB_TEMP_2] = val2 << 16;
_G(globals)[GLB_TEMP_3] = val3 << 16;
_G(globals)[GLB_TEMP_4] = val4 << 16;
_G(globals)[GLB_TEMP_5] = (val5 << 16) / 100;
_G(globals)[GLB_TEMP_6] = val6 << 16;
_G(globals)[GLB_TEMP_7] = x << 16;
_G(globals)[GLB_TEMP_8] = y << 16;
_G(globals)[GLB_TEMP_9] = (scale << 16) / 100;
_G(globals)[GLB_TEMP_10] = layer << 16;
_G(globals)[GLB_TEMP_11] = (flag ? (int)-1 : 1) << 16;
return TriggerMachineByHash(40, nullptr, -1, -1, intrMsg, false, machName);
}
enum {
REGULAR_MSG = 0,
PERSISTENT_MSG
};
static bool SearchMsgList(uint32 msgHash, uint32 msgValue, machine *recvM, int32 whichList, machine *sendM) {
msgRequest *myMsg = nullptr;
// Initialize search vars
bool found = false;
msgRequest *prevMsg = nullptr;
// Find the first msg, based on which list is to be searched
switch (whichList) {
// Regular messages
case REGULAR_MSG:
myMsg = recvM->myMsgs;
break;
// Persistent msgs
case PERSISTENT_MSG:
myMsg = recvM->myPersistentMsgs;
break;
default:
break;
}
// Search through the message list
while (myMsg && (!found)) {
// Check if we've found the msg we're looking for
if ((myMsg->msgHash == msgHash) && ((uint32)myMsg->msgValue == msgValue)) {
// Set found bool
found = true;
// Find out where to begin executing from
const int32 pcOffset = myMsg->pcOffset;
const int32 pcCount = myMsg->pcCount;
// Remove the msg from the list, based on which list
switch (whichList) {
// Regular messages
case REGULAR_MSG:
// If myMsg was first in the list
if (!prevMsg) {
recvM->myMsgs = myMsg->nextMsg;
} else {
// Else it was in the middle
prevMsg->nextMsg = myMsg->nextMsg;
}
// Dispose of the message
dispose_msgRequest(myMsg);
break;
// Persistent messages
case PERSISTENT_MSG:
// If myMsg was first in the list
if (!prevMsg) {
recvM->myPersistentMsgs = myMsg->nextMsg;
} else {
// Else it was in the middle
prevMsg->nextMsg = myMsg->nextMsg;
}
// Move the message to the inactive list
myMsg->nextMsg = recvM->usedPersistentMsgs;
recvM->usedPersistentMsgs = myMsg;
break;
default:
break;
}
// Set up so the recv machine can reply to this message
recvM->msgReplyXM = sendM;
// Service the request
ws_StepWhile(recvM, pcOffset, pcCount);
} else {
// Else check the next message
prevMsg = myMsg;
myMsg = myMsg->nextMsg;
}
}
// Return whether a message was found or not
return found;
}
// This proc is what allows a machine to send a message to another machine(s)
void sendWSMessage(uint32 msgHash, frac16 msgValue, machine *recvM,
uint32 machHash, machine *sendM, int32 msgCount) {
bool sendToAll;
debugC(1, kDebugMessages, "Message %xh, %" PRIxPTR "h, %s, %xh, %s, %d",
msgHash, msgValue, recvM ? recvM->machName : "NONE",
machHash, sendM ? sendM->machName : "NONE", msgCount);
// In this case we are sending to a specific machine: recvM
if (recvM) {
// Search first the regular message list, and if it was not found
if (!SearchMsgList(msgHash, msgValue, recvM, REGULAR_MSG, sendM)) {
// Search the persistent message list
SearchMsgList(msgHash, msgValue, recvM, PERSISTENT_MSG, sendM);
}
// and return
return;
}
// Otherwise...
// Not sending to a specific machine, so send to <msgCount> machines with the given hash
// Prepare a global message structure
globalMsgReq *tempGlobalMsg = (globalMsgReq *)mem_alloc(sizeof(globalMsgReq), "globalMsgReq");
if (tempGlobalMsg == nullptr) {
ws_LogErrorMsg(FL, "Out of memory - mem requested: %d.", sizeof(machine));
ws_Error(nullptr, ERR_INTERNAL, 0x2fe, "SendWSMessage() failed.");
}
tempGlobalMsg->msgHash = msgHash;
tempGlobalMsg->msgValue = msgValue;
tempGlobalMsg->machHash = machHash;
tempGlobalMsg->sendM = sendM;
tempGlobalMsg->msgCount = msgCount;
tempGlobalMsg->next = nullptr;
// If we are in the middle of a "global send message", queue the request and exit
// Question: is this a re-entrancy check?
// Answer: not really. If a machine sends out a "global send message", then we
// to completely process the "global send message" before any other
// "global send message" statements are executed. Suppose machine:A
// accepted two different messages. Machine:B sends out message:1
// Machine:C also receives message:1 sent by machine:B, and sends out
// message:2. Since machine:C received message:1 before machine:A, now
// both message:1 and message:2 have been sent. Which does machine:A
// respond to? Queueing message:2 until message:1 has been completely
// processed ensures predictability. ie. In this case, machine:A
// will respond to message:1 before any machine responds to message:2.
//
// Check to see if we are already in the middle of processing global messages
if (_GWS(myGlobalMessages)) {
// Find the end of the global list
globalMsgReq *myGlobalMsgs = _GWS(myGlobalMessages);
while (myGlobalMsgs->next) {
myGlobalMsgs = myGlobalMsgs->next;
}
// myGlobalMsgs is the last element, now tempGlobalMsg is.
myGlobalMsgs->next = tempGlobalMsg;
// Since we are already processing a global message, this one is now queued, and we return
return;
}
// We are not currently processing another global message, therefore put this on the queue
// To prevent future global requests from processing until this request is serviced
_GWS(myGlobalMessages) = tempGlobalMsg;
// Loop through and service all global requests.
while (_GWS(myGlobalMessages)) {
// Sending to all machines, or just a bunch of them?
int32 myCount = _GWS(myGlobalMessages)->msgCount;
if (myCount <= 0) {
sendToAll = true;
} else {
sendToAll = false;
}
// Search machine list
bool more_to_send = true;
machine *currMachine = _GWS(firstMachine);
while (currMachine && more_to_send) {
// Set nextXM up in case this machine is deleted during the ws_StepWhile
// nextXM will be maintained by ShutDownMachine()
_GWS(nextXM) = getValidNext(currMachine);
// Have we got a machine of the specified hash
if (currMachine->myHash == _GWS(myGlobalMessages)->machHash) {
// Search the machines regular list.
bool found = SearchMsgList(msgHash, msgValue, currMachine, REGULAR_MSG, sendM);
// If the message wasn't found in the regular list, search the persistent list
if (!found) {
found = SearchMsgList(msgHash, msgValue, currMachine, PERSISTENT_MSG, sendM);
}
// Check to see if found
if (found) {
myCount--;
if ((!sendToAll) && (myCount <= 0)) {
more_to_send = false;
}
}
}
currMachine = _GWS(nextXM);
}
// Note: ws_StepWhile could have added more messages to the proceedings
// Discard a global message and queue up the next one:
tempGlobalMsg = _GWS(myGlobalMessages);
_GWS(myGlobalMessages) = _GWS(myGlobalMessages)->next;
mem_free(tempGlobalMsg);
}
}
void sendWSMessage(int32 val1, machine *recv, int32 series1, int32 val3, int32 val4,
int32 trigger, int32 series2, int32 val6, int32 val7, int32 val8) {
if (!trigger)
trigger = -1;
_G(globals)[GLB_TEMP_1] = val1 << 16;
_G(globals)[GLB_TEMP_2] = series1 << 24;
_G(globals)[GLB_TEMP_3] = val3 << 16;
_G(globals)[GLB_TEMP_4] = val4 << 16;
_G(globals)[GLB_TEMP_5] = kernel_trigger_create(trigger);
_G(globals)[GLB_TEMP_6] = val6 << 16;
_G(globals)[GLB_TEMP_7] = val7 << 16;
_G(globals)[GLB_TEMP_8] = val8 << 16;
_G(globals)[GLB_TEMP_9] = series2 << 24;
sendWSMessage(0x10000, 0, recv, 0, nullptr, 1);
}
} // End of namespace M4