Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows tick events to auto-requeue when registered as listeners #2484

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Documentation

## API
- ``EventManager``: revises tick events to work as listeners. Tick events registered with ``registerListener`` will now automatically requeue at their designated frequency.

## Lua

Expand Down
10 changes: 6 additions & 4 deletions library/include/modules/EventManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ namespace DFHack {

struct EventHandler {
typedef void (*callback_t)(color_ostream&, void*); //called when the event happens
callback_t eventHandler;
int32_t freq; //how often event is allowed to fire (in ticks) use 0 to always fire when possible
const callback_t eventHandler;
const int32_t freq; //how often event is allowed to fire (in ticks) use 0 to always fire when possible
int32_t when = -1; //when to fire event (global tick count)

EventHandler(callback_t eventHandlerIn, int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
}
EventHandler(callback_t eventHandlerIn, int32_t freqIn) :
eventHandler(eventHandlerIn),
freq(freqIn) {}

bool operator==(const EventHandler& handle) const {
return eventHandler == handle.eventHandler && freq == handle.freq;
Expand Down
56 changes: 36 additions & 20 deletions library/modules/EventManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,25 @@ static int32_t eventLastTick[EventType::EVENT_MAX];

static const int32_t ticksPerYear = 403200;

// this function is only used within the file in registerListener and manageTickEvent
void enqueueTickEvent(EventHandler &handler){
int32_t when = 0;
df::world* world = df::global::world;
if ( world ) {
when = world->frame_counter + handler.freq;
} else {
if ( Once::doOnce("EventManager registerListener unhonored absolute=false") )
Core::getInstance().getConsole().print("EventManager::registerTick: warning! absolute flag=false not honored.\n");
}
handler.when = when;
tickQueue.emplace(handler.when, handler);
}

void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
DEBUG(log).print("registering handler %p from plugin %s for event %d\n", handler.eventHandler, plugin->getName().c_str(), e);
if(e == EventType::TICK){
enqueueTickEvent(handler);
}
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
}

Expand All @@ -82,10 +99,12 @@ int32_t DFHack::EventManager::registerTick(EventHandler handler, int32_t when, P
Core::getInstance().getConsole().print("EventManager::registerTick: warning! absolute flag=false not honored.\n");
}
}
handler.freq = when;
tickQueue.insert(pair<int32_t, EventHandler>(handler.freq, handler));
DEBUG(log).print("registering handler %p from plugin %s for event TICK\n", handler.eventHandler, plugin->getName().c_str());
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
handler.when = when;
tickQueue.emplace(handler.when, handler);
// we don't track this handler, this allows registerTick to retain the old behaviour of needing to re-register the tick event
//handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
// since the event isn't added to the handlers, we don't need to unregister these events
return when;
}

Expand All @@ -112,9 +131,10 @@ void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handl
}
DEBUG(log).print("unregistering handler %p from plugin %s for event %d\n", handler.eventHandler, plugin->getName().c_str(), e);
i = handlers[e].erase(i);
if ( e == EventType::TICK )
removeFromTickQueue(handler);
}
// we've removed it from the handlers multimap, all that's left is to make sure it's not in the tick queue
if ( e == EventType::TICK )
removeFromTickQueue(handler);
}

void DFHack::EventManager::unregisterAll(Plugin* plugin) {
Expand Down Expand Up @@ -393,29 +413,25 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
static void manageTickEvent(color_ostream& out) {
if (!df::global::world)
return;
unordered_set<EventHandler> toRemove;
unordered_set<EventHandler> toRequeue;
int32_t tick = df::global::world->frame_counter;
while ( !tickQueue.empty() ) {
if ( tick < (*tickQueue.begin()).first )
auto iter = tickQueue.begin();
if ( tick < iter->first )
break;
EventHandler &handle = (*tickQueue.begin()).second;
tickQueue.erase(tickQueue.begin());
EventHandler &handle = iter->second;
tickQueue.erase(iter);
DEBUG(log,out).print("calling handler for tick event\n");
handle.eventHandler(out, (void*)intptr_t(tick));
toRemove.insert(handle);
toRequeue.emplace(handle);
}
if ( toRemove.empty() )
if ( toRequeue.empty() )
return;
for ( auto a = handlers[EventType::TICK].begin(); a != handlers[EventType::TICK].end(); ) {
EventHandler &handle = (*a).second;
if ( toRemove.find(handle) == toRemove.end() ) {
a++;
continue;
for (auto pair : handlers[EventType::TICK]) {
if (toRequeue.count(pair.second)) {
EventHandler &handler = pair.second;
enqueueTickEvent(handler);
}
a = handlers[EventType::TICK].erase(a);
toRemove.erase(handle);
if ( toRemove.empty() )
break;
}
}

Expand Down
174 changes: 95 additions & 79 deletions plugins/channel-safely/channel-safely-plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ This skeletal logic has not been kept up-to-date since ~v0.5
#include <df/tile_traffic.h>
#include <df/block_square_event_designation_priorityst.h>

#include <memory>
#include <cinttypes>
#include <unordered_map>
#include <unordered_set>
Expand Down Expand Up @@ -153,6 +154,9 @@ namespace CSP {
std::unordered_map<int32_t, df::coord> last_safe;
std::unordered_set<df::coord> dignow_queue;

std::unique_ptr<EM::EventHandler> scanningHandler;
std::unique_ptr<EM::EventHandler> monitorHandler;

void ClearData() {
ChannelManager::Get().destroy_groups();
dignow_queue.clear();
Expand Down Expand Up @@ -361,28 +365,9 @@ namespace CSP {
CoreSuspender suspend;
if (enabled && World::isFortressMode() && Maps::IsValid() && !World::ReadPauseState()) {
static int32_t last_tick = df::global::world->frame_counter;
static int32_t last_monitor_tick = df::global::world->frame_counter;
static int32_t last_refresh_tick = df::global::world->frame_counter;
static int32_t last_resurrect_tick = df::global::world->frame_counter;
int32_t tick = df::global::world->frame_counter;

// Refreshing the group data with full scanning
if (tick - last_refresh_tick >= config.refresh_freq) {
last_refresh_tick = tick;
TRACE(monitor).print("OnUpdate() refreshing now\n");
if (config.insta_dig) {
TRACE(monitor).print(" -> evaluate dignow queue\n");
for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) {
auto map_pos = *iter;
dig_now(out, map_pos); // teleports units to the bottom of a simulated fall
ChannelManager::Get().mark_done(map_pos);
iter = dignow_queue.erase(iter);
}
}
UnpauseEvent(false);
TRACE(monitor).print("OnUpdate() refresh done\n");
}

// Clean up stale df::job*
if ((config.monitoring || config.resurrect) && tick - last_tick >= 1) {
last_tick = tick;
Expand All @@ -406,66 +391,6 @@ namespace CSP {
}
}

// Monitoring Active and Resurrecting Dead
if (config.monitoring && tick - last_monitor_tick >= config.monitor_freq) {
last_monitor_tick = tick;
TRACE(monitor).print("OnUpdate() monitoring now\n");

// iterate active jobs
for (auto pair: active_jobs) {
df::job* job = pair.second;
df::unit* unit = active_workers[job->id];
if (!unit) continue;
if (!Maps::isValidTilePos(job->pos)) continue;
TRACE(monitor).print(" -> check for job in tracking\n");
if (Units::isAlive(unit)) {
if (!config.monitoring) continue;
TRACE(monitor).print(" -> compare positions of worker and job\n");

// check for fall safety
if (unit->pos == job->pos && !is_safe_fall(job->pos)) {
// unsafe
WARN(monitor).print(" -> unsafe job\n");
Job::removeWorker(job);

// decide to insta-dig or marker mode
if (config.insta_dig) {
// delete the job
Job::removeJob(job);
// queue digging the job instantly
dignow_queue.emplace(job->pos);
DEBUG(monitor).print(" -> insta-dig\n");
} else if (config.resurrect) {
endangered_units.emplace(unit, tick);
} else {
// set marker mode
Maps::getTileOccupancy(job->pos)->bits.dig_marked = true;

// prevent algorithm from re-enabling designation
for (auto &be: Maps::getBlock(job->pos)->block_events) {
if (auto bsedp = virtual_cast<df::block_square_event_designation_priorityst>(
be)) {
df::coord local(job->pos);
local.x = local.x % 16;
local.y = local.y % 16;
bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1;
break;
}
}
DEBUG(monitor).print(" -> set marker mode\n");
}
}
} else if (config.resurrect) {
resurrect(out, unit->id);
if (last_safe.count(unit->id)) {
df::coord lowest = simulate_fall(last_safe[unit->id]);
Units::teleport(unit, lowest);
}
}
}
TRACE(monitor).print("OnUpdate() monitoring done\n");
}

// Resurrect Dead Workers
if (config.resurrect && tick - last_resurrect_tick >= 1) {
last_resurrect_tick = tick;
Expand Down Expand Up @@ -494,6 +419,80 @@ namespace CSP {
}
}
}

void onTick_FullScan(color_ostream &out, void* tick) {
// Refreshing the group data with full scanning
TRACE(monitor).print("onTick() refreshing now\n");
if (config.insta_dig) {
TRACE(monitor).print(" -> evaluate dignow queue\n");
for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) {
auto map_pos = *iter;
dig_now(out, map_pos); // teleports units to the bottom of a simulated fall
ChannelManager::Get().mark_done(map_pos);
iter = dignow_queue.erase(iter);
}
}
UnpauseEvent(false);
TRACE(monitor).print("onTick() refresh done\n");
}

void onTick_Monitoring(color_ostream &out, void* tick) {
int32_t itick = df::global::world->frame_counter;
// iterate active jobs
TRACE(monitor).print("onTick() monitoring now\n");
for (auto pair: active_jobs) {
df::job* job = pair.second;
df::unit* unit = active_workers[job->id];
if (!unit) continue;
if (!Maps::isValidTilePos(job->pos)) continue;
TRACE(monitor).print(" -> check for job in tracking\n");
if (Units::isAlive(unit)) {
if (!config.monitoring) continue;
TRACE(monitor).print(" -> compare positions of worker and job\n");

// check for fall safety
if (unit->pos == job->pos && !is_safe_fall(job->pos)) {
// unsafe
WARN(monitor).print(" -> unsafe job\n");
Job::removeWorker(job);

// decide to insta-dig or marker mode
if (config.insta_dig) {
// delete the job
Job::removeJob(job);
// queue digging the job instantly
dignow_queue.emplace(job->pos);
DEBUG(monitor).print(" -> insta-dig\n");
} else if (config.resurrect) {
endangered_units.emplace(unit, itick);
} else {
// set marker mode
Maps::getTileOccupancy(job->pos)->bits.dig_marked = true;

// prevent algorithm from re-enabling designation
for (auto &be: Maps::getBlock(job->pos)->block_events) {
if (auto bsedp = virtual_cast<df::block_square_event_designation_priorityst>(
be)) {
df::coord local(job->pos);
local.x = local.x % 16;
local.y = local.y % 16;
bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1;
break;
}
}
DEBUG(monitor).print(" -> set marker mode\n");
}
}
} else if (config.resurrect) {
resurrect(out, unit->id);
if (last_safe.count(unit->id)) {
df::coord lowest = simulate_fall(last_safe[unit->id]);
Units::teleport(unit, lowest);
}
}
}
TRACE(monitor).print("onTick() monitoring done\n");
}
}

command_result channel_safely(color_ostream &out, std::vector<std::string> &parameters);
Expand Down Expand Up @@ -522,13 +521,20 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {

DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (enable && !enabled) {
// just to be safe
EM::unregisterAll(plugin_self);
// register events to check jobs / update tracking
EM::EventHandler jobStartHandler(CSP::JobStartedEvent, 0);
EM::EventHandler jobCompletionHandler(CSP::JobCompletedEvent, 0);
EM::EventHandler reportHandler(CSP::NewReportEvent, 0);
CSP::scanningHandler = std::unique_ptr<EM::EventHandler>(new EM::EventHandler(CSP::onTick_FullScan, config.refresh_freq));
CSP::monitorHandler = std::unique_ptr<EM::EventHandler>(new EM::EventHandler(CSP::onTick_Monitoring, config.monitor_freq));

EM::registerListener(EventType::REPORT, reportHandler, plugin_self);
EM::registerListener(EventType::JOB_STARTED, jobStartHandler, plugin_self);
EM::registerListener(EventType::JOB_COMPLETED, jobCompletionHandler, plugin_self);
EM::registerListener(EventType::TICK, *CSP::scanningHandler, plugin_self);
EM::registerListener(EventType::TICK, *CSP::monitorHandler, plugin_self);
// manage designations to start off (first time building groups [very important])
out.print("channel-safely: enabled!\n");
CSP::UnpauseEvent(true);
Expand Down Expand Up @@ -619,8 +625,18 @@ command_result channel_safely(color_ostream &out, std::vector<std::string> &para
}
} else if (parameters[1] == "refresh-freq" && set && parameters.size() == 3) {
config.refresh_freq = std::abs(std::stol(parameters[2]));
if (enabled) {
EM::unregister(EventType::TICK, *CSP::scanningHandler, plugin_self);
CSP::scanningHandler = std::unique_ptr<EM::EventHandler>(new EM::EventHandler(CSP::onTick_FullScan, config.refresh_freq));
EM::registerListener(EventType::TICK, *CSP::scanningHandler, plugin_self);
}
} else if (parameters[1] == "monitor-freq" && set && parameters.size() == 3) {
config.monitor_freq = std::abs(std::stol(parameters[2]));
if (enabled) {
EM::unregister(EventType::TICK, *CSP::monitorHandler, plugin_self);
CSP::monitorHandler = std::unique_ptr<EM::EventHandler>(new EM::EventHandler(CSP::onTick_Monitoring, config.monitor_freq));
EM::registerListener(EventType::TICK, *CSP::monitorHandler, plugin_self);
}
} else if (parameters[1] == "ignore-threshold" && set && parameters.size() == 3) {
config.ignore_threshold = std::abs(std::stol(parameters[2]));
} else if (parameters[1] == "fall-threshold" && set && parameters.size() == 3) {
Expand Down
4 changes: 2 additions & 2 deletions plugins/devel/eventExample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ command_result eventExample(color_ostream& out, vector<string>& parameters) {
EventManager::registerTick(timeHandler, 4, plugin_self);
EventManager::registerTick(timeHandler, 8, plugin_self);
int32_t t = EventManager::registerTick(timeHandler, 16, plugin_self);
timeHandler.freq = t;
timeHandler.when = t;
EventManager::unregister(EventManager::EventType::TICK, timeHandler, plugin_self);
t = EventManager::registerTick(timeHandler, 32, plugin_self);
t = EventManager::registerTick(timeHandler, 32, plugin_self);
t = EventManager::registerTick(timeHandler, 32, plugin_self);
timeHandler.freq = t;
timeHandler.when = t;
EventManager::unregister(EventManager::EventType::TICK, timeHandler, plugin_self);
EventManager::unregister(EventManager::EventType::TICK, timeHandler, plugin_self);

Expand Down