Extends pausing API in World module

develop
Josh Cooper 2022-08-28 18:08:40 -07:00
parent 33b92e6f69
commit 4fd6c52687
3 changed files with 194 additions and 0 deletions

@ -1911,6 +1911,9 @@ void Core::doUpdate(color_ostream &out, bool first_update)
// Execute per-frame handlers
onUpdate(out);
// Execute per-frame World module maintenance, after plugins and lua timers
World::Update();
d->last_autosave_request = df::global::ui->main.autosave_request;
d->was_load_save = is_load_save;

@ -35,6 +35,7 @@ distribution.
#include "Module.h"
#include "modules/Persistence.h"
#include <ostream>
#include <unordered_set>
#include "DataDefs.h"
@ -61,6 +62,47 @@ namespace DFHack
};
class DFContextShared;
////////////
// Locking mechanisms for control over pausing
namespace Pausing
{
class Lock
{
bool locked = false;
std::string name;
public:
explicit Lock(const char* name) { this->name = name;};
virtual ~Lock()= default;
virtual bool isAnyLocked() const = 0;
virtual bool isOnlyLocked() const = 0;
bool isLocked() const { return locked; }
void lock() { locked = true; };
void unlock() { locked = false; };
};
// non-blocking lock resource used in conjunction with the announcement functions in World
class AnnouncementLock : public Lock
{
static std::unordered_set<Lock*> locks;
public:
explicit AnnouncementLock(const char* name): Lock(name) { locks.emplace(this); }
~AnnouncementLock() override { locks.erase(this); }
bool isAnyLocked() const override; // returns true if any instance of AnnouncementLock is locked
bool isOnlyLocked() const override; // returns true if locked and no other instance is locked
};
// non-blocking lock resource used in conjunction with the Player pause functions in World
class PlayerLock : public Lock
{
static std::unordered_set<Lock*> locks;
public:
explicit PlayerLock(const char* name): Lock(name) { locks.emplace(this); }
~PlayerLock() override { locks.erase(this); }
bool isAnyLocked() const override; // returns true if any instance of PlayerLock is locked
bool isOnlyLocked() const override; // returns true if locked and no other instance is locked
};
}
/**
* The World module
* \ingroup grp_modules
@ -73,6 +115,21 @@ namespace DFHack
///true if paused, false if not
DFHACK_EXPORT void SetPauseState(bool paused);
// todo: prevent anything else from allocating/deleting?
// Acquire & Release pause locks controlling player pausing and announcement pause settings
DFHACK_EXPORT Pausing::AnnouncementLock* AcquireAnnouncementPauseLock(const char*);
DFHACK_EXPORT Pausing::PlayerLock* AcquirePlayerPauseLock(const char*);
DFHACK_EXPORT void ReleasePauseLock(Pausing::Lock*);
DFHACK_EXPORT bool DisableAnnouncementPausing(); // disable announcement pausing if all locks are open
DFHACK_EXPORT bool SaveAnnouncementSettings(); // save current announcement pause settings if all locks are open
DFHACK_EXPORT bool RestoreAnnouncementSettings(); // restore saved announcement pause settings if all locks are open
DFHACK_EXPORT bool EnablePlayerPausing(); // enable player pausing if all locks are open
DFHACK_EXPORT bool DisablePlayerPausing(); // disable player pausing if all locks are open
void Update();
DFHACK_EXPORT uint32_t ReadCurrentTick();
DFHACK_EXPORT uint32_t ReadCurrentYear();
DFHACK_EXPORT uint32_t ReadCurrentMonth();

@ -28,6 +28,7 @@ distribution.
#include <string>
#include <vector>
#include <map>
#include <unordered_set>
#include <cstring>
using namespace std;
@ -51,8 +52,14 @@ using namespace std;
#include "df/map_block.h"
#include "df/block_square_event_world_constructionst.h"
#include "df/viewscreen_legendsst.h"
#include "df/d_init.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/ui.h"
#include "VTableInterpose.h"
#include "PluginManager.h"
using namespace DFHack;
using namespace Pausing;
using namespace df::enums;
using df::global::world;
@ -68,6 +75,133 @@ void World::SetPauseState(bool paused)
DF_GLOBAL_VALUE(pause_state, dummy) = paused;
}
std::unordered_set<Lock*> PlayerLock::locks;
std::unordered_set<Lock*> AnnouncementLock::locks;
bool AnnouncementLock::isAnyLocked() const {
return std::any_of(locks.begin(), locks.end(), [](Lock* lock) { return lock->isLocked(); });
}
bool AnnouncementLock::isOnlyLocked() const {
return std::all_of(locks.begin(), locks.end(), [&](Lock* lock) {
if (lock == this) {
return lock->isLocked();
}
return !lock->isLocked();
});
}
bool PlayerLock::isAnyLocked() const {
return std::any_of(locks.begin(), locks.end(), [](Lock* lock) { return lock->isLocked(); });
}
bool PlayerLock::isOnlyLocked() const {
return std::all_of(locks.begin(), locks.end(), [&](Lock* lock) {
if (lock == this) {
return lock->isLocked();
}
return !lock->isLocked();
});
}
namespace pausing {
AnnouncementLock announcementLock("monitor");
PlayerLock playerLock("monitor");
const size_t array_size = sizeof(decltype(df::announcements::flags)) / sizeof(df::announcement_flags);
bool state_saved = false; // indicates whether a restore state is ok
bool announcements_disabled = false; // indicates whether disable or restore was last enacted, could use a better name
bool saved_states[array_size]; // state to restore
bool locked_states[array_size]; // locked state (re-applied each frame)
bool allow_player_pause = true; // toggles player pause ability
using df::global::ui;
using namespace df::enums;
struct player_pause_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key>* input)) {
if ((ui->main.mode == ui_sidebar_mode::Default) && !allow_player_pause) {
input->erase(interface_key::D_PAUSE);
}
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(player_pause_hook, feed);
}
using namespace pausing;
AnnouncementLock* World::AcquireAnnouncementPauseLock(const char* name) {
return new AnnouncementLock(name);
}
PlayerLock* World::AcquirePlayerPauseLock(const char* name) {
return new PlayerLock(name);
}
void World::ReleasePauseLock(Lock* lock){
delete lock;
}
bool World::DisableAnnouncementPausing() {
if (!announcementLock.isAnyLocked()) {
for (auto& flag : df::global::d_init->announcements.flags) {
flag.bits.PAUSE = false;
}
announcements_disabled = true;
}
return announcements_disabled;
}
bool World::SaveAnnouncementSettings() {
if (!announcementLock.isAnyLocked()) {
for (size_t i = 0; i < array_size; ++i) {
saved_states[i] = df::global::d_init->announcements.flags[i].bits.PAUSE;
}
return true;
}
return false;
}
bool World::RestoreAnnouncementSettings() {
if (!announcementLock.isAnyLocked() && state_saved) {
for (size_t i = 0; i < array_size; ++i) {
df::global::d_init->announcements.flags[i].bits.PAUSE = saved_states[i];
}
announcements_disabled = false;
return true;
}
return false;
}
bool World::EnablePlayerPausing() {
if (!playerLock.isAnyLocked()) {
allow_player_pause = true;
}
return allow_player_pause;
}
bool World::DisablePlayerPausing() {
if (!playerLock.isAnyLocked()) {
allow_player_pause = false;
}
return !allow_player_pause;
}
void World::Update() {
static bool did_once = false;
if (!did_once) {
did_once = true;
INTERPOSE_HOOK(player_pause_hook, feed).apply();
}
if (announcementLock.isAnyLocked()) {
for (size_t i = 0; i < array_size; ++i) {
df::global::d_init->announcements.flags[i].bits.PAUSE = locked_states[i];
}
}
}
uint32_t World::ReadCurrentYear()
{
return DF_GLOBAL_VALUE(cur_year, 0);