352 lines
9.8 KiB
C++
352 lines
9.8 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 "alcachofa/scheduler.h"
|
|
|
|
#include "common/system.h"
|
|
#include "alcachofa/alcachofa.h"
|
|
#include "alcachofa/menu.h"
|
|
|
|
using namespace Common;
|
|
|
|
namespace Alcachofa {
|
|
|
|
TaskReturn::TaskReturn() {
|
|
_type = TaskReturnType::Yield;
|
|
_returnValue = 0;
|
|
_taskToWaitFor = nullptr;
|
|
}
|
|
|
|
TaskReturn TaskReturn::finish(int32 returnValue) {
|
|
TaskReturn r;
|
|
r._type = TaskReturnType::Finished;
|
|
r._returnValue = returnValue;
|
|
return r;
|
|
}
|
|
|
|
TaskReturn TaskReturn::waitFor(Task *task) {
|
|
assert(task != nullptr);
|
|
TaskReturn r;
|
|
r._type = TaskReturnType::Waiting;
|
|
r._taskToWaitFor = task;
|
|
return r;
|
|
}
|
|
|
|
Task::Task(Process &process) : _process(process) {}
|
|
|
|
Task *Task::delay(uint32 millis) {
|
|
return new DelayTask(process(), millis);
|
|
}
|
|
|
|
void Task::syncGame(Serializer &s) {
|
|
s.syncAsUint32LE(_stage);
|
|
}
|
|
|
|
void Task::syncObjectAsString(Serializer &s, ObjectBase *&object, bool optional) const {
|
|
String objectName, roomName;
|
|
if (object != nullptr) {
|
|
roomName = object->room()->name();
|
|
objectName = object->name();
|
|
}
|
|
s.syncString(roomName);
|
|
s.syncString(objectName);
|
|
if (s.isSaving())
|
|
return;
|
|
Room *room = g_engine->world().getRoomByName(roomName.c_str());
|
|
object = room == nullptr ? nullptr : room->getObjectByName(objectName.c_str());
|
|
if (object == nullptr) // main characters are not linked by the room they are in
|
|
object = g_engine->world().globalRoom().getObjectByName(objectName.c_str());
|
|
if (object == nullptr && !optional)
|
|
error("Invalid object name \"%s\" in room \"%s\" in savestate for task %s",
|
|
objectName.c_str(), roomName.c_str(), taskName());
|
|
}
|
|
|
|
void Task::errorForUnexpectedObjectType(const ObjectBase *base) const {
|
|
// Implemented as separate function in order to access ObjectBase methods
|
|
error("Unexpected type of object %s in savestate for task %s (got a %s)",
|
|
base->name().c_str(), taskName(), base->typeName());
|
|
}
|
|
|
|
Process::Process(ProcessId pid, MainCharacterKind characterKind)
|
|
: _pid(pid)
|
|
, _character(characterKind)
|
|
, _name("Unnamed process") {}
|
|
|
|
Process::Process(Serializer &s)
|
|
: _pid(0)
|
|
, _character(MainCharacterKind::None) {
|
|
syncGame(s);
|
|
}
|
|
|
|
Process::~Process() {
|
|
while (!_tasks.empty())
|
|
delete _tasks.pop();
|
|
}
|
|
|
|
bool Process::isActiveForPlayer() const {
|
|
return _character == MainCharacterKind::None || _character == g_engine->player().activeCharacterKind();
|
|
}
|
|
|
|
TaskReturnType Process::run() {
|
|
while (!_tasks.empty()) {
|
|
TaskReturn ret = _tasks.top()->run();
|
|
switch (ret.type()) {
|
|
case TaskReturnType::Yield:
|
|
return TaskReturnType::Yield;
|
|
case TaskReturnType::Waiting:
|
|
_tasks.push(ret.taskToWaitFor());
|
|
break;
|
|
case TaskReturnType::Finished:
|
|
_lastReturnValue = ret.returnValue();
|
|
delete _tasks.pop();
|
|
break;
|
|
default:
|
|
assert(false && "Invalid task return type");
|
|
return TaskReturnType::Finished;
|
|
}
|
|
}
|
|
return TaskReturnType::Finished;
|
|
}
|
|
|
|
void Process::debugPrint() {
|
|
auto *debugger = g_engine->getDebugger();
|
|
const char *characterName;
|
|
switch (_character) {
|
|
case MainCharacterKind::None:
|
|
characterName = " <none>";
|
|
break;
|
|
case MainCharacterKind::Filemon:
|
|
characterName = " Filemon";
|
|
break;
|
|
case MainCharacterKind::Mortadelo:
|
|
characterName = "Mortadelo";
|
|
break;
|
|
default:
|
|
characterName = "<invalid>";
|
|
break;
|
|
}
|
|
debugger->debugPrintf("pid: %3u char: %s ret: %2d \"%s\"\n", _pid, characterName, _lastReturnValue, _name.c_str());
|
|
|
|
for (uint i = 0; i < _tasks.size(); i++) {
|
|
debugger->debugPrintf(" %u: ", i);
|
|
_tasks[i]->debugPrint();
|
|
}
|
|
}
|
|
|
|
#define DEFINE_TASK(TaskName) \
|
|
extern Task *constructTask_##TaskName(Process &process, Serializer &s);
|
|
#include "alcachofa/tasks.h"
|
|
|
|
static Task *readTask(Process &process, Serializer &s) {
|
|
assert(s.isLoading());
|
|
String taskName;
|
|
s.syncString(taskName);
|
|
|
|
#define DEFINE_TASK(TaskName) \
|
|
if (taskName == #TaskName) \
|
|
return constructTask_##TaskName(process, s);
|
|
#include "alcachofa/tasks.h"
|
|
|
|
error("Invalid task type in savestate: %s", taskName.c_str());
|
|
}
|
|
|
|
void Process::syncGame(Serializer &s) {
|
|
s.syncAsUint32LE(_pid);
|
|
syncEnum(s, _character);
|
|
s.syncString(_name);
|
|
s.syncAsSint32LE(_lastReturnValue);
|
|
|
|
uint count = _tasks.size();
|
|
s.syncAsUint32LE(count);
|
|
if (s.isLoading()) {
|
|
assert(_tasks.empty());
|
|
for (uint i = 0; i < count; i++)
|
|
_tasks.push(readTask(*this, s));
|
|
} else {
|
|
String taskName;
|
|
for (uint i = 0; i < count; i++) {
|
|
taskName = _tasks[i]->taskName();
|
|
s.syncString(taskName);
|
|
_tasks[i]->syncGame(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void killProcessesForIn(MainCharacterKind characterKind, Array<Process *> &processes, uint firstIndex) {
|
|
assert(firstIndex <= processes.size());
|
|
for (uint i = 0; i < processes.size() - firstIndex; i++) {
|
|
Process **process = &processes[processes.size() - 1 - i];
|
|
if ((*process)->character() == characterKind || characterKind == MainCharacterKind::None) {
|
|
delete *process;
|
|
processes.erase(process);
|
|
i--; // underflow is fine here
|
|
}
|
|
}
|
|
}
|
|
|
|
Scheduler::~Scheduler() {
|
|
killAllProcesses();
|
|
killProcessesForIn(MainCharacterKind::None, _backupProcesses, 0);
|
|
}
|
|
|
|
Process *Scheduler::createProcessInternal(MainCharacterKind character) {
|
|
Process *process = new Process(_nextPid++, character);
|
|
processesToRunNext().push_back(process);
|
|
return process;
|
|
}
|
|
|
|
void Scheduler::run() {
|
|
assert(processesToRun().empty()); // otherwise we somehow left normal flow
|
|
_currentArrayI = (_currentArrayI + 1) % 2;
|
|
// processesToRun() can be modified during loop so do not replace with iterators
|
|
for (_currentProcessI = 0; _currentProcessI < processesToRun().size(); _currentProcessI++) {
|
|
Process *process = processesToRun()[_currentProcessI];
|
|
auto ret = process->run();
|
|
if (ret == TaskReturnType::Finished)
|
|
delete process;
|
|
else
|
|
processesToRunNext().push_back(process);
|
|
}
|
|
processesToRun().clear();
|
|
_currentProcessI = UINT_MAX;
|
|
}
|
|
|
|
void Scheduler::backupContext() {
|
|
assert(processesToRun().empty());
|
|
_backupProcesses.push_back(processesToRunNext());
|
|
processesToRunNext().clear();
|
|
}
|
|
|
|
void Scheduler::restoreContext() {
|
|
assert(processesToRun().empty());
|
|
processesToRunNext().push_back(_backupProcesses);
|
|
_backupProcesses.clear();
|
|
}
|
|
|
|
void Scheduler::killAllProcesses() {
|
|
killProcessesForIn(MainCharacterKind::None, _processArrays[0], 0);
|
|
killProcessesForIn(MainCharacterKind::None, _processArrays[1], 0);
|
|
}
|
|
|
|
void Scheduler::killAllProcessesFor(MainCharacterKind characterKind) {
|
|
// this method can be called during run() so be careful
|
|
killProcessesForIn(characterKind, processesToRunNext(), 0);
|
|
killProcessesForIn(characterKind, processesToRun(), _currentProcessI == UINT_MAX ? 0 : _currentProcessI + 1);
|
|
}
|
|
|
|
static Process **getProcessByName(Array<Process *> &_processes, const String &name) {
|
|
for (auto &process : _processes) {
|
|
if (process->name() == name)
|
|
return &process;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Scheduler::killProcessByName(const String &name) {
|
|
Process **process = getProcessByName(processesToRunNext(), name);
|
|
if (process != nullptr) {
|
|
delete *process;
|
|
processesToRunNext().erase(process);
|
|
}
|
|
}
|
|
|
|
bool Scheduler::hasProcessWithName(const String &name) {
|
|
assert(processesToRun().empty());
|
|
return getProcessByName(processesToRunNext(), name) != nullptr;
|
|
}
|
|
|
|
void Scheduler::debugPrint() {
|
|
auto &console = g_engine->console();
|
|
bool didPrintSomething = false;
|
|
|
|
if (!processesToRun().empty()) {
|
|
console.debugPrintf("Currently running processes:\n");
|
|
for (uint32 i = 0; i < processesToRun().size(); i++) {
|
|
if (_currentProcessI == UINT_MAX || i > _currentProcessI)
|
|
console.debugPrintf(" ");
|
|
else if (i < _currentProcessI)
|
|
console.debugPrintf("# ");
|
|
else
|
|
console.debugPrintf("> ");
|
|
processesToRun()[i]->debugPrint();
|
|
}
|
|
didPrintSomething = true;
|
|
}
|
|
|
|
if (!processesToRunNext().empty()) {
|
|
if (didPrintSomething)
|
|
console.debugPrintf("\n");
|
|
console.debugPrintf("Scheduled processes:\n");
|
|
for (auto *process : processesToRunNext()) {
|
|
console.debugPrintf(" ");
|
|
process->debugPrint();
|
|
}
|
|
didPrintSomething = true;
|
|
}
|
|
|
|
if (!_backupProcesses.empty()) {
|
|
if (didPrintSomething)
|
|
console.debugPrintf("\n");
|
|
console.debugPrintf("Backed up processes:\n");
|
|
for (auto *process : _backupProcesses) {
|
|
console.debugPrintf(" ");
|
|
process->debugPrint();
|
|
}
|
|
didPrintSomething = true;
|
|
}
|
|
|
|
if (!didPrintSomething)
|
|
console.debugPrintf("No processes running or backed up\n");
|
|
}
|
|
|
|
void Scheduler::prepareSyncGame(Serializer &s) {
|
|
if (s.isLoading()) {
|
|
killAllProcesses();
|
|
killProcessesForIn(MainCharacterKind::None, _backupProcesses, 0);
|
|
}
|
|
}
|
|
|
|
void Scheduler::syncGame(Serializer &s) {
|
|
assert(_currentProcessI == UINT_MAX); // let's not sync during ::run
|
|
assert(s.isSaving() || _backupProcesses.empty());
|
|
|
|
Common::Array<Process *> *processes = s.isSaving() && g_engine->menu().isOpen()
|
|
? &_backupProcesses
|
|
: &processesToRunNext();
|
|
|
|
// we only sync the backupProcesses as these are the ones pertaining to the gameplay
|
|
// the other arrays would be used for the menu
|
|
|
|
s.syncAsUint32LE(_nextPid);
|
|
uint32 count = processes->size();
|
|
s.syncAsUint32LE(count);
|
|
if (s.isLoading()) {
|
|
processes->reserve(count);
|
|
for (uint32 i = 0; i < count; i++)
|
|
processes->push_back(new Process(s));
|
|
} else {
|
|
for (Process *process : *processes)
|
|
process->syncGame(s);
|
|
}
|
|
}
|
|
|
|
}
|