diff --git a/docs/changelog.txt b/docs/changelog.txt index b0b86c4cd7..0c4d440a5e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -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 diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 7808f6d8c6..b5df573c71 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -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; diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 02b1892e95..831ddada63 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -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, 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(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,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,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 toRemove; + unordered_set 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; } } diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 910e0ee7c4..d85d3b99f4 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -67,6 +67,7 @@ This skeletal logic has not been kept up-to-date since ~v0.5 #include #include +#include #include #include #include @@ -153,6 +154,9 @@ namespace CSP { std::unordered_map last_safe; std::unordered_set dignow_queue; + std::unique_ptr scanningHandler; + std::unique_ptr 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( - 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( + 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 ¶meters); @@ -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(new EM::EventHandler(CSP::onTick_FullScan, config.refresh_freq)); + CSP::monitorHandler = std::unique_ptr(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 ¶ } } 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(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(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) { diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 8d03222c34..a20045a33d 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -97,12 +97,12 @@ command_result eventExample(color_ostream& out, vector& 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);