diff --git a/code/ATMOSPHERICS/pipes.dm b/code/ATMOSPHERICS/pipes.dm
index 105aaa8c8b..c23fc16555 100644
--- a/code/ATMOSPHERICS/pipes.dm
+++ b/code/ATMOSPHERICS/pipes.dm
@@ -63,11 +63,11 @@
return parent.return_network(reference)
/obj/machinery/atmospherics/pipe/Destroy()
- qdel(parent)
+ qdel_null(parent)
if(air_temporary)
loc.assume_air(air_temporary)
- ..()
+ . = ..()
/obj/machinery/atmospherics/pipe/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob)
if (istype(src, /obj/machinery/atmospherics/pipe/tank))
@@ -227,10 +227,12 @@
/obj/machinery/atmospherics/pipe/simple/Destroy()
if(node1)
node1.disconnect(src)
+ node1 = null
if(node2)
node2.disconnect(src)
+ node1 = null
- ..()
+ . = ..()
/obj/machinery/atmospherics/pipe/simple/pipeline_expansion()
return list(node1, node2)
@@ -461,12 +463,15 @@
/obj/machinery/atmospherics/pipe/manifold/Destroy()
if(node1)
node1.disconnect(src)
+ node1 = null
if(node2)
node2.disconnect(src)
+ node2 = null
if(node3)
node3.disconnect(src)
+ node3 = null
- ..()
+ . = ..()
/obj/machinery/atmospherics/pipe/manifold/disconnect(obj/machinery/atmospherics/reference)
if(reference == node1)
@@ -705,14 +710,18 @@
/obj/machinery/atmospherics/pipe/manifold4w/Destroy()
if(node1)
node1.disconnect(src)
+ node1 = null
if(node2)
node2.disconnect(src)
+ node2 = null
if(node3)
node3.disconnect(src)
+ node3 = null
if(node4)
node4.disconnect(src)
+ node4 = null
- ..()
+ . = ..()
/obj/machinery/atmospherics/pipe/manifold4w/disconnect(obj/machinery/atmospherics/reference)
if(reference == node1)
@@ -967,8 +976,9 @@
/obj/machinery/atmospherics/pipe/cap/Destroy()
if(node)
node.disconnect(src)
+ node = null
- ..()
+ . = ..()
/obj/machinery/atmospherics/pipe/cap/disconnect(obj/machinery/atmospherics/reference)
if(reference == node)
@@ -1081,8 +1091,9 @@
/obj/machinery/atmospherics/pipe/tank/Destroy()
if(node1)
node1.disconnect(src)
+ node1 = null
- ..()
+ . = ..()
/obj/machinery/atmospherics/pipe/tank/pipeline_expansion()
return list(node1)
@@ -1251,8 +1262,9 @@
/obj/machinery/atmospherics/pipe/vent/Destroy()
if(node1)
node1.disconnect(src)
+ node1 = null
- ..()
+ . = ..()
/obj/machinery/atmospherics/pipe/vent/pipeline_expansion()
return list(node1)
diff --git a/code/__defines/qdel.dm b/code/__defines/qdel.dm
new file mode 100644
index 0000000000..168a6adf0c
--- /dev/null
+++ b/code/__defines/qdel.dm
@@ -0,0 +1,17 @@
+//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
+
+#define GC_QUEUED_FOR_QUEUING -1
+#define GC_QUEUED_FOR_HARD_DEL -2
+#define GC_CURRENTLY_BEING_QDELETED -3
+
+#define QDELETED(X) (!X || X.gc_destroyed)
+#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm
index d7fd20ac77..8ac42dc0b9 100644
--- a/code/_helpers/mobs.dm
+++ b/code/_helpers/mobs.dm
@@ -228,7 +228,7 @@ Proc for attack log creation, because really why not
. = 0
break
- if(target_loc && (!target || target_loc != target.loc))
+ if(target_loc && (QDELETED(target) || target_loc != target.loc))
. = 0
break
diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm
index edcb8d703c..9f08241978 100644
--- a/code/controllers/ProcessScheduler/core/process.dm
+++ b/code/controllers/ProcessScheduler/core/process.dm
@@ -387,6 +387,6 @@
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 02f0411332..0000000000
--- a/code/controllers/Processes/garbage.dm
+++ /dev/null
@@ -1,254 +0,0 @@
-// The time a datum was destroyed by the GC, or null if it hasn't been
-/datum/var/gcDestroyed
-
-#define GC_COLLECTIONS_PER_RUN 300
-#define GC_COLLECTION_TIMEOUT (30 SECONDS)
-#define GC_FORCE_DEL_PER_RUN 30
-
-var/datum/controller/process/garbage_collector/garbage_collector
-var/list/delayed_garbage = list()
-
-/datum/controller/process/garbage_collector
- var/garbage_collect = 1 // Whether or not to actually do work
- var/total_dels = 0 // number of total del()'s
- var/tick_dels = 0 // number of del()'s we've done this tick
- var/soft_dels = 0
- var/hard_dels = 0 // number of hard dels in total
- var/list/destroyed = list() // list of refID's of things that should be garbage collected
- // refID's are associated with the time at which they time out and need to be manually del()
- // we do this so we aren't constantly locating them and preventing them from being gc'd
-
- var/list/logging = list() // list of all types that have failed to GC associated with the number of times that's happened.
- // the types are stored as strings
-
-/datum/controller/process/garbage_collector/setup()
- name = "garbage"
- schedule_interval = 5 SECONDS
- start_delay = 3
-
- if(!garbage_collector)
- garbage_collector = src
-
- for(var/garbage in delayed_garbage)
- qdel(garbage)
- delayed_garbage.Cut()
- delayed_garbage = null
-
-#ifdef GC_FINDREF
-world/loop_checks = 0
-#endif
-
-/datum/controller/process/garbage_collector/doWork()
- if(!garbage_collect)
- return
-
- tick_dels = 0
- var/time_to_kill = world.time - GC_COLLECTION_TIMEOUT
- var/checkRemain = GC_COLLECTIONS_PER_RUN
- var/remaining_force_dels = GC_FORCE_DEL_PER_RUN
-
-
- while(destroyed.len && --checkRemain >= 0)
- if(remaining_force_dels <= 0)
- #ifdef GC_DEBUG
- testing("GC: Reached max force dels per tick [dels] vs [maxDels]")
- #endif
- break // Server's already pretty pounded, everything else can wait 2 seconds
- var/refID = destroyed[1]
- var/GCd_at_time = destroyed[refID]
- if(GCd_at_time > time_to_kill)
- #ifdef GC_DEBUG
- testing("GC: [refID] not old enough, breaking at [world.time] for [GCd_at_time - time_to_kill] deciseconds until [GCd_at_time + collection_timeout]")
- #endif
- break // Everything else is newer, skip them
- var/datum/A = locate(refID)
- #ifdef GC_DEBUG
- testing("GC: [refID] old enough to test: GCd_at_time: [GCd_at_time] time_to_kill: [time_to_kill] current: [world.time]")
- #endif
- if(A && A.gcDestroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
- // Something's still referring to the qdel'd object. Kill it.
- testing("GC: -- \ref[A] | [A.type] was unable to be GC'd and was deleted --")
- logging["[A.type]"]++
- del(A)
-
- hard_dels++
- remaining_force_dels--
- else
- #ifdef GC_DEBUG
- testing("GC: [refID] properly GC'd at [world.time] with timeout [GCd_at_time]")
- #endif
- soft_dels++
- tick_dels++
- total_dels++
- destroyed.Cut(1, 2)
- SCHECK
-
-#undef GC_FORCE_DEL_PER_TICK
-#undef GC_COLLECTION_TIMEOUT
-#undef GC_COLLECTIONS_PER_TICK
-
-#ifdef GC_FINDREF
-/datum/controller/process/garbage_collector/proc/LookForRefs(var/datum/D, var/list/targ)
- . = 0
- for(var/V in D.vars)
- if(V == "contents")
- continue
- if(istype(D.vars[V], /atom))
- var/atom/A = D.vars[V]
- if(A in targ)
- testing("GC: [A] | [A.type] referenced by [D] | [D.type], var [V]")
- . += 1
- else if(islist(D.vars[V]))
- . += LookForListRefs(D.vars[V], targ, D, V)
-
-/datum/controller/process/garbage_collector/proc/LookForListRefs(var/list/L, var/list/targ, var/datum/D, var/V)
- . = 0
- for(var/F in L)
- if(istype(F, /atom))
- var/atom/A = F
- if(A in targ)
- testing("GC: [A] | [A.type] referenced by [D] | [D.type], list [V]")
- . += 1
- if(islist(F))
- . += LookForListRefs(F, targ, D, "[F] in list [V]")
-#endif
-
-/datum/controller/process/garbage_collector/proc/AddTrash(datum/A)
- if(!istype(A) || !isnull(A.gcDestroyed))
- return
- #ifdef GC_DEBUG
- testing("GC: AddTrash(\ref[A] - [A.type])")
- #endif
- A.gcDestroyed = world.time
- destroyed -= "\ref[A]" // Removing any previous references that were GC'd so that the current object will be at the end of the list.
- destroyed["\ref[A]"] = world.time
-
-/datum/controller/process/garbage_collector/statProcess()
- ..()
- stat(null, "[garbage_collect ? "On" : "Off"], [destroyed.len] queued")
- stat(null, "Dels: [total_dels], [soft_dels] soft, [hard_dels] hard, [tick_dels] last run")
-
-
-// Tests if an atom has been deleted.
-/proc/deleted(atom/A)
- return !A || !isnull(A.gcDestroyed)
-
-// 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(var/datum/A)
- if(!A)
- return
- if(!istype(A))
- warning("qdel() passed object of type [A.type]. qdel() can only handle /datum types.")
- crash_with("qdel() passed object of type [A.type]. qdel() can only handle /datum types.")
- del(A)
- if(garbage_collector)
- garbage_collector.total_dels++
- garbage_collector.hard_dels++
- else if(isnull(A.gcDestroyed))
- // Let our friend know they're about to get collected
- . = !A.Destroy()
- if(. && A)
- A.finalize_qdel()
-
-/datum/proc/finalize_qdel()
- del(src)
-
-/atom/finalize_qdel()
- if(garbage_collector)
- garbage_collector.AddTrash(src)
- else
- delayed_garbage |= src
-
-/icon/finalize_qdel()
- del(src)
-
-/image/finalize_qdel()
- del(src)
-
-/mob/finalize_qdel()
- del(src)
-
-/turf/finalize_qdel()
- del(src)
-
-// Default implementation of clean-up code.
-// This should be overridden to remove all references pointing to the object being destroyed.
-// Return true if the the GC controller should allow the object to continue existing.
-/datum/proc/Destroy()
- nanomanager.close_uis(src)
- tag = null
- return
-
-#ifdef TESTING
-/client/var/running_find_references
-
-/mob/verb/create_thing()
- set category = "Debug"
- set name = "Create Thing"
-
- var/path = input("Enter path")
- var/atom/thing = new path(loc)
- thing.find_references()
-
-/atom/verb/find_references()
- set category = "Debug"
- set name = "Find References"
- set background = 1
- set src in world
-
- if(!usr || !usr.client)
- return
-
- if(usr.client.running_find_references)
- testing("CANCELLED search for references to a [usr.client.running_find_references].")
- usr.client.running_find_references = null
- return
-
- if(alert("Running this will create a lot of lag until it finishes. You can cancel it by running it again. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
- return
-
- // Remove this object from the list of things to be auto-deleted.
- if(garbage_collector)
- garbage_collector.destroyed -= "\ref[src]"
-
- usr.client.running_find_references = type
- testing("Beginning search for references to a [type].")
- var/list/things = list()
- for(var/client/thing)
- things += thing
- for(var/datum/thing)
- things += thing
- for(var/atom/thing)
- things += thing
- testing("Collected list of things in search for references to a [type]. ([things.len] Thing\s)")
- for(var/datum/thing in things)
- if(!usr.client.running_find_references) return
- for(var/varname in thing.vars)
- var/variable = thing.vars[varname]
- if(variable == src)
- testing("Found [src.type] \ref[src] in [thing.type]'s [varname] var.")
- else if(islist(variable))
- if(src in variable)
- testing("Found [src.type] \ref[src] in [thing.type]'s [varname] list var.")
- testing("Completed search for references to a [type].")
- usr.client.running_find_references = null
-
-/client/verb/purge_all_destroyed_objects()
- set category = "Debug"
- if(garbage_collector)
- while(garbage_collector.destroyed.len)
- var/datum/o = locate(garbage_collector.destroyed[1])
- if(istype(o) && o.gcDestroyed)
- del(o)
- garbage_collector.dels++
- garbage_collector.destroyed.Cut(1, 2)
-#endif
-
-#ifdef GC_DEBUG
-#undef GC_DEBUG
-#endif
-
-#ifdef GC_FINDREF
-#undef GC_FINDREF
-#endif
\ No newline at end of file
diff --git a/code/controllers/Processes/machinery.dm b/code/controllers/Processes/machinery.dm
index 405615dc1f..1fd1f8ce45 100644
--- a/code/controllers/Processes/machinery.dm
+++ b/code/controllers/Processes/machinery.dm
@@ -20,7 +20,7 @@
/datum/controller/process/machinery/proc/internal_process_machinery()
for(last_object in machines)
var/obj/machinery/M = last_object
- if(M && !M.gcDestroyed)
+ if(M && !QDELETED(M))
if(M.process() == PROCESS_KILL)
//M.inMachineList = 0 We don't use this debugging function
machines.Remove(M)
@@ -34,7 +34,7 @@
/datum/controller/process/machinery/proc/internal_process_power()
for(last_object in powernets)
var/datum/powernet/powerNetwork = last_object
- if(istype(powerNetwork) && isnull(powerNetwork.gcDestroyed))
+ if(istype(powerNetwork) && !QDELETED(powerNetwork))
powerNetwork.reset()
SCHECK
continue
@@ -52,7 +52,7 @@
/datum/controller/process/machinery/proc/internal_process_pipenets()
for(last_object in pipe_networks)
var/datum/pipe_network/pipeNetwork = last_object
- if(istype(pipeNetwork) && isnull(pipeNetwork.gcDestroyed))
+ if(istype(pipeNetwork) && !QDELETED(pipeNetwork))
pipeNetwork.process()
SCHECK
continue
diff --git a/code/controllers/Processes/mob.dm b/code/controllers/Processes/mob.dm
index 8681803ac4..6b623e7730 100644
--- a/code/controllers/Processes/mob.dm
+++ b/code/controllers/Processes/mob.dm
@@ -14,7 +14,7 @@
/datum/controller/process/mob/doWork()
for(last_object in mob_list)
var/mob/M = last_object
- if(M && isnull(M.gcDestroyed))
+ if(M && !QDELETED(M))
try
M.Life()
catch(var/exception/e)
diff --git a/code/controllers/Processes/nanoui.dm b/code/controllers/Processes/nanoui.dm
index 49b9048c07..21d2f8e5f0 100644
--- a/code/controllers/Processes/nanoui.dm
+++ b/code/controllers/Processes/nanoui.dm
@@ -9,7 +9,7 @@
/datum/controller/process/nanoui/doWork()
for(last_object in nanomanager.processing_uis)
var/datum/nanoui/NUI = last_object
- if(istype(NUI) && isnull(NUI.gcDestroyed))
+ if(istype(NUI) && !QDELETED(NUI))
try
NUI.process()
catch(var/exception/e)
diff --git a/code/controllers/Processes/obj.dm b/code/controllers/Processes/obj.dm
index 1010c8cc97..6032cbb541 100644
--- a/code/controllers/Processes/obj.dm
+++ b/code/controllers/Processes/obj.dm
@@ -11,7 +11,7 @@
/datum/controller/process/obj/doWork()
for(last_object in processing_objects)
var/datum/O = last_object
- if(O && isnull(O.gcDestroyed))
+ if(!QDELETED(O))
try
O:process()
catch(var/exception/e)
diff --git a/code/controllers/Processes/scheduler.dm b/code/controllers/Processes/scheduler.dm
index 276249bd55..fb9f85d7c4 100644
--- a/code/controllers/Processes/scheduler.dm
+++ b/code/controllers/Processes/scheduler.dm
@@ -30,8 +30,8 @@
// Note: We won't be informed about tasks being destroyed, but this is the best we can do.
/datum/controller/process/scheduler/copyStateFrom(var/datum/controller/process/scheduler/target)
scheduled_tasks = list()
- for(var/st in target.scheduled_tasks)
- if(!deleted(st) && istype(st, /datum/scheduled_task))
+ for(var/datum/scheduled_task/st in target.scheduled_tasks)
+ if(!QDELETED(st) && istype(st))
schedule(st)
scheduler = src
diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm
index 98c6a5d89d..0589d5fa7f 100644
--- a/code/controllers/failsafe.dm
+++ b/code/controllers/failsafe.dm
@@ -33,13 +33,13 @@ var/datum/controller/failsafe/Failsafe
/datum/controller/failsafe/Initialize()
set waitfor = 0
Failsafe.Loop()
- if(!deleted(src))
+ if(!QDELETED(src))
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
/datum/controller/failsafe/Destroy()
running = FALSE
..()
- // return QDEL_HINT_HARDDEL_NOW // TODO - Once we port garbage.dm
+ return QDEL_HINT_HARDDEL_NOW
/datum/controller/failsafe/proc/Loop()
while(running)
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 981cf6d2bb..360b739bad 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -66,7 +66,7 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING
/datum/controller/master/Destroy()
..()
// Tell qdel() to Del() this object.
- // return QDEL_HINT_HARDDEL_NOW // TODO - Once we port garbage.dm
+ return QDEL_HINT_HARDDEL_NOW
/datum/controller/master/Shutdown()
processing = FALSE
diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm
index 3075682438..c2d04d9c93 100644
--- a/code/controllers/master_controller.dm
+++ b/code/controllers/master_controller.dm
@@ -49,7 +49,7 @@ datum/controller/game_controller/proc/setup_objects()
admin_notice("Initializing objects", R_DEBUG)
sleep(-1)
for(var/atom/movable/object in world)
- if(isnull(object.gcDestroyed))
+ if(!QDELETED(object))
object.initialize()
admin_notice("Initializing areas", R_DEBUG)
@@ -80,8 +80,8 @@ datum/controller/game_controller/proc/setup_objects()
admin_notice("Initializing turbolifts", R_DEBUG)
for(var/thing in turbolifts)
- if(!deleted(thing))
- var/obj/turbolift_map_holder/lift = thing
+ var/obj/turbolift_map_holder/lift = thing
+ if(!QDELETED(lift))
lift.initialize()
sleep(-1)
diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm
new file mode 100644
index 0000000000..e849b64be6
--- /dev/null
+++ b/code/controllers/subsystems/garbage.dm
@@ -0,0 +1,354 @@
+var/datum/controller/subsystem/garbage_collector/SSgarbage
+
+/datum/controller/subsystem/garbage_collector
+ name = "Garbage"
+ priority = 15
+ wait = 5
+ flags = SS_FIRE_IN_LOBBY|SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
+
+ var/collection_timeout = 3000// deciseconds to wait to let running procs finish before we just say fuck it and force del() the object
+ 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/queue = list() // list of refID's of things that should be garbage collected
+ // refID's are associated with the time at which they time out and need to be manually del()
+ // we do this so we aren't constantly locating them and preventing them from being gc'd
+
+ var/list/tobequeued = list() //We store the references of things to be added to the queue seperately so we can spread out GC overhead over a few ticks
+
+ var/list/didntgc = list() // list of all types that have failed to GC associated with the number of times that's happened.
+ // the types are stored as strings
+ var/list/sleptDestroy = list() //Same as above but these are paths that slept during their Destroy call
+
+ var/list/noqdelhint = list()// list of all types that do not return a QDEL_HINT
+ // all types that did not respect qdel(A, force=TRUE) and returned one
+ // of the immortality qdel hints
+ var/list/noforcerespect = list()
+
+#ifdef TESTING
+ var/list/qdel_list = list() // list of all types that have been qdel()eted
+#endif
+
+/datum/controller/subsystem/garbage_collector/New()
+ NEW_SS_GLOBAL(SSgarbage)
+
+/datum/controller/subsystem/garbage_collector/stat_entry(msg)
+ msg += "Q:[queue.len]|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)
+
+/datum/controller/subsystem/garbage_collector/fire()
+ HandleToBeQueued()
+ if(state == SS_RUNNING)
+ HandleQueue()
+
+//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_collector/proc/HandleToBeQueued()
+ var/list/tobequeued = src.tobequeued
+ var/starttime = world.time
+ var/starttimeofday = world.timeofday
+ while(tobequeued.len && starttime == world.time && starttimeofday == world.timeofday)
+ if (MC_TICK_CHECK)
+ break
+ var/ref = tobequeued[1]
+ Queue(ref)
+ tobequeued.Cut(1, 2)
+
+/datum/controller/subsystem/garbage_collector/proc/HandleQueue()
+ delslasttick = 0
+ gcedlasttick = 0
+ var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del()
+ var/list/queue = src.queue
+ var/starttime = world.time
+ var/starttimeofday = world.timeofday
+ while(queue.len && starttime == world.time && starttimeofday == world.timeofday)
+ if (MC_TICK_CHECK)
+ break
+ var/refID = queue[1]
+ if (!refID)
+ queue.Cut(1, 2)
+ continue
+
+ var/GCd_at_time = queue[refID]
+ if(GCd_at_time > time_to_kill)
+ break // Everything else is newer, skip them
+ queue.Cut(1, 2)
+ var/datum/A
+ A = locate(refID)
+ if (A && A.gc_destroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
+ #ifdef GC_FAILURE_HARD_LOOKUP
+ A.find_references()
+ #endif
+
+ // Something's still referring to the qdel'd object. Kill it.
+ var/type = A.type
+ testing("GC: -- \ref[A] | [type] was unable to be GC'd and was deleted --")
+ didntgc["[type]"]++
+ var/time = world.timeofday
+ var/tick = world.tick_usage
+ var/ticktime = world.time
+ del(A)
+ tick = (world.tick_usage-tick+((world.time-ticktime)/world.tick_lag*100))
+
+ 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 then 1 second to delete (took [time/10] seconds to delete)")
+ message_admins("Error: [type]([refID]) took longer then 1 second to delete (took [time/10] seconds to delete).")
+ postpone(time/5)
+ break
+ ++delslasttick
+ ++totaldels
+ else
+ ++gcedlasttick
+ ++totalgcs
+
+/datum/controller/subsystem/garbage_collector/proc/QueueForQueuing(datum/A)
+ if (istype(A) && A.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+ tobequeued += A
+ A.gc_destroyed = GC_QUEUED_FOR_QUEUING
+
+/datum/controller/subsystem/garbage_collector/proc/Queue(datum/A)
+ if (!istype(A) || (!isnull(A.gc_destroyed) && A.gc_destroyed >= 0))
+ return
+ if (A.gc_destroyed == GC_QUEUED_FOR_HARD_DEL)
+ del(A)
+ return
+ var/gctime = world.time
+ var/refid = "\ref[A]"
+
+ A.gc_destroyed = gctime
+
+ 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
+
+/datum/controller/subsystem/garbage_collector/proc/HardQueue(datum/A)
+ if (istype(A) && A.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+ tobequeued += A
+ A.gc_destroyed = GC_QUEUED_FOR_HARD_DEL
+
+/datum/controller/subsystem/garbage_collector/Recover()
+ if (istype(SSgarbage.queue))
+ queue |= SSgarbage.queue
+ if (istype(SSgarbage.tobequeued))
+ tobequeued |= SSgarbage.tobequeued
+
+// 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(!D)
+ return
+#ifdef TESTING
+ SSgarbage.qdel_list += D.type
+#endif
+ if(!istype(D))
+ log_debug("qdel() was passed [log_info_line(D)]. qdel() can only handle instances of (sub)type /datum.")
+ del(D)
+ else if(isnull(D.gc_destroyed))
+ D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
+ var/start_time = world.time
+ var/hint = D.Destroy(force) // Let our friend know they're about to get fucked up.
+ if(world.time != start_time)
+ SSgarbage.sleptDestroy[D.type]++
+ if(!D)
+ return
+ switch(hint)
+ if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
+ SSgarbage.QueueForQueuing(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
+ if(!SSgarbage.noforcerespect[D.type])
+ SSgarbage.noforcerespect[D.type] = D.type
+ crash_with("[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.")
+ SSgarbage.QueueForQueuing(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.
+ del(D)
+ if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
+ SSgarbage.QueueForQueuing(D)
+ #ifdef TESTING
+ D.find_references()
+ #endif
+ else
+ if(!SSgarbage.noqdelhint[D.type])
+ SSgarbage.noqdelhint[D.type] = D.type
+ crash_with("[D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
+ SSgarbage.QueueForQueuing(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")
+
+// 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
+ nanomanager.close_uis(src)
+ return QDEL_HINT_QUEUE
+
+/datum/var/gc_destroyed //Time when this object was destroyed.
+
+#ifdef TESTING
+/datum/var/running_find_references
+/datum/var/last_find_references = 0
+
+/datum/verb/find_refs()
+ set category = "Debug"
+ set name = "Find References"
+ set background = 1
+ 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
+ find_references_in_globals()
+ for(var/datum/thing in world)
+ DoSearchVar(thing, "WorldRef: [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
+
+/client/verb/purge_all_destroyed_objects()
+ set category = "Debug"
+ if(SSgarbage)
+ while(SSgarbage.queue.len)
+ var/datum/o = locate(SSgarbage.queue[1])
+ if(istype(o) && o.gc_destroyed)
+ del(o)
+ SSgarbage.totaldels++
+ SSgarbage.queue.Cut(1, 2)
+
+/datum/verb/qdel_then_find_references()
+ set category = "Debug"
+ set name = "qdel() then Find References"
+ set background = 1
+ set src in world
+
+ qdel(src)
+ if(!running_find_references)
+ find_references(TRUE)
+
+/client/verb/show_qdeleted()
+ set category = "Debug"
+ set name = "Show qdel() Log"
+ set desc = "Render the qdel() log and display it"
+
+ var/dat = "List of things that have been qdel()eted this round
"
+
+ var/tmplist = list()
+ for(var/elem in SSgarbage.qdel_list)
+ if(!(elem in tmplist))
+ tmplist[elem] = 0
+ tmplist[elem]++
+
+ for(var/path in tmplist)
+ dat += "[path] - [tmplist[path]] times
"
+
+ usr << browse(dat, "window=qdeletedlog")
+
+/datum/proc/DoSearchVar(X, Xname)
+ if(usr && usr.client && !usr.client.running_find_references) 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
+ for(var/V in D.vars)
+ for(var/varname in D.vars)
+ var/variable = D.vars[varname]
+ if(variable == src)
+ testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
+ else if(islist(variable))
+ if(src in variable)
+ testing("Found [src.type] \ref[src] in [D.type]'s [varname] list var. Global: [Xname]")
+#ifdef GC_FAILURE_HARD_LOOKUP
+ for(var/I in variable)
+ DoSearchVar(I, TRUE)
+ else
+ DoSearchVar(variable, "[Xname]: [varname]")
+#endif
+ else if(islist(X))
+ if(src in X)
+ testing("Found [src.type] \ref[src] in list [Xname].")
+#ifdef GC_FAILURE_HARD_LOOKUP
+ for(var/I in X)
+ DoSearchVar(I, Xname + ": list")
+#else
+ CHECK_TICK
+#endif
+
+//if find_references isn't working for some datum
+//update this list using tools/GenerateGlobalVarAccess
+/datum/proc/find_references_in_globals()
+ // TODO - Impement Global Variable Access
+ // for(var/global_var in _all_globals)
+ // DoSearchVar(readglobal(global_var), "Global: [global_var]")
+#endif
+
+
+/image/Destroy()
+ ..()
+ return QDEL_HINT_HARDDEL_NOW
diff --git a/code/datums/helper_datums/global_iterator.dm b/code/datums/helper_datums/global_iterator.dm
index 3bf80f06d0..d511b5d233 100644
--- a/code/datums/helper_datums/global_iterator.dm
+++ b/code/datums/helper_datums/global_iterator.dm
@@ -155,4 +155,5 @@ Data storage vars:
tag = null
arg_list.Cut()
stop()
+ return QDEL_HINT_LETMELIVE
//Do not call ..()
diff --git a/code/datums/repositories/decls.dm b/code/datums/repositories/decls.dm
index e87be74f53..61b24c21e9 100644
--- a/code/datums/repositories/decls.dm
+++ b/code/datums/repositories/decls.dm
@@ -36,4 +36,4 @@
/decls/Destroy()
crash_with("Prevented attempt to delete a decl instance: [log_info_line(src)]")
- return 1 // Prevents Decl destruction
\ No newline at end of file
+ return QDEL_HINT_LETMELIVE // Prevents Decl destruction
diff --git a/code/datums/weakref.dm b/code/datums/weakref.dm
index 1a7d844ca8..348d73b0b2 100644
--- a/code/datums/weakref.dm
+++ b/code/datums/weakref.dm
@@ -7,7 +7,9 @@
//obtain a weak reference to a datum
/proc/weakref(datum/D)
- if(D.gcDestroyed)
+ if(!istype(D))
+ return
+ if(QDELETED(D))
return
if(!D.weakref)
D.weakref = new/weakref(D)
@@ -22,7 +24,7 @@
/weakref/Destroy()
// A weakref datum should not be manually destroyed as it is a shared resource,
// rather it should be automatically collected by the BYOND GC when all references are gone.
- return 0
+ return QDEL_HINT_LETMELIVE
/weakref/proc/resolve()
var/datum/D = locate(ref)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index fe9f99b032..64ae0722f8 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -23,16 +23,6 @@
if(auto_init && ticker && ticker.current_state == GAME_STATE_PLAYING)
initialize()
-/atom/movable/Del()
- if(isnull(gcDestroyed) && loc)
- testing("GC: -- [type] was deleted via del() rather than qdel() --")
- crash_with("GC: -- [type] was deleted via del() rather than qdel() --") // stick a stack trace in the runtime logs
-// else if(isnull(gcDestroyed))
-// testing("GC: [type] was deleted via GC without qdel()") //Not really a huge issue but from now on, please qdel()
-// else
-// testing("GC: [type] was deleted via GC with qdel()")
- ..()
-
/atom/movable/Destroy()
. = ..()
if(reagents)
@@ -53,7 +43,7 @@
pulledby = null
/atom/movable/proc/initialize()
- if(!isnull(gcDestroyed))
+ if(QDELETED(src))
crash_with("GC: -- [type] had initialize() called after qdel() --")
/atom/movable/Bump(var/atom/A, yes)
diff --git a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm
index 6f549d15f4..90ac43379f 100644
--- a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm
+++ b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm
@@ -37,7 +37,6 @@ var/global/universe_has_ended = 0
// Apply changes when entering state
/datum/universal_state/supermatter_cascade/OnEnter()
set background = 1
- garbage_collector.garbage_collect = 0
world << "You are blinded by a brilliant flash of energy."
world << sound('sound/effects/cascade.ogg')
diff --git a/code/game/machinery/CableLayer.dm b/code/game/machinery/CableLayer.dm
index 2b4eae9a0d..2e10ed3b1b 100644
--- a/code/game/machinery/CableLayer.dm
+++ b/code/game/machinery/CableLayer.dm
@@ -73,7 +73,7 @@
visible_message("A red light flashes on \the [src].")
return
cable.use(amount)
- if(deleted(cable))
+ if(QDELETED(cable))
cable = null
return 1
diff --git a/code/game/machinery/atmoalter/portable_atmospherics.dm b/code/game/machinery/atmoalter/portable_atmospherics.dm
index b7bcc53e91..deee8ed76b 100644
--- a/code/game/machinery/atmoalter/portable_atmospherics.dm
+++ b/code/game/machinery/atmoalter/portable_atmospherics.dm
@@ -21,9 +21,9 @@
return 1
/obj/machinery/portable_atmospherics/Destroy()
- qdel(air_contents)
- qdel(holding)
- ..()
+ qdel_null(air_contents)
+ qdel_null(holding)
+ . = ..()
/obj/machinery/portable_atmospherics/initialize()
. = ..()
@@ -40,11 +40,6 @@
else
update_icon()
-/obj/machinery/portable_atmospherics/Destroy()
- qdel(air_contents)
-
- ..()
-
/obj/machinery/portable_atmospherics/proc/StandardAirMix()
return list(
"oxygen" = O2STANDARD * MolesForPressure(),
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index cf7f96d52d..240610358f 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -73,8 +73,7 @@
/obj/machinery/door/Destroy()
density = 0
update_nearby_tiles()
- ..()
- return
+ . = ..()
/obj/machinery/door/process()
if(close_door_at && world.time >= close_door_at)
diff --git a/code/game/machinery/floor_light.dm b/code/game/machinery/floor_light.dm
index a134e68e3f..fdaf284f3f 100644
--- a/code/game/machinery/floor_light.dm
+++ b/code/game/machinery/floor_light.dm
@@ -150,7 +150,7 @@ var/list/floor_light_cache = list()
var/area/A = get_area(src)
if(A)
on = 0
- ..()
+ . = ..()
/obj/machinery/floor_light/cultify()
default_light_colour = "#FF0000"
diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm
index 4abdb685d0..06228dd86f 100644
--- a/code/game/machinery/pipe/construction.dm
+++ b/code/game/machinery/pipe/construction.dm
@@ -485,7 +485,7 @@ Buildable meters
var/turf/T = P.loc
P.level = !T.is_plating() ? 2 : 1
P.initialize()
- if (deleted(P))
+ if (QDELETED(P))
usr << pipefailtext
return 1
P.build_network()
@@ -504,7 +504,7 @@ Buildable meters
var/turf/T = P.loc
P.level = !T.is_plating() ? 2 : 1
P.initialize()
- if (deleted(P))
+ if (QDELETED(P))
usr << pipefailtext
return 1
P.build_network()
@@ -523,7 +523,7 @@ Buildable meters
var/turf/T = P.loc
P.level = !T.is_plating() ? 2 : 1
P.initialize()
- if (deleted(P))
+ if (QDELETED(P))
usr << pipefailtext
return 1
P.build_network()
@@ -542,7 +542,7 @@ Buildable meters
var/turf/T = P.loc
P.level = !T.is_plating() ? 2 : 1
P.initialize()
- if (deleted(P))
+ if (QDELETED(P))
usr << pipefailtext
return 1
P.build_network()
@@ -559,7 +559,7 @@ Buildable meters
P.initialize_directions = pipe_dir //this var it's used to know if the pipe is bent or not
P.initialize_directions_he = pipe_dir
P.initialize()
- if (deleted(P))
+ if (QDELETED(P))
usr << pipefailtext
return 1
P.build_network()
@@ -594,7 +594,7 @@ Buildable meters
var/turf/T = M.loc
M.level = !T.is_plating() ? 2 : 1
M.initialize()
- if (deleted(M))
+ if (QDELETED(M))
usr << pipefailtext
return 1
M.build_network()
@@ -663,7 +663,7 @@ Buildable meters
var/turf/T = M.loc
M.level = !T.is_plating() ? 2 : 1
M.initialize()
- if (deleted(M))
+ if (QDELETED(M))
usr << pipefailtext
return 1
M.build_network()
@@ -740,7 +740,7 @@ Buildable meters
P.initialize_directions = src.get_pdir()
P.initialize_directions_he = src.get_hdir()
P.initialize()
- if (deleted(P))
+ if (QDELETED(P))
usr << pipefailtext //"There's nothing to connect this pipe to! (with how the pipe code works, at least one end needs to be connected to something, otherwise the game deletes the segment)"
return 1
P.build_network()
@@ -922,7 +922,7 @@ Buildable meters
var/turf/T = P.loc
P.level = !T.is_plating() ? 2 : 1
P.initialize()
- if (deleted(P))
+ if (QDELETED(P))
usr << pipefailtext
return 1
P.build_network()
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 09ff0075db..5d7d16a126 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -140,18 +140,14 @@
cell = null
internal_tank = null
- qdel(pr_int_temp_processor)
- qdel(pr_inertial_movement)
- qdel(pr_give_air)
- qdel(pr_internal_damage)
- qdel(spark_system)
- pr_int_temp_processor = null
- pr_give_air = null
- pr_internal_damage = null
- spark_system = null
+ qdel_null(pr_int_temp_processor)
+ qdel_null(pr_inertial_movement)
+ qdel_null(pr_give_air)
+ qdel_null(pr_internal_damage)
+ qdel_null(spark_system)
mechas_list -= src //global mech list
- ..()
+ . = ..()
////////////////////////
////// Helpers /////////
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index 88b53bd342..35697914ee 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -169,7 +169,7 @@
valve_open = 0
- if(deleted(tank_one) || deleted(tank_two))
+ if(QDELETED(tank_one) || QDELETED(tank_two))
return
var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume
diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm
index 84bee72558..649b5677ca 100644
--- a/code/game/objects/items/weapons/flamethrower.dm
+++ b/code/game/objects/items/weapons/flamethrower.dm
@@ -27,15 +27,10 @@
/obj/item/weapon/flamethrower/Destroy()
- if(weldtool)
- qdel(weldtool)
- if(igniter)
- qdel(igniter)
- if(ptank)
- qdel(ptank)
- ..()
- return
-
+ qdel_null(weldtool)
+ qdel_null(igniter)
+ qdel_null(ptank)
+ . = ..()
/obj/item/weapon/flamethrower/process()
if(!lit)
diff --git a/code/game/objects/items/weapons/material/material_weapons.dm b/code/game/objects/items/weapons/material/material_weapons.dm
index 31231964cb..c778ec07a9 100644
--- a/code/game/objects/items/weapons/material/material_weapons.dm
+++ b/code/game/objects/items/weapons/material/material_weapons.dm
@@ -71,7 +71,7 @@
/obj/item/weapon/material/Destroy()
processing_objects -= src
- ..()
+ . = ..()
/obj/item/weapon/material/apply_hit_effect()
..()
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 928246b2b5..eab315809f 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -10,7 +10,7 @@
/obj/structure/Destroy()
if(parts)
new parts(loc)
- ..()
+ . = ..()
/obj/structure/attack_hand(mob/user)
if(breakable)
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index ec4652901d..35b8571a11 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -39,7 +39,7 @@
spawn(1)
if(istype(O)) // If we built a new floor with the lattice, the open turf won't exist anymore.
O.update() // This lattice may be supporting things on top of it. If it's being deleted, they need to fall down.
- ..()
+ . = ..()
/obj/structure/lattice/ex_act(severity)
switch(severity)
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 3f2e0064f2..d34b210b99 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -47,6 +47,7 @@
/turf/Destroy()
turfs -= src
..()
+ return QDEL_HINT_IWILLGC
/turf/ex_act(severity)
return 0
diff --git a/code/modules/admin/view_variables/helpers.dm b/code/modules/admin/view_variables/helpers.dm
index 67eefc018d..95945f82e0 100644
--- a/code/modules/admin/view_variables/helpers.dm
+++ b/code/modules/admin/view_variables/helpers.dm
@@ -1,8 +1,4 @@
-// Keep these two together, they *must* be defined on both
-// If /client ever becomes /datum/client or similar, they can be merged
-/client/proc/get_view_variables_header()
- return "[src]"
/datum/proc/get_view_variables_header()
return "[src]"
@@ -31,9 +27,6 @@
"}
-// Same for these as for get_view_variables_header() above
-/client/proc/get_view_variables_options()
- return ""
/datum/proc/get_view_variables_options()
return ""
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index 169ab9b6a0..1273b004a4 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -187,4 +187,4 @@
if(radio_controller)
radio_controller.remove_object(src,frequency)
frequency = 0
- ..()
+ . = ..()
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index b559652132..5e5a176128 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -1,4 +1,8 @@
/client
+ //////////////////////
+ //BLACK MAGIC THINGS//
+ //////////////////////
+ parent_type = /datum
////////////////
//ADMIN THINGS//
////////////////
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index d908df1b3d..d6f9375e98 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -181,6 +181,9 @@
clients -= src
return ..()
+/client/Destroy()
+ ..()
+ return QDEL_HINT_HARDDEL_NOW
// here because it's similar to below
diff --git a/code/modules/hydroponics/spreading/spreading_growth.dm b/code/modules/hydroponics/spreading/spreading_growth.dm
index fca9f3abaf..0dbe4c9eea 100644
--- a/code/modules/hydroponics/spreading/spreading_growth.dm
+++ b/code/modules/hydroponics/spreading/spreading_growth.dm
@@ -116,7 +116,7 @@
var/obj/effect/plant/child = new(get_turf(src),seed,parent)
spawn(1) // This should do a little bit of animation.
- if(deleted(child))
+ if(QDELETED(child))
return
//move out to the destination
diff --git a/code/modules/lighting/lighting_overlay.dm b/code/modules/lighting/lighting_overlay.dm
index 60c684fd86..6d912cd862 100644
--- a/code/modules/lighting/lighting_overlay.dm
+++ b/code/modules/lighting/lighting_overlay.dm
@@ -131,4 +131,4 @@
T.lighting_overlay = null
T.luminosity = 1
- return ..()
+ . = ..()
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 8a2513a54a..9c372cf2e7 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -443,7 +443,7 @@ var/global/list/damage_icon_parts = list()
//For legacy support.
/mob/living/carbon/human/regenerate_icons()
..()
- if(transforming) return
+ if(transforming || QDELETED(src)) return
update_mutations(0)
update_body(0)
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
index c7fb1d6728..7d5c270f9b 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
@@ -184,7 +184,7 @@
wrapped.force = 0.0
wrapped.attack(M,user)
M.attackby(wrapped, user) //attackby reportedly gets procced by being clicked on, at least according to Anewbe.
- if(deleted(wrapped) || wrapped.loc != src.loc)
+ if(QDELETED(wrapped) || wrapped.loc != src.loc)
wrapped = null
return 1
return 0
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 7574bb4c77..454b58a55c 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -14,6 +14,7 @@
spellremove(src)
ghostize()
..()
+ return QDEL_HINT_HARDDEL_NOW
/mob/proc/remove_screen_obj_references()
hands = null
diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm
index 80f68b979d..be114228de 100644
--- a/code/modules/mob/mob_grab.dm
+++ b/code/modules/mob/mob_grab.dm
@@ -83,7 +83,9 @@
//This makes sure that the grab screen object is displayed in the correct hand.
-/obj/item/weapon/grab/proc/synch()
+/obj/item/weapon/grab/proc/synch() //why is this needed?
+ if(QDELETED(src))
+ return
if(affecting)
if(assailant.r_hand == src)
hud.screen_loc = ui_rhand
@@ -91,7 +93,7 @@
hud.screen_loc = ui_lhand
/obj/item/weapon/grab/process()
- if(gcDestroyed) // GC is trying to delete us, we'll kill our processing so we can cleanly GC
+ if(QDELETED(src)) // GC is trying to delete us, we'll kill our processing so we can cleanly GC
return PROCESS_KILL
confirm()
@@ -221,6 +223,8 @@
animate(affecting, pixel_x =-shift, pixel_y = 0, 5, 1, LINEAR_EASING)
/obj/item/weapon/grab/proc/s_click(obj/screen/S)
+ if(QDELETED(src))
+ return
if(!affecting)
return
if(state == GRAB_UPGRADING)
@@ -291,6 +295,8 @@
return 1
/obj/item/weapon/grab/attack(mob/M, mob/living/user)
+ if(QDELETED(src))
+ return
if(!affecting)
return
if(world.time < (last_action + 20))
@@ -336,7 +342,7 @@
/obj/item/weapon/grab/dropped()
loc = null
- if(!destroying)
+ if(!QDELETED(src))
qdel(src)
/obj/item/weapon/grab/proc/reset_kill_state()
diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm
index 1390c89b29..e0809e32b5 100644
--- a/code/modules/organs/internal/brain.dm
+++ b/code/modules/organs/internal/brain.dm
@@ -67,10 +67,8 @@
brainmob.client.screen.len = null //clear the hud
/obj/item/organ/internal/brain/Destroy()
- if(brainmob)
- qdel(brainmob)
- brainmob = null
- ..()
+ qdel_null(brainmob)
+ . = ..()
/obj/item/organ/internal/brain/proc/transfer_identity(var/mob/living/carbon/H)
diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm
index 49c1099184..60efe9893d 100644
--- a/code/modules/reagents/Chemistry-Holder.dm
+++ b/code/modules/reagents/Chemistry-Holder.dm
@@ -23,7 +23,7 @@
chemical_reagents_list[D.id] = D
/datum/reagents/Destroy()
- ..()
+ . = ..()
if(chemistryProcess)
chemistryProcess.active_holders -= src
diff --git a/code/modules/reagents/Chemistry-Machinery.dm b/code/modules/reagents/Chemistry-Machinery.dm
index 78b33ef36e..ccf7c62ac1 100644
--- a/code/modules/reagents/Chemistry-Machinery.dm
+++ b/code/modules/reagents/Chemistry-Machinery.dm
@@ -488,7 +488,7 @@
var/amount_to_take = max(0,min(stack.amount,round(remaining_volume/REAGENTS_PER_SHEET)))
if(amount_to_take)
stack.use(amount_to_take)
- if(deleted(stack))
+ if(QDELETED(stack))
holdingitems -= stack
beaker.reagents.add_reagent(sheet_reagents[stack.type], (amount_to_take*REAGENTS_PER_SHEET))
continue
diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm
index 723db2ace7..f23e37f516 100644
--- a/code/modules/reagents/Chemistry-Reagents.dm
+++ b/code/modules/reagents/Chemistry-Reagents.dm
@@ -118,8 +118,8 @@
return null
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
- ..()
holder = null
+ . = ..()
/* DEPRECATED - TODO: REMOVE EVERYWHERE */
diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm
index 3191442be8..ef07b03c4f 100644
--- a/code/modules/reagents/reagent_containers/food/snacks.dm
+++ b/code/modules/reagents/reagent_containers/food/snacks.dm
@@ -210,8 +210,8 @@
/obj/item/weapon/reagent_containers/food/snacks/Destroy()
if(contents)
for(var/atom/movable/something in contents)
- something.loc = get_turf(src)
- ..()
+ something.dropInto(loc)
+ . = ..()
////////////////////////////////////////////////////////////////////////////////
/// FOOD END
diff --git a/code/modules/shieldgen/emergency_shield.dm b/code/modules/shieldgen/emergency_shield.dm
index 4872131429..d7ce2063b2 100644
--- a/code/modules/shieldgen/emergency_shield.dm
+++ b/code/modules/shieldgen/emergency_shield.dm
@@ -55,7 +55,7 @@
check_failure()
set_opacity(1)
- spawn(20) if(!deleted(src)) set_opacity(0)
+ spawn(20) if(!QDELETED(src)) set_opacity(0)
..()
@@ -64,7 +64,7 @@
..()
check_failure()
set_opacity(1)
- spawn(20) if(!deleted(src)) set_opacity(0)
+ spawn(20) if(!QDELETED(src)) set_opacity(0)
/obj/machinery/shield/ex_act(severity)
switch(severity)
@@ -114,7 +114,7 @@
//The shield becomes dense to absorb the blow.. purely asthetic.
set_opacity(1)
- spawn(20) if(!deleted(src)) set_opacity(0)
+ spawn(20) if(!QDELETED(src)) set_opacity(0)
..()
return
diff --git a/code/modules/tables/tables.dm b/code/modules/tables/tables.dm
index db445e9f50..cea2c93559 100644
--- a/code/modules/tables/tables.dm
+++ b/code/modules/tables/tables.dm
@@ -79,7 +79,7 @@
update_connections(1) // Update tables around us to ignore us (material=null forces no connections)
for(var/obj/structure/table/T in oview(src, 1))
T.update_icon()
- ..()
+ . = ..()
/obj/structure/table/examine(mob/user)
. = ..()
diff --git a/polaris.dme b/polaris.dme
index 942ada13d4..9cb2df356d 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -37,6 +37,7 @@
#include "code\__defines\mobs.dm"
#include "code\__defines\planets.dm"
#include "code\__defines\process_scheduler.dm"
+#include "code\__defines\qdel.dm"
#include "code\__defines\research.dm"
#include "code\__defines\species_languages.dm"
#include "code\__defines\targeting.dm"
@@ -152,7 +153,6 @@
#include "code\controllers\Processes\emergencyShuttle.dm"
#include "code\controllers\Processes\event.dm"
#include "code\controllers\Processes\game_master.dm"
-#include "code\controllers\Processes\garbage.dm"
#include "code\controllers\Processes\inactivity.dm"
#include "code\controllers\Processes\lighting.dm"
#include "code\controllers\Processes\machinery.dm"
@@ -170,6 +170,7 @@
#include "code\controllers\Processes\vote.dm"
#include "code\controllers\ProcessScheduler\core\process.dm"
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
+#include "code\controllers\subsystems\garbage.dm"
#include "code\datums\ai_law_sets.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\browser.dm"