diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm
index deee7f76a78..fa66c27500e 100644
--- a/code/ATMOSPHERICS/atmospherics.dm
+++ b/code/ATMOSPHERICS/atmospherics.dm
@@ -158,7 +158,7 @@ Pipelines + Other Objects -> Pipe network
P.other_atmosmch -= src
//(De)construction
-/obj/machinery/atmospherics/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob)
+/obj/machinery/atmospherics/attackby(obj/item/weapon/W, mob/user)
if(can_unwrench && istype(W, /obj/item/weapon/wrench))
var/turf/T = get_turf(src)
if(level == 1 && isturf(T) && T.intact)
@@ -179,7 +179,7 @@ Pipelines + Other Objects -> Pipe network
to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?")
unsafe_wrenching = TRUE //Oh dear oh dear
- if(do_after(user, 40 * W.toolspeed, target = src) && isnull(gcDestroyed))
+ if(do_after(user, 40 * W.toolspeed, target = src) && !QDELETED(src))
user.visible_message( \
"[user] unfastens \the [src].", \
"You have unfastened \the [src].", \
diff --git a/code/ATMOSPHERICS/pipes/pipe.dm b/code/ATMOSPHERICS/pipes/pipe.dm
index 05c5d18df99..3ff92ba27d8 100644
--- a/code/ATMOSPHERICS/pipes/pipe.dm
+++ b/code/ATMOSPHERICS/pipes/pipe.dm
@@ -32,7 +32,7 @@
. = ..()
// if we're somehow by ourself
- if(parent && isnull(parent.gcDestroyed) && parent.members.len == 1 && parent.members[1] == src)
+ if(parent && !QDELETED(parent) && parent.members.len == 1 && parent.members[1] == src)
qdel(parent)
parent = null
diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm
index 492918be671..0d20e14a743 100644
--- a/code/__DEFINES/qdel.dm
+++ b/code/__DEFINES/qdel.dm
@@ -1,11 +1,31 @@
//defines that give qdel hints. these can be given as a return in destory() or by calling
+
#define QDEL_HINT_QUEUE 0 //qdel should queue the object for deletion.
#define QDEL_HINT_LETMELIVE 1 //qdel should let the object live after calling destory.
#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.
+//defines for the gc_destroyed var
-// A convenient wrapper around checking for qdeletion/non-existence
-#define exists(A) (A && !qdeleted(A))
+#define GC_QUEUE_PREQUEUE 1
+#define GC_QUEUE_CHECK 2
+#define GC_QUEUE_HARDDELETE 3
+#define GC_QUEUE_COUNT 3 //increase this when adding more steps.
+
+#define GC_QUEUED_FOR_QUEUING -1
+#define GC_QUEUED_FOR_HARD_DEL -2
+#define GC_CURRENTLY_BEING_QDELETED -3
+
+#define QDELING(X) (X.gc_destroyed)
+#define QDELETED(X) (!X || QDELING(X))
+#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+
+/proc/check_datum_qdeleted(datum/D) //for checking if something is a datum, first, then checking gc_destroyed; get rid of this eventually. TO-DO
+ if(!istype(D))
+ return FALSE
+ if(D.gc_destroyed)
+ return TRUE
+ return FALSE
\ No newline at end of file
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 2b02c212659..4b97b67e27e 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -31,6 +31,15 @@
/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b)
return a.priority - b.priority
+/proc/cmp_qdel_item_time(datum/qdel_item/A, datum/qdel_item/B)
+ . = B.hard_delete_time - A.hard_delete_time
+ if(!.)
+ . = B.destroy_time - A.destroy_time
+ if(!.)
+ . = B.failures - A.failures
+ if(!.)
+ . = B.qdels - A.qdels
+
/proc/cmp_atom_layer_asc(atom/A,atom/B)
if(A.plane != B.plane)
return A.plane - B.plane
diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm
index cc4b5459aa0..27ec6e7202f 100644
--- a/code/__HELPERS/icon_smoothing.dm
+++ b/code/__HELPERS/icon_smoothing.dm
@@ -109,7 +109,7 @@
return adjacencies
/proc/smooth_icon(atom/A)
- if(qdeleted(A))
+ if(QDELETED(A))
return
if(A && (A.smooth & SMOOTH_TRUE) || (A.smooth & SMOOTH_MORE))
var/adjacencies = calculate_adjacencies(A)
diff --git a/code/__HELPERS/logging.dm b/code/__HELPERS/logging.dm
index 5b16083dbf2..b31e3a40c88 100644
--- a/code/__HELPERS/logging.dm
+++ b/code/__HELPERS/logging.dm
@@ -15,9 +15,12 @@
/proc/warning(msg)
log_to_dd("## WARNING: [msg]")
-//print a testing-mode debug message to world.log
-/proc/testing(msg)
- log_to_dd("## TESTING: [msg]")
+//print a testing-mode debug message to world.log and world
+#ifdef TESTING
+#define testing(msg) log_to_dd("## TESTING: [msg]"); to_chat(world, "## TESTING: [msg]")
+#else
+#define testing(msg)
+#endif
/proc/log_admin(text)
admin_log.Add(text)
@@ -49,7 +52,7 @@
/proc/log_say(text)
if(config.log_say)
diary << "\[[time_stamp()]]SAY: [text]"
-
+
/proc/log_robot(text)
if(config.log_say)
diary << "\[[time_stamp()]]ROBOT: [text]"
diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm
index 6d28c2c0cd4..d336b1dcd3b 100644
--- a/code/__HELPERS/time.dm
+++ b/code/__HELPERS/time.dm
@@ -1,7 +1,15 @@
// So you can be all 10 SECONDS
#define SECONDS *10
-#define MINUTES *600
-#define HOURS *36000
+
+#define MINUTES SECONDS*60
+
+#define HOURS MINUTES*60
+
+#define TICKS *world.tick_lag
+
+#define DS2TICKS(DS) ((DS)/world.tick_lag)
+
+#define TICKS2DS(T) ((T) TICKS)
#define TimeOfGame (get_game_time())
#define TimeOfTick (world.tick_usage*0.01*world.tick_lag)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index c4d210088fd..fabc9de4dee 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1482,12 +1482,9 @@ var/mob/dview/dview_mob = new
/mob/dview/New() //For whatever reason, if this isn't called, then BYOND will throw a type mismatch runtime when attempting to add this to the mobs list. -Fox
-/proc/IsValidSrc(var/A)
- if(istype(A, /datum))
- var/datum/B = A
- return isnull(B.gcDestroyed)
- if(istype(A, /client))
- return 1
+/proc/IsValidSrc(datum/D)
+ if(istype(D))
+ return !QDELETED(D)
return 0
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 5e9d9462d7f..c6b63b77271 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -1,6 +1,14 @@
#define DEBUG
//#define TESTING
+#ifdef TESTING
+//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing.
+ //implies FIND_REF_NO_CHECK_TICK
+
+//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping
+
+#endif
+
#define IS_MODE_COMPILED(MODE) (ispath(text2path("/datum/game_mode/"+(MODE))))
#define BACKGROUND_ENABLED 0 // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary.
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 58b12ed8857..3abef4e0cc5 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -2,7 +2,7 @@
if(pre_attackby(target, user, params))
// Return 1 in attackby() to prevent afterattack() effects (when safely moving items for example)
var/resolved = target.attackby(src, user, params)
- if(!resolved && target && !qdeleted(src))
+ if(!resolved && target && !QDELETED(src))
afterattack(target, user, 1, params) // 1: clicking something Adjacent
// Called when the item is in the active hand, and clicked; alternately, there is an 'activate held object' verb or you can hit pagedown.
diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm
index 4fc377e9e37..9e777d07ea3 100644
--- a/code/controllers/ProcessScheduler/core/process.dm
+++ b/code/controllers/ProcessScheduler/core/process.dm
@@ -393,7 +393,7 @@
exceptions[eid] = 0
/datum/controller/process/proc/catchBadType(var/datum/caught)
- if(isnull(caught) || !istype(caught) || !isnull(caught.gcDestroyed))
+ if(isnull(caught) || !istype(caught) || QDELETED(caught))
return // Only bother with types we can identify and that don't belong
catchException("Type [caught.type] does not belong in process' queue")
diff --git a/code/controllers/Processes/garbage.dm b/code/controllers/Processes/garbage.dm
deleted file mode 100644
index 1a93c8f13db..00000000000
--- a/code/controllers/Processes/garbage.dm
+++ /dev/null
@@ -1,17 +0,0 @@
-var/global/datum/controller/process/garbage_collector/garbageCollector
-
-/datum/controller/process/garbage_collector/setup()
- name = "garbage"
- schedule_interval = 10
- start_delay = 3
-
-/datum/controller/process/garbage_collector/doWork()
- // Garbage collection code can be found in code\modules\garbage collection\garbage_collector.dm
- processGarbage()
-
-/datum/controller/process/garbage_collector/statProcess()
- ..()
- stat(null, "[del_everything ? "Off" : "On"], [queue.len] queued")
- stat(null, "Dels: [dels_count], [soft_dels] soft, [hard_dels] hard")
-
-DECLARE_GLOBAL_CONTROLLER(garbage_collector, garbageCollector)
diff --git a/code/controllers/Processes/machinery.dm b/code/controllers/Processes/machinery.dm
index 268e1c073a2..2466f802740 100644
--- a/code/controllers/Processes/machinery.dm
+++ b/code/controllers/Processes/machinery.dm
@@ -24,7 +24,7 @@
/datum/controller/process/machinery/proc/process_machines()
for(last_object in machine_processing)
var/obj/machinery/M = last_object
- if(istype(M) && isnull(M.gcDestroyed))
+ if(istype(M) && !QDELETED(M))
try
if(M.process() == PROCESS_KILL)
machine_processing.Remove(M)
@@ -43,7 +43,7 @@
/datum/controller/process/machinery/proc/process_power()
for(last_object in deferred_powernet_rebuilds)
var/obj/O = last_object
- if(istype(O) && isnull(O.gcDestroyed))
+ if(istype(O) && !QDELETED(O))
try
var/datum/powernet/newPN = new()// creates a new powernet...
propagate_network(O, newPN)//... and propagates it to the other side of the cable
@@ -56,7 +56,7 @@
for(last_object in powernets)
var/datum/powernet/powerNetwork = last_object
- if(istype(powerNetwork) && isnull(powerNetwork.gcDestroyed))
+ if(istype(powerNetwork) && !QDELETED(powerNetwork))
try
powerNetwork.reset()
catch(var/exception/e)
diff --git a/code/controllers/Processes/mob.dm b/code/controllers/Processes/mob.dm
index 86c76604606..2b11eff99be 100644
--- a/code/controllers/Processes/mob.dm
+++ b/code/controllers/Processes/mob.dm
@@ -21,7 +21,7 @@ var/global/datum/controller/process/mob/mob_master
/datum/controller/process/mob/doWork()
for(last_object in mob_list)
var/mob/M = last_object
- if(istype(M) && isnull(M.gcDestroyed))
+ if(istype(M) && !QDELETED(M))
try
M.Life()
catch(var/exception/e)
diff --git a/code/controllers/Processes/npcai.dm b/code/controllers/Processes/npcai.dm
index 09d8556187e..1f8d2a34e9e 100644
--- a/code/controllers/Processes/npcai.dm
+++ b/code/controllers/Processes/npcai.dm
@@ -25,7 +25,7 @@ var/global/datum/controller/process/npcai/npcai_master
/datum/controller/process/npcai/doWork()
for(last_object in simple_animal_list)
var/mob/living/simple_animal/M = last_object
- if(istype(M) && isnull(M.gcDestroyed))
+ if(istype(M) && !QDELETED(M))
if(!M.client && M.stat == CONSCIOUS)
try
M.process_ai()
@@ -44,7 +44,7 @@ var/global/datum/controller/process/npcai/npcai_master
for(last_object in snpc_list)
var/mob/living/carbon/human/interactive/M = last_object
- if(istype(M) && isnull(M.gcDestroyed))
+ if(istype(M) && !QDELETED(M))
try
if(!M.alternateProcessing || M.forceProcess)
M.doProcess()
diff --git a/code/controllers/Processes/obj.dm b/code/controllers/Processes/obj.dm
index 0e573bcc05c..454709b6194 100644
--- a/code/controllers/Processes/obj.dm
+++ b/code/controllers/Processes/obj.dm
@@ -15,7 +15,7 @@
/datum/controller/process/obj/doWork()
for(last_object in processing_objects)
var/datum/O = last_object
- if(istype(O) && isnull(O.gcDestroyed))
+ if(istype(O) && !QDELETED(O))
try
// Reagent datums get shoved in here, but the process proc isn't on the
// base datum type, so we just call it blindly.
diff --git a/code/controllers/Processes/timer.dm b/code/controllers/Processes/timer.dm
index 0dcaf128193..4b42d69221d 100644
--- a/code/controllers/Processes/timer.dm
+++ b/code/controllers/Processes/timer.dm
@@ -19,7 +19,7 @@ var/global/datum/controller/process/timer/timer_master
return
for(last_object in processing_timers)
var/datum/timedevent/event = last_object
- if(!event.thingToCall || qdeleted(event.thingToCall))
+ if(!event.thingToCall || check_datum_qdeleted(event.thingToCall))
qdel(event)
if(event.timeToRun <= world.time)
runevent(event)
diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm
index 78303449f26..923dece3c6b 100644
--- a/code/controllers/failsafe.dm
+++ b/code/controllers/failsafe.dm
@@ -28,7 +28,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/datum/controller/failsafe/Initialize()
set waitfor = 0
Failsafe.Loop()
- if(!qdeleted(src))
+ if(!QDELETED(src))
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
/datum/controller/failsafe/Destroy()
diff --git a/code/controllers/subsystem/fires.dm b/code/controllers/subsystem/fires.dm
index 50624aa8db0..ce66405e1c9 100644
--- a/code/controllers/subsystem/fires.dm
+++ b/code/controllers/subsystem/fires.dm
@@ -21,7 +21,7 @@ SUBSYSTEM_DEF(fires)
while(currentrun.len)
var/obj/O = currentrun[currentrun.len]
currentrun.len--
- if(!O || qdeleted(O))
+ if(!O || QDELETED(O))
processing -= O
if(MC_TICK_CHECK)
return
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
new file mode 100644
index 00000000000..17883b4c40b
--- /dev/null
+++ b/code/controllers/subsystem/garbage.dm
@@ -0,0 +1,436 @@
+SUBSYSTEM_DEF(garbage)
+ name = "Garbage"
+ priority = FIRE_PRIORITY_GARBAGE
+ wait = 2 SECONDS
+ flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
+ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ init_order = INIT_ORDER_GARBAGE
+
+ var/list/collection_timeout = list(0, 2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
+
+ //Stat tracking
+ var/delslasttick = 0 // number of del()'s we've done this tick
+ var/gcedlasttick = 0 // number of things that gc'ed last tick
+ var/totaldels = 0
+ var/totalgcs = 0
+
+ var/highest_del_time = 0
+ var/highest_del_tickusage = 0
+
+ var/list/pass_counts
+ var/list/fail_counts
+
+ var/list/items = list() // Holds our qdel_item statistics datums
+
+ //Queue
+ var/list/queues
+
+
+/datum/controller/subsystem/garbage/PreInit()
+ queues = new(GC_QUEUE_COUNT)
+ pass_counts = new(GC_QUEUE_COUNT)
+ fail_counts = new(GC_QUEUE_COUNT)
+ for(var/i in 1 to GC_QUEUE_COUNT)
+ queues[i] = list()
+ pass_counts[i] = 0
+ fail_counts[i] = 0
+
+/datum/controller/subsystem/garbage/stat_entry(msg)
+ var/list/counts = list()
+ for(var/list/L in queues)
+ counts += length(L)
+ msg += "Q:[counts.Join(",")]|D:[delslasttick]|G:[gcedlasttick]|"
+ msg += "GR:"
+ if(!(delslasttick+gcedlasttick))
+ msg += "n/a|"
+ else
+ msg += "[round((gcedlasttick/(delslasttick+gcedlasttick))*100, 0.01)]%|"
+
+ msg += "TD:[totaldels]|TG:[totalgcs]|"
+ if(!(totaldels+totalgcs))
+ msg += "n/a|"
+ else
+ msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
+ msg += " P:[pass_counts.Join(",")]"
+ msg += "|F:[fail_counts.Join(",")]"
+ ..(msg)
+
+/* TO-DO
+/datum/controller/subsystem/garbage/Shutdown()
+ //Adds the del() log to the qdel log file
+ var/list/dellog = list()
+
+ //sort by how long it's wasted hard deleting
+ sortTim(items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
+ for(var/path in items)
+ var/datum/qdel_item/I = items[path]
+ dellog += "Path: [path]"
+ if(I.failures)
+ dellog += "\tFailures: [I.failures]"
+ dellog += "\tqdel() Count: [I.qdels]"
+ dellog += "\tDestroy() Cost: [I.destroy_time]ms"
+ if(I.hard_deletes)
+ dellog += "\tTotal Hard Deletes [I.hard_deletes]"
+ dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
+ if(I.slept_destroy)
+ dellog += "\tSleeps: [I.slept_destroy]"
+ if(I.no_respect_force)
+ dellog += "\tIgnored force: [I.no_respect_force] times"
+ if(I.no_hint)
+ dellog += "\tNo hint: [I.no_hint] times"
+ log_qdel(dellog.Join("\n"))
+*/
+
+/datum/controller/subsystem/garbage/fire()
+ //the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
+ var/queue = GC_QUEUE_PREQUEUE
+
+ while(state == SS_RUNNING)
+ switch (queue)
+ if(GC_QUEUE_PREQUEUE)
+ HandlePreQueue()
+ queue = GC_QUEUE_PREQUEUE+1
+ if(GC_QUEUE_CHECK)
+ HandleQueue(GC_QUEUE_CHECK)
+ queue = GC_QUEUE_CHECK+1
+ if(GC_QUEUE_HARDDELETE)
+ HandleQueue(GC_QUEUE_HARDDELETE)
+ break
+
+ if(state == SS_PAUSED) //make us wait again before the next run.
+ state = SS_RUNNING
+
+//If you see this proc high on the profile, what you are really seeing is the garbage collection/soft delete overhead in byond.
+//Don't attempt to optimize, not worth the effort.
+/datum/controller/subsystem/garbage/proc/HandlePreQueue()
+ var/list/tobequeued = queues[GC_QUEUE_PREQUEUE]
+ var/static/count = 0
+ if(count)
+ var/c = count
+ count = 0 //so if we runtime on the Cut, we don't try again.
+ tobequeued.Cut(1,c+1)
+
+ for(var/ref in tobequeued)
+ count++
+ Queue(ref, GC_QUEUE_PREQUEUE+1)
+ if (MC_TICK_CHECK)
+ break
+ if(count)
+ tobequeued.Cut(1,count+1)
+ count = 0
+
+/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
+ if(level == GC_QUEUE_CHECK)
+ delslasttick = 0
+ gcedlasttick = 0
+ var/cut_off_time = world.time - collection_timeout[level] //ignore entries newer then this
+ var/list/queue = queues[level]
+ var/static/lastlevel
+ var/static/count = 0
+ if(count) //runtime last run before we could do this.
+ var/c = count
+ count = 0 //so if we runtime on the Cut, we don't try again.
+ var/list/lastqueue = queues[lastlevel]
+ lastqueue.Cut(1, c+1)
+
+ lastlevel = level
+
+ for(var/refID in queue)
+ if(!refID)
+ count++
+ if(MC_TICK_CHECK)
+ break
+ continue
+
+ var/GCd_at_time = queue[refID]
+ if(GCd_at_time > cut_off_time)
+ break // Everything else is newer, skip them
+ count++
+
+ var/datum/D
+ D = locate(refID)
+
+ if(!D || D.gc_destroyed != GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
+ ++gcedlasttick
+ ++totalgcs
+ pass_counts[level]++
+ if(MC_TICK_CHECK)
+ break
+ continue
+
+ // Something's still referring to the qdel'd object.
+ fail_counts[level]++
+ switch(level)
+ if(GC_QUEUE_CHECK)
+ #ifdef GC_FAILURE_HARD_LOOKUP
+ D.find_references()
+ #endif
+ var/type = D.type
+ var/datum/qdel_item/I = items[type]
+ testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
+ I.failures++
+ if(GC_QUEUE_HARDDELETE)
+ HardDelete(D)
+ if(MC_TICK_CHECK)
+ break
+ continue
+
+ Queue(D, level+1)
+
+ if(MC_TICK_CHECK)
+ break
+ if(count)
+ queue.Cut(1,count+1)
+ count = 0
+
+/datum/controller/subsystem/garbage/proc/PreQueue(datum/D)
+ if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+ queues[GC_QUEUE_PREQUEUE] += D
+ D.gc_destroyed = GC_QUEUED_FOR_QUEUING
+
+/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
+ if(isnull(D))
+ return
+ if(D.gc_destroyed == GC_QUEUED_FOR_HARD_DEL)
+ level = GC_QUEUE_HARDDELETE
+ if(level > GC_QUEUE_COUNT)
+ HardDelete(D)
+ return
+ var/gctime = world.time
+ var/refid = "\ref[D]"
+
+ D.gc_destroyed = gctime
+ var/list/queue = queues[level]
+ if(queue[refid])
+ queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list.
+
+ queue[refid] = gctime
+
+//this is mainly to separate things profile wise.
+/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
+ var/time = world.timeofday
+ var/tick = TICK_USAGE
+ var/ticktime = world.time
+ ++delslasttick
+ ++totaldels
+ var/type = D.type
+ var/refID = "\ref[D]"
+
+ del(D)
+
+ tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100))
+
+ var/datum/qdel_item/I = items[type]
+
+ I.hard_deletes++
+ I.hard_delete_time += TICK_DELTA_TO_MS(tick)
+
+
+ if(tick > highest_del_tickusage)
+ highest_del_tickusage = tick
+ time = world.timeofday - time
+ if(!time && TICK_DELTA_TO_MS(tick) > 1)
+ time = TICK_DELTA_TO_MS(tick)/100
+ if(time > highest_del_time)
+ highest_del_time = time
+ if(time > 10)
+ log_game("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete)")
+ message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).")
+ postpone(time)
+
+/datum/controller/subsystem/garbage/proc/HardQueue(datum/D)
+ if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+ queues[GC_QUEUE_PREQUEUE] += D
+ D.gc_destroyed = GC_QUEUED_FOR_HARD_DEL
+
+/datum/controller/subsystem/garbage/Recover()
+ if(istype(SSgarbage.queues))
+ for(var/i in 1 to SSgarbage.queues.len)
+ queues[i] |= SSgarbage.queues[i]
+
+
+/datum/qdel_item
+ var/name = ""
+ var/qdels = 0 //Total number of times it's passed thru qdel.
+ var/destroy_time = 0 //Total amount of milliseconds spent processing this type's Destroy()
+ var/failures = 0 //Times it was queued for soft deletion but failed to soft delete.
+ var/hard_deletes = 0 //Different from failures because it also includes QDEL_HINT_HARDDEL deletions
+ var/hard_delete_time = 0//Total amount of milliseconds spent hard deleting this type.
+ var/no_respect_force = 0//Number of times it's not respected force=TRUE
+ var/no_hint = 0 //Number of times it's not even bother to give a qdel hint
+ var/slept_destroy = 0 //Number of times it's slept in its destroy
+
+/datum/qdel_item/New(mytype)
+ name = "[mytype]"
+
+
+// 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.
+/proc/qdel(datum/D, force=FALSE, ...)
+ if(!istype(D))
+ del(D)
+ return
+ var/datum/qdel_item/I = SSgarbage.items[D.type]
+ if(!I)
+ I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
+ I.qdels++
+
+
+ if(isnull(D.gc_destroyed))
+ D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
+ var/start_time = world.time
+ var/start_tick = world.tick_usage
+ var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
+ if(world.time != start_time)
+ I.slept_destroy++
+ else
+ I.destroy_time += TICK_USAGE_TO_MS(start_tick)
+ if(!D)
+ return
+ switch(hint)
+ if(QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
+ SSgarbage.PreQueue(D)
+ if(QDEL_HINT_IWILLGC)
+ D.gc_destroyed = world.time
+ return
+ if(QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
+ if(!force)
+ D.gc_destroyed = null //clear the gc variable (important!)
+ return
+ // Returning LETMELIVE after being told to force destroy
+ // indicates the objects Destroy() does not respect force
+ #ifdef TESTING
+ if(!I.no_respect_force)
+ testing("WARNING: [D.type] has been force deleted, but is \
+ returning an immortal QDEL_HINT, indicating it does \
+ not respect the force flag for qdel(). It has been \
+ placed in the queue, further instances of this type \
+ will also be queued.")
+ #endif
+ I.no_respect_force++
+
+ SSgarbage.PreQueue(D)
+ if(QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate()
+ SSgarbage.HardQueue(D)
+ 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.
+ SSgarbage.PreQueue(D)
+ #ifdef TESTING
+ D.find_references()
+ #endif
+ else
+ #ifdef TESTING
+ if(!I.no_hint)
+ testing("WARNING: [D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
+ #endif
+ I.no_hint++
+ SSgarbage.PreQueue(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)
+ if(!running_find_references)
+ find_references(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/controllers/verbs.dm b/code/controllers/verbs.dm
index 126defb41c2..73104f212ff 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -70,7 +70,7 @@
debug_variables(alarm_manager)
feedback_add_details("admin_verb", "DAlarm")
if("Garbage")
- debug_variables(garbageCollector)
+ debug_variables(SSgarbage)
feedback_add_details("admin_verb","DGarbage")
if("Nano")
debug_variables(SSnanoui)
diff --git a/code/controllers/voting.dm b/code/controllers/voting.dm
index 55abf2834f1..a8890186bae 100644
--- a/code/controllers/voting.dm
+++ b/code/controllers/voting.dm
@@ -20,9 +20,9 @@ var/global/list/round_voters = list() //Keeps track of the individuals voting fo
qdel(vote)
vote = src
spawn(0)
- while(!gcDestroyed)
+ while(!QDELETED(src))
try
- while(!gcDestroyed)
+ while(!QDELETED(src))
sleep(10)
process()
catch(var/exception/e)
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
new file mode 100644
index 00000000000..d56378695ae
--- /dev/null
+++ b/code/datums/datum.dm
@@ -0,0 +1,14 @@
+/datum
+ var/gc_destroyed //Time when this object was destroyed.
+
+#ifdef TESTING
+ var/running_find_references
+ var/last_find_references = 0
+#endif
+
+// 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.
+/datum/proc/Destroy(force = FALSE, ...)
+ tag = null
+ return QDEL_HINT_QUEUE
\ No newline at end of file
diff --git a/code/datums/spells/ethereal_jaunt.dm b/code/datums/spells/ethereal_jaunt.dm
index d855f6a3092..43c3d3a61cd 100644
--- a/code/datums/spells/ethereal_jaunt.dm
+++ b/code/datums/spells/ethereal_jaunt.dm
@@ -54,7 +54,7 @@
target.setDir(holder.dir)
sleep(jaunt_in_time)
qdel(holder)
- if(!qdeleted(target))
+ if(!QDELETED(target))
if(mobloc.density)
for(var/direction in alldirs)
var/turf/T = get_step(mobloc, direction)
diff --git a/code/datums/spells/lichdom.dm b/code/datums/spells/lichdom.dm
index bda640f80a1..84c3a552c03 100644
--- a/code/datums/spells/lichdom.dm
+++ b/code/datums/spells/lichdom.dm
@@ -28,7 +28,7 @@
config.continuous_rounds = 0
return ..()
-/obj/effect/proc_holder/spell/targeted/lichdom/cast(list/targets,mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/lichdom/cast(list/targets,mob/user = usr)
if(!config.continuous_rounds)
existence_stops_round_end = 1
config.continuous_rounds = 1
@@ -49,7 +49,7 @@
charge_counter = charge_max
return
- if(!marked_item || qdeleted(marked_item)) //Wait nevermind
+ if(!marked_item || QDELETED(marked_item)) //Wait nevermind
to_chat(M, "Your phylactery is gone!")
return
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 6ca5047ac6c..3fdc402bb8a 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -144,7 +144,7 @@
if(throwing)
throwing.hit_atom(A)
. = 1
- if(!A || qdeleted(A))
+ if(!A || QDELETED(A))
return
A.Bumped(src)
@@ -224,7 +224,7 @@
//called when src is thrown into hit_atom
/atom/movable/proc/throw_impact(atom/hit_atom, throwingdatum)
set waitfor = 0
- if(exists(hit_atom))
+ if(!QDELETED(hit_atom))
return hit_atom.hitby(src)
/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = 1, blocked)
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index e5f2ce5d4ef..5e304407302 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -98,7 +98,7 @@
return
var/choice = input(user, selection_prompt, selection_title) as null|anything in choosable_items
var/pickedtype = choosable_items[choice]
- if(pickedtype && Adjacent(user) && src && !qdeleted(src) && !user.incapacitated() && cooldowntime <= world.time)
+ if(pickedtype && Adjacent(user) && src && !QDELETED(src) && !user.incapacitated() && cooldowntime <= world.time)
cooldowntime = world.time + creation_delay
var/obj/item/N = new pickedtype(get_turf(src))
to_chat(user, replacetext("[creation_message]", "%ITEM%", "[N]"))
diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm
index 72535d9d1e5..53d569f0c9a 100644
--- a/code/game/gamemodes/cult/ritual.dm
+++ b/code/game/gamemodes/cult/ritual.dm
@@ -252,7 +252,7 @@
if(!possible_runes.len)
return
entered_rune_name = input(user, "Choose a rite to scribe.", "Sigils of Power") as null|anything in possible_runes
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated())
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated())
return
for(var/T in typesof(/obj/effect/rune))
var/obj/effect/rune/R = T
@@ -271,7 +271,7 @@
if(locate(/obj/effect/rune) in runeturf)
to_chat(user, "There is already a rune here.")
return
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated())
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated())
return
if(ispath(rune_to_scribe, /obj/effect/rune/narsie) || ispath(rune_to_scribe, /obj/effect/rune/slaughter))//may need to change this - Fethas
if(finale_runes_ok(user,rune_to_scribe))
@@ -299,7 +299,7 @@
if(!do_after(user, initial(rune_to_scribe.scribe_delay)-scribereduct, target = get_turf(user)))
for(var/V in shields)
var/obj/machinery/shield/S = V
- if(S && !qdeleted(S))
+ if(S && !QDELETED(S))
qdel(S)
return
if(locate(/obj/effect/rune) in runeturf)
@@ -309,7 +309,7 @@
"You finish drawing the arcane markings of [ticker.mode.cultdat.entity_title3].")
for(var/V in shields)
var/obj/machinery/shield/S = V
- if(S && !qdeleted(S))
+ if(S && !QDELETED(S))
qdel(S)
var/obj/effect/rune/R = new rune_to_scribe(runeturf, chosen_keyword)
R.blood_DNA = list()
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index 9b94627d664..24f3c43fef3 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -238,7 +238,7 @@ structure_check() searches for nearby cultist structures required for the invoca
possible_talismans[talisman_cult_name] = J //This is to allow the menu to let cultists select talismans by name
entered_talisman_name = input(user, "Choose a talisman to imbue.", "Talisman Choices") as null|anything in possible_talismans
talisman_type = possible_talismans[entered_talisman_name]
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated() || rune_in_use || !talisman_type)
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || rune_in_use || !talisman_type)
return
..()
visible_message("Dark power begins to channel into the paper!.")
@@ -303,7 +303,7 @@ var/list/teleport_runes = list()
var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked
var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to?
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated() || !actual_selected_rune)
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || !actual_selected_rune)
fail_invoke()
return
@@ -406,7 +406,7 @@ var/list/teleport_runes = list()
var/mob/offering
if(possible_targets.len > 1) //If there's more than one target, allow choice
offering = input(user, "Choose an offering to sacrifice.", "Unholy Tribute") as null|anything in possible_targets
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated())
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated())
return
else if(possible_targets.len) //Otherwise, if there's a target at all, pick the only one
offering = possible_targets[possible_targets.len]
@@ -654,7 +654,7 @@ var/list/teleport_runes = list()
log_game("Raise Dead rune failed - no catalyst corpse")
return
mob_to_sacrifice = input(user, "Choose a corpse to sacrifice.", "Corpse to Sacrifice") as null|anything in potential_sacrifice_mobs
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated() || !mob_to_sacrifice || rune_in_use)
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || !mob_to_sacrifice || rune_in_use)
return
for(var/mob/living/M in T.contents)
if(M.stat == DEAD)
@@ -664,7 +664,7 @@ var/list/teleport_runes = list()
log_game("Raise Dead rune failed - no corpse to revive")
return
mob_to_revive = input(user, "Choose a corpse to revive.", "Corpse to Revive") as null|anything in potential_revive_mobs
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated() || rune_in_use || !mob_to_revive)
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || rune_in_use || !mob_to_revive)
return
if(!in_range(mob_to_sacrifice,src))
to_chat(user, "The sacrificial target has been moved!")
@@ -858,7 +858,7 @@ var/list/teleport_runes = list()
if(!(M.current in invokers) && M.current && M.current.stat != DEAD)
cultists |= M.current
var/mob/living/cultist_to_summon = input(user, "Who do you wish to call to [src]?", "Followers of [ticker.mode.cultdat.entity_title3]") as null|anything in cultists
- if(!Adjacent(user) || !src || qdeleted(src) || user.incapacitated())
+ if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated())
return
if(!cultist_to_summon)
to_chat(user, "You require a summoning target!")
diff --git a/code/game/gamemodes/cult/talisman.dm b/code/game/gamemodes/cult/talisman.dm
index 9b0e2e9ab7e..68c43979b47 100644
--- a/code/game/gamemodes/cult/talisman.dm
+++ b/code/game/gamemodes/cult/talisman.dm
@@ -148,7 +148,7 @@
var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked
var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to?
- if(!src || qdeleted(src) || !user || user.l_hand != src && user.r_hand != src || user.incapacitated() || !actual_selected_rune)
+ if(!src || QDELETED(src) || !user || user.l_hand != src && user.r_hand != src || user.incapacitated() || !actual_selected_rune)
return ..(user, 0)
user.visible_message("Dust flows from [user]'s hand, and they disappear in a flash of red light!", \
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index e644af03f3c..8ab83c01253 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -331,7 +331,7 @@
audible_message("You hear a loud electrical buzzing sound!")
to_chat(src, "Reprogramming machine behaviour...")
spawn(50)
- if(M && !qdeleted(M))
+ if(M && !QDELETED(M))
new /mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, src, 1)
else
to_chat(src, "Out of uses.")
diff --git a/code/game/gamemodes/miniantags/borer/borer.dm b/code/game/gamemodes/miniantags/borer/borer.dm
index ba7f91e2c37..5851023fce9 100644
--- a/code/game/gamemodes/miniantags/borer/borer.dm
+++ b/code/game/gamemodes/miniantags/borer/borer.dm
@@ -134,7 +134,7 @@
if(stat != CONSCIOUS)
return
var/be_borer = alert("Become a cortical borer? (Warning, You can no longer be cloned!)",,"Yes","No")
- if(be_borer == "No" || !src || qdeleted(src))
+ if(be_borer == "No" || !src || QDELETED(src))
return
if(key)
return
@@ -175,7 +175,7 @@
if(!input)
return
- if(src && !qdeleted(src) && !qdeleted(host))
+ if(src && !QDELETED(src) && !QDELETED(host))
var/say_string = (docile) ? "slurs" :"states"
if(host)
to_chat(host, "[truename] [say_string]: [input]")
@@ -538,7 +538,7 @@
/mob/living/simple_animal/borer/proc/let_go()
- if(!host || !src || qdeleted(host) || qdeleted(src))
+ if(!host || !src || QDELETED(host) || QDELETED(src))
return
if(!leaving)
return
@@ -605,7 +605,7 @@
to_chat(src, "You begin delicately adjusting your connection to the host brain...")
- if(qdeleted(src) || qdeleted(host))
+ if(QDELETED(src) || QDELETED(host))
return
bonding = TRUE
@@ -805,7 +805,7 @@
if(!candidate || !candidate.mob)
return
- if(!qdeleted(candidate) || !qdeleted(candidate.mob))
+ if(!QDELETED(candidate) || !QDELETED(candidate.mob))
var/datum/mind/M = create_borer_mind(candidate.ckey)
M.transfer_to(src)
candidate.mob = src
diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
index be18793554f..c3401db2a58 100644
--- a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
+++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
@@ -47,7 +47,7 @@
if(crit_fail)//in case it depowers while ghost is looking at yes/no
to_chat(user, "This swarmer shell is completely depowered. You cannot activate it.")
return
- if(qdeleted(src))
+ if(QDELETED(src))
to_chat(user, "Swarmer has been occupied by someone else.")
return
var/mob/living/simple_animal/hostile/swarmer/S = new /mob/living/simple_animal/hostile/swarmer(get_turf(loc))
diff --git a/code/game/gamemodes/miniantags/guardian/types/lightning.dm b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
index ea11fc5cfdd..3aaeebbc513 100644
--- a/code/game/gamemodes/miniantags/guardian/types/lightning.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
@@ -53,12 +53,12 @@
removechains()
/mob/living/simple_animal/hostile/guardian/beam/proc/cleardeletedchains()
- if(summonerchain && qdeleted(summonerchain))
+ if(summonerchain && QDELETED(summonerchain))
summonerchain = null
if(enemychains.len)
for(var/chain in enemychains)
var/datum/cd = chain
- if(!chain || qdeleted(cd))
+ if(!chain || QDELETED(cd))
enemychains -= chain
/mob/living/simple_animal/hostile/guardian/beam/proc/shockallchains()
diff --git a/code/game/gamemodes/shadowling/shadowling.dm b/code/game/gamemodes/shadowling/shadowling.dm
index 439d3cb237d..76129be2d71 100644
--- a/code/game/gamemodes/shadowling/shadowling.dm
+++ b/code/game/gamemodes/shadowling/shadowling.dm
@@ -240,7 +240,7 @@ Made by Xhuis
M.visible_message("[M] screams and contorts!", \
"THE LIGHT-- YOUR MIND-- BURNS--")
spawn(30)
- if(!M || qdeleted(M))
+ if(!M || QDELETED(M))
return
M.visible_message("[M] suddenly bloats and explodes!", \
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA----")
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 61bcf2a15b8..5e03043d6c3 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -91,7 +91,7 @@
cameranet.addCamera(src)
emped = 0 //Resets the consecutive EMP count
spawn(100)
- if(!qdeleted(src))
+ if(!QDELETED(src))
cancelCameraAlarm()
for(var/mob/O in mob_list)
if(O.client && O.client.eye == src)
@@ -275,7 +275,7 @@
change_msg = "reactivates"
triggerCameraAlarm()
spawn(100)
- if(!qdeleted(src))
+ if(!QDELETED(src))
cancelCameraAlarm()
if(displaymessage)
if(user)
diff --git a/code/game/machinery/dance_machine.dm b/code/game/machinery/dance_machine.dm
index a7ddc648df6..fc8f8032863 100644
--- a/code/game/machinery/dance_machine.dm
+++ b/code/game/machinery/dance_machine.dm
@@ -118,7 +118,7 @@
add_fingerprint(usr)
switch(href_list["action"])
if("toggle")
- if(qdeleted(src))
+ if(QDELETED(src))
return
if(!active)
if(stop > world.time)
@@ -143,7 +143,7 @@
for(var/datum/track/S in songs)
available[S.song_name] = S
var/selected = input(usr, "Choose your song", "Track:") as null|anything in available
- if(qdeleted(src) || !selected || !istype(available[selected], /datum/track))
+ if(QDELETED(src) || !selected || !istype(available[selected], /datum/track))
return
selection = available[selected]
updateUsrDialog()
@@ -165,7 +165,7 @@
deejay('sound/ai/harmalarm.ogg')
/obj/machinery/disco/proc/deejay(S)
- if(qdeleted(src) || !active || charge < 5)
+ if(QDELETED(src) || !active || charge < 5)
to_chat(usr, "The device is not able to play more DJ sounds at this time.")
return
charge -= 5
@@ -241,7 +241,7 @@
/obj/machinery/disco/proc/lights_spin()
for(var/i in 1 to 25)
- if(qdeleted(src) || !active)
+ if(QDELETED(src) || !active)
return
var/obj/effect/overlay/sparkles/S = new /obj/effect/overlay/sparkles(src)
S.alpha = 0
@@ -266,7 +266,7 @@
reveal.alpha = 255
while(active)
for(var/obj/item/device/flashlight/spotlight/glow in spotlights) // The multiples reflects custom adjustments to each colors after dozens of tests
- if(qdeleted(src) || !active || qdeleted(glow))
+ if(QDELETED(src) || !active || QDELETED(glow))
return
if(glow.light_color == "red")
glow.light_color = "nw"
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index 723a2cc3770..88210652e99 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -79,7 +79,7 @@ for reference:
user.visible_message("[user] is prying apart \the [src].", "You begin to pry apart \the [src].")
playsound(src, W.usesound, 200, 1)
- if(do_after(user, 300 * W.toolspeed, target = src) && src && !src.gcDestroyed)
+ if(do_after(user, 300 * W.toolspeed, target = src) && !QDELETED(src))
user.visible_message("[user] pries apart \the [src].", "You pry apart \the [src].")
dismantle()
return
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 527a0155c2e..9ab55f0e1f5 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -180,7 +180,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/autoclose()
autoclose_timer = 0
- if(!qdeleted(src) && !density && !operating && !locked && !welded && autoclose)
+ if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose)
close()
return
@@ -1161,7 +1161,7 @@ About the new airlock wires panel:
operating = TRUE
update_icon(AIRLOCK_EMAG, 1)
sleep(6)
- if(qdeleted(src))
+ if(QDELETED(src))
return
operating = FALSE
if(!open())
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index bfb758bae75..f264484461f 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -353,7 +353,7 @@
/obj/machinery/door/proc/autoclose()
autoclose_timer = 0
- if(!qdeleted(src) && !density && !operating && !locked && !welded && autoclose)
+ if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose)
close()
/obj/machinery/door/proc/update_freelook_sight()
diff --git a/code/game/machinery/quantum_pad.dm b/code/game/machinery/quantum_pad.dm
index d742ab00658..86c11f7cbe9 100644
--- a/code/game/machinery/quantum_pad.dm
+++ b/code/game/machinery/quantum_pad.dm
@@ -71,7 +71,7 @@
to_chat(user, "The panel must be closed before operating this machine!")
return
- if(!linked_pad || qdeleted(linked_pad))
+ if(!linked_pad || QDELETED(linked_pad))
to_chat(user, "There is no linked pad!")
return
@@ -108,14 +108,14 @@
teleporting = 1
spawn(teleport_speed)
- if(!src || qdeleted(src))
+ if(!src || QDELETED(src))
teleporting = 0
return
if(stat & NOPOWER)
to_chat(user, "[src] is unpowered!")
teleporting = 0
return
- if(!linked_pad || qdeleted(linked_pad) || linked_pad.stat & NOPOWER)
+ if(!linked_pad || QDELETED(linked_pad) || linked_pad.stat & NOPOWER)
to_chat(user, "Linked pad is not responding to ping. Teleport aborted.")
teleporting = 0
return
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index 43fda1c6e00..142683e8962 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -112,7 +112,7 @@ var/const/SAFETY_COOLDOWN = 100
for(var/i in to_eat)
var/atom/movable/AM = i
- if(!exists(AM))
+ if(QDELETED(AM))
continue
else if(isliving(AM))
if(emagged)
diff --git a/code/game/mecha/mech_bay.dm b/code/game/mecha/mech_bay.dm
index 7700c2b9a3a..76cd58abed6 100644
--- a/code/game/mecha/mech_bay.dm
+++ b/code/game/mecha/mech_bay.dm
@@ -162,11 +162,11 @@
var/data[0]
if(!recharge_port)
reconnect()
- if(recharge_port && !qdeleted(recharge_port))
+ if(recharge_port && !QDELETED(recharge_port))
data["recharge_port"] = list("mech" = null)
- if(recharge_port.recharging_mecha && !qdeleted(recharge_port.recharging_mecha))
+ if(recharge_port.recharging_mecha && !QDELETED(recharge_port.recharging_mecha))
data["recharge_port"]["mech"] = list("health" = recharge_port.recharging_mecha.health, "maxhealth" = initial(recharge_port.recharging_mecha.health), "cell" = null)
- if(recharge_port.recharging_mecha.cell && !qdeleted(recharge_port.recharging_mecha.cell))
+ if(recharge_port.recharging_mecha.cell && !QDELETED(recharge_port.recharging_mecha.cell))
data["has_mech"] = 1
data["mecha_name"] = recharge_port.recharging_mecha || "None"
data["mecha_charge"] = isnull(recharge_port.recharging_mecha) ? 0 : recharge_port.recharging_mecha.cell.charge
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index c3de7aa4bfb..01867405e85 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -1275,7 +1275,7 @@
occupant = null
return
else
- if(!AI.linked_core || qdeleted(AI.linked_core))
+ if(!AI.linked_core || QDELETED(AI.linked_core))
to_chat(AI, "Inactive core destroyed. Unable to return.")
AI.linked_core = null
return
diff --git a/code/game/objects/effects/decals/contraband.dm b/code/game/objects/effects/decals/contraband.dm
index d825a7a9146..eda338961b8 100644
--- a/code/game/objects/effects/decals/contraband.dm
+++ b/code/game/objects/effects/decals/contraband.dm
@@ -157,7 +157,7 @@
playsound(D.loc, 'sound/items/poster_being_created.ogg', 100, 1)
if(do_after(user, PLACE_SPEED, target = src))
- if(!D || qdeleted(D))
+ if(!D || QDELETED(D))
return
if(iswallturf(src) && user && user.loc == temp_loc) //Let's check if everything is still there
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 9d6ebd06fcb..88769dcbb6d 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -485,7 +485,7 @@ var/global/image/fire_overlay = image("icon" = 'icons/goonstation/effects/fire.d
else ..()
/obj/item/throw_impact(atom/A)
- if(A && !qdeleted(A))
+ if(A && !QDELETED(A))
var/itempush = 1
if(w_class < WEIGHT_CLASS_BULKY)
itempush = 0 // too light to push anything
diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm
index 58de51085de..68ccad0090d 100644
--- a/code/game/objects/items/weapons/explosives.dm
+++ b/code/game/objects/items/weapons/explosives.dm
@@ -132,7 +132,7 @@
/obj/item/weapon/grenade/plastic/c4/prime()
var/turf/location
if(target)
- if(!qdeleted(target))
+ if(!QDELETED(target))
location = get_turf(target)
target.overlays -= image_overlay
else
@@ -159,7 +159,7 @@
/obj/item/weapon/grenade/plastic/x4/prime()
var/turf/location
if(target)
- if(!qdeleted(target))
+ if(!QDELETED(target))
location = get_turf(target)
target.overlays -= image_overlay
else
@@ -193,7 +193,7 @@
/obj/item/weapon/grenade/plastic/c4_shaped/prime()
var/turf/location
if(target)
- if(!qdeleted(target))
+ if(!QDELETED(target))
location = get_turf(target)
target.overlays -= image_overlay
else
diff --git a/code/game/objects/items/weapons/grenades/clusterbuster.dm b/code/game/objects/items/weapons/grenades/clusterbuster.dm
index e238e3bda25..ca96006abad 100644
--- a/code/game/objects/items/weapons/grenades/clusterbuster.dm
+++ b/code/game/objects/items/weapons/grenades/clusterbuster.dm
@@ -67,7 +67,7 @@
walk_away(P,loc,rand(1,4))
spawn(rand(15,60))
- if(P && isnull(P.gcDestroyed))
+ if(!QDELETED(P))
if(istype(P, /obj/item/weapon/grenade))
P.prime()
qdel(src)
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index d5ef8fce545..0738d57e8fb 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -130,7 +130,7 @@
/obj/singularity_act()
ex_act(1)
- if(src && !qdeleted(src))
+ if(src && !QDELETED(src))
qdel(src)
return 2
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 744bd26fc85..ce9f8f2f7c5 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -363,7 +363,7 @@
icon_state = "crema_active"
for(var/mob/living/M in search_contents_for(/mob/living))
- if(!M || !isnull(M.gcDestroyed))
+ if(QDELETED(M))
continue
if(M.stat!=2)
M.emote("scream")
@@ -372,7 +372,7 @@
user.create_attack_log("Cremated [M.name] ([M.ckey])")
log_attack("[user.name] ([user.ckey]) cremated [M.name] ([M.ckey])")
M.death(1)
- if(!M || !isnull(M.gcDestroyed))
+ if(QDELETED(M))
continue // Re-check for mobs that delete themselves on death
M.ghostize()
qdel(M)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 28df27026a6..7614f088f7f 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -159,7 +159,6 @@ var/list/admin_verbs_debug = list(
/client/proc/restart_controller,
/client/proc/enable_debug_verbs,
/client/proc/toggledebuglogs,
- /client/proc/qdel_toggle,
/client/proc/cmd_display_del_log,
/client/proc/debugNatureMapGenerator,
/client/proc/check_bomb_impacts,
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 44fb8b2ef9c..30e6a2b8d80 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -748,6 +748,38 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
if("Respawnable Mobs")
to_chat(usr, jointext(respawnable_list,","))
+/client/proc/cmd_display_del_log()
+ set category = "Debug"
+ set name = "Display del() Log"
+ set desc = "Display del's log of everything that's passed through it."
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ var/list/dellog = list("List of things that have gone through qdel this round