#ifdef EXTOOLS_REFERENCE_TRACKING GLOBAL_LIST_EMPTY(deletion_failures) /world/proc/enable_reference_tracking() if (fexists(EXTOOLS)) //call(EXTOOLS, "ref_tracking_initialize")() /proc/get_back_references(datum/D) CRASH("/proc/get_back_references not hooked by extools, reference tracking will not function!") /proc/get_forward_references(datum/D) CRASH("/proc/get_forward_references not hooked by extools, reference tracking will not function!") /proc/clear_references(datum/D) return /datum/admins/proc/view_refs(atom/D in world) //it actually supports datums as well but byond no likey set category = "Debug" set name = "View References" if(!check_rights(R_DEBUG) || !D) return var/list/backrefs = get_back_references(D) if(isnull(backrefs)) var/datum/browser/popup = new(usr, "ref_view", "
Error
") popup.set_content("Reference tracking not enabled") popup.open(FALSE) return var/list/frontrefs = get_forward_references(D) var/list/dat = list() dat += "

References of \ref[D] - [D]


\[Refresh\]
" dat += "

Back references - these things hold references to this object.

" dat += "" dat += "" for(var/ref in backrefs) var/datum/backreference = ref if(isnull(backreference)) dat += "" if(istype(backreference)) dat += "" else if(islist(backreference)) dat += "" else dat += "" dat += "
RefTypeVariable NameFollow
GC'd Reference
[REF(backreference)][backreference.type][backrefs[backreference]]\[Follow\]
[REF(backreference)]list[backrefs[backreference]]\[Follow\]
Weird reference type. Add more debugging checks.

" dat += "

Forward references - this object is referencing those things.

" dat += "" dat += "" for(var/ref in frontrefs) var/datum/backreference = frontrefs[ref] dat += "" dat += "
Variable nameRefTypeFollow
[ref][REF(backreference)][backreference.type]\[Follow\]

" dat = dat.Join() var/datum/browser/popup = new(usr, "ref_view", "
References of \ref[D]
") popup.set_content(dat) popup.open(FALSE) /datum/admins/proc/view_del_failures() set category = "Debug" set name = "View Deletion Failures" if(!check_rights(R_DEBUG)) return var/list/dat = list("") for(var/t in GLOB.deletion_failures) if(isnull(t)) dat += "" continue var/datum/thing = t dat += "" dat += "
GC'd Reference | Clear Nulls
\ref[thing] | [thing.type][thing.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(thing)]

" dat = dat.Join() var/datum/browser/popup = new(usr, "del_failures", "
Deletion Failures
") popup.set_content(dat) popup.open(FALSE) /datum/proc/find_references() testing("Beginning search for references to a [type].") var/list/backrefs = get_back_references(src) for(var/ref in backrefs) if(isnull(ref)) log_world("## TESTING: Datum reference found, but gone now.") continue if(islist(ref)) log_world("## TESTING: Found [type] \ref[src] in list.") continue var/datum/datum_ref = ref if(!istype(datum_ref)) log_world("## TESTING: Found [type] \ref[src] in unknown type reference: [datum_ref].") return log_world("## TESTING: Found [type] \ref[src] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""]") message_admins("Found [type] \ref[src] [ADMIN_VV(src)] in [datum_ref.type][datum_ref.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(datum_ref)]") testing("Completed search for references to a [type].") #endif #ifdef REFERENCE_TRACKING /datum/proc/find_references(skip_alert) running_find_references = type if(usr?.client) if(usr.client.running_find_references) log_reftracker("CANCELLED search for references to a [usr.client.running_find_references].") usr.client.running_find_references = null running_find_references = null //restart the garbage collector SSgarbage.can_fire = TRUE SSgarbage.update_nextfire(reset_time = TRUE) return if(!skip_alert && 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") running_find_references = null return //this keeps the garbage collector from failing to collect objects being searched for in here SSgarbage.can_fire = FALSE if(usr?.client) usr.client.running_find_references = type log_reftracker("Beginning search for references to a [type].") var/starting_time = world.time //Time to search the whole game for our ref DoSearchVar(GLOB, "GLOB") //globals log_reftracker("Finished searching globals") for(var/datum/thing in world) //atoms (don't beleive its lies) DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time) log_reftracker("Finished searching atoms") for(var/datum/thing) //datums DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time) log_reftracker("Finished searching datums") //Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself for(var/client/thing) //clients DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time) log_reftracker("Finished searching clients") log_reftracker("Completed search for references to a [type].") if(usr?.client) usr.client.running_find_references = null running_find_references = null //restart the garbage collector SSgarbage.can_fire = TRUE SSgarbage.update_nextfire(reset_time = TRUE) /datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time) #ifdef REFERENCE_TRACKING_DEBUG if(!found_refs && SSgarbage.should_save_refs) found_refs = list() #endif if(usr?.client && !usr.client.running_find_references) return if(!recursive_limit) log_reftracker("Recursion limit reached. [container_name]") 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(istype(potential_container, /datum)) 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 for(var/varname in vars_list) #ifndef FIND_REF_NO_CHECK_TICK CHECK_TICK #endif if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references continue var/variable = vars_list[varname] if(variable == src) #ifdef REFERENCE_TRACKING_DEBUG if(SSgarbage.should_save_refs) found_refs[varname] = TRUE #endif log_reftracker("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]") continue if(islist(variable)) DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time) else if(islist(potential_container)) var/normal = IS_NORMAL_LIST(potential_container) var/list/potential_cache = potential_container for(var/element_in_list in potential_cache) #ifndef FIND_REF_NO_CHECK_TICK CHECK_TICK #endif //Check normal entrys if(element_in_list == src) #ifdef REFERENCE_TRACKING_DEBUG if(SSgarbage.should_save_refs) found_refs[potential_cache] = TRUE #endif log_reftracker("Found [type] \ref[src] in list [container_name].") continue var/assoc_val = null if(!isnum(element_in_list) && normal) assoc_val = potential_cache[element_in_list] //Check assoc entrys if(assoc_val == src) #ifdef REFERENCE_TRACKING_DEBUG if(SSgarbage.should_save_refs) found_refs[potential_cache] = TRUE #endif log_reftracker("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]") continue //We need to run both of these checks, since our object could be hiding in either of them //Check normal sublists if(islist(element_in_list)) DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time) //Check assoc sublists if(islist(assoc_val)) DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", recursive_limit - 1, search_time) /proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE) thing_to_del.qdel_and_find_ref_if_fail(force) /datum/proc/qdel_and_find_ref_if_fail(force = FALSE) SSgarbage.reference_find_on_fail["\ref[src]"] = TRUE qdel(src, force) #endif