Initial commit
This commit is contained in:
351
engines/alcachofa/scheduler.cpp
Normal file
351
engines/alcachofa/scheduler.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user