diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 6f33d5c8a..615c223c2 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -111,6 +111,7 @@ include/modules/Burrows.h include/modules/Constructions.h include/modules/Units.h include/modules/Engravings.h +include/modules/EventManager.h include/modules/Gui.h include/modules/Items.h include/modules/Job.h @@ -133,6 +134,7 @@ modules/Burrows.cpp modules/Constructions.cpp modules/Units.cpp modules/Engravings.cpp +modules/EventManager.cpp modules/Gui.cpp modules/Items.cpp modules/Job.cpp diff --git a/library/Core.cpp b/library/Core.cpp index 26c0acbb0..5c397dd05 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -44,6 +44,7 @@ using namespace std; #include "VersionInfo.h" #include "PluginManager.h" #include "ModuleFactory.h" +#include "modules/EventManager.h" #include "modules/Gui.h" #include "modules/World.h" #include "modules/Graphic.h" @@ -904,6 +905,7 @@ bool Core::Init() cerr << "Initializing Plugins.\n"; // create plugin manager plug_mgr = new PluginManager(this); + plug_mgr->init(this); IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; @@ -1238,6 +1240,8 @@ static int buildings_timer = 0; void Core::onUpdate(color_ostream &out) { + EventManager::manageEvents(out); + // convert building reagents if (buildings_do_onupdate && (++buildings_timer & 1)) buildings_onUpdate(out); @@ -1251,6 +1255,8 @@ void Core::onUpdate(color_ostream &out) void Core::onStateChange(color_ostream &out, state_change_event event) { + EventManager::onStateChange(out, event); + buildings_onStateChange(out, event); plug_mgr->OnStateChange(out, event); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 0c80639b4..86bab66cd 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -22,6 +22,7 @@ must not be misrepresented as being the original software. distribution. */ +#include "modules/EventManager.h" #include "Internal.h" #include "Core.h" #include "MemAccess.h" @@ -270,6 +271,7 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) @@ -598,6 +600,22 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn) } PluginManager::PluginManager(Core * core) +{ + cmdlist_mutex = new mutex(); + eval_ruby = NULL; +} + +PluginManager::~PluginManager() +{ + for(size_t i = 0; i < all_plugins.size();i++) + { + delete all_plugins[i]; + } + all_plugins.clear(); + delete cmdlist_mutex; +} + +void PluginManager::init(Core * core) { #ifdef LINUX_BUILD string path = core->getHackPath() + "plugins/"; @@ -606,8 +624,6 @@ PluginManager::PluginManager(Core * core) string path = core->getHackPath() + "plugins\\"; const string searchstr = ".plug.dll"; #endif - cmdlist_mutex = new mutex(); - eval_ruby = NULL; vector filez; getdir(path, filez); for(size_t i = 0; i < filez.size();i++) @@ -622,16 +638,6 @@ PluginManager::PluginManager(Core * core) } } -PluginManager::~PluginManager() -{ - for(size_t i = 0; i < all_plugins.size();i++) - { - delete all_plugins[i]; - } - all_plugins.clear(); - delete cmdlist_mutex; -} - Plugin *PluginManager::getPluginByName (const std::string & name) { for(size_t i = 0; i < all_plugins.size(); i++) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 9ef16703a..62a195867 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -205,6 +205,7 @@ namespace DFHack friend class Plugin; PluginManager(Core * core); ~PluginManager(); + void init(Core* core); void OnUpdate(color_ostream &out); void OnStateChange(color_ostream &out, state_change_event event); void registerCommands( Plugin * p ); diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h new file mode 100644 index 000000000..c1c09da7d --- /dev/null +++ b/library/include/modules/EventManager.h @@ -0,0 +1,47 @@ +#pragma once +#ifndef EVENT_MANAGER_H_INCLUDED +#define EVENT_MANAGER_H_INCLUDED + +#include "Core.h" +#include "Export.h" +#include "ColorText.h" +#include "PluginManager.h" +#include "Console.h" + +namespace DFHack { + namespace EventManager { + namespace EventType { + enum EventType { + TICK, + JOB_INITIATED, + JOB_COMPLETED, + UNIT_DEATH, + ITEM_CREATED, + EVENT_MAX + }; + } + + struct EventHandler { + void (*eventHandler)(color_ostream&, void*); //called when the event happens + + EventHandler(void (*eventHandlerIn)(color_ostream&, void*)): eventHandler(eventHandlerIn) { + } + + bool operator==(EventHandler& handle) const { + return eventHandler == handle.eventHandler; + } + bool operator!=(EventHandler& handle) const { + return !( *this == handle); + } + }; + + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); + DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void unregisterAll(Plugin* plugin); + void manageEvents(color_ostream& out); + void onStateChange(color_ostream& out, state_change_event event); + } +} + +#endif diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 853813073..e865273d9 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -47,7 +47,7 @@ namespace DFHack { namespace Job { // Duplicate the job structure. It is not linked into any DF lists. - DFHACK_EXPORT df::job *cloneJobStruct(df::job *job); + DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false); // Delete a cloned structure. DFHACK_EXPORT void deleteJobStruct(df::job *job); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp new file mode 100644 index 000000000..553a69668 --- /dev/null +++ b/library/modules/EventManager.cpp @@ -0,0 +1,288 @@ +#include "Core.h" +#include "Console.h" +#include "modules/EventManager.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "df/global_objects.h" +#include "df/item.h" +#include "df/job.h" +#include "df/job_list_link.h" +#include "df/unit.h" +#include "df/world.h" + +//#include +#include +//#include +using namespace std; +using namespace DFHack; +using namespace EventManager; + +/* + * TODO: + * error checking + **/ + +//map > tickQueue; +multimap tickQueue; + +multimap handlers[EventType::EVENT_MAX]; + +const uint32_t ticksPerYear = 403200; + +void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) { + handlers[e].insert(pair(plugin, handler)); +} + +void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + if ( !Core::getInstance().isWorldLoaded() ) { + tick = 0; + if ( absolute ) { + Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n"); + } + } + if ( absolute ) { + tick = 0; + } + + tickQueue.insert(pair(tick+(uint32_t)when, handler)); + handlers[EventType::TICK].insert(pair(plugin,handler)); + return; +} + +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { + for ( multimap::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) { + if ( (*i).first != plugin ) + break; + EventHandler handle = (*i).second; + if ( handle == handler ) { + handlers[e].erase(i); + break; + } + } + return; +} + +void DFHack::EventManager::unregisterAll(Plugin* plugin) { + for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) { + if ( (*i).first != plugin ) + break; + + //shenanigans to avoid concurrent modification + EventHandler getRidOf = (*i).second; + bool didSomething; + do { + didSomething = false; + for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) { + EventHandler candidate = (*j).second; + if ( getRidOf != candidate ) + continue; + tickQueue.erase(j); + didSomething = true; + break; + } + } while(didSomething); + } + for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { + handlers[a].erase(plugin); + } + return; +} + +static void manageTickEvent(color_ostream& out); +static void manageJobInitiatedEvent(color_ostream& out); +static void manageJobCompletedEvent(color_ostream& out); +static void manageUnitDeathEvent(color_ostream& out); +static void manageItemCreationEvent(color_ostream& out); + +static uint32_t lastTick = 0; +static int32_t lastJobId = -1; +static map prevJobs; +static set livingUnits; +static int32_t nextItem; + +void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { + if ( event == DFHack::SC_MAP_UNLOADED ) { + lastTick = 0; + lastJobId = -1; + for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + Job::deleteJobStruct((*i).second); + } + prevJobs.clear(); + tickQueue.clear(); + livingUnits.clear(); + nextItem = -1; + } else if ( event == DFHack::SC_MAP_LOADED ) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + multimap newTickQueue; + for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) { + newTickQueue.insert(pair(tick + (*i).first, (*i).second)); + } + tickQueue.clear(); + + tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); + + nextItem = *df::global::item_next_id; + } +} + +void DFHack::EventManager::manageEvents(color_ostream& out) { + if ( !Core::getInstance().isWorldLoaded() ) { + return; + } + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + if ( tick <= lastTick ) + return; + lastTick = tick; + + manageTickEvent(out); + manageJobInitiatedEvent(out); + manageJobCompletedEvent(out); + manageUnitDeathEvent(out); + manageItemCreationEvent(out); + + return; +} + +static void manageTickEvent(color_ostream& out) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + while ( !tickQueue.empty() ) { + if ( tick < (*tickQueue.begin()).first ) + break; + EventHandler handle = (*tickQueue.begin()).second; + tickQueue.erase(tickQueue.begin()); + handle.eventHandler(out, (void*)tick); + } + +} + +static void manageJobInitiatedEvent(color_ostream& out) { + if ( handlers[EventType::JOB_INITIATED].empty() ) + return; + + if ( lastJobId == -1 ) { + lastJobId = *df::global::job_next_id - 1; + return; + } + + if ( lastJobId+1 == *df::global::job_next_id ) { + return; //no new jobs + } + + for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + if ( link->item == NULL ) + continue; + if ( link->item->id <= lastJobId ) + continue; + for ( multimap::iterator i = handlers[EventType::JOB_INITIATED].begin(); i != handlers[EventType::JOB_INITIATED].end(); i++ ) { + (*i).second.eventHandler(out, (void*)link->item); + } + } + + lastJobId = *df::global::job_next_id - 1; +} + + +static void manageJobCompletedEvent(color_ostream& out) { + if ( handlers[EventType::JOB_COMPLETED].empty() ) { + return; + } + + map nowJobs; + for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + if ( link->item == NULL ) + continue; + nowJobs[link->item->id] = link->item; + } + + for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + if ( nowJobs.find((*i).first) != nowJobs.end() ) + continue; + + //recently finished or cancelled job! + for ( multimap::iterator j = handlers[EventType::JOB_COMPLETED].begin(); j != handlers[EventType::JOB_COMPLETED].end(); j++ ) { + (*j).second.eventHandler(out, (void*)(*i).second); + } + } + + //erase old jobs, copy over possibly altered jobs + for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + Job::deleteJobStruct((*i).second); + } + prevJobs.clear(); + + //create new jobs + for ( map::iterator j = nowJobs.begin(); j != nowJobs.end(); j++ ) { + /*map::iterator i = prevJobs.find((*j).first); + if ( i != prevJobs.end() ) { + continue; + }*/ + + df::job* newJob = Job::cloneJobStruct((*j).second, true); + prevJobs[newJob->id] = newJob; + } + + /*//get rid of old pointers to deallocated jobs + for ( size_t a = 0; a < toDelete.size(); a++ ) { + prevJobs.erase(a); + }*/ +} + +static void manageUnitDeathEvent(color_ostream& out) { + if ( handlers[EventType::UNIT_DEATH].empty() ) { + return; + } + + for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { + df::unit* unit = df::global::world->units.active[a]; + if ( unit->counters.death_id == -1 ) { + livingUnits.insert(unit->id); + continue; + } + //dead: if dead since last check, trigger events + if ( livingUnits.find(unit->id) == livingUnits.end() ) + continue; + + for ( auto i = handlers[EventType::UNIT_DEATH].begin(); i != handlers[EventType::UNIT_DEATH].end(); i++ ) { + (*i).second.eventHandler(out, (void*)unit->id); + } + livingUnits.erase(unit->id); + } +} + +static void manageItemCreationEvent(color_ostream& out) { + if ( handlers[EventType::ITEM_CREATED].empty() ) { + return; + } + + if ( nextItem >= *df::global::item_next_id ) { + return; + } + + size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false); + for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { + df::item* item = df::global::world->items.all[a]; + //invaders + if ( item->flags.bits.foreign ) + continue; + //traders who bring back your items? + if ( item->flags.bits.trader ) + continue; + //migrants + if ( item->flags.bits.owned ) + continue; + //spider webs don't count + if ( item->flags.bits.spider_web ) + continue; + for ( auto i = handlers[EventType::ITEM_CREATED].begin(); i != handlers[EventType::ITEM_CREATED].end(); i++ ) { + (*i).second.eventHandler(out, (void*)item->id); + } + } + nextItem = *df::global::item_next_id; +} + diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index def3b4192..c0e18f44a 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -54,7 +54,7 @@ using namespace std; using namespace DFHack; using namespace df::enums; -df::job *DFHack::Job::cloneJobStruct(df::job *job) +df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData) { CHECK_NULL_POINTER(job); @@ -75,7 +75,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) { df::general_ref *ref = pnew->references[i]; - if (virtual_cast(ref)) + if (!keepWorkerData && virtual_cast(ref)) vector_erase_at(pnew->references, i); else pnew->references[i] = ref->clone(); diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..80d627fa9 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +DFHACK_PLUGIN(eventExample eventExample.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp new file mode 100644 index 000000000..2099be110 --- /dev/null +++ b/plugins/devel/eventExample.cpp @@ -0,0 +1,79 @@ + +#include "Console.h" +#include "Core.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/EventManager.h" +#include "DataDefs.h" + +#include "df/item.h" +#include "df/world.h" + +#include + +using namespace DFHack; +using namespace std; + +DFHACK_PLUGIN("eventExample"); + +void jobInitiated(color_ostream& out, void* job); +void jobCompleted(color_ostream& out, void* job); +void timePassed(color_ostream& out, void* ptr); +void unitDeath(color_ostream& out, void* ptr); +void itemCreate(color_ostream& out, void* ptr); + +command_result eventExample(color_ostream& out, vector& parameters); + + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample)); + return CR_OK; +} + +command_result eventExample(color_ostream& out, vector& parameters) { + EventManager::EventHandler initiateHandler(jobInitiated); + EventManager::EventHandler completeHandler(jobCompleted); + EventManager::EventHandler timeHandler(timePassed); + EventManager::EventHandler deathHandler(unitDeath); + EventManager::EventHandler itemHandler(itemCreate); + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); + + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); + EventManager::registerTick(timeHandler, 1, me); + EventManager::registerTick(timeHandler, 2, me); + EventManager::registerTick(timeHandler, 4, me); + EventManager::registerTick(timeHandler, 8, me); + EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me); + EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me); + out.print("Events registered.\n"); + return CR_OK; +} + +void jobInitiated(color_ostream& out, void* job) { + out.print("Job initiated! 0x%X\n", job); +} + +void jobCompleted(color_ostream& out, void* job) { + out.print("Job completed! 0x%X\n", job); +} + +void timePassed(color_ostream& out, void* ptr) { + out.print("Time: %d\n", (int32_t)(ptr)); +} + +void unitDeath(color_ostream& out, void* ptr) { + out.print("Death: %d\n", (int32_t)(ptr)); +} + +void itemCreate(color_ostream& out, void* ptr) { + int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (int32_t)ptr); + if ( item_index == -1 ) { + out.print("%s, %d: Error.\n", __FILE__, __LINE__); + } + df::item* item = df::global::world->items.all[item_index]; + df::item_type type = item->getType(); + df::coord pos = item->pos; + out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); +} +