Skip to content

Commit efe1e1e

Browse files
committed
implement interface, move documentation, provide stats
1 parent 3c4723d commit efe1e1e

3 files changed

Lines changed: 98 additions & 18 deletions

File tree

docs/plugins/suspendmanager.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
suspendmanager
2+
==============
3+
4+
.. dfhack-tool::
5+
:summary: Intelligently suspend and unsuspend jobs.
6+
:tags: fort auto jobs
7+
8+
This tool will watch your active jobs and:
9+
10+
- unsuspend jobs that have become suspended due to inaccessible materials,
11+
items temporarily in the way, or worker dwarves getting scared by wildlife
12+
- suspend most construction jobs that would prevent a dwarf from reaching another
13+
construction job, such as when building a wall corner or high walls
14+
- suspend construction jobs on top of a smoothing, engraving or track carving
15+
designation. This prevents the construction job from being completed first,
16+
which would erase the designation.
17+
- suspend construction jobs that would cave in immediately on completion,
18+
such as when building walls or floors next to grates/bars.
19+
20+
Usage
21+
-----
22+
23+
``suspendmanager``
24+
Display the current status
25+
26+
``suspendmanager (enable|disable)``
27+
Enable or disable ``suspendmanager``
28+
29+
``suspendmanager set preventblocking (true|false)``
30+
Prevent construction jobs from blocking each others (enabled by default). See `suspend`.

plugins/lua/suspendmanager.lua

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ function isBuildingPlanJob(job)
2626
return building and building.mat_type == -1
2727
end
2828

29-
function runOnce(prevent_blocking)
29+
function runOnce(prevent_blocking, quiet)
3030
suspendmanager_runOnce(prevent_blocking)
31+
local stats = suspendmanager_getStatus()
32+
if (not quiet) then
33+
print(stats)
34+
end
3135
end
3236

33-
3437
-- Overlay Widgets
3538
StatusOverlay = defclass(StatusOverlay, overlay.OverlayWidget)
3639
StatusOverlay.ATTRS{

plugins/suspendmanager.cpp

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "Core.h"
22
#include "Debug.h"
3+
#include "LuaTools.h"
34
#include "PluginManager.h"
45
#include "TileTypes.h"
56

@@ -18,6 +19,7 @@
1819
#include "df/world.h"
1920

2021
#include <bitset>
22+
#include <format>
2123
#include <functional>
2224
#include <ranges>
2325
#include <string>
@@ -76,6 +78,21 @@ inline bool isExternalReason(Reason reason) {
7678
return reason == Reason::BUILDINGPLAN || reason == Reason::UNDER_WATER;
7779
}
7880

81+
static string reasonToString(Reason reason) {
82+
switch (reason) {
83+
case Reason::DEADEND:
84+
return "Blocks another build job";
85+
case Reason::RISK_BLOCKING:
86+
return "May block another build job";
87+
case Reason::UNSUPPORTED:
88+
return "Would collapse immediately";
89+
case Reason::ERASE_DESIGNATION:
90+
return "Waiting for carve/smooth/engrave";
91+
default:
92+
return "External reason";
93+
}
94+
}
95+
7996
using df::coord;
8097

8198
// set() is constexpr starting with C++23
@@ -497,10 +514,30 @@ class SuspendManager {
497514

498515
std::map<int,Reason> suspensions;
499516
std::set<int> leadsToDeadend;
517+
size_t num_suspend = 0, num_unsuspend = 0;
500518

501519
public:
502520
bool prevent_blocking = true;
503521

522+
// gather some statistics about the last call to do_cycle()
523+
string getStatus (color_ostream &out) {
524+
std::map<Reason,int> stats;
525+
526+
for (auto [_ , reason] : suspensions) {
527+
stats[reason == Reason::DEADEND ? Reason::RISK_BLOCKING : reason]++;
528+
}
529+
530+
string res;
531+
res += std::format("suspended {} and unsuspend {} jobs\n", num_suspend, num_unsuspend);
532+
res += std::format("maintaining {} suspension reasons\n", suspensions.size());
533+
for (auto stat : stats) {
534+
res += std::format(" {:5} {}\n", stat.second, reasonToString(stat.first));
535+
}
536+
537+
DEBUG(control,out).print("res: %s", res.c_str());
538+
return res;
539+
}
540+
504541
void refresh(color_ostream &out)
505542
{
506543
DEBUG(cycle,out).print("starting refresh, prevent blocking is %s\n",
@@ -551,7 +588,7 @@ class SuspendManager {
551588

552589
void do_cycle (color_ostream &out) {
553590
refresh(out);
554-
size_t num_suspend = 0, num_unsuspend = 0;
591+
num_suspend = 0, num_unsuspend = 0;
555592

556593
Reason reason;
557594

@@ -588,18 +625,7 @@ class SuspendManager {
588625
std::string suspensionDescription (df::job *job) {
589626
Reason reason;
590627
if (tryGetReason(job,reason)) {
591-
switch (reason) {
592-
case Reason::DEADEND:
593-
return "Blocks another build job";
594-
case Reason::RISK_BLOCKING:
595-
return "May block another build job";
596-
case Reason::UNSUPPORTED:
597-
return "Would collapse immediately";
598-
case Reason::ERASE_DESIGNATION:
599-
return "Waiting for carve/smooth/engrave";
600-
default:
601-
return "External reason";
602-
}
628+
return reasonToString(reason);
603629
}
604630
return "not suspended by suspendmanager";
605631
}
@@ -699,7 +725,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) {
699725
return CR_OK;
700726
}
701727

702-
703728
static command_result do_command(color_ostream &out, vector<string> &parameters) {
704729
// be sure to suspend the core if any DF state is read or modified
705730
CoreSuspender suspend;
@@ -711,11 +736,26 @@ static command_result do_command(color_ostream &out, vector<string> &parameters)
711736

712737
if (parameters[0] == "now") {
713738
do_cycle(out);
739+
return CR_OK;
740+
} else if (parameters[0] == "enable") {
741+
return plugin_enable(out,true);
742+
} else if (parameters[0] == "disable") {
743+
return plugin_enable(out,false);
744+
} else if (parameters[0] == "set" && parameters[1] == "preventblocking") {
745+
if (parameters[3] == "true") {
746+
suspendmanager_instance->prevent_blocking = true;
747+
if (is_enabled) do_cycle(out);
748+
return CR_OK;
749+
} else if (parameters[3] == "false") {
750+
suspendmanager_instance->prevent_blocking = false;
751+
if (is_enabled) do_cycle(out);
752+
return CR_OK;
753+
} else
754+
return CR_WRONG_USAGE;
714755
} else {
715756
return CR_WRONG_USAGE;
716757
}
717758

718-
return CR_OK;
719759
}
720760

721761
static void jobCompletedHandler(color_ostream& out, void* ptr) {
@@ -738,6 +778,7 @@ static void do_cycle(color_ostream &out) {
738778
suspendmanager_instance->do_cycle(out);
739779
}
740780

781+
741782
/////////////////////////////////////////////////////
742783
// Lua exports
743784
//
@@ -763,15 +804,21 @@ static bool suspendmanager_isKeptSuspended(df::job *job) {
763804
}
764805

765806
static void suspendmanager_runOnce(color_ostream &out, bool blocking) {
807+
auto save = suspendmanager_instance->prevent_blocking;
766808
suspendmanager_instance->prevent_blocking = blocking;
767809
do_cycle(out);
768-
suspendmanager_instance->prevent_blocking = true;
810+
suspendmanager_instance->prevent_blocking = save;
811+
}
812+
813+
static string suspendmanager_getStatus(color_ostream &out) {
814+
return suspendmanager_instance->getStatus(out);
769815
}
770816

771817

772818
DFHACK_PLUGIN_LUA_FUNCTIONS {
773819
DFHACK_LUA_FUNCTION(suspendmanager_suspensionDescription),
774820
DFHACK_LUA_FUNCTION(suspendmanager_isKeptSuspended),
775821
DFHACK_LUA_FUNCTION(suspendmanager_runOnce),
822+
DFHACK_LUA_FUNCTION(suspendmanager_getStatus),
776823
DFHACK_LUA_END
777824
};

0 commit comments

Comments
 (0)