refactor: Execute delayed actions through the ActionQueue

This commit is contained in:
Vsevolod Kremianskii 2020-10-11 18:16:48 +07:00
parent 33bd87fe2d
commit e820e0a175
12 changed files with 83 additions and 95 deletions

View file

@ -17,8 +17,6 @@
#include "action.h"
using namespace std;
namespace reone {
namespace game {

View file

@ -43,8 +43,8 @@ ActionExecutor::ActionExecutor(Area *area) : _area(area) {
}
}
void ActionExecutor::executeActions(Creature &creature, float dt) {
ActionQueue &actionQueue = creature.actionQueue();
void ActionExecutor::executeActions(Object &object, float dt) {
ActionQueue &actionQueue = object.actionQueue();
Action *action = actionQueue.currentAction();
if (!action) return;
@ -52,22 +52,22 @@ void ActionExecutor::executeActions(Creature &creature, float dt) {
ActionType type = action->type();
switch (type) {
case ActionType::MoveToPoint:
executeMoveToPoint(creature, *dynamic_cast<MoveToPointAction *>(action), dt);
executeMoveToPoint(static_cast<Creature &>(object), *dynamic_cast<MoveToPointAction *>(action), dt);
break;
case ActionType::MoveToObject:
executeMoveToObject(creature, *dynamic_cast<MoveToObjectAction *>(action), dt);
executeMoveToObject(static_cast<Creature &>(object), *dynamic_cast<MoveToObjectAction *>(action), dt);
break;
case ActionType::Follow:
executeFollow(creature, *dynamic_cast<FollowAction *>(action), dt);
executeFollow(static_cast<Creature &>(object), *dynamic_cast<FollowAction *>(action), dt);
break;
case ActionType::DoCommand:
executeDoCommand(creature, *dynamic_cast<CommandAction *>(action), dt);
executeDoCommand(object, *dynamic_cast<CommandAction *>(action), dt);
break;
case ActionType::StartConversation:
executeStartConversation(creature, *dynamic_cast<StartConversationAction *>(action), dt);
executeStartConversation(static_cast<Creature &>(object), *dynamic_cast<StartConversationAction *>(action), dt);
break;
default:
warn("Area: action not implemented: " + to_string(static_cast<int>(type)));
warn("ActionExecutor: action not implemented: " + to_string(static_cast<int>(type)));
action->isCompleted();
break;
}
@ -101,9 +101,9 @@ void ActionExecutor::executeFollow(Creature &creature, FollowAction &action, flo
navigateCreature(creature, dest, distance, dt);
}
void ActionExecutor::executeDoCommand(Creature &creature, CommandAction &action, float dt) {
void ActionExecutor::executeDoCommand(Object &object, CommandAction &action, float dt) {
ExecutionContext ctx(action.context());
ctx.callerId = creature.id();
ctx.callerId = object.id();
ScriptExecution(ctx.savedState->program, move(ctx)).run();
action.complete();

View file

@ -36,7 +36,7 @@ class ActionExecutor {
public:
ActionExecutor(Area *area);
void executeActions(Creature &creature, float dt);
void executeActions(Object &object, float dt);
private:
Area *_area { nullptr };
@ -54,7 +54,7 @@ private:
void executeMoveToPoint(Creature &creature, MoveToPointAction &action, float dt);
void executeMoveToObject(Creature &creature, MoveToObjectAction &action, float dt);
void executeFollow(Creature &creature, FollowAction &action, float dt);
void executeDoCommand(Creature &creature, CommandAction &command, float dt);
void executeDoCommand(Object &object, CommandAction &command, float dt);
void executeStartConversation(Creature &creature, StartConversationAction &action, float dt);
// END Actions

View file

@ -17,6 +17,10 @@
#include "actionqueue.h"
#include <algorithm>
#include "SDL2/SDL_timer.h"
using namespace std;
namespace reone {
@ -29,11 +33,23 @@ void ActionQueue::clear() {
}
}
void ActionQueue::push(unique_ptr<Action> action) {
void ActionQueue::add(unique_ptr<Action> action) {
_actions.push(move(action));
}
void ActionQueue::delay(unique_ptr<Action> action, float seconds) {
DelayedAction delayed;
delayed.action = move(action);
delayed.timestamp = SDL_GetTicks() + static_cast<int>(1000.0f * seconds);
_delayed.push_back(move(delayed));
}
void ActionQueue::update() {
removeCompletedActions();
updateDelayedActions();
}
void ActionQueue::removeCompletedActions() {
while (true) {
const Action *action = currentAction();
if (!action || !action->isCompleted()) return;
@ -42,6 +58,22 @@ void ActionQueue::update() {
}
}
void ActionQueue::updateDelayedActions() {
uint32_t now = SDL_GetTicks();
for (auto &delayed : _delayed) {
if (now >= delayed.timestamp) {
_actions.push(move(delayed.action));
}
}
auto delayedToRemove = remove_if(
_delayed.begin(),
_delayed.end(),
[&now](const DelayedAction &delayed) { return now >= delayed.timestamp; });
_delayed.erase(delayedToRemove, _delayed.end());
}
Action *ActionQueue::currentAction() {
return _actions.empty() ? nullptr : _actions.front().get();
}

View file

@ -29,13 +29,23 @@ namespace game {
class ActionQueue {
public:
void clear();
void push(std::unique_ptr<Action> action);
void add(std::unique_ptr<Action> action);
void delay(std::unique_ptr<Action> action, float seconds);
void update();
Action *currentAction();
private:
struct DelayedAction {
std::unique_ptr<Action> action;
uint32_t timestamp { 0 };
};
std::queue<std::unique_ptr<Action>> _actions;
std::vector<DelayedAction> _delayed;
void removeCompletedActions();
void updateDelayedActions();
};
} // namespace game

View file

@ -43,12 +43,6 @@ MultiplayerArea::MultiplayerArea(
_scriptsEnabled = mode == MultiplayerMode::Server;
}
void MultiplayerArea::updateCreature(Creature &creature, float dt) {
if (static_cast<MultiplayerCreature &>(creature).isControlled()) return;
Area::updateCreature(creature, dt);
}
void MultiplayerArea::execute(const Command &cmd) {
switch (cmd.type()) {
case CommandType::LoadCreature:

View file

@ -44,8 +44,6 @@ public:
private:
IMultiplayerCallbacks *_callbacks { nullptr };
void updateCreature(Creature &creature, float dt) override;
void executeLoadCreature(const Command &cmd);
void executeSetPlayerRole(const Command &cmd);
void executeSetObjectTransform(const Command &cmd);

View file

@ -351,7 +351,7 @@ void Area::loadParty(const PartyConfiguration &party, const glm::vec3 &position,
_partyMember1 = partyMember;
unique_ptr<FollowAction> action(new FollowAction(_partyLeader, kPartyMemberFollowDistance));
partyMember->actionQueue().push(move(action));
partyMember->actionQueue().add(move(action));
}
if (party.memberCount > 2) {
shared_ptr<Creature> partyMember(makeCharacter(party.member2, kPartyMember2Tag, position, heading));
@ -361,7 +361,7 @@ void Area::loadParty(const PartyConfiguration &party, const glm::vec3 &position,
_partyMember2 = partyMember;
unique_ptr<FollowAction> action(new FollowAction(_partyLeader, kPartyMemberFollowDistance));
partyMember->actionQueue().push(move(action));
partyMember->actionQueue().add(move(action));
}
}
@ -422,47 +422,20 @@ bool Area::getElevationAt(const glm::vec2 &position, Room *&room, float &z) cons
}
void Area::update(const UpdateContext &updateCtx) {
updateDelayedCommands();
Object::update(updateCtx);
_actionExecutor.executeActions(*this, updateCtx.deltaTime);
for (auto &creature : _objectsByType[ObjectType::Creature]) {
_actionExecutor.executeActions(static_cast<Creature &>(*creature), updateCtx.deltaTime);
}
for (auto &room : _rooms) {
room.second->update(updateCtx.deltaTime);
}
for (auto &object : _objects) {
object->update(updateCtx);
_actionExecutor.executeActions(*object, updateCtx.deltaTime);
}
_objectSelector.update();
_sceneGraph->prepare();
}
void Area::updateDelayedCommands() {
uint32_t now = SDL_GetTicks();
for (auto &command : _delayed) {
if (now >= command.timestamp) {
shared_ptr<ScriptProgram> program(command.context.savedState->program);
debug(boost::format("Area: run delayed: %s %08x") % program->name() % command.context.savedState->insOffset);
ScriptExecution(program, command.context).run();
command.executed = true;
}
}
auto it = remove_if(
_delayed.begin(),
_delayed.end(),
[](const DelayedCommand &command) { return command.executed; });
_delayed.erase(it, _delayed.end());
}
void Area::updateCreature(Creature &creature, float dt) {
_actionExecutor.executeActions(creature, dt);
}
bool Area::moveCreatureTowards(Creature &creature, const glm::vec2 &dest, bool run, float dt) {
glm::vec3 position(creature.position());
glm::vec2 delta(dest - glm::vec2(position));
@ -512,13 +485,6 @@ void Area::updateTriggers(const Creature &creature) {
}
}
void Area::delayCommand(uint32_t timestamp, const ExecutionContext &ctx) {
DelayedCommand action;
action.timestamp = timestamp;
action.context = ctx;
_delayed.push_back(action);
}
int Area::eventUserDefined(int eventNumber) {
int id = _eventCounter++;

View file

@ -79,7 +79,6 @@ public:
Camera *getCamera() const;
void startDialog(Creature &creature, const std::string &resRef);
void delayCommand(uint32_t timestamp, const script::ExecutionContext &ctx);
int eventUserDefined(int eventNumber);
void signalEvent(int eventId);
@ -137,7 +136,6 @@ protected:
virtual void add(const std::shared_ptr<SpatialObject> &object);
void determineObjectRoom(SpatialObject &object);
void landObject(SpatialObject &object);
virtual void updateCreature(Creature &creature, float dt);
private:
enum class ScriptType {
@ -147,12 +145,6 @@ private:
OnUserDefined
};
struct DelayedCommand {
uint32_t timestamp { 0 };
script::ExecutionContext context;
bool executed { false };
};
struct UserDefinedEvent {
int eventNumber { 0 };
};
@ -169,7 +161,6 @@ private:
CameraStyle _cameraStyle;
std::string _music;
std::unordered_map<ScriptType, std::string> _scripts;
std::list<DelayedCommand> _delayed;
std::map<int, UserDefinedEvent> _events;
int _eventCounter { 0 };
@ -179,7 +170,6 @@ private:
std::function<void(CameraType)> _onCameraChanged;
std::shared_ptr<Creature> makeCharacter(const CreatureConfiguration &character, const std::string &tag, const glm::vec3 &position, float heading);
void updateDelayedCommands();
bool getElevationAt(const glm::vec2 &position, Room *&room, float &z) const;
void addPartyMemberPortrait(const std::shared_ptr<SpatialObject> &object, GuiContext &ctx);
void addDebugInfo(const UpdateContext &updateCtx, GuiContext &guiCtx);

View file

@ -72,35 +72,34 @@ void RoutineManager::add(
}
const Routine &RoutineManager::get(int index) {
assert(index >= 0 && index < _routines.size());
return _routines[index];
}
shared_ptr<Object> RoutineManager::getObjectById(uint32_t id, const ExecutionContext &ctx) const {
uint32_t finalId = 0;
uint32_t objectId = 0;
switch (id) {
case kObjectSelf:
finalId = ctx.callerId;
objectId = ctx.callerId;
break;
case kObjectInvalid:
warn("Routine: invalid object id: " + to_string(id));
return nullptr;
default:
finalId = id;
objectId = id;
break;
}
shared_ptr<Module> module(_game->module());
if (finalId == module->id()) {
if (module->id() == objectId) {
return module;
}
shared_ptr<Area> area(module->area());
if (finalId == area->id()) {
if (area->id() == objectId) {
return area;
}
return area->find(finalId);
return area->find(objectId);
}
} // namespace game

View file

@ -177,18 +177,19 @@ Variable RoutineManager::setLocalNumber(const vector<Variable> &args, ExecutionC
}
Variable RoutineManager::delayCommand(const vector<Variable> &args, ExecutionContext &ctx) {
uint32_t timestamp = SDL_GetTicks() + static_cast<int>(args[0].floatValue * 1000.0f);
_game->module()->area()->delayCommand(timestamp, args[1].context);
unique_ptr<CommandAction> action(new CommandAction(args[1].context));
shared_ptr<Object> object(getObjectById(ctx.callerId, ctx));
object->actionQueue().delay(move(action), args[0].floatValue);
return Variable();
}
Variable RoutineManager::assignCommand(const vector<Variable> &args, ExecutionContext &ctx) {
ExecutionContext newCtx(args[1].context);
newCtx.callerId = args[0].objectId;
newCtx.triggererId = kObjectInvalid;
unique_ptr<CommandAction> action(new CommandAction(args[1].context));
_game->module()->area()->delayCommand(SDL_GetTicks(), move(newCtx));
shared_ptr<Object> object(getObjectById(args[0].objectId, ctx));
object->actionQueue().add(move(action));
return Variable();
}
@ -224,7 +225,7 @@ Variable RoutineManager::actionDoCommand(const vector<Variable> &args, Execution
Creature *creature = dynamic_cast<Creature *>(subject.get());
if (creature) {
unique_ptr<CommandAction> action(new CommandAction(args[0].context));
creature->actionQueue().push(move(action));
creature->actionQueue().add(move(action));
}
return Variable();
@ -240,7 +241,7 @@ Variable RoutineManager::actionMoveToObject(const vector<Variable> &args, Execut
if (subject) {
Creature &creature = static_cast<Creature &>(*subject);
unique_ptr<MoveToObjectAction> action(new MoveToObjectAction(object, distance));
creature.actionQueue().push(move(action));
creature.actionQueue().add(move(action));
} else {
warn("Routine: object not found: " + to_string(objectId));
}
@ -258,7 +259,7 @@ Variable RoutineManager::actionStartConversation(const vector<Variable> &args, E
if (creature) {
string dialogResRef((args.size() >= 2 && !args[1].strValue.empty()) ? args[1].strValue : creature->conversation());
unique_ptr<StartConversationAction> action(new StartConversationAction(object, dialogResRef));
creature->actionQueue().push(move(action));
creature->actionQueue().add(move(action));
} else {
warn("Routine: creature not found: " + to_string(ctx.callerId));
}
@ -272,7 +273,7 @@ Variable RoutineManager::actionPauseConversation(const vector<Variable> &args, E
if (creature) {
unique_ptr<Action> action(new Action(ActionType::PauseConversation));
creature->actionQueue().push(move(action));
creature->actionQueue().add(move(action));
} else {
warn("Routine: creature not found: " + to_string(ctx.callerId));
}
@ -286,7 +287,7 @@ Variable RoutineManager::actionResumeConversation(const vector<Variable> &args,
if (creature) {
unique_ptr<Action> action(new Action(ActionType::ResumeConversation));
creature->actionQueue().push(move(action));
creature->actionQueue().add(move(action));
} else {
warn("Routine: creature not found: " + to_string(ctx.callerId));
}
@ -303,7 +304,7 @@ Variable RoutineManager::actionOpenDoor(const vector<Variable> &args, ExecutionC
Creature *creature = dynamic_cast<Creature *>(subject.get());
if (creature) {
unique_ptr<ObjectAction> action(new ObjectAction(ActionType::OpenDoor, object));
creature->actionQueue().push(move(action));
creature->actionQueue().add(move(action));
}
Door *door = dynamic_cast<Door *>(subject.get());
if (door) {
@ -325,7 +326,7 @@ Variable RoutineManager::actionCloseDoor(const vector<Variable> &args, Execution
Creature *creature = dynamic_cast<Creature *>(subject.get());
if (creature) {
unique_ptr<ObjectAction> action(new ObjectAction(ActionType::CloseDoor, object));
creature->actionQueue().push(move(action));
creature->actionQueue().add(move(action));
}
Door *door = dynamic_cast<Door *>(subject.get());
if (door) {

View file

@ -27,8 +27,8 @@ using namespace reone::game;
BOOST_AUTO_TEST_CASE(test_action_completion) {
ActionQueue actionQueue;
actionQueue.push(make_unique<Action>(ActionType::PauseConversation));
actionQueue.push(make_unique<Action>(ActionType::ResumeConversation));
actionQueue.add(make_unique<Action>(ActionType::PauseConversation));
actionQueue.add(make_unique<Action>(ActionType::ResumeConversation));
Action *currentAction = actionQueue.currentAction();
BOOST_TEST((currentAction && currentAction->type() == ActionType::PauseConversation));