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

    ") + sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE) + for(var/path in SSgarbage.items) + var/datum/qdel_item/I = SSgarbage.items[path] + dellog += "
  1. [path]
      " + if(I.failures) + dellog += "
    • Failures: [I.failures]
    • " + dellog += "
    • qdel() Count: [I.qdels]
    • " + dellog += "
    • Destroy() Cost: [I.destroy_time]ms
    • " + if(I.hard_deletes) + dellog += "
    • Total Hard Deletes [I.hard_deletes]
    • " + dellog += "
    • Time Spent Hard Deleting: [I.hard_delete_time]ms
    • " + if(I.slept_destroy) + dellog += "
    • Sleeps: [I.slept_destroy]
    • " + if(I.no_respect_force) + dellog += "
    • Ignored force: [I.no_respect_force]
    • " + if(I.no_hint) + dellog += "
    • No hint: [I.no_hint]
    • " + dellog += "
  2. " + + dellog += "
" + + usr << browse(dellog.Join(), "window=dellog") + /client/proc/cmd_admin_toggle_block(var/mob/M,var/block) if(!check_rights(R_SPAWN)) diff --git a/code/modules/awaymissions/maploader/writer.dm b/code/modules/awaymissions/maploader/writer.dm index 842a51fbce5..c6d1fd589cf 100644 --- a/code/modules/awaymissions/maploader/writer.dm +++ b/code/modules/awaymissions/maploader/writer.dm @@ -136,13 +136,13 @@ // Objects loop if(!(flags & DMM_IGNORE_OBJS)) for(var/obj/O in model.contents) - if(O.dont_save || !isnull(O.gcDestroyed)) + if(O.dont_save || QDELETED(O)) continue obj_template += "[O.type][check_attributes(O,use_json=use_json)]," // Mobs Loop for(var/mob/M in model.contents) - if(M.dont_save || !isnull(M.gcDestroyed)) + if(M.dont_save || QDELETED(M)) continue if(M.client) if(!(flags & DMM_IGNORE_PLAYERS)) diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index ca3d17e9c50..b64f965a81a 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -20,6 +20,13 @@ var/adminhelped = 0 + var/gc_destroyed //Time when this object was destroyed. + +#ifdef TESTING + var/running_find_references + var/last_find_references = 0 +#endif + /////////////// //SOUND STUFF// /////////////// diff --git a/code/modules/countdown/countdown.dm b/code/modules/countdown/countdown.dm index 7e651fb910f..54ec874c743 100644 --- a/code/modules/countdown/countdown.dm +++ b/code/modules/countdown/countdown.dm @@ -42,7 +42,7 @@ return /obj/effect/countdown/process() - if(!attached_to || qdeleted(attached_to)) + if(!attached_to || QDELETED(attached_to)) qdel(src) forceMove(get_turf(attached_to)) var/new_val = get_value() diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index 67f4a93d339..f687f33803d 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -32,7 +32,7 @@ log_debug("Original brand intelligence machine: [originMachine] [ADMIN_VV(originMachine)] [ADMIN_JMP(originMachine)]") /datum/event/brand_intelligence/tick() - if(!originMachine || !isnull(originMachine.gcDestroyed) || originMachine.shut_up || originMachine.wires.IsAllCut()) //if the original vending machine is missing or has it's voice switch flipped + if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.IsAllCut()) //if the original vending machine is missing or has it's voice switch flipped for(var/obj/machinery/vending/saved in infectedMachines) saved.shoot_inventory = 0 if(originMachine) @@ -43,7 +43,7 @@ if(!vendingMachines.len) //if every machine is infected for(var/obj/machinery/vending/upriser in infectedMachines) - if(prob(70) && isnull(upriser.gcDestroyed)) + if(prob(70) && !QDELETED(upriser)) var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks M.faction = list("profit") M.speak = rampant_speeches.Copy() diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm index e4237cc49fd..cdad961afd5 100644 --- a/code/modules/events/spacevine.dm +++ b/code/modules/events/spacevine.dm @@ -607,7 +607,7 @@ var/list/obj/structure/spacevine/queue_end = list() for(var/obj/structure/spacevine/SV in growth_queue) - if(qdeleted(SV)) + if(QDELETED(SV)) continue i++ queue_end += SV diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index d180bb9c2ac..c75037778ea 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -264,7 +264,7 @@ Gunshots/explosions/opening doors/less rare audio (done) to_chat(T, "Primary [rand(1000,9999)] states: [pick("Hello","Hi","You're my slave now!","Don't try to get rid of me...")]") break sleep(4) - if(!qdeleted(borer)) + if(!QDELETED(borer)) qdel(borer) qdel(src) diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm index 1235727d67a..43f690ac16d 100644 --- a/code/modules/food_and_drinks/food/snacks.dm +++ b/code/modules/food_and_drinks/food/snacks.dm @@ -1248,7 +1248,7 @@ return 1 /obj/item/weapon/reagent_containers/food/snacks/monkeycube/proc/Expand() - if(isnull(gcDestroyed)) + if(!QDELETED(src)) visible_message("[src] expands!") new/mob/living/carbon/human(get_turf(src), monkey_type) qdel(src) diff --git a/code/modules/garbage_collection/__gc_info.dm b/code/modules/garbage_collection/__gc_info.dm deleted file mode 100644 index e36b5fd2b1b..00000000000 --- a/code/modules/garbage_collection/__gc_info.dm +++ /dev/null @@ -1,65 +0,0 @@ -/* - -=============================================================================== - How Garbage Collection Works -=============================================================================== - -In BYOND, there are exactly two ways anything gets deleted: - -- A "soft delete", which occurs when an object's reference count hits 0, meaning it is not being referenced by anything - in the world (more on references further down). When an object is unreferenced, nothing can access it, so it has no - reason to exist, and BYOND's garbage collector simply deletes the object. This is fast. - -- A "hard delete", which occurs when the del keyword is used to directly delete an object. This forces BYOND to find - every reference to the object being deleted, and null them all out. This is slow. - -A reference is anything that refers to an object, and can thus be used to access it. A variable on another object, a -variable in a proc, an argument to a proc, the src of a running proc, the locs of contained objects, another object's -contents list (but not the world's automatic contents list), anything. BYOND keeps track of how many references there -are to an object - the reference count goes up when you make a new reference to an object, and back down when that -reference gets set to null, goes out of scope, or is deleted. When the reference count hits 0, there are no remaining -references to the object, and it is deleted by BYOND's garbage collector. - -When you have something that exists in the world, and you want it to stop existing (maybe it got blown up, or eaten by -the singularity, or whatever), it needs to be deleted. You can use the del keyword to make sure it's deleted instantly, -but del is slow, and you want things to be deleted as quickly as possible, especially if you're deleting a whole lot of -them. You want soft deletes. - -That's where the garbage collection system comes in - it prepares things to be soft deleted, and hard deletes anything -that can't be. There are two main procs involved in this process: - -/proc/qdel(datumToDelete) - This is, effectively, a replacement for del that tells an object to prepare itself to be soft deleted by calling its - Destroy() proc. Depending on the qdel hint returned by Destroy(), qdel will queue the object in the garbage collector - (to be hard deleted if it isn't soft deleted), directly delete the object, or ignore the object and - assume it will handle deleting itself. An object passed into qdel will have its gcDestroyed var set, so - isnull(gcDestroyed) will be true if an object is not being destroyed, and false if it is (which means you should get - rid of the reference you have to it). - - Note that qdel can only work with datum-based objects, which excludes the world (deleting this shuts down the world), - clients (deleting these disconnect the client), lists, and savefiles. If any of these are passed to qdel, they will - be directly deleted, just as if they had been passed straight to del, so it should never be unsafe to qdel anything - you could del. - -/datum/proc/Destroy() - This is, effectively, a replacement for Del() (with some exceptions) which is also responsible for nulling out - references to or on the object it is called on. Unlike Del, the Destroy proc will only be called by qdel; generally, - this should only happen to datums that are no longer referenced by anything, which shouldn't be an issue. - - The exceptions where Destroy cannot replace Del are for the same non-datum types mentioned under qdel, above. Those - should use a Del proc for any necessary cleanup, as a Destroy proc on them will not automatically get called. - - When called by qdel, Destroy is expected to return a qdel hint, which determines whether the object should be directly - deleted, queued for deletion, or ignored entirely; which of these are appropriate will vary, though objects - should be pooled or queued whenever possible. The full list of qdel hints are in the code for the qdel proc. - -As mentioned above, gcDestroyed can be used to tell whether an object is being destroyed. This is important, because if -an object is being destroyed, it WILL still exist, and any code that references it should stop referencing it as soon as -possible. In the same places that you check whether a reference still exists, you should also check for something like -isnull(myRefVar.gcDestroyed), which will be false if your object is being destroyed, meaning you should throw out the -reference immediately. - -The inner workings of the GC itself and the stuff related to testing it probably don't need a detailed description - if -you intend to work with them, it would be best to read the code to understand what they do. - -*/ \ No newline at end of file diff --git a/code/modules/garbage_collection/garbage_collector.dm b/code/modules/garbage_collection/garbage_collector.dm deleted file mode 100644 index 51493432ace..00000000000 --- a/code/modules/garbage_collection/garbage_collector.dm +++ /dev/null @@ -1,185 +0,0 @@ -// Main garbage collection code - -// For general information about how the GC works and how to use it, see __gc_info.dm - -#define GC_COLLECTIONS_PER_TICK 150 // Was 100. -#define GC_COLLECTION_TIMEOUT (30 SECONDS) -#define GC_FORCE_DEL_PER_TICK 30 -//#define GC_DEBUG - -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 - -// The time a datum was destroyed by the GC, or null if it hasn't been -/datum/var/gcDestroyed -// Whether a datum was hard-deleted by the GC; 0 if not, 1 if it was queued, -1 if directly deleted -/datum/var/hard_deleted = 0 - - - -/datum/controller/process/garbage_collector - var/list/queue = new - var/del_everything = 0 - - // To let them know how hardworking am I :^). - var/dels_count = 0 - var/hard_dels = 0 - var/soft_dels = 0 - - // all types that did not respect qdel(A, force=TRUE) and returned one - // of the immortality qdel hints - var/list/noforcerespect = list() - - -/datum/controller/process/garbage_collector/proc/addTrash(var/datum/D) - if(!istype(D) || del_everything) - del(D) - hard_dels++ - dels_count++ - return - - queue -= "\ref[D]" // If this is a re-used ref, remove the old ref from the queue - queue["\ref[D]"] = world.time - -/datum/controller/process/garbage_collector/proc/processGarbage() - var/remainingCollectionPerTick = GC_COLLECTIONS_PER_TICK - var/remainingForceDelPerTick = GC_FORCE_DEL_PER_TICK - var/collectionTimeScope = world.time - GC_COLLECTION_TIMEOUT - while(queue.len && --remainingCollectionPerTick >= 0) - var/refID = queue[1] - var/destroyedAtTime = queue[refID] - - if(destroyedAtTime > collectionTimeScope) - break - - var/datum/D = locate(refID) - // If the object still exists, and it's the same object, hard del it - if(D && D.gcDestroyed == destroyedAtTime) - if(remainingForceDelPerTick <= 0) - break - - #ifdef GC_DEBUG - gcwarning("GC process force delete [D.type]") - #endif - - hardDel(D) - queue.Cut(1, 2) - - remainingForceDelPerTick-- - else // Otherwise, it was GC'd - remove it from the queue - queue.Cut(1, 2) - soft_dels++ - dels_count++ - SCHECK - -#ifdef GC_DEBUG -#undef GC_DEBUG -#endif - -#undef GC_FORCE_DEL_PER_TICK -#undef GC_COLLECTION_TIMEOUT -#undef GC_COLLECTIONS_PER_TICK - -/datum/controller/process/garbage_collector/proc/hardDel(var/datum/D) - didntgc["[D.type]"]++ - D.hard_deleted = 1 - if(!D.gcDestroyed) - spawn(-1) - D.Destroy() - D.gcDestroyed = world.time - del(D) - hard_dels++ - dels_count++ - -// Effectively replaces del for any datum-based type -/proc/qdel(datum/D, force=FALSE) - if(isnull(D)) - return - - if(!istype(D)) // A non-datum was passed into qdel - just delete it outright. - // warning("qdel() passed object of type [D.type]. qdel() can only handle /datum/ types.") - del(D) - return - - if(isnull(garbageCollector)) - D.Destroy() - del(D) - return - - if(isnull(D.gcDestroyed)) - // Let our friend know they're about to get fucked up. - var/hint - D.gcDestroyed = world.time - try - hint = D.Destroy(force) - catch(var/exception/e) - if(istype(e)) - log_runtime(e, D, "Caught by qdel() destroying [D.type]") - else - gcwarning("qdel() caught runtime destroying [D.type]: [e]") - // Destroy runtimed? Panic! Hard delete! - D.hard_deleted = -1 - del(D) - if(garbageCollector) - garbageCollector.dels_count++ - return - if(!isnull(D.gcDestroyed) && D.gcDestroyed != world.time) - gcwarning("Sleep detected in Destroy() call of [D.type]") - sleptDestroy["[D.type]"]++ - D.gcDestroyed = world.time - - switch(hint) - if(QDEL_HINT_QUEUE) //qdel should queue the object for deletion - garbageCollector.addTrash(D) - if (QDEL_HINT_LETMELIVE, QDEL_HINT_IWILLGC) //qdel should let the object live after calling destory. - if(!force) - return - // Returning LETMELIVE after being told to force destroy - // indicates the objects Destroy() does not respect force - if(!("[D.type]" in garbageCollector.noforcerespect)) - garbageCollector.noforcerespect += "[D.type]" - gcwarning("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.") - garbageCollector.addTrash(D) - if(QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste. - D.hard_deleted = -1 // -1 means "this hard del skipped the queue", used for profiling - del(D) - if(garbageCollector) - garbageCollector.dels_count++ - if(QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion. - #ifdef TESTING - D.find_references(remove_from_queue = FALSE) - #endif - garbageCollector.addTrash(D) - else - if(!noqdelhint["[D.type]"]) - noqdelhint["[D.type]"] = "[D.type]" - gcwarning("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.") - garbageCollector.addTrash(D) - - -// Returns 1 if the object has been queued for deletion. -/proc/qdeleted(var/datum/D) - if(!istype(D)) - return 0 - if(!isnull(D.gcDestroyed)) - return 1 - return 0 - -/* - * Like Del(), but for qdel. - * Called BEFORE qdel moves shit. - */ -/datum/proc/Destroy(force=FALSE) - tag = null - return QDEL_HINT_QUEUE // Garbage Collect everything. - -/proc/gcwarning(msg) - log_to_dd("## GC WARNING: [msg]") - log_runtime(EXCEPTION(msg)) diff --git a/code/modules/garbage_collection/gc_testing.dm b/code/modules/garbage_collection/gc_testing.dm deleted file mode 100644 index 934a93adc21..00000000000 --- a/code/modules/garbage_collection/gc_testing.dm +++ /dev/null @@ -1,105 +0,0 @@ -// Garbage collection testing/debugging/profiling code - -/client/proc/qdel_toggle() - set name = "(GC) Toggle Queueing" - set desc = "Toggle qdel usage between normal and force del()." - set category = "Debug" - - if(!check_rights(R_DEBUG)) - return - - garbageCollector.del_everything = !garbageCollector.del_everything -// to_chat(world, "GC: qdel turned [garbageCollector.del_everything ? "off" : "on"].") - log_admin("[key_name(usr)] turned qdel [garbageCollector.del_everything ? "off" : "on"].") - message_admins("[key_name_admin(usr)] turned qdel [garbageCollector.del_everything ? "off" : "on"].", 1) - -/client/proc/cmd_display_del_log() - set category = "Debug" - set name = "(GC) Display del() Log" - set desc = "Displays a list of things that have failed to GC this round" - - if(!check_rights(R_DEBUG)) - return - - var/dat = "List of things that failed to GC this round

" - for(var/path in didntgc) - dat += "[path] - [didntgc[path]] times
" - - dat += "List of paths that did not return a qdel hint in Destroy()

" - for(var/path in noqdelhint) - dat += "[path]
" - - dat += "List of paths that slept in Destroy()

" - for(var/path in sleptDestroy) - dat += "[path]
" - - usr << browse(dat, "window=dellog") - -#ifdef TESTING -/client/var/running_find_references -/datum/var/running_find_references - -/datum/verb/find_references(remove_from_queue = TRUE as num) - set category = "Debug" - set name = "Find References" - set background = 1 - set src in world - - 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 - 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") - running_find_references = null - return - // Remove this object from the list of things to be auto-deleted. - if(remove_from_queue && garbageCollector && ("\ref[src]" in garbageCollector.queue)) - garbageCollector.queue -= "\ref[src]" - if(usr && usr.client) - 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 - testing("Collected list of things in search for references to a [type]. ([things.len] Thing\s)") - for(var/datum/thing in things) - if(usr && usr.client && !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].") - if(usr && usr.client) - usr.client.running_find_references = null - running_find_references = null - -/client/verb/purge_all_destroyed_objects() - set category = "Debug" - if(garbageCollector) - while(garbageCollector.queue.len) - var/datum/o = locate(garbageCollector.queue[1]) - if(istype(o) && !isnull(o.gcDestroyed)) - del(o) - garbageCollector.dels_count++ - garbageCollector.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(remove_from_queue = FALSE) -#endif diff --git a/code/modules/hydroponics/grown/tomato.dm b/code/modules/hydroponics/grown/tomato.dm index 78c3e3dfded..2785c39359e 100644 --- a/code/modules/hydroponics/grown/tomato.dm +++ b/code/modules/hydroponics/grown/tomato.dm @@ -131,7 +131,7 @@ awakening = 1 spawn(30) - if(!qdeleted(src)) + if(!QDELETED(src)) var/mob/living/simple_animal/hostile/killertomato/K = new /mob/living/simple_animal/hostile/killertomato(get_turf(loc)) K.maxHealth += round(seed.endurance / 3) K.melee_damage_lower += round(seed.potency / 10) diff --git a/code/modules/mining/lavaland/loot/colossus_loot.dm b/code/modules/mining/lavaland/loot/colossus_loot.dm index 425fbee2b8a..e5aed837d62 100644 --- a/code/modules/mining/lavaland/loot/colossus_loot.dm +++ b/code/modules/mining/lavaland/loot/colossus_loot.dm @@ -427,7 +427,7 @@ /obj/structure/closet/stasis/process() if(holder_animal) - if(holder_animal.stat == DEAD && !qdeleted(holder_animal)) + if(holder_animal.stat == DEAD && !QDELETED(holder_animal)) dump_contents() holder_animal.gib() return @@ -455,7 +455,7 @@ L.disabilities &= ~MUTE L.status_flags &= ~GODMODE L.notransform = 0 - if(holder_animal && !qdeleted(holder_animal)) + if(holder_animal && !QDELETED(holder_animal)) holder_animal.mind.transfer_to(L) L.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/exit_possession) if(kill || !isanimal(loc)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index a522119eae4..d0420aa27f5 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -993,7 +993,7 @@ return 1 /mob/living/proc/harvest(mob/living/user) - if(qdeleted(src)) + if(QDELETED(src)) return if(butcher_results) for(var/path in butcher_results) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index ecbef74ee1d..67e4f33cf0c 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1236,7 +1236,7 @@ var/list/ai_verbs_default = list( malfhacking = 0 clear_alert("hackingapc") - if(!istype(apc) || qdeleted(apc) || apc.stat & BROKEN) + if(!istype(apc) || QDELETED(apc) || apc.stat & BROKEN) to_chat(src, "Hack aborted. The designated APC no longer exists on the power network.") playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, 1) else if(apc.aidisabled) diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm index c710e4c12db..13b5308bc86 100644 --- a/code/modules/mob/living/silicon/ai/life.dm +++ b/code/modules/mob/living/silicon/ai/life.dm @@ -21,7 +21,7 @@ death() return 0 - if(!eyeobj || qdeleted(eyeobj) || !eyeobj.loc) + if(!eyeobj || QDELETED(eyeobj) || !eyeobj.loc) view_core() if(machine) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/dragon.dm index fe0374561a4..e58ed9e4289 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/dragon.dm @@ -218,7 +218,7 @@ Difficulty: Medium fire_rain() icon_state = "dragon" - if(swoop_target && !qdeleted(swoop_target) && swoop_target.z == src.z) + if(swoop_target && !QDELETED(swoop_target) && swoop_target.z == src.z) tturf = get_turf(swoop_target) else tturf = get_turf(src) @@ -233,7 +233,7 @@ Difficulty: Medium L.gib() else L.adjustBruteLoss(75) - if(L && !qdeleted(L)) // Some mobs are deleted on death + if(L && !QDELETED(L)) // Some mobs are deleted on death var/throw_dir = get_dir(src, L) if(L.loc == loc) throw_dir = pick(alldirs) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 054c94d7a25..43e2fa4c13f 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -446,7 +446,7 @@ Difficulty: Hard if(!currently_seeking) currently_seeking = TRUE targetturf = get_turf(target) - while(target && src && !qdeleted(src) && currently_seeking && x && y && targetturf) //can this target actually be sook out + while(target && src && !QDELETED(src) && currently_seeking && x && y && targetturf) //can this target actually be sook out if(!moving) //we're out of tiles to move, find more and where the target is! more_previouser_moving_dir = previous_moving_dir previous_moving_dir = moving_dir diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm index 592f8230edd..b52abff90c9 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm @@ -31,7 +31,7 @@ return if(error_on_humanize == "") var/spider_ask = alert(humanize_prompt, "Join as Terror Spider?", "Yes", "No") - if(spider_ask == "No" || !src || qdeleted(src)) + if(spider_ask == "No" || !src || QDELETED(src)) return else to_chat(user, "Cannot inhabit spider: [error_on_humanize]") diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index dab100b839e..91632ec5cc2 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -146,7 +146,7 @@ /obj/machinery/power/solar/ex_act(severity, target) ..() - if(isnull(gcDestroyed)) + if(!QDELETED(src)) switch(severity) if(2) if(prob(50) && broken()) @@ -516,7 +516,7 @@ /obj/machinery/power/solar_control/ex_act(severity, target) ..() - if(isnull(gcDestroyed)) + if(!QDELETED(src)) switch(severity) if(2) if(prob(50)) diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index ef953d84113..d7bea130272 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -75,7 +75,7 @@ /obj/structure/reagent_dispensers/fueltank/bullet_act(obj/item/projectile/P) ..() - if(!qdeleted(src)) //wasn't deleted by the projectile's effects. + if(!QDELETED(src)) //wasn't deleted by the projectile's effects. if(!P.nodamage && ((P.damage_type == BURN) || (P.damage_type == BRUTE))) message_admins("[key_name_admin(P.firer)] triggered a fueltank explosion.") log_game("[key_name(P.firer)] triggered a fueltank explosion.") diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/assault_pod.dm index fe3d059a08a..db5f68ba752 100644 --- a/code/modules/shuttle/assault_pod.dm +++ b/code/modules/shuttle/assault_pod.dm @@ -34,7 +34,7 @@ var/target_area target_area = input("Area to land", "Select a Landing Zone", target_area) in teleportlocs var/area/picked_area = teleportlocs[target_area] - if(!src || qdeleted(src)) + if(!src || QDELETED(src)) return var/turf/T = safepick(get_area_turfs(picked_area)) diff --git a/paradise.dme b/paradise.dme index aa7b1f84a02..25159dd0c48 100644 --- a/paradise.dme +++ b/paradise.dme @@ -190,7 +190,6 @@ #include "code\controllers\Processes\alarm.dm" #include "code\controllers\Processes\event.dm" #include "code\controllers\Processes\fast_process.dm" -#include "code\controllers\Processes\garbage.dm" #include "code\controllers\Processes\lighting.dm" #include "code\controllers\Processes\machinery.dm" #include "code\controllers\Processes\mob.dm" @@ -206,6 +205,7 @@ #include "code\controllers\ProcessScheduler\core\processScheduler.dm" #include "code\controllers\subsystem\air.dm" #include "code\controllers\subsystem\fires.dm" +#include "code\controllers\subsystem\garbage.dm" #include "code\controllers\subsystem\nanoui.dm" #include "code\controllers\subsystem\spacedrift.dm" #include "code\controllers\subsystem\sun.dm" @@ -219,6 +219,7 @@ #include "code\datums\cargoprofile.dm" #include "code\datums\computerfiles.dm" #include "code\datums\datacore.dm" +#include "code\datums\datum.dm" #include "code\datums\datumvars.dm" #include "code\datums\gas_mixture.dm" #include "code\datums\hud.dm" @@ -1412,8 +1413,6 @@ #include "code\modules\food_and_drinks\recipes\recipes_microwave.dm" #include "code\modules\food_and_drinks\recipes\recipes_oven.dm" #include "code\modules\food_and_drinks\recipes\tablecraft\recipes_table.dm" -#include "code\modules\garbage_collection\garbage_collector.dm" -#include "code\modules\garbage_collection\gc_testing.dm" #include "code\modules\holiday\christmas.dm" #include "code\modules\holiday\holiday.dm" #include "code\modules\hydroponics\biogenerator.dm"