Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[timestream] ensure item scanning is not starved #1258

Merged
merged 1 commit into from
Aug 5, 2024
Merged
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
93 changes: 71 additions & 22 deletions timestream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ local argparse = require('argparse')
local repeatutil = require("repeat-util")
local utils = require('utils')

DEBUG = DEBUG or false
-- set to verbosity level
-- 1: dev warning messages
-- 2: timeskip tracing
-- 3: coverage tracing
DEBUG = DEBUG or 0

------------------------------------
-- state management
Expand Down Expand Up @@ -66,6 +70,20 @@ local TICK_TRIGGERS = {
-- "owed" ticks we would like to skip at the next opportunity
local timeskip_deficit = 0.0

-- coverage record for cur_year_tick % 50 so we can be sure that all items are being scanned
-- (DF scans 1/50th of items every tick based on cur_year_tick % 50)
-- we want every section hit at least once every 1000 ticks
local tick_coverage = {}

-- only throttle due to tick_coverage at most once per season tick to avoid clustering
local season_tick_throttled = false

local function reset_ephemeral_state()
timeskip_deficit = 0.0
tick_coverage = {}
season_tick_throttled = false
end

local function get_desired_timeskip(real_fps, desired_fps)
-- minus 1 to account for the current frame
return (desired_fps / real_fps) - 1
Expand Down Expand Up @@ -196,15 +214,15 @@ local function adjust_activities(timeskip)
-- countdown appears to never move from 0
decrement_counter(ev, 'countdown', timeskip)
elseif df.activity_event_harassmentst:is_instance(ev) then
if DEBUG then
if DEBUG >= 1 then
print('activity_event_harassmentst ready for analysis at index', i)
end
elseif df.activity_event_encounterst:is_instance(ev) then
if DEBUG then
if DEBUG >= 1 then
print('activity_event_encounterst ready for analysis at index', i)
end
elseif df.activity_event_reunionst:is_instance(ev) then
if DEBUG then
if DEBUG >= 1 then
print('activity_event_reunionst ready for analysis at index', i)
end
elseif df.activity_event_conversationst:is_instance(ev) then
Expand Down Expand Up @@ -244,41 +262,67 @@ local function adjust_activities(timeskip)
elseif df.activity_event_performancest:is_instance(ev) then
increment_counter(ev, 'current_position', timeskip)
elseif df.activity_event_store_objectst:is_instance(ev) then
if DEBUG then
if DEBUG >= 1 then
print('activity_event_store_objectst ready for analysis at index', i)
end
end
end
end
end

local function on_tick()
local function throttle_for_coverage(timeskip)
if season_tick_throttled then return timeskip end
for val=1,timeskip do
local coverage_slot = (df.global.cur_year_tick+val) % 50
if not tick_coverage[coverage_slot] then
season_tick_throttled = true
return val-1
end
end
return timeskip
end

local function record_coverage()
local coverage_slot = (df.global.cur_year_tick+1) % 50
if DEBUG >= 3 then
print('recording coverage for slot:', coverage_slot, tick_coverage[coverage_slot] and '' or '(newly covered)')
end
tick_coverage[coverage_slot] = true
end

local function on_tick_impl()
if df.global.cur_year_tick % 10 == 0 then
season_tick_throttled = false
if df.global.cur_year_tick % 1000 == 0 then
if DEBUG >= 1 then
if DEBUG >= 3 then
print('checking coverage')
end
for coverage_slot=0,49 do
if not tick_coverage[coverage_slot] then
print('coverage slot not covered:', coverage_slot)
end
end
end
tick_coverage = {}
end
end

local real_fps = math.max(1, dfhack.internal.getUnpausedFps())
if real_fps >= state.settings.fps then
timeskip_deficit = 0.0
return
end

local desired_timeskip = get_desired_timeskip(real_fps, state.settings.fps) + timeskip_deficit
local timeskip = math.floor(clamp_timeskip(desired_timeskip))

-- add some jitter so we don't fall into a constant pattern
-- this reduces the risk of repeatedly missing an unknown threshold
-- also keeps the game from looking robotic at lower frame rates
local jitter_strategy = math.random(1, 10)
if jitter_strategy <= 1 then
timeskip = math.random(0, timeskip)
elseif jitter_strategy <= 3 then
timeskip = math.random(math.max(0, timeskip-2), timeskip)
elseif jitter_strategy <= 5 then
timeskip = math.random(math.max(0, timeskip-4), timeskip)
end
local timeskip = throttle_for_coverage(clamp_timeskip(math.floor(desired_timeskip)))

-- don't let our deficit grow unbounded if we can never catch up
timeskip_deficit = math.min(desired_timeskip - timeskip, 100.0)

if DEBUG and (tonumber(DEBUG) or 0) >= 2 then
print(('timeskip (%d, +%.2f)'):format(timeskip, timeskip_deficit))
if DEBUG >= 2 then
print(('cur_year_tick: %d, timeskip: (%d, +%.2f)'):format(
df.global.cur_year_tick, timeskip, timeskip_deficit))
end
if timeskip <= 0 then return end

Expand All @@ -289,11 +333,16 @@ local function on_tick()
adjust_activities(timeskip)
end

local function on_tick()
on_tick_impl()
record_coverage()
end

------------------------------------
-- hook management

local function do_enable()
timeskip_deficit = 0.0
reset_ephemeral_state()
state.enabled = true
repeatutil.scheduleEvery(GLOBAL_KEY, 1, 'ticks', on_tick)
end
Expand Down