Files
scummvm-cursorfix/engines/ultima/ultima8/kernel/kernel.cpp
2026-02-02 04:50:13 +01:00

501 lines
13 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 "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/id_man.h"
#include "ultima/ultima8/misc/set.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
Kernel *Kernel::_kernel = nullptr;
const uint32 Kernel::TICKS_PER_FRAME = 2;
const uint32 Kernel::TICKS_PER_SECOND = 60;
const uint32 Kernel::FRAMES_PER_SECOND = Kernel::TICKS_PER_SECOND / Kernel::TICKS_PER_FRAME;
// A special proc type which means "all"
const uint16 Kernel::PROC_TYPE_ALL = 6;
// The same as above, but for Crusader.
// Used in Usecode functions to translate.
static const uint16 CRU_PROC_TYPE_ALL = 0xc;
Kernel::Kernel() : _loading(false), _tickNum(0), _paused(0),
_runningProcess(nullptr), _frameByFrame(false) {
debug(1, "Creating Kernel...");
_kernel = this;
_pIDs = new idMan(1, 32766, 128);
_currentProcess = _processes.end();
}
Kernel::~Kernel() {
reset();
debug(1, "Destroying Kernel...");
_kernel = nullptr;
delete _pIDs;
}
void Kernel::reset() {
debug(1, "Resetting Kernel...");
for (auto *p : _processes) {
if (p->_flags & Process::PROC_TERM_DISPOSE && p != _runningProcess) {
delete p;
} else {
p->_flags |= Process::PROC_TERMINATED;
}
}
_processes.clear();
_currentProcess = _processes.end();
_pIDs->clearAll();
_paused = 0;
_runningProcess = nullptr;
// if we're in frame-by-frame mode, reset to a _paused state
if (_frameByFrame) _paused = 1;
}
ProcId Kernel::assignPID(Process *proc) {
// to prevent new processes from getting a PID while loading
if (_loading) return 0xFFFF;
// Get a pID
proc->_pid = _pIDs->getNewID();
return proc->_pid;
}
ProcId Kernel::addProcess(Process *proc, bool dispose) {
#if 0
for (const auto *p : _processes) {
if (p == proc)
return 0;
}
#endif
assert(proc->_pid != 0 && proc->_pid != 0xFFFF);
#if 0
debug(1, "[Kernel] Adding process %p, pid = %u type %s",
proc, proc->_pid, proc->GetClassType()._className);
#endif
if (dispose) {
proc->_flags |= Process::PROC_TERM_DISPOSE;
}
setNextProcess(proc);
return proc->_pid;
}
ProcId Kernel::addProcessExec(Process *proc, bool dispose) {
#if 0
for (const auto *p : _processes) {
if (p == proc)
return 0;
}
#endif
assert(proc->_pid != 0 && proc->_pid != 0xFFFF);
#if 0
debug(1, "[Kernel] Adding process %p, pid = %u type %s",
proc, proc->_pid, proc->GetClassType()._className);
#endif
if (dispose) {
proc->_flags |= Process::PROC_TERM_DISPOSE;
}
_processes.push_back(proc);
proc->_flags |= Process::PROC_ACTIVE;
Process *oldrunning = _runningProcess;
_runningProcess = proc;
proc->run();
_runningProcess = oldrunning;
return proc->_pid;
}
void Kernel::runProcesses() {
if (!_paused)
_tickNum++;
if (_processes.size() == 0) {
warning("Process queue is empty?! Aborting.");
return;
}
int num_run = 0;
_currentProcess = _processes.begin();
while (_currentProcess != _processes.end()) {
Process *p = *_currentProcess;
if (!_paused && ((p->_flags & (Process::PROC_TERMINATED |
Process::PROC_TERM_DEFERRED))
== Process::PROC_TERM_DEFERRED)) {
p->terminate();
}
if (!(p->is_terminated() || p->is_suspended()) &&
(!_paused || (p->_flags & Process::PROC_RUNPAUSED)) &&
(_paused || _tickNum % p->getTicksPerRun() == 0)) {
_runningProcess = p;
p->run();
_runningProcess = nullptr;
num_run++;
//
// WORKAROUND:
// In Crusader: No Remorse, the HOVER near the end of Mission 3
// (Floor 1) gets stuck in a tight loop after moving to the
// destination (path egg frame 0).
//
// Something is probably not right about the switch trigger, but until
// we can work out what it is avoid the game totally hanging at this
// point.
//
// If this threshold is set too low, it can cause issues with U8 map
// transitions (eg, bug #12913). If it's too high, Crusader locks up
// for a really long time at this point. Set it high enough that
// a process going through all map items should still terminate.
//
if (((num_run > 8192 && GAME_IS_CRUSADER) || num_run > 65534)
&& !p->is_terminated()) {
warning("Seem to be stuck in process loop - killing current process");
p->fail();
}
if (_currentProcess == _processes.end()) {
// If this happens then the list was reset so delete the process and return.
if (p->_flags & Process::PROC_TERM_DISPOSE) {
delete p;
}
return;
}
}
if (!_paused && (p->_flags & Process::PROC_TERMINATED)) {
// process is killed, so remove it from the list
_currentProcess = _processes.erase(_currentProcess);
// Clear pid
_pIDs->clearID(p->_pid);
if (p->_flags & Process::PROC_TERM_DISPOSE) {
delete p;
}
} else if (!_paused && (p->_flags & Process::PROC_TERM_DEFERRED) && GAME_IS_CRUSADER) {
//
// In Crusader, move term deferred processes to the end to clean up after
// others have run. This gets the right speed on ELEVAT (which should
// execute one movement per tick)
//
// In U8, frame-count comparison for Devon turning at the start shows this
// *shouldn't* be used, and the process should be cleaned up next tick.
//
_processes.push_back(p);
_currentProcess = _processes.erase(_currentProcess);
} else {
++_currentProcess;
}
}
if (!_paused && _frameByFrame) pause();
}
void Kernel::setNextProcess(Process *proc) {
if (_currentProcess != _processes.end() && *_currentProcess == proc) return;
if (proc->_flags & Process::PROC_ACTIVE) {
for (ProcessIterator it = _processes.begin();
it != _processes.end(); ++it) {
if (*it == proc) {
_processes.erase(it);
break;
}
}
} else {
proc->_flags |= Process::PROC_ACTIVE;
}
if (_currentProcess == _processes.end()) {
// Not currently running processes, add to the start of the next run.
_processes.push_front(proc);
} else {
ProcessIterator t = _currentProcess;
++t;
_processes.insert(t, proc);
}
}
Process *Kernel::getProcess(ProcId pid) {
for (auto *p : _processes) {
if (p->_pid == pid)
return p;
}
return nullptr;
}
void Kernel::kernelStats() {
g_debugger->debugPrintf("Kernel memory stats:\n");
g_debugger->debugPrintf("Processes : %u/32765\n", _processes.size());
}
void Kernel::processTypes() {
g_debugger->debugPrintf("Current process types:\n");
Common::HashMap<Common::String, unsigned int> processtypes;
for (const auto *p : _processes) {
processtypes[p->GetClassType()._className]++;
}
for (const auto &i : processtypes) {
g_debugger->debugPrintf("%s: %u\n", i._key.c_str(), i._value);
}
}
uint32 Kernel::getNumProcesses(ObjId objid, uint16 processtype) {
uint32 count = 0;
for (const auto *p : _processes) {
// Don't count us, we are not really here
if (p->is_terminated()) continue;
if ((objid == 0 || objid == p->_itemNum) &&
(processtype == PROC_TYPE_ALL || processtype == p->_type))
count++;
}
return count;
}
Process *Kernel::findProcess(ObjId objid, uint16 processtype) {
for (auto *p : _processes) {
// Don't count us, we are not really here
if (p->is_terminated()) continue;
if ((objid == 0 || objid == p->_itemNum) &&
(processtype == PROC_TYPE_ALL || processtype == p->_type)) {
return p;
}
}
return nullptr;
}
void Kernel::killProcesses(ObjId objid, uint16 processtype, bool fail) {
for (auto *p : _processes) {
if (p->_itemNum != 0 && (objid == 0 || objid == p->_itemNum) &&
(processtype == PROC_TYPE_ALL || processtype == p->_type) &&
!(p->_flags & Process::PROC_TERMINATED) &&
!(p->_flags & Process::PROC_TERM_DEFERRED)) {
if (fail)
p->fail();
else
p->terminate();
}
}
}
void Kernel::killProcessesNotOfType(ObjId objid, uint16 processtype, bool fail) {
for (auto *p : _processes) {
// * If objid is 0, terminate procs for all objects.
// * Never terminate procs with objid 0
if (p->_itemNum != 0 && (objid == 0 || objid == p->_itemNum) &&
(p->_type != processtype) &&
!(p->_flags & Process::PROC_TERMINATED) &&
!(p->_flags & Process::PROC_TERM_DEFERRED)) {
if (fail)
p->fail();
else
p->terminate();
}
}
}
void Kernel::killAllProcessesNotOfTypeExcludeCurrent(uint16 processtype, bool fail) {
Common::HashMap<ProcId, bool> procsToSave;
Common::Array<ProcId> queue;
// Create a list of all the processes and their waiting parents that we can't kill.
if (_runningProcess) {
queue.push_back(_runningProcess->_pid);
while (!queue.empty()) {
ProcId procToSave = queue.back();
queue.pop_back();
if (!procsToSave.contains(procToSave)) {
Process *p = getProcess(procToSave);
if (p) {
procsToSave[procToSave] = true;
queue.push_back(p->_waiting);
}
}
}
}
for (auto *p : _processes) {
// Don't kill the running process
if (procsToSave.contains(p->_pid))
continue;
if ((p->_type != processtype) &&
!(p->_flags & Process::PROC_TERMINATED) &&
!(p->_flags & Process::PROC_TERM_DEFERRED)) {
if (fail)
p->fail();
else
p->terminate();
}
}
}
bool Kernel::canSave() {
for (const auto *p : _processes) {
if (!p->is_terminated() && p->_flags & Process::PROC_PREVENT_SAVE) {
return false;
}
}
return true;
}
void Kernel::save(Common::WriteStream *ws) {
ws->writeUint32LE(_tickNum);
_pIDs->save(ws);
ws->writeUint32LE(_processes.size());
for (auto *p : _processes) {
const Std::string & classname = p->GetClassType()._className; // virtual
assert(classname.size());
Common::HashMap<Common::String, ProcessLoadFunc>::iterator iter;
iter = _processLoaders.find(classname);
if (iter == _processLoaders.end()) {
error("Process class cannot save without registered loader: %s", classname.c_str());
}
ws->writeUint16LE(classname.size());
ws->write(classname.c_str(), classname.size());
p->saveData(ws);
}
}
bool Kernel::load(Common::ReadStream *rs, uint32 version) {
_tickNum = rs->readUint32LE();
if (!_pIDs->load(rs, version)) return false;
const uint32 pcount = rs->readUint32LE();
for (unsigned int i = 0; i < pcount; ++i) {
Process *p = loadProcess(rs, version);
if (!p) return false;
_processes.push_back(p);
}
// Integrity check for processes
Set<ProcId> procs;
for (const auto *p : _processes) {
if (!_pIDs->isIDUsed(p->getPid())) {
warning("Process id %d exists but not marked used. Corrupt save?", p->getPid());
return false;
}
if (procs.find(p->getPid()) != procs.end()) {
warning("Duplicate process id %d in processes. Corrupt save?", p->getPid());
return false;
}
procs.insert(p->getPid());
if (!p->validateWaiters()) {
return false;
}
if (p->getTicksPerRun() > 100) {
warning("Improbable value for ticks per run %d in process id %d . Corrupt save?", p->getTicksPerRun(), p->getPid());
return false;
}
if (p->getType() > 0x1000) {
warning("Improbable value for proctype %x in process id %d . Corrupt save?", p->getType(), p->getPid());
return false;
}
}
return true;
}
Process *Kernel::loadProcess(Common::ReadStream *rs, uint32 version) {
const uint16 classlen = rs->readUint16LE();
assert(classlen > 0);
char *buf = new char[classlen + 1];
rs->read(buf, classlen);
buf[classlen] = 0;
Std::string classname = buf;
delete[] buf;
Common::HashMap<Common::String, ProcessLoadFunc>::iterator iter;
iter = _processLoaders.find(classname);
if (iter == _processLoaders.end()) {
warning("Unknown Process class: %s", classname.c_str());
return nullptr;
}
_loading = true;
Process *p = (*(iter->_value))(rs, version);
_loading = false;
return p;
}
uint32 Kernel::I_getNumProcesses(const uint8 *args, unsigned int /*argsize*/) {
ARG_OBJID(item);
ARG_UINT16(type);
if (GAME_IS_CRUSADER && type == CRU_PROC_TYPE_ALL)
type = PROC_TYPE_ALL;
return Kernel::get_instance()->getNumProcesses(item, type);
}
uint32 Kernel::I_resetRef(const uint8 *args, unsigned int /*argsize*/) {
ARG_OBJID(item);
ARG_UINT16(type);
if (GAME_IS_CRUSADER && type == CRU_PROC_TYPE_ALL)
type = PROC_TYPE_ALL;
Kernel::get_instance()->killProcesses(item, type, true);
return 0;
}
} // End of namespace Ultima8
} // End of namespace Ultima