From 053370208575e69dec2cf42c1f679a01c9203400 Mon Sep 17 00:00:00 2001 From: CHOMPStation2StaffMirrorBot <94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com> Date: Sat, 26 Oct 2024 16:00:51 -0700 Subject: [PATCH] [MIRROR] Timer subsystem update (#9320) Co-authored-by: Selis <12716288+ItsSelis@users.noreply.github.com> Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> --- code/__defines/_helpers.dm | 1 - code/__defines/_lists.dm | 42 --------------- code/__defines/misc.dm | 1 - code/__defines/subsystems.dm | 52 +++++++++++++------ code/_helpers/_lists.dm | 40 ++++++++++++++ .../configuration/entries/general.dm | 3 ++ code/controllers/subsystems/timer.dm | 31 +++-------- code/datums/datum.dm | 35 +++++++++---- config/example/logging.txt | 3 ++ 9 files changed, 112 insertions(+), 96 deletions(-) diff --git a/code/__defines/_helpers.dm b/code/__defines/_helpers.dm index aae3931cb6..cba0f3222c 100644 --- a/code/__defines/_helpers.dm +++ b/code/__defines/_helpers.dm @@ -14,4 +14,3 @@ #define ICON_SIZE_X 32 /// The Y/Height dimension of ICON_SIZE. This will more than likely be the smaller axis. #define ICON_SIZE_Y 32 -yy diff --git a/code/__defines/_lists.dm b/code/__defines/_lists.dm index e98881d000..ebf923672d 100644 --- a/code/__defines/_lists.dm +++ b/code/__defines/_lists.dm @@ -46,46 +46,4 @@ #define reverseList(L) reverseRange(L.Copy()) -// CHOMPEdit Start -/// Passed into BINARY_INSERT to compare keys -#define COMPARE_KEY __BIN_LIST[__BIN_MID] -/// Passed into BINARY_INSERT to compare values -#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]] - -/**** - * Binary search sorted insert - * INPUT: Object to be inserted - * LIST: List to insert object into - * TYPECONT: The typepath of the contents of the list - * COMPARE: The object to compare against, usualy the same as INPUT - * COMPARISON: The variable on the objects to compare - * COMPTYPE: How should the values be compared? Either COMPARE_KEY or COMPARE_VALUE. - */ -#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ - do {\ - var/list/__BIN_LIST = LIST;\ - var/__BIN_CTTL = length(__BIN_LIST);\ - if(!__BIN_CTTL) {\ - __BIN_LIST += INPUT;\ - } else {\ - var/__BIN_LEFT = 1;\ - var/__BIN_RIGHT = __BIN_CTTL;\ - var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ - var ##TYPECONT/__BIN_ITEM;\ - while(__BIN_LEFT < __BIN_RIGHT) {\ - __BIN_ITEM = COMPTYPE;\ - if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\ - __BIN_LEFT = __BIN_MID + 1;\ - } else {\ - __BIN_RIGHT = __BIN_MID;\ - };\ - __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ - };\ - __BIN_ITEM = COMPTYPE;\ - __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\ - __BIN_LIST.Insert(__BIN_MID, INPUT);\ - };\ - } while(FALSE) -// CHOMPEdit End - #define islist(L) istype(L, /list) diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index f15d21292b..dd6fd0c06e 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -31,7 +31,6 @@ #define MAX_CLIENT_FPS 200 // Some arbitrary defines to be used by self-pruning global lists. (see master_controller) -#define PROCESS_KILL 26 // Used to trigger removal from a processing list. #define MAX_GEAR_COST 15 // Used in chargen for accessory loadout limit. // For secHUDs and medHUDs and variants. The number is the location of the image on the list hud_list of humans. diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 11872ac0d3..f1986a0359 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -19,7 +19,7 @@ * Timing should be based on how timing progresses on clients, not the server. * * Tracking this is more expensive, - * should only be used in conjuction with things that have to progress client side, such as + * should only be used in conjunction with things that have to progress client side, such as * animate() or sound() */ #define TIMER_CLIENT_TIME (1<<2) @@ -43,15 +43,36 @@ ///Empty ID define #define TIMER_ID_NULL -1 -#define INITIALIZATION_INSSATOMS 0 //New should not call Initialize -#define INITIALIZATION_INNEW_MAPLOAD 1 //New should call Initialize(TRUE) -#define INITIALIZATION_INNEW_REGULAR 2 //New should call Initialize(FALSE) +/// Used to trigger object removal from a processing list +#define PROCESS_KILL 26 -#define INITIALIZE_HINT_NORMAL 0 //Nothing happens -#define INITIALIZE_HINT_LATELOAD 1 //Call LateInitialize -#define INITIALIZE_HINT_QDEL 2 //Call qdel on the atom -//CHOMPEdit Begin -//type and all subtypes should always call Initialize in New() + +//! ## Initialization subsystem + +///New should not call Initialize +#define INITIALIZATION_INSSATOMS 0 +///New should call Initialize(TRUE) +#define INITIALIZATION_INNEW_MAPLOAD 1 +///New should call Initialize(FALSE) +#define INITIALIZATION_INNEW_REGULAR 2 + +//! ### Initialization hints + +///Nothing happens +#define INITIALIZE_HINT_NORMAL 0 +/** + * call LateInitialize at the end of all atom Initialization + * + * The item will be added to the late_loaders list, this is iterated over after + * initialization of subsystems is complete and calls LateInitalize on the atom + * see [this file for the LateIntialize proc](atom.html#proc/LateInitialize) + */ +#define INITIALIZE_HINT_LATELOAD 1 + +///Call qdel on the atom after initialization +#define INITIALIZE_HINT_QDEL 2 + +///type and all subtypes should always immediately call Initialize in New() #define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\ ..();\ if(!(flags & ATOM_INITIALIZED)) {\ @@ -75,24 +96,23 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G //! ### SS initialization hints /** - * Negative values incidate a failure or warning of some kind, positive are good. - * 0 and 1 are unused so that TRUE and FALSE are guarenteed to be invalid values. + * Negative values indicate a failure or warning of some kind, positive are good. + * 0 and 1 are unused so that TRUE and FALSE are guaranteed to be invalid values. */ /// Subsystem failed to initialize entirely. Print a warning, log, and disable firing. #define SS_INIT_FAILURE -2 -/// The default return value which must be overriden. Will succeed with a warning. +/// The default return value which must be overridden. Will succeed with a warning. #define SS_INIT_NONE -1 -/// Subsystem initialized sucessfully. +/// Subsystem initialized successfully. #define SS_INIT_SUCCESS 2 -/// Successful, but don't print anything. Useful if subsystem was disabled. +/// If your system doesn't need to be initialized (by being disabled or something) #define SS_INIT_NO_NEED 3 //! ### SS initialization load orders - // Subsystem init_order, from highest priority to lowest priority // Subsystems shutdown in the reverse of the order they initialize in // The numbers just define the ordering, they are meaningless otherwise. @@ -139,8 +159,6 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define INIT_ORDER_STATPANELS -98 #define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init. - - // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) #define FIRE_PRIORITY_PLAYERTIPS 5 diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index 5f5eae7f78..c4f1c33ee8 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -921,6 +921,46 @@ var/global/list/json_cache = list() ///Remove an untyped item to a list, taking care to handle list items by wrapping them in a list to remove the footgun #define UNTYPED_LIST_REMOVE(list, item) (list -= LIST_VALUE_WRAP_LISTS(item)) +/// Passed into BINARY_INSERT to compare keys +#define COMPARE_KEY __BIN_LIST[__BIN_MID] +/// Passed into BINARY_INSERT to compare values +#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]] + +/**** + * Binary search sorted insert + * INPUT: Object to be inserted + * LIST: List to insert object into + * TYPECONT: The typepath of the contents of the list + * COMPARE: The object to compare against, usualy the same as INPUT + * COMPARISON: The variable on the objects to compare + * COMPTYPE: How should the values be compared? Either COMPARE_KEY or COMPARE_VALUE. + */ +#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ + do {\ + var/list/__BIN_LIST = LIST;\ + var/__BIN_CTTL = length(__BIN_LIST);\ + if(!__BIN_CTTL) {\ + __BIN_LIST += INPUT;\ + } else {\ + var/__BIN_LEFT = 1;\ + var/__BIN_RIGHT = __BIN_CTTL;\ + var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + var ##TYPECONT/__BIN_ITEM;\ + while(__BIN_LEFT < __BIN_RIGHT) {\ + __BIN_ITEM = COMPTYPE;\ + if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + };\ + __BIN_ITEM = COMPTYPE;\ + __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\ + __BIN_LIST.Insert(__BIN_MID, INPUT);\ + };\ + } while(FALSE) + //CHOMPAdd start /proc/pick_weight(list/list_to_pick) var/total = 0 diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 555ae7c9fe..4bfcdf1092 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -60,6 +60,9 @@ /// logs graffiti /datum/config_entry/flag/log_graffiti +/// logs all timers in buckets on automatic bucket reset (Useful for timer debugging) +/datum/config_entry/flag/log_timers_on_bucket_reset + // FIXME: Unused ///datum/config_entry/string/nudge_script_path // where the nudge.py script is located // default = "nudge.py" diff --git a/code/controllers/subsystems/timer.dm b/code/controllers/subsystems/timer.dm index 7a5402e6fe..c314fa7d38 100644 --- a/code/controllers/subsystems/timer.dm +++ b/code/controllers/subsystems/timer.dm @@ -1,4 +1,5 @@ -#define BUCKET_LEN (round(10*(60/world.tick_lag), 1)) //how many ticks should we keep in the bucket. (1 minutes worth) +/// Controls how many buckets should be kept, each representing a tick. (1 minutes worth) +#define BUCKET_LEN (world.fps*1*60) /// Helper for getting the correct bucket for a given timer #define BUCKET_POS(timer) (((ROUND_UP((timer.timeToRun - timer.timer_subsystem.head_offset) / world.tick_lag)+1) % BUCKET_LEN) || BUCKET_LEN) /// Gets the maximum time at which timers will be invoked from buckets, used for deferring to secondary queue @@ -55,7 +56,7 @@ SUBSYSTEM_DEF(timer) bucket_resolution = world.tick_lag /datum/controller/subsystem/timer/stat_entry(msg) - msg = "B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)] RST:[bucket_reset_count]" // CHOMPEdit + msg = "B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)] RST:[bucket_reset_count]" return ..() /datum/controller/subsystem/timer/proc/dump_timer_buckets(full = TRUE) @@ -101,25 +102,7 @@ SUBSYSTEM_DEF(timer) WARNING(msg) if(bucket_auto_reset) bucket_resolution = 0 - - log_world("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") - for (var/i in 1 to length(bucket_list)) - var/datum/timedevent/bucket_head = bucket_list[i] - if (!bucket_head) - continue - - log_world("Active timers at index [i]:") - - var/datum/timedevent/bucket_node = bucket_head - var/anti_loop_check = 1000 - do - log_world(get_timer_debug_string(bucket_node)) - bucket_node = bucket_node.next - anti_loop_check-- - while(bucket_node && bucket_node != bucket_head && anti_loop_check) - log_world("Active timers in the second_queue queue:") - for(var/I in second_queue) - log_world(get_timer_debug_string(I)) + dump_timer_buckets(CONFIG_GET(flag/log_timers_on_bucket_reset)) // Process client-time timers if (next_clienttime_timer_index) @@ -541,7 +524,7 @@ SUBSYSTEM_DEF(timer) 2 = timeToRun, 3 = wait, 4 = flags, - 5 = callBack, /* Safe to hold this directly becasue it's never del'd */ + 5 = callBack, /* Safe to hold this directly because it's never del'd */ 6 = "[callBack.object]", 7 = text_ref(callBack.object), 8 = getcallingtype(), @@ -556,7 +539,7 @@ SUBSYSTEM_DEF(timer) 2 = timeToRun, 3 = wait, 4 = flags, - 5 = callBack, /* Safe to hold this directly becasue it's never del'd */ + 5 = callBack, /* Safe to hold this directly because it's never del'd */ 6 = "[callBack.object]", 7 = getcallingtype(), 8 = callBack.delegate, @@ -662,7 +645,7 @@ SUBSYSTEM_DEF(timer) hash_timer.hash = null // but keep it from accidentally deleting us else if (flags & TIMER_OVERRIDE) - hash_timer.hash = null // no need having it delete it's hash if we are going to replace it + hash_timer.hash = null // no need having it delete its hash if we are going to replace it qdel(hash_timer) else if (hash_timer.flags & TIMER_STOPPABLE) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index f83ce128c2..5dd6461fc9 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -22,7 +22,7 @@ var/list/open_tguis // CHOMPEdit: FIXME: open_uis /// Active timers with this datum as the target - var/list/_active_timers // CHOMPEdit + var/list/_active_timers /// Status traits attached to this datum. associative list of the form: list(trait name (string) = list(source1, source2, source3,...)) var/list/_status_traits @@ -71,9 +71,22 @@ // Create and destroy is weird and I wanna cover my bases var/harddel_deets_dumped = FALSE -// Default implementation of clean-up code. -// This should be overridden to remove all references pointing to the object being destroyed. -// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE. +/** + * Default implementation of clean-up code. + * + * This should be overridden to remove all references pointing to the object being destroyed, if + * you do override it, make sure to call the parent and return its return value by default + * + * Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion; + * in most cases this is [QDEL_HINT_QUEUE]. + * + * The base case is responsible for doing the following + * * Erasing timers pointing to this datum + * * Erasing compenents on this datum + * * Notifying datums listening to signals from this datum that we are going away + * + * Returns [QDEL_HINT_QUEUE] + */ /datum/proc/Destroy(force=FALSE) SHOULD_CALL_PARENT(TRUE) // SHOULD_NOT_SLEEP(TRUE) FIXME: Causing some big issues still @@ -81,12 +94,13 @@ weak_reference = null //ensure prompt GCing of weakref. //clear timers - var/list/timers = _active_timers // CHOMPEdit - _active_timers = null // CHOMPEdit - for(var/datum/timedevent/timer as anything in timers) - if (timer.spent) - continue - qdel(timer) + if(_active_timers) + var/list/timers = _active_timers + _active_timers = null + for(var/datum/timedevent/timer as anything in timers) + if (timer.spent && !(timer.flags & TIMER_DELETE_ME)) + continue + qdel(timer) #ifdef REFERENCE_TRACKING #ifdef REFERENCE_TRACKING_DEBUG @@ -110,7 +124,6 @@ _clear_signal_refs() //END: ECS SHIT - tag = null SStgui.close_uis(src) #ifdef REFERENCE_TRACKING diff --git a/config/example/logging.txt b/config/example/logging.txt index a3c2a9caeb..3d5e436d2b 100644 --- a/config/example/logging.txt +++ b/config/example/logging.txt @@ -44,6 +44,9 @@ LOG_PDA ## log graffiti drawings LOG_GRAFFITI +## Log all timers on timer auto reset +# LOG_TIMERS_ON_BUCKET_RESET + ## log world.log messages # LOG_WORLD_OUTPUT