Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions devel/jobwatch.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
-- Watches unit job assignments and reports transitions in the console
--@ enable = true
--[====[

devel/jobwatch
==============
Watches ``unit.job.current_job`` for all units in ``world.units.active``
and prints a line whenever a unit gains, loses, or switches jobs —
including switches between two jobs of the same type, which is how
claim races (a unit losing its target posting to another unit) appear.

Useful for observing job churn that is otherwise invisible: the
assignment auction runs inside the tick and can only be seen through
its effects on ``current_job``.

:enable|start: Begin watching
:disable|stop: Stop watching
:unit <id>|all: Only report transitions for one unit id (or all units)
:verbose: Also print each job's posting index and flags
]====]

active = active or false
filter_unit = filter_unit or nil --as:number
verbose = verbose or false
last_jobs = last_jobs or {} --as:{[number]:{id:number,desc:string}}

local function describe(unit)
local job = unit.job.current_job
if not job then return -1, 'none' end
local s = df.job_type[job.job_type] or ('?type ' .. job.job_type)
if verbose then
s = ('%s [id=%d posting=%d do_now=%s special=%s]'):format(
s, job.id, job.posting_index,
tostring(job.flags.do_now), tostring(job.flags.special))
end
return job.id, s
end

local function check()
local seen = {}
for _, unit in ipairs(df.global.world.units.active) do
local id, desc = describe(unit)
seen[unit.id] = {id=id, desc=desc}
local prev = last_jobs[unit.id]
if prev and prev.id ~= id
and (not filter_unit or unit.id == filter_unit) then
print(('%d unit %d %s: %s -> %s'):format(
df.global.cur_year_tick, unit.id,
dfhack.df2console(dfhack.units.getReadableName(unit)),
prev.desc, desc))
end
end
last_jobs = seen
if active then
dfhack.timeout(1, 'ticks', check)
end
end

local args = {...}
if dfhack_flags and dfhack_flags.enable then
table.insert(args, dfhack_flags.enable_state and 'enable' or 'disable')
end

local cmd = args[1]
if cmd == 'enable' or cmd == 'start' then
if not active then
active = true
dfhack.timeout(1, 'ticks', check)
end
elseif cmd == 'disable' or cmd == 'stop' then
active = false
elseif cmd == 'unit' then
filter_unit = (args[2] == 'all') and nil or tonumber(args[2])
print('jobwatch: unit filter = ' .. tostring(filter_unit or 'all'))
elseif cmd == 'verbose' then
verbose = not verbose
print('jobwatch: verbose = ' .. tostring(verbose))
else
print(dfhack.script_help())
end
26 changes: 26 additions & 0 deletions docs/devel/jobwatch.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
devel/jobwatch
==============

.. dfhack-tool::
:summary: Report unit job transitions as they happen.
:tags: dev

Aids script development and reverse engineering by observing job churn that is
otherwise invisible: the game's assignment pass runs inside the tick, so only
its effects on ``unit.job.current_job`` can be seen from Lua. This tool polls
all active units once per tick and prints a line whenever a unit gains, loses,
or switches jobs -- including switches between two jobs of the same type, which
is how claim races appear.

Usage
-----

::

enable devel/jobwatch
devel/jobwatch unit <id>|all
devel/jobwatch verbose
disable devel/jobwatch

``unit`` restricts output to a single unit id. ``verbose`` additionally prints
each job's id, posting index, and ``do_now``/``special`` flags.