buildingplan - suspendmanager integration

develop
Myk Taylor 2023-03-27 02:24:56 -07:00
parent b9767740e4
commit 8da7e216a4
No known key found for this signature in database
6 changed files with 49 additions and 22 deletions

@ -52,6 +52,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Misc Improvements ## Misc Improvements
- `buildingplan`: filters and global settings are now ignored when manually choosing items for a building - `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 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`: 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 - `stockpiles`: additive and subtractive modes when applying a second stockpile configuration on top of a first

@ -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` and a dwarf will come and build the building. If you have the `unsuspend`
overlay enabled (it is enabled by default), then buildingplan-suspended 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 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, 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 buildingplan has full support for quality and material filters (see `below

@ -72,7 +72,7 @@ static Tasks tasks;
void PlannedBuilding::remove(color_ostream &out) { void PlannedBuilding::remove(color_ostream &out) {
DEBUG(status,out).print("removing persistent data for building %d\n", id); DEBUG(status,out).print("removing persistent data for building %d\n", id);
World::DeletePersistentData(bld_config); World::DeletePersistentData(bld_config);
if (planned_buildings.count(id) > 0) if (planned_buildings.count(id))
planned_buildings.erase(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<string> &parameters); static command_result do_command(color_ostream &out, vector<string> &parameters);
void buildingplan_cycle(color_ostream &out, Tasks &tasks, void buildingplan_cycle(color_ostream &out, Tasks &tasks,
unordered_map<int32_t, PlannedBuilding> &planned_buildings); unordered_map<int32_t, PlannedBuilding> &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 <PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DEBUG(status,out).print("initializing %s\n", plugin_name); DEBUG(status,out).print("initializing %s\n", plugin_name);
@ -295,6 +295,16 @@ static int16_t get_subtype(df::building *bld) {
return subtype; 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) { DFhackCExport command_result plugin_load_data (color_ostream &out) {
cycle_timestamp = 0; cycle_timestamp = 0;
config = World::GetPersistentData(CONFIG_KEY); config = World::GetPersistentData(CONFIG_KEY);
@ -320,6 +330,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {
vector<PersistentDataItem> building_configs; vector<PersistentDataItem> building_configs;
World::GetPersistentData(&building_configs, BLD_CONFIG_KEY); World::GetPersistentData(&building_configs, BLD_CONFIG_KEY);
const size_t num_building_configs = building_configs.size(); 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) { for (size_t idx = 0; idx < num_building_configs; ++idx) {
PlannedBuilding pb(out, building_configs[idx]); PlannedBuilding pb(out, building_configs[idx]);
df::building *bld = df::building::find(pb.id); df::building *bld = df::building::find(pb.id);
@ -334,7 +345,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) {
pb.remove(out); pb.remove(out);
continue; continue;
} }
registerPlannedBuilding(out, pb); registerPlannedBuilding(out, pb, unsuspend_on_finalize);
} }
return CR_OK; return CR_OK;
@ -347,7 +358,8 @@ static void do_cycle(color_ostream &out) {
cycle_timestamp = world->frame_counter; cycle_timestamp = world->frame_counter;
cycle_requested = false; 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"); call_buildingplan_lua(&out, "signal_reset");
} }
@ -469,7 +481,7 @@ vector<df::job_item_vector_id> getVectorIds(color_ostream &out, const df::job_it
return ret; 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); df::building * bld = pb.getBuildingIfValidOrRemoveIfNot(out);
if (!bld) if (!bld)
return false; return false;
@ -479,10 +491,15 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) {
return false; return false;
} }
// suspend jobs
for (auto job : bld->jobs)
job->flags.bits.suspend = true;
auto job_items = bld->jobs[0]->job_items; auto job_items = bld->jobs[0]->job_items;
if (isJobReady(out, job_items)) { if (isJobReady(out, job_items)) {
// all items are already attached // all items are already attached
finalizeBuilding(out, bld); finalizeBuilding(out, bld, unsuspend_on_finalize);
pb.remove(out);
return true; 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 // add the planned buildings to our register
planned_buildings.emplace(bld->id, pb); 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()); BuildingTypeKey key(bld->getType(), subtype, bld->getCustomType());
PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key)); 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) { static void doCycle(color_ostream &out) {

@ -52,4 +52,4 @@ bool itemPassesScreen(df::item * item);
df::job_item getJobItemWithHeatSafety(const df::job_item *job_item, HeatSafety heat); 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<std::string> &special); bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter, const std::set<std::string> &special);
bool isJobReady(DFHack::color_ostream &out, const std::vector<df::job_item *> &jitems); bool isJobReady(DFHack::color_ostream &out, const std::vector<df::job_item *> &jitems);
void finalizeBuilding(DFHack::color_ostream &out, df::building *bld); void finalizeBuilding(DFHack::color_ostream &out, df::building *bld, bool unsuspend_on_finalize);

@ -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 // 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 // remove them to keep the "finalize with buildingplan active" path as similar
// as possible to the "finalize with buildingplan disabled" path. // 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); DEBUG(cycle,out).print("finalizing building %d\n", bld->id);
auto job = bld->jobs[0]; auto job = bld->jobs[0];
@ -143,8 +143,10 @@ void finalizeBuilding(color_ostream &out, df::building *bld) {
} }
// we're good to go! // we're good to go!
if (unsuspend_on_finalize) {
job->flags.bits.suspend = false; job->flags.bits.suspend = false;
Job::checkBuildingsNow(); Job::checkBuildingsNow();
}
} }
static df::building * popInvalidTasks(color_ostream &out, Bucket &task_queue, 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, static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
map<string, Bucket> &buckets, map<string, Bucket> &buckets,
unordered_map<int32_t, PlannedBuilding> &planned_buildings) { unordered_map<int32_t, PlannedBuilding> &planned_buildings,
bool unsuspend_on_finalize) {
auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id);
auto item_vector = df::global::world->items.other[other_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", 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; --jitems[filter_idx]->quantity;
task_queue.pop_front(); task_queue.pop_front();
if (isJobReady(out, jitems)) { if (isJobReady(out, jitems)) {
finalizeBuilding(out, bld); finalizeBuilding(out, bld, unsuspend_on_finalize);
planned_buildings.at(id).remove(out); planned_buildings.at(id).remove(out);
} }
if (task_queue.empty()) { if (task_queue.empty()) {
@ -274,7 +277,7 @@ struct VectorsToScanLast {
}; };
void buildingplan_cycle(color_ostream &out, Tasks &tasks, void buildingplan_cycle(color_ostream &out, Tasks &tasks,
unordered_map<int32_t, PlannedBuilding> &planned_buildings) { unordered_map<int32_t, PlannedBuilding> &planned_buildings, bool unsuspend_on_finalize) {
static const VectorsToScanLast vectors_to_scan_last; static const VectorsToScanLast vectors_to_scan_last;
DEBUG(cycle,out).print( DEBUG(cycle,out).print(
@ -292,7 +295,7 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks,
} }
auto & buckets = it->second; auto & buckets = it->second;
doVector(out, vector_id, buckets, planned_buildings); doVector(out, vector_id, buckets, planned_buildings, unsuspend_on_finalize);
if (buckets.empty()) { if (buckets.empty()) {
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n", DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), 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) if (tasks.count(vector_id) == 0)
continue; continue;
auto & buckets = tasks[vector_id]; 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()) { if (buckets.empty()) {
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n", DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),

@ -51,6 +51,11 @@ function parse_commandline(...)
return true return true
end end
function is_suspendmanager_enabled()
local ok, sm = pcall(reqscript, 'suspendmanager')
return ok and sm.isEnabled()
end
function get_num_filters(btype, subtype, custom) function get_num_filters(btype, subtype, custom)
local filters = dfhack.buildings.getFiltersByType({}, btype, subtype, custom) local filters = dfhack.buildings.getFiltersByType({}, btype, subtype, custom)
return filters and #filters or 0 return filters and #filters or 0