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