/* 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 . * */ #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 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