diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm index 058115f4fd..c7cbf7b591 100644 --- a/code/__defines/_compile_options.dm +++ b/code/__defines/_compile_options.dm @@ -28,6 +28,7 @@ //#define REFERENCE_TRACKING #ifdef REFERENCE_TRACKING +//#define FIND_REF_NO_CHECK_TICK //CHOMPEdit new ref tracking ///Should we be logging our findings or not #define REFERENCE_TRACKING_LOG diff --git a/code/datums/reference_tracking.dm b/code/datums/reference_tracking.dm index 0f317b026c..d9d0b5a38e 100644 --- a/code/datums/reference_tracking.dm +++ b/code/datums/reference_tracking.dm @@ -1,3 +1,4 @@ +//CHOMPEdit -- This file is unticked, see reference_tracking_ch.dm #ifdef REFERENCE_TRACKING /datum/proc/find_references(skip_alert = TRUE) diff --git a/code/datums/reference_tracking_ch.dm b/code/datums/reference_tracking_ch.dm new file mode 100644 index 0000000000..a3cd94a611 --- /dev/null +++ b/code/datums/reference_tracking_ch.dm @@ -0,0 +1,211 @@ +#ifdef REFERENCE_TRACKING +#define REFSEARCH_RECURSE_LIMIT 64 + +/datum + var/references_to_clear + +/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.next_fire = world.time + world.tick_lag + //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"])" diff --git a/code/game/atoms_movable_ch.dm b/code/game/atoms_movable_ch.dm index fa2fd014c5..b1ae68fb7c 100644 --- a/code/game/atoms_movable_ch.dm +++ b/code/game/atoms_movable_ch.dm @@ -12,6 +12,10 @@ set_listening(listening_recursive) /atom/movable/Destroy() + if(em_block) + cut_overlay(em_block) + UnregisterSignal(em_block, COMSIG_PARENT_QDELETING) + QDEL_NULL(em_block) . = ..() set_listening(NON_LISTENING_ATOM) diff --git a/code/modules/ai/ai_holder_combat.dm b/code/modules/ai/ai_holder_combat.dm index 591292f11d..9bef7cdada 100644 --- a/code/modules/ai/ai_holder_combat.dm +++ b/code/modules/ai/ai_holder_combat.dm @@ -15,7 +15,7 @@ ai_log("engage_target() : Entering.", AI_LOG_DEBUG) // Can we still see them? - if(!target || !can_attack(target)) + if(QDELETED(target) || !can_attack(target)) //CHOMPEdit ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE) if(lose_target()) // We lost them (returns TRUE if we found something else to do) ai_log("engage_target() : Pursuing other options (last seen, or a new target).", AI_LOG_TRACE) diff --git a/vorestation.dme b/vorestation.dme index 1ad2d46725..c56a446c99 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -405,7 +405,7 @@ #include "code\datums\organs.dm" #include "code\datums\position_point_vector.dm" #include "code\datums\progressbar.dm" -#include "code\datums\reference_tracking.dm" +#include "code\datums\reference_tracking_ch.dm" #include "code\datums\riding.dm" #include "code\datums\signals.dm" #include "code\datums\soul_link.dm"