mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-23 20:16:55 +01:00
Ports TG's Garbage Subsystem
This commit is contained in:
@@ -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, "<span class='warning'>As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?</span>")
|
||||
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].", \
|
||||
"<span class='notice'>You have unfastened \the [src].</span>", \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+22
-2
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]"
|
||||
|
||||
+10
-2
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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, "<span class='warning'>Your phylactery is gone!</span>")
|
||||
return
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]"))
|
||||
|
||||
@@ -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, "<span class='cult'>There is already a rune here.</span>")
|
||||
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 @@
|
||||
"<span class='cult'>You finish drawing the arcane markings of [ticker.mode.cultdat.entity_title3].</span>")
|
||||
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()
|
||||
|
||||
@@ -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("<span class='warning'>Dark power begins to channel into the paper!.</span>")
|
||||
@@ -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, "<span class='cultitalic'>The sacrificial target has been moved!</span>")
|
||||
@@ -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, "<span class='cultitalic'>You require a summoning target!</span>")
|
||||
|
||||
@@ -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("<span class='warning'>Dust flows from [user]'s hand, and they disappear in a flash of red light!</span>", \
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
audible_message("<span class='italics'>You hear a loud electrical buzzing sound!</span>")
|
||||
to_chat(src, "<span class='warning'>Reprogramming machine behaviour...</span>")
|
||||
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, "<span class='notice'>Out of uses.</span>")
|
||||
|
||||
@@ -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, "<span class='changeling'><i>[truename] [say_string]:</i> [input]</span>")
|
||||
@@ -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
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
if(crit_fail)//in case it depowers while ghost is looking at yes/no
|
||||
to_chat(user, "<span class='warning'>This swarmer shell is completely depowered. You cannot activate it.</span>")
|
||||
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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -240,7 +240,7 @@ Made by Xhuis
|
||||
M.visible_message("<span class='big'>[M] screams and contorts!</span>", \
|
||||
"<span class='userdanger'>THE LIGHT-- YOUR MIND-- <i>BURNS--</i></span>")
|
||||
spawn(30)
|
||||
if(!M || qdeleted(M))
|
||||
if(!M || QDELETED(M))
|
||||
return
|
||||
M.visible_message("<span class='warning'>[M] suddenly bloats and explodes!</span>", \
|
||||
"<span class='warning'><b>AAAAAAAAA<font size=3>AAAAAAAAAAAAA</font><font size=4>AAAAAAAAAAAA----</font></span>")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, "<span class='warning'>The device is not able to play more DJ sounds at this time.</span>")
|
||||
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"
|
||||
|
||||
@@ -79,7 +79,7 @@ for reference:
|
||||
user.visible_message("<span class='notice'>[user] is prying apart \the [src].</span>", "<span class='notice'>You begin to pry apart \the [src].</span>")
|
||||
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("<span class='notice'>[user] pries apart \the [src].</span>", "<span class='notice'>You pry apart \the [src].</span>")
|
||||
dismantle()
|
||||
return
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
to_chat(user, "<span class='warning'>The panel must be closed before operating this machine!</span>")
|
||||
return
|
||||
|
||||
if(!linked_pad || qdeleted(linked_pad))
|
||||
if(!linked_pad || QDELETED(linked_pad))
|
||||
to_chat(user, "<span class='warning'>There is no linked pad!</span>")
|
||||
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, "<span class='warning'>[src] is unpowered!</span>")
|
||||
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, "<span class='warning'>Linked pad is not responding to ping. Teleport aborted.</span>")
|
||||
teleporting = 0
|
||||
return
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, "<span class='userdanger'>Inactive core destroyed. Unable to return.</span>")
|
||||
AI.linked_core = null
|
||||
return
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
|
||||
/obj/singularity_act()
|
||||
ex_act(1)
|
||||
if(src && !qdeleted(src))
|
||||
if(src && !QDELETED(src))
|
||||
qdel(src)
|
||||
return 2
|
||||
|
||||
|
||||
@@ -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("<font color='red'>Cremated [M.name] ([M.ckey])</font>")
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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("<B>List of things that have gone through qdel this round</B><BR><BR><ol>")
|
||||
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 += "<li><u>[path]</u><ul>"
|
||||
if(I.failures)
|
||||
dellog += "<li>Failures: [I.failures]</li>"
|
||||
dellog += "<li>qdel() Count: [I.qdels]</li>"
|
||||
dellog += "<li>Destroy() Cost: [I.destroy_time]ms</li>"
|
||||
if(I.hard_deletes)
|
||||
dellog += "<li>Total Hard Deletes [I.hard_deletes]</li>"
|
||||
dellog += "<li>Time Spent Hard Deleting: [I.hard_delete_time]ms</li>"
|
||||
if(I.slept_destroy)
|
||||
dellog += "<li>Sleeps: [I.slept_destroy]</li>"
|
||||
if(I.no_respect_force)
|
||||
dellog += "<li>Ignored force: [I.no_respect_force]</li>"
|
||||
if(I.no_hint)
|
||||
dellog += "<li>No hint: [I.no_hint]</li>"
|
||||
dellog += "</ul></li>"
|
||||
|
||||
dellog += "</ol>"
|
||||
|
||||
usr << browse(dellog.Join(), "window=dellog")
|
||||
|
||||
|
||||
/client/proc/cmd_admin_toggle_block(var/mob/M,var/block)
|
||||
if(!check_rights(R_SPAWN))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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//
|
||||
///////////////
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -264,7 +264,7 @@ Gunshots/explosions/opening doors/less rare audio (done)
|
||||
to_chat(T, "<span class='changeling'><i>Primary [rand(1000,9999)] states:</i> [pick("Hello","Hi","You're my slave now!","Don't try to get rid of me...")]</span>")
|
||||
break
|
||||
sleep(4)
|
||||
if(!qdeleted(borer))
|
||||
if(!QDELETED(borer))
|
||||
qdel(borer)
|
||||
qdel(src)
|
||||
|
||||
|
||||
@@ -1248,7 +1248,7 @@
|
||||
return 1
|
||||
|
||||
/obj/item/weapon/reagent_containers/food/snacks/monkeycube/proc/Expand()
|
||||
if(isnull(gcDestroyed))
|
||||
if(!QDELETED(src))
|
||||
visible_message("<span class='notice'>[src] expands!</span>")
|
||||
new/mob/living/carbon/human(get_turf(src), monkey_type)
|
||||
qdel(src)
|
||||
|
||||
@@ -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.
|
||||
|
||||
*/
|
||||
@@ -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))
|
||||
@@ -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, "<b>GC: qdel turned [garbageCollector.del_everything ? "off" : "on"].</b>")
|
||||
log_admin("[key_name(usr)] turned qdel [garbageCollector.del_everything ? "off" : "on"].")
|
||||
message_admins("<span class='notice'>[key_name_admin(usr)] turned qdel [garbageCollector.del_everything ? "off" : "on"].</span>", 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 = "<B>List of things that failed to GC this round</B><BR><BR>"
|
||||
for(var/path in didntgc)
|
||||
dat += "[path] - [didntgc[path]] times<BR>"
|
||||
|
||||
dat += "<B>List of paths that did not return a qdel hint in Destroy()</B><BR><BR>"
|
||||
for(var/path in noqdelhint)
|
||||
dat += "[path]<BR>"
|
||||
|
||||
dat += "<B>List of paths that slept in Destroy()</B><BR><BR>"
|
||||
for(var/path in sleptDestroy)
|
||||
dat += "[path]<BR>"
|
||||
|
||||
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
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, "<span class='danger'>Hack aborted. The designated APC no longer exists on the power network.</span>")
|
||||
playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, 1)
|
||||
else if(apc.aidisabled)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
death()
|
||||
return 0
|
||||
|
||||
if(!eyeobj || qdeleted(eyeobj) || !eyeobj.loc)
|
||||
if(!eyeobj || QDELETED(eyeobj) || !eyeobj.loc)
|
||||
view_core()
|
||||
|
||||
if(machine)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]")
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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))
|
||||
|
||||
+2
-3
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user