diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm
index 4296e3c2e98..3c9d40c1dfa 100644
--- a/code/__DEFINES/qdel.dm
+++ b/code/__DEFINES/qdel.dm
@@ -6,11 +6,18 @@
#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference.
#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste.
-#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm.
- //if TESTING is enabled, qdel will call this object's find_references() verb.
-#define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails.
//defines for the gc_destroyed var
+#ifdef LEGACY_REFERENCE_TRACKING
+/** If LEGACY_REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb.
+ *
+ * Functionally identical to QDEL_HINT_QUEUE if GC_FAILURE_HARD_LOOKUP is not enabled in _compiler_options.dm.
+*/
+#define QDEL_HINT_FINDREFERENCE 5
+/// Behavior as QDEL_HINT_FINDREFERENCE, but only if the GC fails and a hard delete is forced.
+#define QDEL_HINT_IFFAIL_FINDREFERENCE 6
+#endif
+
#define GC_QUEUE_CHECK 1
#define GC_QUEUE_HARDDELETE 2
#define GC_QUEUE_COUNT 2 //increase this when adding more steps.
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 1c7daf5ddf9..129cc33b700 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -11,16 +11,27 @@
#ifdef TESTING
#define DATUMVAR_DEBUGGING_MODE
-//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing.
- //implies FIND_REF_NO_CHECK_TICK
+/*
+* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete.
+*
+* * Requires TESTING to be defined to work.
+*/
+//#define REFERENCE_TRACKING
-//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping
+///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools.
+//#define LEGACY_REFERENCE_TRACKING
+#ifdef LEGACY_REFERENCE_TRACKING
+
+///Use the legacy reference on things hard deleting by default.
+//#define GC_FAILURE_HARD_LOOKUP
+#ifdef GC_FAILURE_HARD_LOOKUP
+#define FIND_REF_NO_CHECK_TICK
+#endif //ifdef GC_FAILURE_HARD_LOOKUP
+
+#endif //ifdef LEGACY_REFERENCE_TRACKING
//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
-#endif
-
-//#define REFERENCE_TRACKING //Enables extools-powered reference tracking system, letting you see what is
- //referencing objects that refuse to hard delete
+#endif //ifdef TESTING
//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER
@@ -47,10 +58,6 @@
#warn compiling in TESTING mode. testing() debug messages will be visible.
#endif
-#ifdef GC_FAILURE_HARD_LOOKUP
-#define FIND_REF_NO_CHECK_TICK
-#endif
-
#ifdef TRAVISBUILDING
#define UNIT_TESTS
#endif
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 094fc1217dd..11731b79c78 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -24,8 +24,7 @@ SUBSYSTEM_DEF(garbage)
//Queue
var/list/queues
-
- #ifdef TESTING
+ #ifdef LEGACY_REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
#endif
@@ -136,8 +135,8 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
- #ifdef TESTING
- reference_find_on_fail -= refID //It's deleted we don't care anymore.
+ #ifdef LEGACY_REFERENCE_TRACKING
+ reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -147,18 +146,32 @@ SUBSYSTEM_DEF(garbage)
fail_counts[level]++
switch (level)
if (GC_QUEUE_CHECK)
- #ifdef TESTING
+ #ifdef REFERENCE_TRACKING
+ D.find_references()
+ #elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
- D.find_references()
+ D.find_references_legacy()
#ifdef GC_FAILURE_HARD_LOOKUP
else
- D.find_references()
+ D.find_references_legacy()
#endif
reference_find_on_fail -= refID
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
+ #ifdef TESTING
+ log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
+ var/client/admin = c
+ if(!check_rights_for(admin, R_ADMIN))
+ continue
+ to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
+ #endif
+ #ifdef REFERENCE_TRACKING
+ GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
+ continue
+ #endif
I.failures++
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
@@ -242,11 +255,6 @@ SUBSYSTEM_DEF(garbage)
/datum/qdel_item/New(mytype)
name = "[mytype]"
-#ifdef TESTING
-/proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE)
- SSgarbage.reference_find_on_fail[REF(D)] = TRUE
- qdel(D, force)
-#endif
// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
@@ -301,16 +309,14 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
- if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
+ #ifdef LEGACY_REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D)
- #ifdef TESTING
- D.find_references()
- #endif
+ D.find_references_legacy()
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
SSgarbage.Queue(D)
- #ifdef TESTING
SSgarbage.reference_find_on_fail[REF(D)] = TRUE
- #endif
+ #endif
else
#ifdef TESTING
if(!I.no_hint)
@@ -320,116 +326,3 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
-
-#ifdef TESTING
-
-/datum/verb/find_refs()
- set category = "Debug"
- set name = "Find References"
- set src in world
-
- find_references(FALSE)
-
-/datum/proc/find_references(skip_alert)
- running_find_references = type
- if(usr && usr.client)
- if(usr.client.running_find_references)
- testing("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 = 1
- SSgarbage.next_fire = world.time + world.tick_lag
- return
-
- if(!skip_alert)
- if(alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
- running_find_references = null
- return
-
- //this keeps the garbage collector from failing to collect objects being searched for in here
- SSgarbage.can_fire = 0
-
- if(usr && usr.client)
- usr.client.running_find_references = type
-
- testing("Beginning search for references to a [type].")
- last_find_references = world.time
-
- DoSearchVar(GLOB) //globals
- for(var/datum/thing in world) //atoms (don't beleive it's lies)
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/datum/thing) //datums
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/client/thing) //clients
- DoSearchVar(thing, "World -> [thing]")
-
- testing("Completed search for references to a [type].")
- if(usr && usr.client)
- usr.client.running_find_references = null
- running_find_references = null
-
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
-
-/datum/verb/qdel_then_find_references()
- set category = "Debug"
- set name = "qdel() then Find References"
- set src in world
-
- qdel(src, TRUE) //Force.
- if(!running_find_references)
- find_references(TRUE)
-
-/datum/verb/qdel_then_if_fail_find_references()
- set category = "Debug"
- set name = "qdel() then Find References if GC failure"
- set src in world
-
- qdel_and_find_ref_if_fail(src, TRUE)
-
-/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
- if(usr && usr.client && !usr.client.running_find_references)
- return
- if (!recursive_limit)
- return
-
- if(istype(X, /datum))
- var/datum/D = X
- if(D.last_find_references == last_find_references)
- return
-
- D.last_find_references = last_find_references
- var/list/L = D.vars
-
- for(var/varname in L)
- if (varname == "vars")
- continue
- var/variable = L[varname]
-
- if(variable == src)
- testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
-
- else if(islist(variable))
- DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
-
- else if(islist(X))
- var/normal = IS_NORMAL_LIST(X)
- for(var/I in X)
- if (I == src)
- testing("Found [src.type] \ref[src] in list [Xname].")
-
- else if (I && !isnum(I) && normal && X[I] == src)
- testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
-
- else if (islist(I))
- DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
-
-#ifndef FIND_REF_NO_CHECK_TICK
- CHECK_TICK
-#endif
-
-#endif
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 0290b3d8934..5dfedf37d35 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -170,7 +170,10 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/test_cardpack_distribution,
/client/proc/print_cards,
/datum/admins/proc/create_or_modify_area,
- /client/proc/view_refs
+#ifdef REFERENCE_TRACKING
+ /datum/admins/proc/view_refs,
+ /datum/admins/proc/view_del_failures,
+#endif
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
GLOBAL_PROTECT(admin_verbs_possess)
diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm
index e35afa901a9..bd8ce48a43f 100644
--- a/code/modules/admin/view_variables/reference_tracking.dm
+++ b/code/modules/admin/view_variables/reference_tracking.dm
@@ -1,3 +1,7 @@
+#ifdef REFERENCE_TRACKING
+
+GLOBAL_LIST_EMPTY(deletion_failures)
+
/world/proc/enable_reference_tracking()
var/extools = world.GetConfig("env", "EXTOOLS_DLL") || (world.system_type == MS_WINDOWS ? "./byond-extools.dll" : "./libbyond-extools.so")
if (fexists(extools))
@@ -12,17 +16,20 @@
/proc/clear_references(datum/D)
return
-/client/proc/view_refs(atom/D) //it actually supports datums as well but byond no likey
+/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))
+ if(!check_rights(R_DEBUG) || !D)
return
var/list/backrefs = get_back_references(D)
if(isnull(backrefs))
- usr << browse("Reference tracking not enabled", "window=ref_view")
+ 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\]
"
@@ -30,16 +37,190 @@
dat += ""
dat += "| Ref | Type | Variable Name | Follow | "
for(var/ref in backrefs)
- var/datum/R = ref
- dat += "
|---|
| [REF(R)] | [R.type] | [backrefs[R]] | \[Follow\] |
"
+ var/datum/backreference = ref
+ if(isnull(backreference))
+ dat += "| GC'd Reference |
"
+ if(istype(backreference))
+ dat += "| [REF(backreference)] | [backreference.type] | [backrefs[backreference]] | \[Follow\] |
"
+ else if(islist(backreference))
+ dat += "| [REF(backreference)] | list | [backrefs[backreference]] | \[Follow\] |
"
+ else
+ dat += "| Weird reference type. Add more debugging checks. |
"
dat += "
"
dat += "Forward references - this object is referencing those things.
"
dat += ""
dat += "| Variable name | Ref | Type | Follow | "
for(var/ref in frontrefs)
- var/datum/R = frontrefs[ref]
- dat += "
|---|
| [ref] | [REF(R)] | [R.type] | \[Follow\] |
"
+ var/datum/backreference = frontrefs[ref]
+ dat += "| [ref] | [REF(backreference)] | [backreference.type] | \[Follow\] |
"
dat += "
"
dat = dat.Join()
- usr << browse(dat, "window=ref_view") //Done this way rather than tgui to facilitate porting to other codebases or even byond games
+ 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 += "| GC'd Reference | Clear Nulls |
"
+ continue
+ var/datum/thing = t
+ dat += "| \ref[thing] | [thing.type][thing.gc_destroyed ? " (destroyed)" : ""] [ADMIN_VV(thing)] |
"
+ dat += "
"
+ 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 LEGACY_REFERENCE_TRACKING
+
+/datum/verb/legacy_find_refs()
+ set category = "Debug"
+ set name = "Find References"
+ set src in world
+
+ find_references(FALSE)
+
+
+/datum/proc/find_references_legacy(skip_alert)
+ running_find_references = type
+ if(usr?.client)
+ if(usr.client.running_find_references)
+ testing("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.next_fire = world.time + world.tick_lag
+ return
+
+ if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "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
+
+ testing("Beginning search for references to a [type].")
+ last_find_references = world.time
+
+ DoSearchVar(GLOB) //globals
+ for(var/datum/thing in world) //atoms (don't beleive its lies)
+ DoSearchVar(thing, "World -> [thing]")
+
+ for(var/datum/thing) //datums
+ DoSearchVar(thing, "World -> [thing]")
+
+ for(var/client/thing) //clients
+ DoSearchVar(thing, "World -> [thing]")
+
+ testing("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.next_fire = world.time + world.tick_lag
+
+
+/datum/verb/qdel_then_find_references()
+ set category = "Debug"
+ set name = "qdel() then Find References"
+ set src in world
+
+ qdel(src, TRUE) //force a qdel
+ if(!running_find_references)
+ find_references(TRUE)
+
+
+/datum/verb/qdel_then_if_fail_find_references()
+ set category = "Debug"
+ set name = "qdel() then Find References if GC failure"
+ set src in world
+
+ qdel_and_find_ref_if_fail(src, TRUE)
+
+
+/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64)
+ if(usr?.client && !usr.client.running_find_references)
+ return
+
+ if(!recursive_limit)
+ return
+
+ if(istype(potential_container, /datum))
+ var/datum/datum_container = potential_container
+ if(datum_container.last_find_references == last_find_references)
+ return
+
+ datum_container.last_find_references = last_find_references
+ var/list/vars_list = datum_container.vars
+
+ for(var/varname in vars_list)
+ if (varname == "vars")
+ continue
+ var/variable = vars_list[varname]
+
+ if(variable == src)
+ testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]")
+
+ else if(islist(variable))
+ DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1)
+
+ else if(islist(potential_container))
+ var/normal = IS_NORMAL_LIST(potential_container)
+ for(var/element_in_list in potential_container)
+ if(element_in_list == src)
+ testing("Found [type] \ref[src] in list [container_name].")
+
+ else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src)
+ testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
+
+ else if(islist(element_in_list))
+ DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1)
+
+ #ifndef FIND_REF_NO_CHECK_TICK
+ CHECK_TICK
+ #endif
+
+
+/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE)
+ SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE
+ qdel(thing_to_del, force)
+
+#endif
diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm
index 6e3f2bd1a01..4949139c658 100644
--- a/code/modules/admin/view_variables/topic_basic.dm
+++ b/code/modules/admin/view_variables/topic_basic.dm
@@ -45,6 +45,18 @@
usr.client.admin_delete(target)
if (isturf(src)) // show the turf that took its place
usr.client.debug_variables(src)
+ return
+
+ #ifdef REFERENCE_TRACKING
+ if(href_list[VV_HK_VIEW_REFERENCES])
+ var/datum/D = locate(href_list[VV_HK_TARGET])
+ if(!D)
+ to_chat(usr, "Unable to locate item.")
+ return
+ usr.client.holder.view_refs(target)
+ return
+ #endif
+
if(href_list[VV_HK_MARK])
usr.client.mark_datum(target)
if(href_list[VV_HK_ADDCOMPONENT])
@@ -77,5 +89,4 @@
message_admins("[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(src)].")
if(href_list[VV_HK_CALLPROC])
usr.client.callproc_datum(target)
- if(href_list[VV_HK_VIEW_REFERENCES])
- usr.client.view_refs(target)
+