Skip to content

Commit 0be4be7

Browse files
authored
Merge pull request #4908 from Tjudge1/patch-2
Port timestream to cpp
2 parents 05e1109 + 18ef51b commit 0be4be7

5 files changed

Lines changed: 819 additions & 0 deletions

File tree

docs/changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Template for new versions:
6767
- Quickfort blueprint library: ``aquifer_tap`` blueprint now designated at priority 3 and marks the stairway tile below the tap in "blueprint" mode to prevent drips while the drainage pipe is being prepared
6868
- `preserve-rooms`: automatically release room reservations for captured squad members. we were kidding ourselves with our optimistic kept reservations. they're unlikely to come back : ((
6969
- `buildingplan`: add value info to item selection dialog (effectively ungrouping items with different values) and add sorting by value
70+
- `timestream`: reduce CPU utilization
7071

7172
## Documentation
7273
- Dreamfort: add link to Dreamfort tutorial youtube series: https://www.youtube.com/playlist?list=PLzXx9JcB9oXxmrtkO1y8ZXzBCFEZrKxve

docs/plugins/timestream.rst

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
timestream
2+
==========
3+
4+
.. dfhack-tool::
5+
:summary: Fix FPS death.
6+
:tags: fort gameplay fps
7+
8+
Do you remember when you first start a new fort, your initial 7 dwarves zip
9+
around the screen and get things done so quickly? As a player, you never had
10+
to wait for your initial dwarves to move across the map. Do you wish that your
11+
fort of 200 dwarves and 800 animals could be as zippy? This tool can help.
12+
13+
``timestream`` keeps the game running quickly by tweaking the game simulation
14+
according to the frames per second that your computer can support. This means
15+
that your dwarves spend the same amount of time relative to the in-game
16+
calendar to do their tasks, but the time that you, the player, have to wait for
17+
the dwarves to do get things done is reduced. The result is that the dwarves in
18+
your fully developed fort appear as energetic as the dwarves in a newly created
19+
fort, and mature forts are much more fun to play.
20+
21+
Note that whereas your dwarves zip around like you're running at 100 FPS, the
22+
vanilla onscreen FPS counter, if enabled, will still show a lower number. See
23+
the `Technical details`_ section below if you're interested in what's going on
24+
under the hood.
25+
26+
Usage
27+
-----
28+
29+
::
30+
31+
enable timestream
32+
timestream [status]
33+
timestream set <key> <value>
34+
timestream reset
35+
36+
Examples
37+
--------
38+
39+
``enable timestream``
40+
Start adjusting the simulation to run at the currently configured apparent
41+
FPS (default is whatever you have the FPS cap set to in the DF settings,
42+
which is usually 100).
43+
44+
``timestream set fps 50``
45+
Tweak the simulation so it runs at an apparent 50 frames per second.
46+
47+
``timestream reset``
48+
Reset settings to defaults: the vanilla FPS cap with no calendar speed
49+
advantage or disadvantage.
50+
51+
Settings
52+
--------
53+
54+
:fps: Set the target simulated FPS. The default target FPS is whatever you have
55+
the FPS cap set to in the DF settings, and the minimum is 10. Setting the
56+
target FPS *below* your current actual FPS will have no effect. You have
57+
to set the vanilla FPS cap for that. Set a target FPS of -1 to make no
58+
adjustment at all to the apparent FPS of the game.
59+
60+
Technical details
61+
-----------------
62+
63+
So what is this magic? How does this tool make it look like the game is
64+
suddenly running so much faster?
65+
66+
Maybe an analogy would help. Pretend you're standing at the bottom of a
67+
staircase and you want to walk up the stairs. You can walk up one stair every
68+
second, and there are 100 stairs, so it will take you 100 seconds to walk up
69+
all the stairs.
70+
71+
Now let's use the Hand of Armok and fiddle with reality a bit. Let's say that
72+
instead of walking up one step, you walk up 5 steps at once. At the same time
73+
we move the wall clock 5 seconds ahead. If you look at the clock after reaching
74+
the top of the stairs, it will still look like it took 100 seconds, but you did
75+
it all in fewer "steps".
76+
77+
That's essentially what ``timestream`` is doing to the game. All "actions" in
78+
DF have counters associated with them. For example, when a dwarf wants to walk
79+
to the next tile, a counter is initialized to 8. Every "tick" of the game (the
80+
"frame" in FPS) decrements that counter by 1. When the counter gets to zero,
81+
the dwarf appears on the next tile.
82+
83+
When ``timestream`` is active, it monitors all those counters and makes them
84+
decrement more per tick. It then balances things out by proportionally
85+
advancing the in-game calendar. Therefore, more "happens" per step, and DF has
86+
to simulate fewer "steps" for the same amount of work to get done.
87+
88+
The cost of this simplification is that the world becomes less "smooth". As the
89+
discrepancy between the actual and simulated FPS grows, more and more dwarves
90+
will move to their next tiles at *exactly* the same time. Moreover, the rate of
91+
action completion per unit is effectively capped at the granularity of the
92+
simulation, so very fast units (say, those in a martial trance) will lose some
93+
of their advantage.
94+
95+
Limitations
96+
-----------
97+
98+
DF does critial game tasks every 10 calendar ticks that must not be skipped, so
99+
`timestream` cannot advance more than 9 ticks at a time. This puts an upper
100+
limit on how much `timestream` can help. With the default target of 100 FPS,
101+
the game will start showing signs of slowdown if the real FPS drops below about
102+
15. The interface will also become less responsive to mouse gestures as the
103+
real FPS drops.
104+
105+
Finally, not all aspects of the game are perfectly adjusted. For example,
106+
armies on world map will move at the same (real-time) rate regardless of
107+
changes that ``timestream`` is making to the calendar.
108+
109+
Here is a (possibly incomplete) list of game elements that are not adjusted by
110+
``timestream`` and will appear "slow" in-game:
111+
112+
- Army movement across the world map (including raids sent out from the fort)
113+
- Liquid movement and evaporation

plugins/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ if(BUILD_SUPPORTED)
165165
dfhack_plugin(suspendmanager suspendmanager.cpp COMPILE_FLAGS_GCC -fno-gnu-unique LINK_LIBRARIES lua)
166166
dfhack_plugin(tailor tailor.cpp LINK_LIBRARIES lua)
167167
dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
168+
dfhack_plugin(timestream timestream.cpp LINK_LIBRARIES lua)
168169
#dfhack_plugin(title-folder title-folder.cpp)
169170
dfhack_plugin(tubefill tubefill.cpp)
170171
add_subdirectory(tweak)

plugins/lua/timestream.lua

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
local _ENV = mkmodule('plugins.timestream')
2+
3+
function migrate_old_config()
4+
local GLOBAL_KEY = 'timestream'
5+
local old_config = dfhack.persistent.getSiteData(GLOBAL_KEY)
6+
if not old_config then return end
7+
if old_config.enabled then dfhack.run_command('enable', GLOBAL_KEY) end
8+
if old_config.settings and type(old_config.settings) == 'table' and tonumber(old_config.settings.fps) then
9+
timestream_setFps(tonumber(old_config.settings.fps))
10+
end
11+
end
12+
13+
local function do_set(setting_name, arg)
14+
local numarg = tonumber(arg)
15+
if setting_name ~= 'fps' or not numarg then
16+
qerror('must specify setting and value')
17+
end
18+
timestream_setFps(arg)
19+
print(('set %s to %s'):format(setting_name, timestream_getFps()))
20+
end
21+
22+
local function do_reset()
23+
timestream_resetSettings()
24+
end
25+
26+
local function print_status()
27+
print('timestream is ' .. (isEnabled() and 'enabled' or 'not enabled'))
28+
print()
29+
print('target FPS is set to: ' .. tostring(timestream_getFps()))
30+
end
31+
32+
function parse_commandline(args)
33+
local command = table.remove(args, 1)
34+
35+
if command == 'help' then
36+
return false
37+
elseif command == 'set' then
38+
do_set(args[1], args[2])
39+
elseif command == 'reset' then
40+
do_reset()
41+
elseif not command or command == 'status' then
42+
print_status()
43+
else
44+
return false
45+
end
46+
47+
return true
48+
end
49+
50+
return _ENV

0 commit comments

Comments
 (0)