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
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
@@ -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

10 changes: 6 additions & 4 deletions library/include/modules/EventManager.h
Original file line number Diff line number Diff line change
@@ -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;
56 changes: 36 additions & 20 deletions library/modules/EventManager.cpp
Original file line number Diff line number Diff line change
@@ -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));
}

@@ -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;
}

@@ -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) {
@@ -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;
}
}

174 changes: 95 additions & 79 deletions plugins/channel-safely/channel-safely-plugin.cpp
Original file line number Diff line number Diff line change
@@ -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>
@@ -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();
@@ -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;
@@ -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;
@@ -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);
@@ -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);
@@ -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) {
4 changes: 2 additions & 2 deletions plugins/devel/eventExample.cpp
Original file line number Diff line number Diff line change
@@ -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);