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

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

@ -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<string> &parameters);
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) {
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<PersistentDataItem> 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<df::job_item_vector_id> 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) {

@ -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<std::string> &special);
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
// 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<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 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<int32_t, PlannedBuilding> &planned_buildings) {
unordered_map<int32_t, PlannedBuilding> &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(),

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