mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
## About The Pull Request ### Reftracking BS Alllright so reftracking is slow, really really slow. That's a problem for me, both because I want it to be fast so I can more efficiently torture players by running it on live, but also because it impedes both local and CI runs. So I've set out to micro optimize the DoSearchVar proc, one of the hottest in the game. I've done this in a few different ways. #### The simple shit Removing redundant proc args Yeeting assoc arg setting (extra cost) Moving if statements around to prioritize the more common case Ignoring empty lists. #### The not simple shit Throwing our snowflake list checking into the sun (Background, byond has some special lists that cannot be accessed like an assoc list, trying to will lead to runtimes) The way we handle this involves inspecting their ref string, and it eats a LOT of time. Faster then to mark all the lists we know are special by var name, and then use try/catch to detect and silence anything that sneaks through (this is on the order of like 1/3 per run, kinda curious what they are tbh) Thanks to MSO for the idea for this btw. Removes the vars and logic that tied ref searching to clients. It's not how this code is used, and it slows everything else down for really no reason Added support for handing in a known "hanging reference" count, and then searching for that. This lets us early exit the ref search if we find everything we were looking for, which is REALLY powerful, and why I asked for refcount() in the first place. ### Harddel Fixes [Fixes some harddels w gulag stuff born of the 515 one way ref issues](046d7daa03) [Ensures proximity cameras clean their ref to their proximity datum if it's deleted](ff607e9ccb) [Deleting a pipe connected via the gas_machine_connector datum to a machine should also delete that machine (harddel fix)](9eecca22e7) ## Why It's Good For The Game All this combined speeds up refsearching massively, on the order of hundreds of seconds, and makes it far less time consuming for both CI and running on live. I'll be bullying some servers semi soon, want to see what I can cut out.
208 lines
7.6 KiB
Plaintext
208 lines
7.6 KiB
Plaintext
#ifdef REFERENCE_TRACKING
|
|
#define REFSEARCH_RECURSE_LIMIT 64
|
|
|
|
/datum/proc/find_references(references_to_clear = INFINITY)
|
|
if(usr?.client)
|
|
if(tgui_alert(usr,"Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", list("Yes", "No")) != "Yes")
|
|
return
|
|
|
|
src.references_to_clear = references_to_clear
|
|
//this keeps the garbage collector from failing to collect objects being searched for in here
|
|
SSgarbage.can_fire = FALSE
|
|
|
|
_search_references()
|
|
//restart the garbage collector
|
|
SSgarbage.can_fire = TRUE
|
|
SSgarbage.update_nextfire(reset_time = TRUE)
|
|
|
|
/datum/proc/_search_references()
|
|
log_reftracker("Beginning search for references to a [type], looking for [references_to_clear] refs.")
|
|
|
|
var/starting_time = world.time
|
|
//Time to search the whole game for our ref
|
|
DoSearchVar(GLOB, "GLOB", starting_time) //globals
|
|
log_reftracker("Finished searching globals")
|
|
if(src.references_to_clear == 0)
|
|
return
|
|
|
|
//Yes we do actually need to do this. The searcher refuses to read weird lists
|
|
//And global.vars is a really weird list
|
|
var/global_vars = list()
|
|
for(var/key in global.vars)
|
|
global_vars[key] = global.vars[key]
|
|
|
|
DoSearchVar(global_vars, "Native Global", starting_time)
|
|
log_reftracker("Finished searching native globals")
|
|
if(src.references_to_clear == 0)
|
|
return
|
|
|
|
for(var/datum/thing in world) //atoms (don't beleive its lies)
|
|
DoSearchVar(thing, "World -> [thing.type]", starting_time)
|
|
if(src.references_to_clear == 0)
|
|
break
|
|
log_reftracker("Finished searching atoms")
|
|
if(src.references_to_clear == 0)
|
|
return
|
|
|
|
for(var/datum/thing) //datums
|
|
DoSearchVar(thing, "Datums -> [thing.type]", starting_time)
|
|
if(src.references_to_clear == 0)
|
|
break
|
|
log_reftracker("Finished searching datums")
|
|
if(src.references_to_clear == 0)
|
|
return
|
|
|
|
//Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
|
|
#ifndef REFERENCE_DOING_IT_LIVE
|
|
for(var/client/thing) //clients
|
|
DoSearchVar(thing, "Clients -> [thing.type]", starting_time)
|
|
if(src.references_to_clear == 0)
|
|
break
|
|
log_reftracker("Finished searching clients")
|
|
if(src.references_to_clear == 0)
|
|
return
|
|
#endif
|
|
|
|
log_reftracker("Completed search for references to a [type].")
|
|
|
|
/datum/proc/DoSearchVar(potential_container, container_name, search_time, recursion_count, is_special_list)
|
|
if(recursion_count >= REFSEARCH_RECURSE_LIMIT)
|
|
log_reftracker("Recursion limit reached. [container_name]")
|
|
return
|
|
|
|
if(references_to_clear == 0)
|
|
return
|
|
|
|
//Check each time you go down a layer. This makes it a bit slow, but it won't effect the rest of the game at all
|
|
#ifndef FIND_REF_NO_CHECK_TICK
|
|
CHECK_TICK
|
|
#endif
|
|
|
|
if(isdatum(potential_container))
|
|
var/datum/datum_container = potential_container
|
|
if(datum_container.last_find_references == search_time)
|
|
return
|
|
|
|
datum_container.last_find_references = search_time
|
|
var/list/vars_list = datum_container.vars
|
|
|
|
var/is_atom = FALSE
|
|
var/is_area = FALSE
|
|
if(isatom(datum_container))
|
|
is_atom = TRUE
|
|
if(isarea(datum_container))
|
|
is_area = TRUE
|
|
for(var/varname in vars_list)
|
|
var/variable = vars_list[varname]
|
|
if(islist(variable))
|
|
//Fun fact, vis_locs don't count for references
|
|
if(varname == "vars" || (is_atom && (varname == "vis_locs" || varname == "overlays" || varname == "underlays" || varname == "filters" || varname == "verbs" || (is_area && varname == "contents"))))
|
|
continue
|
|
// We do this after the varname check to avoid area contents (reading it incures a world loop's worth of cost)
|
|
if(!length(variable))
|
|
continue
|
|
DoSearchVar(variable,\
|
|
"[container_name] [datum_container.ref_search_details()] -> [varname] (list)",\
|
|
search_time,\
|
|
recursion_count + 1,\
|
|
/*is_special_list = */ is_atom && (varname == "contents" || varname == "vis_contents" || varname == "locs"))
|
|
else if(variable == src)
|
|
#ifdef REFERENCE_TRACKING_DEBUG
|
|
if(SSgarbage.should_save_refs)
|
|
if(!found_refs)
|
|
found_refs = list()
|
|
found_refs[varname] = TRUE
|
|
continue //End early, don't want these logging
|
|
else
|
|
log_reftracker("Found [type] [text_ref(src)] in [datum_container.type]'s [datum_container.ref_search_details()] [varname] var. [container_name]")
|
|
#else
|
|
log_reftracker("Found [type] [text_ref(src)] in [datum_container.type]'s [datum_container.ref_search_details()] [varname] var. [container_name]")
|
|
#endif
|
|
references_to_clear -= 1
|
|
if(references_to_clear == 0)
|
|
log_reftracker("All references to [type] [text_ref(src)] found, exiting.")
|
|
return
|
|
continue
|
|
|
|
else if(islist(potential_container))
|
|
var/list/potential_cache = potential_container
|
|
for(var/element_in_list in potential_cache)
|
|
//Check normal sublists
|
|
if(islist(element_in_list))
|
|
if(length(element_in_list))
|
|
DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", search_time, recursion_count + 1)
|
|
//Check normal entrys
|
|
else if(element_in_list == src)
|
|
#ifdef REFERENCE_TRACKING_DEBUG
|
|
if(SSgarbage.should_save_refs)
|
|
if(!found_refs)
|
|
found_refs = list()
|
|
found_refs[potential_cache] = TRUE
|
|
continue
|
|
else
|
|
log_reftracker("Found [type] [text_ref(src)] in list [container_name].")
|
|
#else
|
|
log_reftracker("Found [type] [text_ref(src)] in list [container_name].")
|
|
#endif
|
|
|
|
// This is dumb as hell I'm sorry
|
|
// I don't want the garbage subsystem to count as a ref for the purposes of this number
|
|
// If we find all other refs before it I want to early exit, and if we don't I want to keep searching past it
|
|
var/ignore_ref = FALSE
|
|
var/list/queues = SSgarbage.queues
|
|
for(var/list/queue in queues)
|
|
if(potential_cache in queue)
|
|
ignore_ref = TRUE
|
|
break
|
|
if(ignore_ref)
|
|
log_reftracker("[container_name] does not count as a ref for our count")
|
|
else
|
|
references_to_clear -= 1
|
|
if(references_to_clear == 0)
|
|
log_reftracker("All references to [type] [text_ref(src)] found, exiting.")
|
|
return
|
|
|
|
if(!isnum(element_in_list) && !is_special_list)
|
|
// This exists to catch an error that throws when we access a special list
|
|
// is_special_list is a hint, it can be wrong
|
|
try
|
|
var/assoc_val = potential_cache[element_in_list]
|
|
//Check assoc sublists
|
|
if(islist(assoc_val))
|
|
if(length(assoc_val))
|
|
DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", search_time, recursion_count + 1)
|
|
//Check assoc entry
|
|
else if(assoc_val == src)
|
|
#ifdef REFERENCE_TRACKING_DEBUG
|
|
if(SSgarbage.should_save_refs)
|
|
if(!found_refs)
|
|
found_refs = list()
|
|
found_refs[potential_cache] = TRUE
|
|
continue
|
|
else
|
|
log_reftracker("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
|
|
#else
|
|
log_reftracker("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
|
|
#endif
|
|
references_to_clear -= 1
|
|
if(references_to_clear == 0)
|
|
log_reftracker("All references to [type] [text_ref(src)] found, exiting.")
|
|
return
|
|
catch
|
|
// So if it goes wrong we kill it
|
|
is_special_list = TRUE
|
|
log_reftracker("Curiosity: [container_name] lead to an error when acessing [element_in_list], what is it?")
|
|
|
|
#undef REFSEARCH_RECURSE_LIMIT
|
|
#endif
|
|
|
|
// Kept outside the ifdef so overrides are easy to implement
|
|
|
|
/// Return info about us for reference searching purposes
|
|
/// Will be logged as a representation of this datum if it's a part of a search chain
|
|
/datum/proc/ref_search_details()
|
|
return text_ref(src)
|
|
|
|
/datum/callback/ref_search_details()
|
|
return "[text_ref(src)] (obj: [object] proc: [delegate] args: [json_encode(arguments)] user: [user?.resolve() || "null"])"
|