Files
siliconsandGitHub 9d6a141e5b mc update: init stages, better subsystem init tracking, etc (#6857)
modifies the hollow metrics API; that'll probably be implemented soon
adds init stages
changes SSatoms to not abuse its `initialized` variable

closes #6858
2024-11-15 14:41:25 -07:00

181 lines
6.7 KiB
Plaintext

//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
/// if we fall behind this much, we reset buckets
#define BUCKET_CATASTROPHIC_LAG_THRESHOLD AI_SCHEDULING_TOLERANCE
/// this many buckets are kept
#define BUCKET_AMOUNT ((AI_SCHEDULING_TOLERANCE + AI_SCHEDULING_LIMIT) * 0.1 * world.fps)
/**
* Handles ticking AI holders
*/
SUBSYSTEM_DEF(ai_holders)
name = "AI Holders"
subsystem_flags = NONE
priority = FIRE_PRIORITY_AI_HOLDERS
init_order = INIT_ORDER_AI_HOLDERS
init_stage = INIT_STAGE_EARLY
wait = 0
/// all ticking ai holders
var/static/list/datum/ai_holder/active_holders
/// rolling bucket list; these hold the head node of linked ai_holders.
///
/// the head bucket is the last one we processed
/// after subsystem ticks, if it isn't interrupted, the head bucket
/// should be empty.
///
/// placing stuff onto head bucket = run immediately
/// placing stuff onto next bucket = run the tick after head
var/tmp/list/buckets
/// world.time of bucket head
var/bucket_head_time
/// index of bucket head
var/bucket_head_index
/// world.fps buckets were made for
var/bucket_fps
/datum/controller/subsystem/ai_holders/Initialize()
active_holders = list()
rebuild()
return SS_INIT_SUCCESS
/datum/controller/subsystem/ai_holders/on_ticklag_changed(old_ticklag, new_ticklag)
rebuild()
return ..()
/datum/controller/subsystem/ai_holders/Recover()
rebuild()
return ..()
/datum/controller/subsystem/ai_holders/fire(resumed)
if(bucket_fps != world.fps)
subsystem_log("rebuilding buckets - world.fps [world.fps] != [bucket_fps]")
rebuild()
return
var/elapsed = world.time - bucket_head_time
if((elapsed) > BUCKET_CATASTROPHIC_LAG_THRESHOLD)
subsystem_log("rebuilding buckets - lagged too far behind")
rebuild()
return
// cache for speed
var/list/buckets = src.buckets
var/buckets_needing_processed = round(elapsed * 0.1 * world.fps)
var/buckets_processed
var/bucket_amount = length(buckets)
var/head_index = bucket_head_index
var/now_index_raw = bucket_head_index + round(DS2TICKS(elapsed))
// go through buckets
for(buckets_processed in 0 to buckets_needing_processed)
var/bucket_offset = ((head_index + buckets_processed) % bucket_amount) + 1
var/datum/ai_holder/being_processed
while((being_processed = buckets[bucket_offset]))
being_processed.tick(++being_processed.ticking_cycles)
// check if we are still ticking; if not, we got ejected, so we abort as we don't need to eject or insert again
if(buckets[bucket_offset] == being_processed)
// eject; we don't change being_processed.ticking_(next|previous)
if(being_processed.ticking_next == being_processed)
#ifdef CF_AI_HOLDER_DEBUG_ASSERTIONS
ASSERT(buckets[bucket_offset] == being_processed)
#endif
buckets[bucket_offset] = null
else
buckets[bucket_offset] = being_processed.ticking_next
being_processed.ticking_next.ticking_previous = being_processed.ticking_previous
being_processed.ticking_previous.ticking_next = being_processed.ticking_next
// insert; we now set its ticking_(next|previous)
// note that we don't do catchup
var/inject_offset = ((now_index_raw + round(DS2TICKS(being_processed.ticking))) % bucket_amount) + 1
if(buckets[inject_offset])
var/datum/ai_holder/being_injected = buckets[inject_offset]
being_processed.ticking_next = being_injected
being_processed.ticking_previous = being_injected.ticking_previous
being_processed.ticking_previous.ticking_next = being_processed
being_processed.ticking_next.ticking_previous = being_processed
else
buckets[inject_offset] = being_processed
being_processed.ticking_next = being_processed.ticking_previous = being_processed
being_processed.ticking_position = inject_offset
if(MC_TICK_CHECK)
break
if(state != SS_RUNNING)
// got tick check'd; break so we stay on this bucket for now
// otherwise we'd go forwards 1 and drop this bucket
break
bucket_head_index = ((bucket_head_index + buckets_processed) % length(buckets)) + 1
bucket_head_time = bucket_head_time + TICKS2DS(buckets_processed)
/datum/controller/subsystem/ai_holders/proc/bucket_insert(datum/ai_holder/holder)
#ifdef CF_AI_HOLDER_DEBUG_ASSERTIONS
ASSERT(holder.ticking <= AI_SCHEDULING_LIMIT)
ASSERT(!holder.ticking_position)
#endif
var/new_index = round(bucket_head_index + ((world.time - bucket_head_time) * 0.1 * world.fps) + rand(0, holder.ticking * 0.1 * world.fps))
new_index = (new_index % length(buckets)) + 1
var/datum/ai_holder/existing = buckets[new_index]
if(existing)
existing.ticking_next.ticking_previous = holder
holder.ticking_next = existing.ticking_next
holder.ticking_previous = existing
existing.ticking_next = holder
else
buckets[new_index] = holder
holder.ticking_next = holder.ticking_previous = holder
holder.ticking_position = new_index
/datum/controller/subsystem/ai_holders/proc/bucket_evict(datum/ai_holder/holder)
ASSERT(holder.ticking_position)
// if we're linking to ourselves, we're the head of the linked list
if(holder.ticking_next == holder)
#ifdef CF_AI_HOLDER_DEBUG_ASSERTIONS
ASSERT(buckets[holder.ticking_position] == holder)
#endif
buckets[holder.ticking_position] = null
// else, eject from linked list
else
if(buckets[holder.ticking_position] == holder)
buckets[holder.ticking_position] = holder.ticking_next
holder.ticking_next.ticking_previous = holder.ticking_previous
holder.ticking_previous.ticking_next = holder.ticking_next
holder.ticking_next = holder.ticking_previous = holder.ticking_position = null
/**
* perform error checking
* rebuild all buckets
*/
/datum/controller/subsystem/ai_holders/proc/rebuild()
bucket_head_time = world.time
bucket_head_index = 1
bucket_fps = world.fps
// todo; recover active_holders as well maybe?
buckets = new /list(BUCKET_AMOUNT)
var/bucket_amount = length(buckets)
for(var/datum/ai_holder/holder as anything in active_holders)
if(!istype(holder))
active_holders -= holder
continue
if(!holder.ticking || (holder.ticking > AI_SCHEDULING_LIMIT))
holder.ticking = null
holder.ticking_position = null
holder.ticking_next = null
holder.ticking_previous = null
active_holders -= holder
continue
var/new_index = bucket_head_index + rand(0, round(holder.ticking * 0.1 * world.fps))
new_index = (new_index % bucket_amount) + 1
var/datum/ai_holder/existing = buckets[new_index]
if(existing)
existing.ticking_next.ticking_previous = holder
holder.ticking_next = existing.ticking_next
holder.ticking_previous = existing
existing.ticking_next = holder
else
buckets[new_index] = holder
holder.ticking_next = holder.ticking_previous = holder
holder.ticking_position = new_index
#undef BUCKET_CATASTROPHIC_LAG_THRESHOLD
#undef BUCKET_AMOUNT