From 8da7e216a4a2dac1576ff68045c3fd79e34fcc9f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 27 Mar 2023 02:24:56 -0700 Subject: [PATCH] buildingplan - suspendmanager integration --- docs/changelog.txt | 1 + docs/plugins/buildingplan.rst | 5 ++- plugins/buildingplan/buildingplan.cpp | 39 ++++++++++++++------- plugins/buildingplan/buildingplan.h | 2 +- plugins/buildingplan/buildingplan_cycle.cpp | 19 +++++----- plugins/lua/buildingplan.lua | 5 +++ 6 files changed, 49 insertions(+), 22 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 65194dcfa..df153d43e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `buildingplan`: filters and global settings are now ignored when manually choosing items for a building +- `buildingplan`: if `suspendmanager` is running, then planned buildings will be left suspended when their items are all attached. `suspendmanager` will unsuspsend them for construction when it is safe to do so. - `stockpiles`: support applying stockpile configurations with fully enabled categories to stockpiles in worlds other than the one where the configuration was exported from - `stockpiles`: support partial application of a saved config based on dynamic filtering - `stockpiles`: additive and subtractive modes when applying a second stockpile configuration on top of a first diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index 71951f386..9cc7e68a2 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -20,7 +20,10 @@ building. Once all items are attached, the construction job will be unsuspended and a dwarf will come and build the building. If you have the `unsuspend` overlay enabled (it is enabled by default), then buildingplan-suspended buildings will appear with a ``P`` marker on the main map, as opposed to the -usual ``x`` marker for "regular" suspended buildings. +usual ``x`` marker for "regular" suspended buildings. If you have +`suspendmanager` running, then buildings will be left suspended when their +items are all attached and ``suspendmanager`` will unsuspend them for +construction when it is safe to do so. If you want to impose restrictions on which items are chosen for the buildings, buildingplan has full support for quality and material filters (see `below diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 101349fde..11e08c2a0 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -72,7 +72,7 @@ static Tasks tasks; void PlannedBuilding::remove(color_ostream &out) { DEBUG(status,out).print("removing persistent data for building %d\n", id); World::DeletePersistentData(bld_config); - if (planned_buildings.count(id) > 0) + if (planned_buildings.count(id)) planned_buildings.erase(id); } @@ -212,9 +212,9 @@ static DefaultItemFilters & get_item_filters(color_ostream &out, const BuildingT static command_result do_command(color_ostream &out, vector ¶meters); void buildingplan_cycle(color_ostream &out, Tasks &tasks, - unordered_map &planned_buildings); + unordered_map &planned_buildings, bool unsuspend_on_finalize); -static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb); +static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb, bool unsuspend_on_finalize); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { DEBUG(status,out).print("initializing %s\n", plugin_name); @@ -295,6 +295,16 @@ static int16_t get_subtype(df::building *bld) { return subtype; } +static bool is_suspendmanager_enabled(color_ostream &out) { + bool suspendmanager_enabled = false; + call_buildingplan_lua(&out, "is_suspendmanager_enabled", 0, 1, + Lua::DEFAULT_LUA_LAMBDA, + [&](lua_State *L){ + suspendmanager_enabled = lua_toboolean(L, -1); + }); + return suspendmanager_enabled; +} + DFhackCExport command_result plugin_load_data (color_ostream &out) { cycle_timestamp = 0; config = World::GetPersistentData(CONFIG_KEY); @@ -320,6 +330,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { vector building_configs; World::GetPersistentData(&building_configs, BLD_CONFIG_KEY); const size_t num_building_configs = building_configs.size(); + bool unsuspend_on_finalize = !is_suspendmanager_enabled(out); for (size_t idx = 0; idx < num_building_configs; ++idx) { PlannedBuilding pb(out, building_configs[idx]); df::building *bld = df::building::find(pb.id); @@ -334,7 +345,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { pb.remove(out); continue; } - registerPlannedBuilding(out, pb); + registerPlannedBuilding(out, pb, unsuspend_on_finalize); } return CR_OK; @@ -347,7 +358,8 @@ static void do_cycle(color_ostream &out) { cycle_timestamp = world->frame_counter; cycle_requested = false; - buildingplan_cycle(out, tasks, planned_buildings); + bool unsuspend_on_finalize = !is_suspendmanager_enabled(out); + buildingplan_cycle(out, tasks, planned_buildings, unsuspend_on_finalize); call_buildingplan_lua(&out, "signal_reset"); } @@ -469,7 +481,7 @@ vector getVectorIds(color_ostream &out, const df::job_it return ret; } -static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { +static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb, bool unsuspend_on_finalize) { df::building * bld = pb.getBuildingIfValidOrRemoveIfNot(out); if (!bld) return false; @@ -479,10 +491,15 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { return false; } + // suspend jobs + for (auto job : bld->jobs) + job->flags.bits.suspend = true; + auto job_items = bld->jobs[0]->job_items; if (isJobReady(out, job_items)) { // all items are already attached - finalizeBuilding(out, bld); + finalizeBuilding(out, bld, unsuspend_on_finalize); + pb.remove(out); return true; } @@ -508,10 +525,6 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { } } - // suspend jobs - for (auto job : bld->jobs) - job->flags.bits.suspend = true; - // add the planned buildings to our register planned_buildings.emplace(bld->id, pb); @@ -627,7 +640,9 @@ static bool addPlannedBuilding(color_ostream &out, df::building *bld) { BuildingTypeKey key(bld->getType(), subtype, bld->getCustomType()); PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key)); - return registerPlannedBuilding(out, pb); + + bool unsuspend_on_finalize = !is_suspendmanager_enabled(out); + return registerPlannedBuilding(out, pb, unsuspend_on_finalize); } static void doCycle(color_ostream &out) { diff --git a/plugins/buildingplan/buildingplan.h b/plugins/buildingplan/buildingplan.h index 756a81f83..26aa77fbc 100644 --- a/plugins/buildingplan/buildingplan.h +++ b/plugins/buildingplan/buildingplan.h @@ -52,4 +52,4 @@ bool itemPassesScreen(df::item * item); df::job_item getJobItemWithHeatSafety(const df::job_item *job_item, HeatSafety heat); bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter, const std::set &special); bool isJobReady(DFHack::color_ostream &out, const std::vector &jitems); -void finalizeBuilding(DFHack::color_ostream &out, df::building *bld); +void finalizeBuilding(DFHack::color_ostream &out, df::building *bld, bool unsuspend_on_finalize); diff --git a/plugins/buildingplan/buildingplan_cycle.cpp b/plugins/buildingplan/buildingplan_cycle.cpp index 803f1f130..3213e741d 100644 --- a/plugins/buildingplan/buildingplan_cycle.cpp +++ b/plugins/buildingplan/buildingplan_cycle.cpp @@ -111,7 +111,7 @@ static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) { // now all at 0, so there is no risk of having extra items attached. we don't // remove them to keep the "finalize with buildingplan active" path as similar // as possible to the "finalize with buildingplan disabled" path. -void finalizeBuilding(color_ostream &out, df::building *bld) { +void finalizeBuilding(color_ostream &out, df::building *bld, bool unsuspend_on_finalize) { DEBUG(cycle,out).print("finalizing building %d\n", bld->id); auto job = bld->jobs[0]; @@ -143,8 +143,10 @@ void finalizeBuilding(color_ostream &out, df::building *bld) { } // we're good to go! - job->flags.bits.suspend = false; - Job::checkBuildingsNow(); + if (unsuspend_on_finalize) { + job->flags.bits.suspend = false; + Job::checkBuildingsNow(); + } } static df::building * popInvalidTasks(color_ostream &out, Bucket &task_queue, @@ -181,7 +183,8 @@ static bool isAccessibleFrom(color_ostream &out, df::item *item, df::job *job) { static void doVector(color_ostream &out, df::job_item_vector_id vector_id, map &buckets, - unordered_map &planned_buildings) { + unordered_map &planned_buildings, + bool unsuspend_on_finalize) { auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); auto item_vector = df::global::world->items.other[other_id]; DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n", @@ -239,7 +242,7 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, --jitems[filter_idx]->quantity; task_queue.pop_front(); if (isJobReady(out, jitems)) { - finalizeBuilding(out, bld); + finalizeBuilding(out, bld, unsuspend_on_finalize); planned_buildings.at(id).remove(out); } if (task_queue.empty()) { @@ -274,7 +277,7 @@ struct VectorsToScanLast { }; void buildingplan_cycle(color_ostream &out, Tasks &tasks, - unordered_map &planned_buildings) { + unordered_map &planned_buildings, bool unsuspend_on_finalize) { static const VectorsToScanLast vectors_to_scan_last; DEBUG(cycle,out).print( @@ -292,7 +295,7 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks, } auto & buckets = it->second; - doVector(out, vector_id, buckets, planned_buildings); + doVector(out, vector_id, buckets, planned_buildings, unsuspend_on_finalize); if (buckets.empty()) { DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n", ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), @@ -306,7 +309,7 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks, if (tasks.count(vector_id) == 0) continue; auto & buckets = tasks[vector_id]; - doVector(out, vector_id, buckets, planned_buildings); + doVector(out, vector_id, buckets, planned_buildings, unsuspend_on_finalize); if (buckets.empty()) { DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n", ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 077470409..a33c1684f 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -51,6 +51,11 @@ function parse_commandline(...) return true end +function is_suspendmanager_enabled() + local ok, sm = pcall(reqscript, 'suspendmanager') + return ok and sm.isEnabled() +end + function get_num_filters(btype, subtype, custom) local filters = dfhack.buildings.getFiltersByType({}, btype, subtype, custom) return filters and #filters or 0