diff --git a/code/__DEFINES/dcs/signals/signals.dm b/code/__DEFINES/dcs/signals/signals.dm
index f68655a7a3a4..2293c5bf8fd5 100644
--- a/code/__DEFINES/dcs/signals/signals.dm
+++ b/code/__DEFINES/dcs/signals/signals.dm
@@ -42,6 +42,7 @@
#define COMSIG_COMPONENT_REMOVING "component_removing"
/// before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted"
+#define COMSIG_PREQDELETED "parent_preqdeleted"
/// just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called
#define COMSIG_PARENT_QDELETING "parent_qdeleting"
/// generic topic handler (usr, href_list)
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 302c0de4a427..46090ebb48c5 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -489,6 +489,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
continue
if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time)
continue
+ if (SS.postponed_fires >= 1)
+ SS.postponed_fires--
+ SS.update_nextfire()
+ continue
SS.enqueue()
. = 1
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index f6e35bec6e59..332fcef0787c 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -75,6 +75,9 @@
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
+ /// How many fires have we been requested to postpone
+ var/postponed_fires = 0
+
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
@@ -132,6 +135,26 @@
Master.subsystems -= src
return ..()
+/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE)
+ var/queue_node_flags = flags
+
+ if (reset_time)
+ postponed_fires = 0
+ if (queue_node_flags & SS_TICKER)
+ next_fire = world.time + (world.tick_lag * wait)
+ else
+ next_fire = world.time + wait
+ return
+
+ if (queue_node_flags & SS_TICKER)
+ next_fire = world.time + (world.tick_lag * wait)
+ else if (queue_node_flags & SS_POST_FIRE_TIMING)
+ next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100))
+ else if (queue_node_flags & SS_KEEP_TIMING)
+ next_fire += wait
+ else
+ next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100))
+
//Queue it to run.
// (we loop thru a linked list until we get to the end or find the right point)
// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
@@ -251,8 +274,8 @@
//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
//for instance, during cpu intensive operations like explosions
/datum/controller/subsystem/proc/postpone(cycles = 1)
- if(next_fire - world.time < wait)
- next_fire += (wait*cycles)
+ if (can_fire && cycles >= 1)
+ postponed_fires += cycles
//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index da58d4764516..60372d39d95b 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -94,32 +94,38 @@ SUBSYSTEM_DEF(garbage)
/datum/controller/subsystem/garbage/Shutdown()
//Adds the del() log to the qdel log file
- var/list/dellog = list()
+ var/list/del_log = 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]"
+ var/list/entry = list()
+ del_log[path] = entry
+
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
- dellog += "\tSUSPENDED FOR LAG"
+ entry["SUSPENDED FOR LAG"] = TRUE
if (I.failures)
- dellog += "\tFailures: [I.failures]"
- dellog += "\tqdel() Count: [I.qdels]"
- dellog += "\tDestroy() Cost: [I.destroy_time]ms"
+ entry["Failures"] = I.failures
+ entry["qdel() Count"] = I.qdels
+ entry["Destroy() Cost (ms)"] = I.destroy_time
+
if (I.hard_deletes)
- dellog += "\tTotal Hard Deletes: [I.hard_deletes]"
- dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
- dellog += "\tHighest Time Spent Hard Deleting: [I.hard_delete_max]ms"
+ entry["Total Hard Deletes"] = I.hard_deletes
+ entry["Time Spend Hard Deleting (ms)"] = I.hard_delete_time
+ entry["Highest Time Spend Hard Deleting (ms)"] = I.hard_delete_max
if (I.hard_deletes_over_threshold)
- dellog += "\tHard Deletes Over Threshold: [I.hard_deletes_over_threshold]"
+ entry["Hard Deletes Over Threshold"] = I.hard_deletes_over_threshold
if (I.slept_destroy)
- dellog += "\tSleeps: [I.slept_destroy]"
+ entry["Total Sleeps"] = I.slept_destroy
if (I.no_respect_force)
- dellog += "\tIgnored force: [I.no_respect_force] times"
+ entry["Total Ignored Force"] = I.no_respect_force
if (I.no_hint)
- dellog += "\tNo hint: [I.no_hint] times"
- log_qdel(dellog.Join("\n"))
+ entry["Total No Hint"] = I.no_hint
+ if(LAZYLEN(I.extra_details))
+ entry["Deleted Metadata"] = I.extra_details
+
+ log_qdel("", del_log)
/datum/controller/subsystem/garbage/fire()
//the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
@@ -139,8 +145,6 @@ SUBSYSTEM_DEF(garbage)
state = SS_RUNNING
break
-
-
/datum/controller/subsystem/garbage/proc/InitQueues()
if (isnull(queues)) // Only init the queues if they don't already exist, prevents overriding of recovered lists
queues = new(GC_QUEUE_COUNT)
@@ -167,7 +171,10 @@ SUBSYSTEM_DEF(garbage)
lastlevel = level
- //We do this rather then for(var/refID in queue) because that sort of for loop copies the whole list.
+// 1 from the hard reference in the queue, and 1 from the variable used before this
+#define REFS_WE_EXPECT 2
+
+ //We do this rather then for(var/list/ref_info in queue) because that sort of for loop copies the whole list.
//Normally this isn't expensive, but the gc queue can grow to 40k items, and that gets costly/causes overrun.
for (var/i in 1 to length(queue))
var/list/L = queue[i]
@@ -178,21 +185,19 @@ SUBSYSTEM_DEF(garbage)
continue
var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME]
- var/GCd_at_time = L[GC_QUEUE_ITEM_GCD_DESTROYED]
if(queued_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
- var/refID = L[GC_QUEUE_ITEM_REF]
- var/datum/D
- D = locate(refID)
+ var/datum/D = L[GC_QUEUE_ITEM_REF]
- if (!D || D.gc_destroyed != GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
+ // If that's all we've got, send er off
+ if (refcount(D) == REFS_WE_EXPECT)
++gcedlasttick
++totalgcs
pass_counts[level]++
#ifdef REFERENCE_TRACKING
- reference_find_on_fail -= refID //It's deleted we don't care anymore.
+ reference_find_on_fail -= text_ref(D) //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -208,20 +213,30 @@ SUBSYSTEM_DEF(garbage)
switch (level)
if (GC_QUEUE_CHECK)
#ifdef REFERENCE_TRACKING
- if(reference_find_on_fail[refID])
- INVOKE_ASYNC(D, TYPE_PROC_REF(/datum, find_references))
+ // Decides how many refs to look for (potentially)
+ // Based off the remaining and the ones we can account for
+ var/remaining_refs = refcount(D) - REFS_WE_EXPECT
+ if(reference_find_on_fail[text_ref(D)])
+ INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references), remaining_refs)
ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
- INVOKE_ASYNC(D, TYPE_PROC_REF(/datum, find_references))
+ INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references), remaining_refs)
ref_searching = TRUE
#endif
- reference_find_on_fail -= refID
+ reference_find_on_fail -= text_ref(D)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
- log_world("## TESTING: GC: -- [text_ref(D)] | [type] was unable to be GC'd --")
+ var/message = "## TESTING: GC: -- [text_ref(D)] | [type] was unable to be GC'd --"
+ message = "[message] (ref count of [refcount(D)])"
+ log_world(message)
+
+ var/detail = D.dump_harddel_info()
+ if(detail)
+ LAZYADD(I.extra_details, detail)
+
#ifdef TESTING
for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
var/client/admin = c
@@ -231,6 +246,12 @@ SUBSYSTEM_DEF(garbage)
#endif
I.failures++
+ if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
+ #ifdef REFERENCE_TRACKING
+ if(ref_searching)
+ return //ref searching intentionally cancels all further fires while running so things that hold references don't end up getting deleted, so we want to return here instead of continue
+ #endif
+ continue
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
if (MC_TICK_CHECK)
@@ -250,41 +271,41 @@ SUBSYSTEM_DEF(garbage)
queue.Cut(1,count+1)
count = 0
+#undef REFS_WE_EXPECT
+
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_FILTER)
if (isnull(D))
return
if (level > GC_QUEUE_COUNT)
- HardDelete(D, TRUE)
+ HardDelete(D)
return
var/queue_time = world.time
- var/refid = text_ref(D)
if (D.gc_destroyed <= 0)
D.gc_destroyed = queue_time
var/list/queue = queues[level]
-
- queue[++queue.len] = list(queue_time, refid, D.gc_destroyed) // not += for byond reasons
+ queue[++queue.len] = list(queue_time, D, D.gc_destroyed) // not += for byond reasons
//this is mainly to separate things profile wise.
-/datum/controller/subsystem/garbage/proc/HardDelete(datum/D, force)
+/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
++delslasttick
++totaldels
var/type = D.type
var/refID = text_ref(D)
- var/datum/qdel_item/I = items[type]
-
- if (!force && I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
- return
+ var/datum/qdel_item/type_info = items[type]
+ var/detail = D.dump_harddel_info()
+ if(detail)
+ LAZYADD(type_info.extra_details, detail)
var/tick_usage = TICK_USAGE
del(D)
tick_usage = TICK_USAGE_TO_MS(tick_usage)
- I.hard_deletes++
- I.hard_delete_time += tick_usage
- if (tick_usage > I.hard_delete_max)
- I.hard_delete_max = tick_usage
+ type_info.hard_deletes++
+ type_info.hard_delete_time += tick_usage
+ if (tick_usage > type_info.hard_delete_max)
+ type_info.hard_delete_max = tick_usage
if (tick_usage > highest_del_ms)
highest_del_ms = tick_usage
highest_del_type_string = "[type]"
@@ -295,14 +316,14 @@ SUBSYSTEM_DEF(garbage)
postpone(time)
var/threshold = CONFIG_GET(number/hard_deletes_overrun_threshold)
if (threshold && (time > threshold SECONDS))
- if (!(I.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
+ if (!(type_info.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
log_game("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)")
message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete).")
- I.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
- I.hard_deletes_over_threshold++
+ type_info.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
+ type_info.hard_deletes_over_threshold++
var/overrun_limit = CONFIG_GET(number/hard_deletes_overrun_limit)
- if (overrun_limit && I.hard_deletes_over_threshold >= overrun_limit)
- I.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
+ if (overrun_limit && type_info.hard_deletes_over_threshold >= overrun_limit)
+ type_info.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
/datum/controller/subsystem/garbage/Recover()
InitQueues() //We first need to create the queues before recovering data
@@ -324,79 +345,85 @@ SUBSYSTEM_DEF(garbage)
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
var/qdel_flags = 0 //!Flags related to this type's trip thru qdel.
+ var/list/extra_details //!Lazylist of string metadata about the deleted objects
/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)
+/proc/qdel(datum/to_delete, force = FALSE)
+ if(!istype(to_delete))
+ del(to_delete)
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++
+ var/datum/qdel_item/trash = SSgarbage.items[to_delete.type]
+ if (isnull(trash))
+ trash = SSgarbage.items[to_delete.type] = new /datum/qdel_item(to_delete.type)
+ trash.qdels++
- if(isnull(D.gc_destroyed))
- if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
- return
- D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
- var/start_time = world.time
- var/start_tick = world.tick_usage
- SEND_SIGNAL(D, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
- 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)
+ if(!isnull(to_delete.gc_destroyed))
+ if(to_delete.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
+ CRASH("[to_delete.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
+ return
+
+ if (SEND_SIGNAL(to_delete, COMSIG_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
+ return
+
+ to_delete.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
+ var/start_time = world.time
+ var/start_tick = world.tick_usage
+ SEND_SIGNAL(to_delete, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
+ var/hint = to_delete.Destroy(force) // Let our friend know they're about to get fucked up.
+
+ if(world.time != start_time)
+ trash.slept_destroy++
+ else
+ trash.destroy_time += TICK_USAGE_TO_MS(start_tick)
+
+ if(isnull(to_delete))
+ return
+
+ switch(hint)
+ if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
+ SSgarbage.Queue(to_delete)
+ if (QDEL_HINT_IWILLGC)
+ to_delete.gc_destroyed = world.time
return
- switch(hint)
- if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
- SSgarbage.Queue(D)
- if (QDEL_HINT_IWILLGC)
- D.gc_destroyed = world.time
+ if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
+ if(!force)
+ to_delete.gc_destroyed = null //clear the gc variable (important!)
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++
+ // Returning LETMELIVE after being told to force destroy
+ // indicates the objects Destroy() does not respect force
+ #ifdef TESTING
+ if(!trash.no_respect_force)
+ testing("WARNING: [to_delete.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
+ trash.no_respect_force++
- SSgarbage.Queue(D)
- if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
- SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
- if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
- SSgarbage.HardDelete(D, TRUE)
- #ifdef REFERENCE_TRACKING
- if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
- SSgarbage.Queue(D)
- D.find_references()
- if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
- SSgarbage.Queue(D)
- SSgarbage.reference_find_on_fail[text_ref(D)] = TRUE
+ SSgarbage.Queue(to_delete)
+ if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
+ SSgarbage.Queue(to_delete, GC_QUEUE_HARDDELETE)
+ if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
+ SSgarbage.HardDelete(to_delete)
+ #ifdef REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
+ SSgarbage.Queue(to_delete)
+ INVOKE_ASYNC(to_delete, TYPE_PROC_REF(/datum, find_references))
+ if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
+ SSgarbage.Queue(to_delete)
+ SSgarbage.reference_find_on_fail[text_ref(to_delete)] = TRUE
+ #endif
+ else
+ #ifdef TESTING
+ if(!trash.no_hint)
+ testing("WARNING: [to_delete.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
#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.Queue(D)
- else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
- CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
+ trash.no_hint++
+ SSgarbage.Queue(to_delete)
diff --git a/code/datums/aquarium.dm b/code/datums/aquarium.dm
index da8c3afeb531..37a38f7849e6 100644
--- a/code/datums/aquarium.dm
+++ b/code/datums/aquarium.dm
@@ -136,7 +136,7 @@
. = ..()
REMOVE_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, REF(src))
-/datum/component/aquarium_content/Destroy(force, silent)
+/datum/component/aquarium_content/Destroy(force)
if(current_aquarium)
remove_from_aquarium()
QDEL_NULL(vc_obj)
diff --git a/code/datums/components/admin_popup.dm b/code/datums/components/admin_popup.dm
index 88ef0d97fabf..98d0eccfbf2a 100644
--- a/code/datums/components/admin_popup.dm
+++ b/code/datums/components/admin_popup.dm
@@ -26,7 +26,7 @@
PROC_REF(delete_self),
)
-/datum/component/admin_popup/Destroy(force, silent)
+/datum/component/admin_popup/Destroy(force)
var/client/parent_client = parent
parent_client?.screen -= admin_popup
diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm
index da8c3afeb531..37a38f7849e6 100644
--- a/code/datums/components/aquarium.dm
+++ b/code/datums/components/aquarium.dm
@@ -136,7 +136,7 @@
. = ..()
REMOVE_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, REF(src))
-/datum/component/aquarium_content/Destroy(force, silent)
+/datum/component/aquarium_content/Destroy(force)
if(current_aquarium)
remove_from_aquarium()
QDEL_NULL(vc_obj)
diff --git a/code/datums/components/attachment.dm b/code/datums/components/attachment.dm
index 01e3abedd80b..1d1edcd44545 100644
--- a/code/datums/components/attachment.dm
+++ b/code/datums/components/attachment.dm
@@ -52,7 +52,7 @@
for(var/signal in signals)
RegisterSignal(parent, signal, signals[signal])
-/datum/component/attachment/Destroy(force, silent)
+/datum/component/attachment/Destroy(force)
REMOVE_TRAIT(parent, TRAIT_ATTACHABLE, "attachable")
if(actions && length(actions))
var/obj/item/gun/parent = src.parent
diff --git a/code/datums/components/attachment_holder.dm b/code/datums/components/attachment_holder.dm
index 82968a17604b..f83a55eb201a 100644
--- a/code/datums/components/attachment_holder.dm
+++ b/code/datums/components/attachment_holder.dm
@@ -57,7 +57,7 @@
SIGNAL_HANDLER
qdel(src)
-/datum/component/attachment_holder/Destroy(force, silent)
+/datum/component/attachment_holder/Destroy(force)
QDEL_LIST(attachments)
attachments = null
return ..()
diff --git a/code/datums/components/creamed.dm b/code/datums/components/creamed.dm
index 019bb7362bd2..c2cf5d07a28e 100644
--- a/code/datums/components/creamed.dm
+++ b/code/datums/components/creamed.dm
@@ -39,7 +39,7 @@ GLOBAL_LIST_INIT(creamable, typecacheof(list(
var/atom/A = parent
A.add_overlay(creamface)
-/datum/component/creamed/Destroy(force, silent)
+/datum/component/creamed/Destroy(force)
var/atom/A = parent
A.cut_overlay(creamface)
qdel(creamface)
diff --git a/code/datums/components/deadchat_control.dm b/code/datums/components/deadchat_control.dm
index f34960db1072..6030214bf8b7 100644
--- a/code/datums/components/deadchat_control.dm
+++ b/code/datums/components/deadchat_control.dm
@@ -24,7 +24,7 @@
notify_ghosts("[parent] is now deadchat controllable!", source = parent, action = NOTIFY_ORBIT, header="Something Interesting!")
-/datum/component/deadchat_control/Destroy(force, silent)
+/datum/component/deadchat_control/Destroy(force)
inputs = null
orbiters = null
ckey_to_cooldown = null
diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm
index cde77f969911..729c50f2349f 100644
--- a/code/datums/components/food/edible.dm
+++ b/code/datums/components/food/edible.dm
@@ -142,7 +142,7 @@ Behavior that's still missing from this component that original food items had t
src.after_eat = after_eat
src.on_consume = on_consume
-/datum/component/edible/Destroy(force, silent)
+/datum/component/edible/Destroy(force)
QDEL_NULL(pre_eat)
QDEL_NULL(on_compost)
QDEL_NULL(after_eat)
diff --git a/code/datums/components/food/food_storage.dm b/code/datums/components/food/food_storage.dm
index 259ef4a8b6c6..6bc3641711b3 100644
--- a/code/datums/components/food/food_storage.dm
+++ b/code/datums/components/food/food_storage.dm
@@ -29,7 +29,7 @@
bad_chance_of_discovery = _bad_chance
good_chance_of_discovery = _good_chance
-/datum/component/food_storage/Destroy(force, silent)
+/datum/component/food_storage/Destroy(force)
if(stored_item)
stored_item.forceMove(stored_item.drop_location())
stored_item.dropped()
diff --git a/code/datums/components/gunpoint.dm b/code/datums/components/gunpoint.dm
index 85701e9c7626..ab7b1e641410 100644
--- a/code/datums/components/gunpoint.dm
+++ b/code/datums/components/gunpoint.dm
@@ -46,7 +46,7 @@
addtimer(CALLBACK(src, PROC_REF(update_stage), 2), GUNPOINT_DELAY_STAGE_2)
-/datum/component/gunpoint/Destroy(force, silent)
+/datum/component/gunpoint/Destroy(force)
var/mob/living/shooter = parent
shooter.remove_status_effect(STATUS_EFFECT_HOLDUP)
target.remove_status_effect(STATUS_EFFECT_HELDUP)
diff --git a/code/datums/components/manual_blinking.dm b/code/datums/components/manual_blinking.dm
index d97e88ca8fe9..e33d5f558d81 100644
--- a/code/datums/components/manual_blinking.dm
+++ b/code/datums/components/manual_blinking.dm
@@ -22,7 +22,7 @@
last_blink = world.time
to_chat(C, "You suddenly realize you're blinking manually.")
-/datum/component/manual_blinking/Destroy(force, silent)
+/datum/component/manual_blinking/Destroy(force)
E = null
STOP_PROCESSING(SSdcs, src)
to_chat(parent, "You revert back to automatic blinking.")
diff --git a/code/datums/components/manual_breathing.dm b/code/datums/components/manual_breathing.dm
index bcae15536ca7..882887f0ccc5 100644
--- a/code/datums/components/manual_breathing.dm
+++ b/code/datums/components/manual_breathing.dm
@@ -22,7 +22,7 @@
last_breath = world.time
to_chat(C, "You suddenly realize you're breathing manually.")
-/datum/component/manual_breathing/Destroy(force, silent)
+/datum/component/manual_breathing/Destroy(force)
L = null
STOP_PROCESSING(SSdcs, src)
to_chat(parent, "You revert back to automatic breathing.")
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 19b1e2094993..9ef5b57d1fa1 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -60,7 +60,7 @@
else if(isgrenade(parent) || islandmine(parent) || issupplypod(parent))
radius = magnitude
-/datum/component/pellet_cloud/Destroy(force, silent)
+/datum/component/pellet_cloud/Destroy(force)
purple_hearts = null
pellets = null
targets_hit = null
diff --git a/code/datums/components/shielded.dm b/code/datums/components/shielded.dm
index 81cb0c2b4d40..7c2c3473e2d0 100644
--- a/code/datums/components/shielded.dm
+++ b/code/datums/components/shielded.dm
@@ -50,7 +50,7 @@
if(recharge_start_delay)
START_PROCESSING(SSdcs, src)
-/datum/component/shielded/Destroy(force, silent)
+/datum/component/shielded/Destroy(force)
if(wearer)
shield_icon = "broken"
UnregisterSignal(wearer, COMSIG_ATOM_UPDATE_OVERLAYS)
diff --git a/code/datums/components/weatherannouncer.dm b/code/datums/components/weatherannouncer.dm
index a5e622d8669e..7da27dcbba2f 100644
--- a/code/datums/components/weatherannouncer.dm
+++ b/code/datums/components/weatherannouncer.dm
@@ -38,7 +38,7 @@
speaker.update_appearance(UPDATE_ICON)
update_light_color()
-/datum/component/weather_announcer/Destroy(force, silent)
+/datum/component/weather_announcer/Destroy(force)
STOP_PROCESSING(SSprocessing, src)
return ..()
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index e2f478ba7834..97da48745fae 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -44,8 +44,12 @@
var/datum/weakref/weak_reference
#ifdef REFERENCE_TRACKING
- var/running_find_references
+ /// When was this datum last touched by a reftracker?
+ /// If this value doesn't match with the start of the search
+ /// We know this datum has never been seen before, and we should check it
var/last_find_references = 0
+ /// How many references we're trying to find when searching
+ var/references_to_clear = 0
#ifdef REFERENCE_TRACKING_DEBUG
///Stores info about where refs are found, used for sanity checks and testing
var/list/found_refs
@@ -226,3 +230,19 @@
qdel(D)
else
return returned
+
+/// Return text from this proc to provide extra context to hard deletes that happen to it
+/// Optional, you should use this for cases where replication is difficult and extra context is required
+/// Can be called more then once per object, use harddel_deets_dumped to avoid duplicate calls (I am so sorry)
+/datum/proc/dump_harddel_info()
+ return
+
+/image
+ var/harddel_deets_dumped = FALSE
+
+///images are pretty generic, this should help a bit with tracking harddels related to them
+/image/dump_harddel_info()
+ if(harddel_deets_dumped)
+ return
+ harddel_deets_dumped = TRUE
+ return "Image icon: [icon] - icon_state: [icon_state] [loc ? "loc: [loc] ([loc.x],[loc.y],[loc.z])" : ""]"
diff --git a/code/datums/elements/food/edible.dm b/code/datums/elements/food/edible.dm
index a06a5ec28b79..615422198d88 100644
--- a/code/datums/elements/food/edible.dm
+++ b/code/datums/elements/food/edible.dm
@@ -143,7 +143,7 @@ Behavior that's still missing from this component that original food items had t
src.after_eat = after_eat
src.on_consume = on_consume
-/datum/component/edible/Destroy(force, silent)
+/datum/component/edible/Destroy(force)
QDEL_NULL(pre_eat)
QDEL_NULL(on_compost)
QDEL_NULL(after_eat)
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 7134d2e8ecef..9dea05393577 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -16,7 +16,8 @@
var/last_progress = 0
///Variable to ensure smooth visual stacking on multiple progress bars.
var/listindex = 0
-
+ ///The type of our last value for bar_loc, for debugging
+ var/location_type
/datum/progressbar/New(mob/User, goal_number, atom/target)
. = ..()
@@ -32,6 +33,7 @@
return
goal = goal_number
bar_loc = target
+ location_type = bar_loc.type
bar = image('icons/effects/progressbar.dmi', bar_loc, "prog_bar_0", HUD_LAYER)
bar.plane = ABOVE_HUD_PLANE
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
@@ -135,6 +137,10 @@
QDEL_IN(src, PROGRESSBAR_ANIMATION_TIME)
+///Progress bars are very generic, and what hangs a ref to them depends heavily on the context in which they're used
+///So let's make hunting harddels easier yeah?
+/datum/progressbar/dump_harddel_info()
+ return "Owner's type: [location_type]"
#undef PROGRESSBAR_ANIMATION_TIME
#undef PROGRESSBAR_HEIGHT
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index fda41eab7789..21eb3cc362b7 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -99,6 +99,11 @@
/obj/machinery/camera/proc/create_prox_monitor()
if(!proximity_monitor)
proximity_monitor = new(src, 1)
+ RegisterSignal(proximity_monitor, COMSIG_PARENT_QDELETING, PROC_REF(proximity_deleted))
+
+/obj/machinery/camera/proc/proximity_deleted()
+ SIGNAL_HANDLER
+ proximity_monitor = null
/obj/machinery/camera/proc/set_area_motion(area/A)
area_motion = A
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 2c269ce1ee9a..61d8b2757836 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -614,6 +614,9 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
dellog += "
Ignored force: [I.no_respect_force]"
if (I.no_hint)
dellog += "No hint: [I.no_hint]"
+ if(LAZYLEN(I.extra_details))
+ var/details = I.extra_details.Join("")
+ dellog += "Extra Info: "
dellog += ""
dellog += ""
diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm
index a9a84986416d..b9fd0e6d2ad6 100644
--- a/code/modules/admin/view_variables/reference_tracking.dm
+++ b/code/modules/admin/view_variables/reference_tracking.dm
@@ -1,36 +1,29 @@
#ifdef REFERENCE_TRACKING
+#define REFSEARCH_RECURSE_LIMIT 64
-/datum/proc/find_references(skip_alert)
- running_find_references = type
+/datum/proc/find_references(references_to_clear = INFINITY)
if(usr?.client)
- if(usr.client.running_find_references)
- log_reftracker("CANCELLED search for references to a [usr.client.running_find_references].")
- usr.client.running_find_references = null
- running_find_references = null
- //restart the garbage collector
- SSgarbage.can_fire = TRUE
- SSgarbage.next_fire = world.time + world.tick_lag
+ if(tgui_alert(usr,"Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", list("Yes", "No")) != "Yes")
return
- #ifndef FIND_REF_NO_CHECK_TICK
- if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes")
- running_find_references = null
- return
- #endif
-
+ src.references_to_clear = references_to_clear
//this keeps the garbage collector from failing to collect objects being searched for in here
SSgarbage.can_fire = FALSE
- if(usr?.client)
- usr.client.running_find_references = type
+ _search_references()
+ //restart the garbage collector
+ SSgarbage.can_fire = TRUE
+ SSgarbage.update_nextfire(reset_time = TRUE)
- log_reftracker("Beginning search for references to a [type].")
+/datum/proc/_search_references()
+ log_reftracker("Beginning search for references to a [type], looking for [references_to_clear] refs.")
var/starting_time = world.time
-
//Time to search the whole game for our ref
- DoSearchVar(GLOB, "GLOB", search_time = starting_time) //globals
+ DoSearchVar(GLOB, "GLOB", starting_time) //globals
log_reftracker("Finished searching globals")
+ if(src.references_to_clear == 0)
+ return
//Yes we do actually need to do this. The searcher refuses to read weird lists
//And global.vars is a really weird list
@@ -38,45 +31,46 @@
for(var/key in global.vars)
global_vars[key] = global.vars[key]
- DoSearchVar(global_vars, "Native Global", search_time = starting_time)
+ DoSearchVar(global_vars, "Native Global", starting_time)
log_reftracker("Finished searching native globals")
+ if(src.references_to_clear == 0)
+ return
for(var/datum/thing in world) //atoms (don't beleive its lies)
- DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time)
+ DoSearchVar(thing, "World -> [thing.type]", starting_time)
+ if(src.references_to_clear == 0)
+ break
log_reftracker("Finished searching atoms")
+ if(src.references_to_clear == 0)
+ return
for(var/datum/thing) //datums
- DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time)
+ DoSearchVar(thing, "Datums -> [thing.type]", starting_time)
+ if(src.references_to_clear == 0)
+ break
log_reftracker("Finished searching datums")
+ if(src.references_to_clear == 0)
+ return
-#ifndef REFERENCE_DOING_IT_LIVE
//Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
+#ifndef REFERENCE_DOING_IT_LIVE
for(var/client/thing) //clients
- DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time)
+ DoSearchVar(thing, "Clients -> [thing.type]", starting_time)
+ if(src.references_to_clear == 0)
+ break
log_reftracker("Finished searching clients")
-
- log_reftracker("Completed search for references to a [type].")
+ if(src.references_to_clear == 0)
+ return
#endif
- if(usr?.client)
- usr.client.running_find_references = null
- running_find_references = null
-
- //restart the garbage collector
- SSgarbage.can_fire = TRUE
- SSgarbage.next_fire = world.time + world.tick_lag
-
-/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time)
- #ifdef REFERENCE_TRACKING_DEBUG
- if(SSgarbage.should_save_refs && !found_refs)
- found_refs = list()
- #endif
+ log_reftracker("Completed search for references to a [type].")
- if(usr?.client && !usr.client.running_find_references)
+/datum/proc/DoSearchVar(potential_container, container_name, search_time, recursion_count, is_special_list)
+ if(recursion_count >= REFSEARCH_RECURSE_LIMIT)
+ log_reftracker("Recursion limit reached. [container_name]")
return
- if(!recursive_limit)
- log_reftracker("Recursion limit reached. [container_name]")
+ if(references_to_clear == 0)
return
//Check each time you go down a layer. This makes it a bit slow, but it won't effect the rest of the game at all
@@ -84,7 +78,7 @@
CHECK_TICK
#endif
- if(istype(potential_container, /datum))
+ if(isdatum(potential_container))
var/datum/datum_container = potential_container
if(datum_container.last_find_references == search_time)
return
@@ -92,68 +86,122 @@
datum_container.last_find_references = search_time
var/list/vars_list = datum_container.vars
+ var/is_atom = FALSE
+ var/is_area = FALSE
+ if(isatom(datum_container))
+ is_atom = TRUE
+ if(isarea(datum_container))
+ is_area = TRUE
for(var/varname in vars_list)
- #ifndef FIND_REF_NO_CHECK_TICK
- CHECK_TICK
- #endif
- if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references
- continue
var/variable = vars_list[varname]
-
- if(variable == src)
+ if(islist(variable))
+ //Fun fact, vis_locs don't count for references
+ if(varname == "vars" || (is_atom && (varname == "vis_locs" || varname == "overlays" || varname == "underlays" || varname == "filters" || varname == "verbs" || (is_area && varname == "contents"))))
+ continue
+ // We do this after the varname check to avoid area contents (reading it incures a world loop's worth of cost)
+ if(!length(variable))
+ continue
+ DoSearchVar(variable,\
+ "[container_name] [datum_container.ref_search_details()] -> [varname] (list)",\
+ search_time,\
+ recursion_count + 1,\
+ /*is_special_list = */ is_atom && (varname == "contents" || varname == "vis_contents" || varname == "locs"))
+ else if(variable == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
+ if(!found_refs)
+ found_refs = list()
found_refs[varname] = TRUE
continue //End early, don't want these logging
+ else
+ log_reftracker("Found [type] [text_ref(src)] in [datum_container.type]'s [datum_container.ref_search_details()] [varname] var. [container_name]")
+ #else
+ log_reftracker("Found [type] [text_ref(src)] in [datum_container.type]'s [datum_container.ref_search_details()] [varname] var. [container_name]")
#endif
- log_reftracker("Found [type] [text_ref(src)] in [datum_container.type]'s [text_ref(datum_container)] [varname] var. [container_name]")
+ references_to_clear -= 1
+ if(references_to_clear == 0)
+ log_reftracker("All references to [type] [text_ref(src)] found, exiting.")
+ return
continue
- if(islist(variable))
- DoSearchVar(variable, "[container_name] [text_ref(datum_container)] -> [varname] (list)", recursive_limit - 1, search_time)
-
else if(islist(potential_container))
- var/normal = IS_NORMAL_LIST(potential_container)
var/list/potential_cache = potential_container
for(var/element_in_list in potential_cache)
- #ifndef FIND_REF_NO_CHECK_TICK
- CHECK_TICK
- #endif
+ //Check normal sublists
+ if(islist(element_in_list))
+ if(length(element_in_list))
+ DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", search_time, recursion_count + 1)
//Check normal entrys
- if(element_in_list == src)
+ else if(element_in_list == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
+ if(!found_refs)
+ found_refs = list()
found_refs[potential_cache] = TRUE
- continue //End early, don't want these logging
- #endif
+ continue
+ else
+ log_reftracker("Found [type] [text_ref(src)] in list [container_name].")
+ #else
log_reftracker("Found [type] [text_ref(src)] in list [container_name].")
- continue
-
- var/assoc_val = null
- if(!isnum(element_in_list) && normal)
- assoc_val = potential_cache[element_in_list]
- //Check assoc entrys
- if(assoc_val == src)
- #ifdef REFERENCE_TRACKING_DEBUG
- if(SSgarbage.should_save_refs)
- found_refs[potential_cache] = TRUE
- continue //End early, don't want these logging
#endif
- log_reftracker("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
- continue
- //We need to run both of these checks, since our object could be hiding in either of them
- //Check normal sublists
- if(islist(element_in_list))
- DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time)
- //Check assoc sublists
- if(islist(assoc_val))
- DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", recursive_limit - 1, search_time)
-/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE)
- thing_to_del.qdel_and_find_ref_if_fail(force)
+ // This is dumb as hell I'm sorry
+ // I don't want the garbage subsystem to count as a ref for the purposes of this number
+ // If we find all other refs before it I want to early exit, and if we don't I want to keep searching past it
+ var/ignore_ref = FALSE
+ var/list/queues = SSgarbage.queues
+ for(var/list/queue in queues)
+ if(potential_cache in queue)
+ ignore_ref = TRUE
+ break
+ if(ignore_ref)
+ log_reftracker("[container_name] does not count as a ref for our count")
+ else
+ references_to_clear -= 1
+ if(references_to_clear == 0)
+ log_reftracker("All references to [type] [text_ref(src)] found, exiting.")
+ return
+
+ if(!isnum(element_in_list) && !is_special_list)
+ // This exists to catch an error that throws when we access a special list
+ // is_special_list is a hint, it can be wrong
+ try
+ var/assoc_val = potential_cache[element_in_list]
+ //Check assoc sublists
+ if(islist(assoc_val))
+ if(length(assoc_val))
+ DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", search_time, recursion_count + 1)
+ //Check assoc entry
+ else if(assoc_val == src)
+ #ifdef REFERENCE_TRACKING_DEBUG
+ if(SSgarbage.should_save_refs)
+ if(!found_refs)
+ found_refs = list()
+ found_refs[potential_cache] = TRUE
+ continue
+ else
+ log_reftracker("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
+ #else
+ log_reftracker("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
+ #endif
+ references_to_clear -= 1
+ if(references_to_clear == 0)
+ log_reftracker("All references to [type] [text_ref(src)] found, exiting.")
+ return
+ catch
+ // So if it goes wrong we kill it
+ is_special_list = TRUE
+ log_reftracker("Curiosity: [container_name] lead to an error when acessing [element_in_list], what is it?")
+
+#undef REFSEARCH_RECURSE_LIMIT
+#endif
-/datum/proc/qdel_and_find_ref_if_fail(force = FALSE)
- SSgarbage.reference_find_on_fail[text_ref(src)] = TRUE
- qdel(src, force)
+// Kept outside the ifdef so overrides are easy to implement
-#endif
+/// Return info about us for reference searching purposes
+/// Will be logged as a representation of this datum if it's a part of a search chain
+/datum/proc/ref_search_details()
+ return text_ref(src)
+
+/datum/callback/ref_search_details()
+ return "[text_ref(src)] (obj: [object] proc: [delegate] args: [json_encode(arguments)] user: [user?.resolve() || "null"])"
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index f5c37b18997d..7fb618f34ddb 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -12,10 +12,12 @@
// Damn our lack of abstract interfeces
if (istype(target, /obj/item/clothing/head/chameleon/drone))
var/obj/item/clothing/head/chameleon/drone/X = target
- X.chameleon_action.random_look(owner)
+ var/datum/action/item_action/chameleon/change/chameleon_action_x = locate() in X.actions
+ chameleon_action_x.random_look(owner)
if (istype(target, /obj/item/clothing/mask/chameleon/drone))
var/obj/item/clothing/mask/chameleon/drone/Z = target
- Z.chameleon_action.random_look(owner)
+ var/datum/action/item_action/chameleon/change/chameleon_action_z = locate() in Z.actions
+ chameleon_action_z.random_look(owner)
return 1
@@ -133,7 +135,8 @@
if(helmet_type)
var/obj/item/clothing/head/chameleon/hat = H.head
- hat.chameleon_action.update_look(user, helmet_type)
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in hat.actions
+ chameleon_action.update_look(user, helmet_type)
// ID card sechud
if(outfit.job_icon)
@@ -320,11 +323,9 @@
can_adjust = FALSE
armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/under/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/under
chameleon_action.chameleon_name = "Jumpsuit"
chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/under, /obj/item/clothing/under/color, /obj/item/clothing/under/rank, /obj/item/clothing/under/changeling), only_root_path = TRUE)
@@ -334,10 +335,12 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/under/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/clothing/suit/chameleon
@@ -351,11 +354,9 @@
resistance_flags = NONE
armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/suit/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/suit
chameleon_action.chameleon_name = "Suit"
chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/suit/armor/abductor, /obj/item/clothing/suit/changeling), only_root_path = TRUE)
@@ -365,10 +366,12 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/suit/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/clothing/glasses/chameleon
@@ -379,11 +382,9 @@
resistance_flags = NONE
armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/glasses/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/glasses
chameleon_action.chameleon_name = "Glasses"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE)
@@ -393,10 +394,12 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/glasses/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/clothing/gloves/chameleon
@@ -408,11 +411,9 @@
resistance_flags = NONE
armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/gloves/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/gloves
chameleon_action.chameleon_name = "Gloves"
chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/gloves, /obj/item/clothing/gloves/color, /obj/item/clothing/gloves/changeling), only_root_path = TRUE)
@@ -422,10 +423,12 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/gloves/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/clothing/head/chameleon
@@ -436,11 +439,9 @@
resistance_flags = NONE
armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/head/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/head
chameleon_action.chameleon_name = "Hat"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/head/changeling, only_root_path = TRUE)
@@ -450,10 +451,12 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/head/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/clothing/head/chameleon/drone
@@ -465,6 +468,7 @@
/obj/item/clothing/head/chameleon/drone/Initialize()
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.random_look()
var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src)
togglehatmask_action.UpdateButtonIcon()
@@ -486,11 +490,9 @@
var/voice_change = 1 ///This determines if the voice changer is on or off.
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/mask/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/mask
chameleon_action.chameleon_name = "Mask"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/mask/changeling, only_root_path = TRUE)
@@ -500,10 +502,12 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/mask/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/clothing/mask/chameleon/attack_self(mob/user)
@@ -519,6 +523,7 @@
/obj/item/clothing/mask/chameleon/drone/Initialize()
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.random_look()
var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src)
togglehatmask_action.UpdateButtonIcon()
@@ -537,11 +542,9 @@
armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/shoes/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/shoes
chameleon_action.chameleon_name = "Shoes"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/shoes/changeling, only_root_path = TRUE)
@@ -551,6 +554,7 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/shoes/chameleon/noslip
@@ -562,15 +566,15 @@
/obj/item/clothing/shoes/chameleon/noslip/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/storage/backpack/chameleon
name = "backpack"
- var/datum/action/item_action/chameleon/change/chameleon_action
/obj/item/storage/backpack/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/storage/backpack
chameleon_action.chameleon_name = "Backpack"
chameleon_action.initialize_disguises()
@@ -579,21 +583,22 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/storage/backpack/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/storage/belt/chameleon
name = "toolbelt"
desc = "Holds tools."
- var/datum/action/item_action/chameleon/change/chameleon_action
/obj/item/storage/belt/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/storage/belt
chameleon_action.chameleon_name = "Belt"
chameleon_action.initialize_disguises()
@@ -607,19 +612,17 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/storage/belt/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
-/obj/item/radio/headset/chameleon
- name = "radio headset"
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/radio/headset/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/radio/headset
chameleon_action.chameleon_name = "Headset"
chameleon_action.initialize_disguises()
@@ -628,19 +631,17 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/radio/headset/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
-/obj/item/pda/chameleon
- name = "PDA"
- var/datum/action/item_action/chameleon/change/pda/chameleon_action
-
/obj/item/pda/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/pda
chameleon_action.chameleon_name = "PDA"
chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/pda/heads, /obj/item/pda/ai, /obj/item/pda/ai/pai), only_root_path = TRUE)
@@ -650,24 +651,24 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/pda/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
-/obj/item/stamp/chameleon
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/stamp/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/stamp
chameleon_action.chameleon_name = "Stamp"
chameleon_action.initialize_disguises()
/obj/item/stamp/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
/obj/item/clothing/neck/chameleon
@@ -677,12 +678,9 @@
resistance_flags = NONE
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
-/obj/item/clothing/neck/chameleon
- var/datum/action/item_action/chameleon/change/chameleon_action
-
/obj/item/clothing/neck/chameleon/Initialize()
. = ..()
- chameleon_action = new(src)
+ var/datum/action/item_action/chameleon/change/chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/neck
chameleon_action.chameleon_name = "Neck Accessory"
chameleon_action.initialize_disguises()
@@ -691,8 +689,10 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise()
/obj/item/clothing/neck/chameleon/broken/Initialize()
. = ..()
+ var/datum/action/item_action/chameleon/change/chameleon_action = locate() in actions
chameleon_action.emp_randomise(INFINITY)
diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm
index f16ad0055056..b7c1b924fd53 100644
--- a/code/modules/unit_tests/create_and_destroy.dm
+++ b/code/modules/unit_tests/create_and_destroy.dm
@@ -198,6 +198,9 @@
TEST_FAIL("[item.name] failed to respect force deletion [item.no_respect_force] times out of a total del count of [item.qdels]")
if(item.no_hint)
TEST_FAIL("[item.name] failed to return a qdel hint [item.no_hint] times out of a total del count of [item.qdels]")
+ if(LAZYLEN(item.extra_details))
+ var/details = item.extra_details.Join("\n")
+ TEST_FAIL("[item.name] failed with extra info: \n[details]")
cache_for_sonic_speed = SSatoms.BadInitializeCalls
for(var/path in cache_for_sonic_speed)
diff --git a/code/modules/unit_tests/find_reference_sanity.dm b/code/modules/unit_tests/find_reference_sanity.dm
index 67b6072d3b96..f6736ccb1d58 100644
--- a/code/modules/unit_tests/find_reference_sanity.dm
+++ b/code/modules/unit_tests/find_reference_sanity.dm
@@ -15,6 +15,8 @@
return ..()
/atom/movable/ref_test
+ // Gotta make sure we do a full check
+ references_to_clear = INFINITY
var/atom/movable/ref_test/self_ref
/atom/movable/ref_test/Destroy(force)
@@ -27,8 +29,8 @@
SSgarbage.should_save_refs = TRUE
//Sanity check
- victim.DoSearchVar(testbed, "Sanity Check", search_time = 1) //We increment search time to get around an optimization
- TEST_ASSERT(!victim.found_refs.len, "The ref-tracking tool found a ref where none existed")
+ victim.DoSearchVar(testbed, "Sanity Check") //We increment search time to get around an optimization
+ TEST_ASSERT(!LAZYLEN(victim.found_refs), "The ref-tracking tool found a ref where none existed")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_baseline/Run()
@@ -41,11 +43,11 @@
testbed.test_list += victim
testbed.test_assoc_list["baseline"] = victim
- victim.DoSearchVar(testbed, "First Run", search_time = 2)
+ victim.DoSearchVar(testbed, "First Run")
- TEST_ASSERT(victim.found_refs["test"], "The ref-tracking tool failed to find a regular value")
- TEST_ASSERT(victim.found_refs[testbed.test_list], "The ref-tracking tool failed to find a list entry")
- TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list value")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, "test"), "The ref-tracking tool failed to find a regular value")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_list), "The ref-tracking tool failed to find a list entry")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list value")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_exotic/Run()
@@ -58,12 +60,12 @@
testbed.vis_contents += victim
testbed.test_assoc_list[victim] = TRUE
- victim.DoSearchVar(testbed, "Second Run", search_time = 3)
+ victim.DoSearchVar(testbed, "Second Run")
//This is another sanity check
- TEST_ASSERT(!victim.found_refs[testbed.overlays], "The ref-tracking tool found an overlays entry? That shouldn't be possible")
- TEST_ASSERT(victim.found_refs[testbed.vis_contents], "The ref-tracking tool failed to find a vis_contents entry")
- TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list key")
+ TEST_ASSERT(!LAZYACCESS(victim.found_refs, testbed.overlays), "The ref-tracking tool found an overlays entry? That shouldn't be possible")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.vis_contents), "The ref-tracking tool failed to find a vis_contents entry")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find an assoc list key")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_esoteric/Run()
@@ -78,11 +80,11 @@
var/list/to_find_assoc = list(victim)
testbed.test_assoc_list["Nesting"] = to_find_assoc
- victim.DoSearchVar(victim, "Third Run Self", search_time = 4)
- victim.DoSearchVar(testbed, "Third Run Testbed", search_time = 4)
- TEST_ASSERT(victim.found_refs["self_ref"], "The ref-tracking tool failed to find a self reference")
- TEST_ASSERT(victim.found_refs[to_find], "The ref-tracking tool failed to find a nested list entry")
- TEST_ASSERT(victim.found_refs[to_find_assoc], "The ref-tracking tool failed to find a nested assoc list entry")
+ victim.DoSearchVar(victim, "Third Run Self")
+ victim.DoSearchVar(testbed, "Third Run Testbed")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, "self_ref"), "The ref-tracking tool failed to find a self reference")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find), "The ref-tracking tool failed to find a nested list entry")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_assoc), "The ref-tracking tool failed to find a nested assoc list entry")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_null_key_entry/Run()
@@ -93,8 +95,8 @@
//Calm before the storm
testbed.test_assoc_list = list(null = victim)
- victim.DoSearchVar(testbed, "Fourth Run", search_time = 5)
- TEST_ASSERT(testbed.test_assoc_list, "The ref-tracking tool failed to find a null key'd assoc list entry")
+ victim.DoSearchVar(testbed, "Fourth Run")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_assoc_list), "The ref-tracking tool failed to find a null key'd assoc list entry")
/datum/unit_test/find_reference_assoc_investigation/Run()
var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
@@ -107,9 +109,9 @@
var/list/to_find_null_assoc_nested = list(victim)
testbed.test_assoc_list[null] = to_find_null_assoc_nested
- victim.DoSearchVar(testbed, "Fifth Run", search_time = 6)
- TEST_ASSERT(victim.found_refs[to_find_in_key], "The ref-tracking tool failed to find a nested assoc list key")
- TEST_ASSERT(victim.found_refs[to_find_null_assoc_nested], "The ref-tracking tool failed to find a null key'd nested assoc list entry")
+ victim.DoSearchVar(testbed, "Fifth Run")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_in_key), "The ref-tracking tool failed to find a nested assoc list key")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_null_assoc_nested), "The ref-tracking tool failed to find a null key'd nested assoc list entry")
SSgarbage.should_save_refs = FALSE
/datum/unit_test/find_reference_static_investigation/Run()
@@ -126,7 +128,7 @@
for(var/key in global.vars)
global_vars[key] = global.vars[key]
- victim.DoSearchVar(global_vars, "Sixth Run", search_time = 7)
+ victim.DoSearchVar(global_vars, "Sixth Run")
- TEST_ASSERT(victim.found_refs[global_vars], "The ref-tracking tool failed to find a natively global variable")
+ TEST_ASSERT(LAZYACCESS(victim.found_refs, global_vars), "The ref-tracking tool failed to find a natively global variable")
SSgarbage.should_save_refs = FALSE