diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm index d2f61e13dca..0c60ba0a5dd 100644 --- a/code/__defines/_compile_options.dm +++ b/code/__defines/_compile_options.dm @@ -1,3 +1,16 @@ // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary. // 1 will enable set background. 0 will disable set background. #define BACKGROUND_ENABLED 0 + +// If REFTRACK_IN_CI is defined, the reftracker will run in CI. +#define REFTRACK_IN_CI +#if defined(REFTRACK_IN_CI) && defined(UNIT_TEST) && !defined(SPACEMAN_DMM) +#define REFTRACKING_ENABLED +#define GC_FAILURE_HARD_LOOKUP +#define FIND_REF_NO_CHECK_TICK +#endif + +// parity with previous behavior where TESTING enabled reftracking +#ifdef TESTING +#define REFTRACKING_ENABLED +#endif \ No newline at end of file diff --git a/code/__defines/qdel.dm b/code/__defines/qdel.dm index 48a9f0e0973..fa9cf0a8deb 100644 --- a/code/__defines/qdel.dm +++ b/code/__defines/qdel.dm @@ -5,8 +5,8 @@ #define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it. #define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference. #define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste. -#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm. - //if TESTING is enabled, qdel will call this object's find_references() verb. +#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if REFTRACKING_ENABLED is not enabled in _compiler_options.dm. + //if REFTRACKING_ENABLED is enabled, qdel will call this object's find_references() verb. #define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails. //defines for the gc_destroyed var @@ -15,6 +15,12 @@ #define GC_QUEUE_HARDDELETE 3 #define GC_QUEUE_COUNT 3 //increase this when adding more steps. +// Defines for the ssgarbage queue items +#define GC_QUEUE_ITEM_QUEUE_TIME 1 //! Time this item entered the queue +#define GC_QUEUE_ITEM_REF 2 //! Ref to the item +#define GC_QUEUE_ITEM_GCD_DESTROYED 3 //! Item's gc_destroyed var value. Used to detect ref reuse. +#define GC_QUEUE_ITEM_INDEX_COUNT 3 //! Number of item indexes, used for allocating the nested lists. Don't forget to increase this if you add a new queue item index + #define GC_QUEUED_FOR_HARD_DEL -1 #define GC_CURRENTLY_BEING_QDELETED -2 diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index 47c1be9e245..18f92570395 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -37,7 +37,7 @@ SUBSYSTEM_DEF(garbage) //Queue var/list/queues - #ifdef TESTING + #ifdef REFTRACKING_ENABLED var/list/reference_find_on_fail = list() #endif @@ -130,30 +130,33 @@ SUBSYSTEM_DEF(garbage) lastlevel = level +// 1 from the hard reference in the queue, and 1 from `D` in the code below +#define REFS_WE_EXPECT 2 + //We do this rather then for(var/refID 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/refidx in 1 to length(queue)) - var/refID = queue[refidx] - if (isnull(refID)) + for (var/i in 1 to length(queue)) + var/list/L = queue[i] + if (length(L) < GC_QUEUE_ITEM_INDEX_COUNT) count++ if (MC_TICK_CHECK) return continue - var/GCd_at_time = queue[refID] - if(GCd_at_time > cut_off_time) + var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME] + if(queued_at_time > cut_off_time) break // Everything else is newer, skip them count++ - var/datum/D - D = locate(refID) + var/datum/D = L[GC_QUEUE_ITEM_REF] - if (isnull(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 TESTING - reference_find_on_fail -= refID //It's deleted we don't care anymore. + #ifdef REFTRACKING_ENABLED + reference_find_on_fail -= ref(D) //It's deleted we don't care anymore. #endif if (MC_TICK_CHECK) return @@ -161,14 +164,19 @@ SUBSYSTEM_DEF(garbage) // Something's still referring to the qdel'd object. fail_counts[level]++ + switch (level) if (GC_QUEUE_CHECK) - #ifdef TESTING + #ifdef REFTRACKING_ENABLED + // 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 + var/refID = ref(D) if(reference_find_on_fail[refID]) - D.find_references() + D.find_references(remaining_refs) #ifdef GC_FAILURE_HARD_LOOKUP else - D.find_references() + D.find_references(remaining_refs) #endif reference_find_on_fail -= refID #endif @@ -201,12 +209,14 @@ SUBSYSTEM_DEF(garbage) if (level > GC_QUEUE_COUNT) HardDelete(D) return - var/gctime = world.time - var/refid = "\ref[D]" + var/queue_time = world.time - D.gc_destroyed = gctime + if(D.gc_destroyed <= 0) // hasn't been queued yet, or is queued for harddel/actively being qdeleted + D.gc_destroyed = queue_time var/list/queue = queues[level] - queue[refid] = gctime + // not += for byond reasons + // we include D.gc_destroyed to skip things under the cutoff + queue[++queue.len] = list(queue_time, D, D.gc_destroyed) //this is mainly to separate things profile wise. /datum/controller/subsystem/garbage/proc/HardDelete(datum/D) @@ -240,11 +250,6 @@ SUBSYSTEM_DEF(garbage) message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).") postpone(time) -/datum/controller/subsystem/garbage/proc/HardQueue(datum/D) - if (D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) - queues[GC_QUEUE_FILTER] += D - D.gc_destroyed = GC_QUEUED_FOR_HARD_DEL - /datum/controller/subsystem/garbage/Recover() if (istype(SSgarbage.queues)) for (var/i in 1 to SSgarbage.queues.len) @@ -270,7 +275,7 @@ SUBSYSTEM_DEF(garbage) /datum/qdel_item/New(mytype) name = "[mytype]" -#ifdef TESTING +#ifdef REFTRACKING_ENABLED /proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE) SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE qdel(D, force) @@ -328,7 +333,7 @@ SUBSYSTEM_DEF(garbage) return // Returning LETMELIVE after being told to force destroy // indicates the objects Destroy() does not respect force - #ifdef TESTING + #ifdef REFTRACKING_ENABLED if(!I.no_respect_force) PRINT_STACK_TRACE("WARNING: [D.type] has been force deleted, but is \ returning an immortal QDEL_HINT, indicating it does \ @@ -341,21 +346,22 @@ SUBSYSTEM_DEF(garbage) SSgarbage.Queue(D) if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate() GC_CHECK_AM_NULLSPACE(D, "QDEL_HINT_HARDDEL") - SSgarbage.HardQueue(D) + 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) - if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion. + if (QDEL_HINT_FINDREFERENCE)//qdel will, if REFTRACKING_ENABLED is enabled, display all references to this object, then queue the object for deletion. SSgarbage.Queue(D) - #ifdef TESTING - D.find_references() + #ifdef REFTRACKING_ENABLED + var/remaining_refs = refcount(D) - REFS_WE_EXPECT + D.find_references(remaining_refs) #endif if (QDEL_HINT_IFFAIL_FINDREFERENCE) SSgarbage.Queue(D) - #ifdef TESTING + #ifdef REFTRACKING_ENABLED SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE #endif else - #ifdef TESTING + #ifdef REFTRACKING_ENABLED if(!I.no_hint) PRINT_STACK_TRACE("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 @@ -364,7 +370,7 @@ SUBSYSTEM_DEF(garbage) else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic") -#ifdef TESTING +#ifdef REFTRACKING_ENABLED /datum/verb/find_refs() set category = "Debug" @@ -378,8 +384,9 @@ SUBSYSTEM_DEF(garbage) return find_references() -/datum/proc/find_references() +/datum/proc/find_references(references_to_clear = INFINITY) running_find_references = type + src.references_to_clear = references_to_clear if(usr && usr.client) if(usr.client.running_find_references) testing("CANCELLED search for references to a [usr.client.running_find_references].") @@ -406,13 +413,19 @@ SUBSYSTEM_DEF(garbage) normal_globals[global_var] = global.vars[global_var] DoSearchVar(normal_globals, "(global) -> ") //globals testing("Finished searching globals") + if(src.references_to_clear == 0) // Found all expected references! + return for(var/atom/atom_thing) //atoms DoSearchVar(atom_thing, "World -> [atom_thing]") + if(src.references_to_clear == 0) // Found all expected references! + return testing("Finished searching atoms") for (var/datum/datum_thing) //datums DoSearchVar(datum_thing, "World -> [datum_thing]") + if(src.references_to_clear == 0) // Found all expected references! + return testing("Finished searching datums") #ifndef FIND_REF_SKIP_CLIENTS @@ -420,6 +433,8 @@ SUBSYSTEM_DEF(garbage) // IT WILL CRASH!!! for (var/client/client_thing) //clients DoSearchVar(client_thing, "World -> [client_thing]") + if(src.references_to_clear == 0) // Found all expected references! + return testing("Finished searching clients") #endif @@ -455,37 +470,47 @@ SUBSYSTEM_DEF(garbage) #define GET_TYPEID(ref) ( ( (length(ref) <= 10) ? "TYPEID_NULL" : copytext(ref, 4, length(ref)-6) ) ) #define IS_NORMAL_LIST(L) (GET_TYPEID("\ref[L]") == TYPEID_NORMAL_LIST) -/datum/proc/DoSearchVar(X, Xname, recursive_limit = 128) +/datum/proc/DoSearchVar(X, container_name, recursive_limit = 128) if(usr && usr.client && !usr.client.running_find_references) return if (!recursive_limit) return + if(references_to_clear == 0) + return #ifndef FIND_REF_NO_CHECK_TICK CHECK_TICK #endif if(istype(X, /datum)) - var/datum/D = X - if(D.last_find_references == last_find_references) + var/datum/datum_container = X + if(datum_container.last_find_references == last_find_references) return - D.last_find_references = last_find_references - var/list/L = D.vars + datum_container.last_find_references = last_find_references + var/list/vars_list = datum_container.vars - for(var/varname in L) + 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") + //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 - var/variable = L[varname] + var/variable = vars_list[varname] if(variable == src) - testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]") + testing("Found [src.type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]") + references_to_clear -= 1 else if(islist(variable)) - DoSearchVar(variable, "[Xname] -> [varname] (list)", recursive_limit-1) + DoSearchVar(variable, "[container_name] -> [varname] (list)", recursive_limit-1) else if(islist(X)) var/normal = IS_NORMAL_LIST(X) @@ -494,16 +519,33 @@ SUBSYSTEM_DEF(garbage) CHECK_TICK #endif if (I == src) - testing("Found [src.type] \ref[src] in list [Xname].") + testing("Found [src.type] \ref[src] in list [container_name].") + + // 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(X in queue) + ignore_ref = TRUE + break + if(ignore_ref) + testing("[container_name] does not count as a ref for our count") + else + references_to_clear -= 1 + if(references_to_clear == 0) + testing("All references to [type] \ref[src] found, exiting.") + return else if (I && !isnum(I) && normal) if(X[I] == src) - testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]") + testing("Found [src.type] \ref[src] in list [container_name]\[[I]\]") else if(islist(X[I])) - DoSearchVar(X[I], "[Xname]\[[I]\]", recursive_limit-1) + DoSearchVar(X[I], "[container_name]\[[I]\]", recursive_limit-1) else if (islist(I)) var/list/Xlist = X - DoSearchVar(I, "[Xname]\[[Xlist.Find(I)]\] -> list", recursive_limit-1) + DoSearchVar(I, "[container_name]\[[Xlist.Find(I)]\] -> list", recursive_limit-1) #endif diff --git a/code/controllers/subsystems/processing/nano.dm b/code/controllers/subsystems/processing/nano.dm index dfaae9e7999..dd358012c92 100644 --- a/code/controllers/subsystems/processing/nano.dm +++ b/code/controllers/subsystems/processing/nano.dm @@ -37,11 +37,10 @@ PROCESSING_SUBSYSTEM_DEF(nano) * @return /nanoui Returns the found ui, or null if none exists */ /datum/controller/subsystem/processing/nano/proc/get_open_ui(mob/user, src_object, ui_key) - var/src_object_key = "\ref[src_object]" - if (!open_uis[src_object_key] || !open_uis[src_object_key][ui_key]) + if (!open_uis[src_object] || !open_uis[src_object][ui_key]) return - for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key]) + for (var/datum/nanoui/ui in open_uis[src_object][ui_key]) if (ui.user == user) return ui @@ -54,12 +53,11 @@ PROCESSING_SUBSYSTEM_DEF(nano) */ /datum/controller/subsystem/processing/nano/proc/update_uis(src_object) . = 0 - var/src_object_key = "\ref[src_object]" - if (!open_uis[src_object_key]) + if (!open_uis[src_object]) return - for (var/ui_key in open_uis[src_object_key]) - for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key]) + for (var/ui_key in open_uis[src_object]) + for (var/datum/nanoui/ui in open_uis[src_object][ui_key]) if(ui.src_object && ui.user && ui.src_object.nano_host()) ui.try_update(1) .++ @@ -78,12 +76,11 @@ PROCESSING_SUBSYSTEM_DEF(nano) if (!length(open_uis)) return - var/src_object_key = "\ref[src_object]" - if (!open_uis[src_object_key]) + if (!open_uis[src_object]) return - for (var/ui_key in open_uis[src_object_key]) - for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key]) + for (var/ui_key in open_uis[src_object]) + for (var/datum/nanoui/ui in open_uis[src_object][ui_key]) ui.close() // If it's missing src_object or user, we want to close it even more. .++ @@ -134,9 +131,9 @@ PROCESSING_SUBSYSTEM_DEF(nano) * @return nothing */ /datum/controller/subsystem/processing/nano/proc/ui_opened(datum/nanoui/ui) - var/src_object_key = "\ref[ui.src_object]" - LAZYINITLIST(open_uis[src_object_key]) - LAZYDISTINCTADD(open_uis[src_object_key][ui.ui_key], ui) + var/src_object = ui.src_object + LAZYINITLIST(open_uis[src_object]) + LAZYDISTINCTADD(open_uis[src_object][ui.ui_key], ui) LAZYDISTINCTADD(ui.user.open_uis, ui) START_PROCESSING(SSnano, ui) @@ -149,18 +146,18 @@ PROCESSING_SUBSYSTEM_DEF(nano) * @return int 0 if no ui was removed, 1 if removed successfully */ /datum/controller/subsystem/processing/nano/proc/ui_closed(var/datum/nanoui/ui) - var/src_object_key = "\ref[ui.src_object]" - if (!open_uis[src_object_key] || !open_uis[src_object_key][ui.ui_key]) + var/src_object = ui.src_object + if (!open_uis[src_object] || !open_uis[src_object][ui.ui_key]) return 0 // wasn't open STOP_PROCESSING(SSnano, ui) if(ui.user) // Sanity check in case a user has been deleted (say a blown up borg watching the alarm interface) LAZYREMOVE(ui.user.open_uis, ui) - open_uis[src_object_key][ui.ui_key] -= ui - if(!length(open_uis[src_object_key][ui.ui_key])) - open_uis[src_object_key] -= ui.ui_key - if(!length(open_uis[src_object_key])) - open_uis -= src_object_key + open_uis[src_object][ui.ui_key] -= ui + if(!length(open_uis[src_object][ui.ui_key])) + open_uis[src_object] -= ui.ui_key + if(!length(open_uis[src_object])) + open_uis -= src_object return 1 /** diff --git a/code/datums/datum.dm b/code/datums/datum.dm index a0aeed07808..aa855c0b1e3 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -10,9 +10,14 @@ /// Used to avoid unnecessary refstring creation in Destroy(). var/tmp/has_state_machine = FALSE -#ifdef TESTING +#ifdef REFTRACKING_ENABLED var/tmp/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/tmp/last_find_references = 0 + /// How many references we're trying to find when searching + var/tmp/references_to_clear = 0 #endif // Default implementation of clean-up code. @@ -54,11 +59,11 @@ cleanup_events(src) if(has_state_machine) - var/list/machines = global.state_machines["\ref[src]"] + var/list/machines = global.state_machines[src] if(length(machines)) for(var/base_type in machines) qdel(machines[base_type]) - global.state_machines -= "\ref[src]" + global.state_machines -= src return QDEL_HINT_QUEUE diff --git a/code/datums/extensions/state_machine.dm b/code/datums/extensions/state_machine.dm index 63e58f0dd67..edba84c7965 100644 --- a/code/datums/extensions/state_machine.dm +++ b/code/datums/extensions/state_machine.dm @@ -3,16 +3,15 @@ var/global/list/state_machines = list() /proc/get_state_machine(var/datum/holder, var/base_type) if(istype(holder) && base_type && holder.has_state_machine) - var/list/machines = global.state_machines["\ref[holder]"] + var/list/machines = global.state_machines[holder] return islist(machines) && machines[base_type] /proc/add_state_machine(var/datum/holder, var/datum/state_machine/fsm_type) if(istype(holder) && fsm_type) - var/holder_ref = "\ref[holder]" - var/list/machines = global.state_machines[holder_ref] + var/list/machines = global.state_machines[holder] if(!islist(machines)) machines = list() - global.state_machines[holder_ref] = machines + global.state_machines[holder] = machines var/base_type = fsm_type::base_type if(!machines[base_type]) var/datum/state_machine/machine = new fsm_type(holder) @@ -22,12 +21,11 @@ var/global/list/state_machines = list() /proc/remove_state_machine(var/datum/holder, var/base_type) if(istype(holder) && base_type && holder.has_state_machine) - var/holder_ref = "\ref[holder]" - var/list/machines = global.state_machines[holder_ref] + var/list/machines = global.state_machines[holder] if(length(machines)) machines -= base_type if(!length(machines)) - global.state_machines -= holder_ref + global.state_machines -= holder holder.has_state_machine = FALSE return TRUE return FALSE diff --git a/code/datums/extensions/storage/_storage.dm b/code/datums/extensions/storage/_storage.dm index c79e91a7357..4ff70967e1c 100644 --- a/code/datums/extensions/storage/_storage.dm +++ b/code/datums/extensions/storage/_storage.dm @@ -252,6 +252,8 @@ var/global/list/_test_storage_items = list() storage_ui?.on_insertion() /datum/storage/proc/update_ui_after_item_removal(obj/item/removed) + if(QDELETED(holder)) + return prepare_ui() storage_ui?.on_post_remove() diff --git a/code/datums/extensions/storage/_storage_ui.dm b/code/datums/extensions/storage/_storage_ui.dm index 6f4db924f32..a3e1f23bfcb 100644 --- a/code/datums/extensions/storage/_storage_ui.dm +++ b/code/datums/extensions/storage/_storage_ui.dm @@ -216,7 +216,8 @@ closer.screen_loc = "LEFT+[SCREEN_LOC_MOD_FIRST + cols + 1]:[SCREEN_LOC_MOD_DIVIDED],BOTTOM+[SCREEN_LOC_MOD_SECOND]:[SCREEN_LOC_MOD_DIVIDED]" /datum/storage_ui/default/proc/space_orient_objs() - + if(QDELETED(_storage?.holder)) // don't bother if we've been deleted + return var/baseline_max_storage_space = DEFAULT_BOX_STORAGE //storage size corresponding to 224 pixels var/storage_cap_width = 2 //length of sprite for start and end of the box representing total storage space var/stored_cap_width = 4 //length of sprite for start and end of the box representing the stored item diff --git a/code/datums/graph/graph.dm b/code/datums/graph/graph.dm index 249f513e419..1610a6b4397 100644 --- a/code/datums/graph/graph.dm +++ b/code/datums/graph/graph.dm @@ -35,6 +35,8 @@ /datum/graph/proc/Connect(var/datum/node/node, var/list/neighbours, var/queue = TRUE) SHOULD_NOT_SLEEP(TRUE) SHOULD_NOT_OVERRIDE(TRUE) + if(QDELETED(src)) + CRASH("Attempted to connect node [node] to a qdeleted graph!") if(!istype(neighbours)) neighbours = list(neighbours) if(!length(neighbours)) @@ -90,8 +92,7 @@ if(neighbours_to_disconnect) neighbours |= neighbours_to_disconnect - if(neighbours.len) - LAZYSET(pending_disconnections, node, neighbours) + LAZYSET(pending_disconnections, node, neighbours) if(queue) SSgraphs_update.Queue(src) @@ -193,8 +194,6 @@ neighbour_edges |= N LAZYCLEARLIST(pending_connections) - if(!LAZYLEN(pending_disconnections)) - return for(var/pending_node_disconnect in pending_disconnections) var/pending_edge_disconnects = pending_disconnections[pending_node_disconnect] @@ -211,7 +210,8 @@ if(!length(other_pending_edge_disconnects)) pending_disconnections -= connected_node - edges[pending_node_disconnect] -= pending_edge_disconnects + if(edges[pending_node_disconnect]) + edges[pending_node_disconnect] -= pending_edge_disconnects if(!length(edges[pending_node_disconnect])) edges -= pending_node_disconnect @@ -225,12 +225,15 @@ var/checked_nodes = list() var/list/nodes_to_traverse = list(root_node) while(length(nodes_to_traverse)) - var/node_to_check = nodes_to_traverse[nodes_to_traverse.len] + var/datum/node/node_to_check = nodes_to_traverse[nodes_to_traverse.len] nodes_to_traverse.len-- + if(QDELETED(node_to_check)) + continue checked_nodes += node_to_check nodes_to_traverse |= ((edges[node_to_check] || list()) - checked_nodes) - all_nodes -= checked_nodes - subgraphs[++subgraphs.len] = checked_nodes + if(length(checked_nodes)) + all_nodes -= checked_nodes + subgraphs[++subgraphs.len] = checked_nodes if(length(subgraphs) == 1) if(!length(nodes)) diff --git a/code/game/alpha_masks.dm b/code/game/alpha_masks.dm index 05b23f45eaa..9c8a341a482 100644 --- a/code/game/alpha_masks.dm +++ b/code/game/alpha_masks.dm @@ -57,7 +57,7 @@ var/global/list/_alpha_masks = list() // Proc called by /turf/Entered() to update a mob's mask overlay. /atom/movable/proc/update_turf_alpha_mask() set waitfor = FALSE - if(!simulated || updating_turf_alpha_mask) + if(!simulated || QDELETED(src) || updating_turf_alpha_mask) return updating_turf_alpha_mask = TRUE sleep(0) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 5dd985c6064..d13760956e3 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -145,10 +145,12 @@ /atom/proc/try_on_reagent_change() SHOULD_NOT_OVERRIDE(TRUE) set waitfor = FALSE - if(_reagent_update_started >= world.time) + if(QDELETED(src) ||_reagent_update_started >= world.time) return FALSE _reagent_update_started = world.time sleep(0) // Defer to end of tick so we don't drop subsequent reagent updates. + if(QDELETED(src)) + return return on_reagent_change() /atom/proc/on_reagent_change() diff --git a/code/game/atoms_init.dm b/code/game/atoms_init.dm index 3a2f3b619c1..d1cd533c796 100644 --- a/code/game/atoms_init.dm +++ b/code/game/atoms_init.dm @@ -80,7 +80,7 @@ /atom/Destroy() // must be done before deletion // TODO: ADD PRE_DELETION OBSERVATION - if(isatom(loc) && loc.storage) + if(isatom(loc) && loc.storage && !QDELETED(loc.storage)) loc.storage.on_item_pre_deletion(src) UNQUEUE_TEMPERATURE_ATOM(src) QDEL_NULL(reagents) @@ -94,7 +94,7 @@ QDEL_NULL(atom_codex_ref) var/atom/oldloc = loc . = ..() - if(isatom(oldloc) && oldloc.storage) + if(isatom(oldloc) && oldloc.storage && !QDELETED(loc.storage)) oldloc.storage.on_item_post_deletion(src) // must be done after deletion // This might need to be moved onto a Del() override at some point. QDEL_NULL(storage) diff --git a/code/game/machinery/vending/_vending.dm b/code/game/machinery/vending/_vending.dm index 866062f2a54..b3036e17317 100644 --- a/code/game/machinery/vending/_vending.dm +++ b/code/game/machinery/vending/_vending.dm @@ -309,10 +309,8 @@ if (href_list["vend"] && !currently_vending) var/key = text2num(href_list["vend"]) - if(!is_valid_index(key, product_records)) - return TOPIC_REFRESH - var/datum/stored_items/vending_products/R = product_records[key] - if(!istype(R)) + var/datum/stored_items/vending_products/R = LAZYACCESS(product_records, key) + if(!R) return TOPIC_REFRESH // This should not happen unless the request from NanoUI was bad diff --git a/code/game/objects/auras/aura.dm b/code/game/objects/auras/aura.dm index 8d1d35a0680..5de18f3adf6 100644 --- a/code/game/objects/auras/aura.dm +++ b/code/game/objects/auras/aura.dm @@ -15,6 +15,7 @@ They should also be used for when you want to effect the ENTIRE mob, like having /obj/aura/Destroy() if(user) user.remove_aura(src) + removed() return ..() /obj/aura/proc/added_to(var/mob/living/target) diff --git a/code/game/objects/item_mob_overlay.dm b/code/game/objects/item_mob_overlay.dm index d6881129c23..fe253ea6f0b 100644 --- a/code/game/objects/item_mob_overlay.dm +++ b/code/game/objects/item_mob_overlay.dm @@ -8,13 +8,14 @@ var/global/list/bodypart_to_slot_lookup_table = list( ) /obj/item/proc/reconsider_single_icon(var/update_icon) - use_single_icon = check_state_in_icon(ICON_STATE_INV, icon) || check_state_in_icon(ICON_STATE_WORLD, icon) + var/list/icon_states = get_states_in_icon_cached(icon) // pre-cache to make our up-to-three checks faster + // except now we only do two because i rewrote it + has_inventory_icon = use_single_icon = icon_states[ICON_STATE_INV] + if(!has_inventory_icon) + use_single_icon = icon_states[ICON_STATE_WORLD] if(use_single_icon) - has_inventory_icon = check_state_in_icon(ICON_STATE_INV, icon) icon_state = get_world_inventory_state() . = TRUE - else - has_inventory_icon = FALSE if(. || update_icon) update_icon() @@ -27,6 +28,23 @@ var/global/list/icon_state_cache = list() // isicon() is apparently quite expensive so short-circuit out early if we can. if(!istext(checkstate) || isnull(checkicon) || !(isfile(checkicon) || isicon(checkicon))) return FALSE + var/list/check = _fetch_icon_state_cache_entry(checkicon) // should never return null once we reach this point + return check[checkstate] + +/// A proc for getting an associative list of icon states in an icon. +/// Uses the same cache as check_state_in_icon. +/// Does not copy, MUST NOT BE MUTATED. +/proc/get_states_in_icon_cached(checkicon) /* as OD_MAP(text, OD_BOOL) */ + return _fetch_icon_state_cache_entry(checkicon) || list() + +/// get_states_in_icon_cached but it does a copy, so the return value can be mutated. +/proc/get_states_in_icon(checkicon) /* as OD_MAP(text, OD_BOOL) */ + var/list/out = get_states_in_icon_cached(checkicon) + return out.Copy() + +/proc/_fetch_icon_state_cache_entry(checkicon) + if(isnull(checkicon) || !(isfile(checkicon) || isicon(checkicon))) + return null var/checkkey = "\ref[checkicon]" var/list/check = global.icon_state_cache[checkkey] if(!check) @@ -34,7 +52,7 @@ var/global/list/icon_state_cache = list() for(var/istate in icon_states(checkicon)) check[istate] = TRUE global.icon_state_cache[checkkey] = check - . = check[checkstate] + return check /obj/item/proc/update_world_inventory_state() if(use_single_icon && has_inventory_icon) diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm index c9c67e53fc7..5a082bb9180 100644 --- a/code/game/objects/items/blueprints.dm +++ b/code/game/objects/items/blueprints.dm @@ -59,7 +59,7 @@ return FALSE name += " - [S.name]" - desc = "Blueprints of \the [S.name]. There is a \"Classified\" stamp and several coffee stains on it." + desc = "Blueprints of \the [S]. There is a \"Classified\" stamp and several coffee stains on it." valid_z_levels += S.map_z area_prefix = S.name return TRUE diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index 3565259a08e..34115f49b63 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -110,7 +110,7 @@ if(!user.try_unequip(L)) return TRUE AddUses(1) - to_chat(user, "You insert \the [L.name] into \the [src]. You have [uses] light\s remaining.") + to_chat(user, "You insert \the [L] into \the [src]. You have [uses] light\s remaining.") qdel(L) return TRUE else diff --git a/code/game/objects/items/devices/paint_sprayer.dm b/code/game/objects/items/devices/paint_sprayer.dm index a9dbd8a0878..88c1e1195d7 100644 --- a/code/game/objects/items/devices/paint_sprayer.dm +++ b/code/game/objects/items/devices/paint_sprayer.dm @@ -223,7 +223,7 @@ return FALSE if(!flooring.can_paint || F.is_floor_damaged()) - to_chat(user, SPAN_WARNING("\The [src] cannot paint \the [F.name].")) + to_chat(user, SPAN_WARNING("\The [src] cannot paint \the [F].")) return FALSE var/list/decal_data = decals[decal] diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index b83f35bca48..2f2506b6aa0 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -7,11 +7,27 @@ var/obj/item/tank/tank_one var/obj/item/tank/tank_two var/obj/item/assembly/attached_device - var/mob/attacher = null + var/weakref/attacher_ref = null var/valve_open = 0 var/toggle = 1 movable_flags = MOVABLE_FLAG_PROXMOVE +/obj/item/transfer_valve/Destroy() + if(!QDELETED(tank_one)) + QDEL_NULL(tank_one) + else + tank_one = null + if(!QDELETED(tank_two)) + QDEL_NULL(tank_two) + else + tank_two = null + if(!QDELETED(attached_device)) + QDEL_NULL(attached_device) + else + attached_device = null + attacher_ref = null + return ..() + /obj/item/transfer_valve/attackby(obj/item/item, mob/user) var/turf/location = get_turf(src) // For admin logs if(istype(item, /obj/item/tank)) @@ -56,7 +72,7 @@ global.bombers += "[key_name(user)] attached a [item] to a transfer valve." message_admins("[key_name_admin(user)] attached a [item] to a transfer valve. (JMP)") log_game("[key_name_admin(user)] attached a [item] to a transfer valve.") - attacher = user + attacher_ref = weakref(user) . = TRUE if(.) update_icon() @@ -189,6 +205,7 @@ var/area/A = get_area(bombturf) var/attacher_name = "" + var/mob/attacher = attacher_ref.resolve() if(!attacher) attacher_name = "Unknown" else diff --git a/code/game/objects/items/weapons/ecigs.dm b/code/game/objects/items/weapons/ecigs.dm index dc05320c090..0a55a70a950 100644 --- a/code/game/objects/items/weapons/ecigs.dm +++ b/code/game/objects/items/weapons/ecigs.dm @@ -22,6 +22,10 @@ ec_cartridge = new cartridge_type(src) . = ..() +/obj/item/clothing/mask/smokable/ecig/Destroy() + QDEL_NULL(ec_cartridge) + return ..() + /obj/item/clothing/mask/smokable/ecig/setup_power_supply(loaded_cell_type, accepted_cell_type, power_supply_extension_type, charge_value) loaded_cell_type = loaded_cell_type || /obj/item/cell/device/standard accepted_cell_type = accepted_cell_type || /obj/item/cell/device diff --git a/code/game/objects/items/weapons/soap.dm b/code/game/objects/items/weapons/soap.dm index c14047c71f6..7667753288a 100644 --- a/code/game/objects/items/weapons/soap.dm +++ b/code/game/objects/items/weapons/soap.dm @@ -68,13 +68,13 @@ //So this is a workaround. This also makes more sense from an IC standpoint. ~Carn var/cleaned = FALSE if(user.client && (target in user.client.screen)) - to_chat(user, SPAN_NOTICE("You need to take that [target.name] off before cleaning it.")) + to_chat(user, SPAN_NOTICE("You need to take \the [target] off before cleaning it.")) else if(istype(target,/obj/effect/decal/cleanable/blood)) - to_chat(user, SPAN_NOTICE("You scrub \the [target.name] out.")) + to_chat(user, SPAN_NOTICE("You scrub \the [target] out.")) target.clean() //Blood is a cleanable decal, therefore needs to be accounted for before all cleanable decals. cleaned = TRUE else if(istype(target,/obj/effect/decal/cleanable)) - to_chat(user, SPAN_NOTICE("You scrub \the [target.name] out.")) + to_chat(user, SPAN_NOTICE("You scrub \the [target] out.")) qdel(target) cleaned = TRUE else if(isturf(target) || istype(target, /obj/structure/catwalk)) @@ -90,7 +90,7 @@ to_chat(user, SPAN_NOTICE("You wet \the [src] in the sink.")) wet() else - to_chat(user, SPAN_NOTICE("You clean \the [target.name].")) + to_chat(user, SPAN_NOTICE("You clean \the [target].")) target.clean() //Clean bloodied atoms. Blood decals themselves need to be handled above. cleaned = TRUE diff --git a/code/game/objects/structures/bookcase.dm b/code/game/objects/structures/bookcase.dm index 06d58e357aa..944bcd3fe0f 100644 --- a/code/game/objects/structures/bookcase.dm +++ b/code/game/objects/structures/bookcase.dm @@ -168,7 +168,7 @@ var/global/list/station_bookcases = list() add_overlay(book_overlay) var/page_state = "[book_overlay.icon_state]-pages" - if(check_state_in_icon(book_overlay.icon, page_state)) + if(check_state_in_icon(page_state, book_overlay.icon)) var/image/page_overlay = overlay_image(book_overlay.icon, page_state, COLOR_WHITE, RESET_COLOR) page_overlay.pixel_x = book_overlay.pixel_x page_overlay.pixel_y = book_overlay.pixel_y diff --git a/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm b/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm index caa3e14df93..67640a583fd 100644 --- a/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm +++ b/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm @@ -141,7 +141,7 @@ user.visible_message("[user] starts to lay out \the [src].") if(do_after(user, 4 SECONDS, src)) var/obj/structure/bed/chair/wheelchair/W = new structure_form_type(get_turf(user)) - user.visible_message(SPAN_NOTICE("[user] lays out \the [W.name].")) + user.visible_message(SPAN_NOTICE("[user] lays out \the [W].")) W.add_fingerprint(user) qdel(src) diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index a4cf742c410..47dcc913a45 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -12,7 +12,7 @@ var/secured = 1 var/list/attached_overlays = null - var/obj/item/assembly_holder/holder = null + var/obj/item/assembly_holder/holder = null // currently can be a TTV or assemblyholder, todo make ttv use assemblyholder var/cooldown = 0//To prevent spam var/wires = WIRE_RECEIVE | WIRE_PULSE @@ -24,10 +24,7 @@ /obj/item/assembly/Destroy() if(!QDELETED(holder)) - if(holder.a_left == src) - holder.a_left = null - if(holder.a_right == src) - holder.a_right = null + // the holder has the responsibility to clear its associated vars on destroy QDEL_NULL(holder) else holder = null diff --git a/code/modules/client/ui_styles/_ui_style.dm b/code/modules/client/ui_styles/_ui_style.dm index 5e0a5ba34c4..1a5885cfac6 100644 --- a/code/modules/client/ui_styles/_ui_style.dm +++ b/code/modules/client/ui_styles/_ui_style.dm @@ -46,7 +46,7 @@ var/check_icon = icons[ui_key] var/list/missing_states = list() var/list/checking_states = states_to_check[ui_key] - var/list/remaining_states = icon_states(check_icon) + var/list/remaining_states = get_states_in_icon(check_icon) for(var/check_state in checking_states) remaining_states -= check_state if(!check_state_in_icon(check_state, check_icon)) diff --git a/code/modules/clothing/masks/voice.dm b/code/modules/clothing/masks/voice.dm index edee4acd8eb..8ecc47d727c 100644 --- a/code/modules/clothing/masks/voice.dm +++ b/code/modules/clothing/masks/voice.dm @@ -30,3 +30,7 @@ /obj/item/clothing/mask/chameleon/voice/Initialize() . = ..() changer = new(src) + +/obj/item/clothing/mask/chameleon/voice/Destroy() + QDEL_NULL(changer) + return ..() diff --git a/code/modules/economy/worth_currency.dm b/code/modules/economy/worth_currency.dm index ed12b938e92..9e5de940b12 100644 --- a/code/modules/economy/worth_currency.dm +++ b/code/modules/economy/worth_currency.dm @@ -70,13 +70,13 @@ if(!name_singular) . += "No singular name set." - var/list/coinage_states = icon_states(icon) + var/list/coinage_states = get_states_in_icon_cached(icon) // cache this to avoid excessive ref() usage for(var/datum/denomination/denomination in denominations) if(!istext(denomination.name)) . += "Non-text name found for '[denomination.type]'." else if(!(denomination.state in coinage_states)) . += "State '[denomination.state]' not found in icon file for '[denomination.type]'." - else if(denomination.mark && !(denomination.mark in coinage_states)) + else if(denomination.mark && !coinage_states[denomination.mark]) . += "Mark state '[denomination.mark]' not found in icon file for '[denomination.type]'." else if(!isnum(denomination.marked_value)) . += "Non-numerical denomination marked value found for '[denomination]'." diff --git a/code/modules/hydroponics/seed_storage.dm b/code/modules/hydroponics/seed_storage.dm index 2d609cf629e..840d453ce50 100644 --- a/code/modules/hydroponics/seed_storage.dm +++ b/code/modules/hydroponics/seed_storage.dm @@ -3,14 +3,12 @@ var/amount var/datum/seed/seed_type // Keeps track of what our seed is var/list/obj/item/seeds/seeds = list() // Tracks actual objects contained in the pile - var/ID -/datum/seed_pile/New(var/obj/item/seeds/O, var/ID) +/datum/seed_pile/New(var/obj/item/seeds/O) name = O.name amount = 1 seed_type = O.seed seeds += O - src.ID = ID /datum/seed_pile/proc/matches(var/obj/item/seeds/O) if (O.seed == seed_type) @@ -195,7 +193,8 @@ if ("soil" in scanner) dat += "NutriWater" dat += "NotesAmount" - for (var/datum/seed_pile/S in piles) + for (var/key in 1 to length(piles)) + var/datum/seed_pile/S = piles[key] var/datum/seed/seed = S.seed_type if(!seed) continue @@ -281,7 +280,7 @@ dat += "LUM " dat += "" dat += "[S.amount]" - dat += "Vend Purge" + dat += "Vend Purge" dat += "" dat += "" @@ -292,29 +291,26 @@ if (..()) return var/task = href_list["task"] - var/ID = text2num(href_list["id"]) + var/id = text2num(href_list["id"]) + var/datum/seed_pile/our_pile = LAZYACCESS(piles, id) - for (var/datum/seed_pile/N in piles) - if (N.ID == ID) - if (task == "vend") - var/obj/O = pick(N.seeds) - if (O) - --N.amount - N.seeds -= O - if (N.amount <= 0 || N.seeds.len <= 0) - piles -= N - qdel(N) - flick("[initial(icon_state)]-vend", src) - O.dropInto(loc) - else - piles -= N - qdel(N) - else if (task == "purge") - for (var/obj/O in N.seeds) - qdel(O) - piles -= N - qdel(N) - break + switch(task) + if ("vend") + var/obj/O = pick(our_pile.seeds) + if (O) + --our_pile.amount + our_pile.seeds -= O + if (our_pile.amount <= 0 || our_pile.seeds.len <= 0) + piles -= our_pile + qdel(our_pile) + flick("[initial(icon_state)]-vend", src) + O.dropInto(loc) + if ("purge") + QDEL_LIST(our_pile.seeds) + our_pile.seeds.Cut() + if(!length(our_pile.seeds)) + piles -= our_pile + QDEL_NULL(our_pile) updateUsrDialog() /obj/machinery/seed_storage/attackby(var/obj/item/O, var/mob/user) @@ -334,7 +330,7 @@ if (loaded) user.visible_message(SPAN_NOTICE("\The [user] puts the seeds from \the [O] into \the [src].")) else - to_chat(user, SPAN_WARNING("There are no seeds in \the [O.name].")) + to_chat(user, SPAN_WARNING("There are no seeds in \the [O].")) return TRUE return ..() @@ -349,28 +345,13 @@ O.loc?.storage?.remove_from_storage(null, O, src) O.forceMove(src) - var/newID = 0 for (var/datum/seed_pile/N in piles) if (N.matches(O)) ++N.amount N.seeds += (O) return - else if(N.ID >= newID) - newID = N.ID + 1 - piles += new /datum/seed_pile(O, newID) + piles += new /datum/seed_pile(O) flick("[initial(icon_state)]-vend", src) return - -/obj/machinery/seed_storage/cannot_transition_to(state_path, mob/user) - if(state_path == /decl/machine_construction/default/deconstructed) - var/alert = alert(user, "Are you certain you wish to deconstruct this? It will destroy all seeds stored inside!", "Deconstruct Warning", "Yes", "No") - if(alert != "Yes" || !CanPhysicallyInteract(user)) - return MCS_BLOCK - return ..() - -/obj/machinery/seed_storage/dismantle() - for(var/obj/item/seeds/seed in src) - qdel(seed) // ..() would dump them; this would cause lots of client lag. We did warn them above... - return ..() \ No newline at end of file diff --git a/code/modules/mechs/equipment/_equipment.dm b/code/modules/mechs/equipment/_equipment.dm index 03ba4a1ac81..9df6c691597 100644 --- a/code/modules/mechs/equipment/_equipment.dm +++ b/code/modules/mechs/equipment/_equipment.dm @@ -71,10 +71,6 @@ owner = null canremove = TRUE -/obj/item/mech_equipment/Destroy() - owner = null - . = ..() - /obj/item/mech_equipment/proc/get_effective_obj() return src diff --git a/code/modules/mechs/equipment/medical.dm b/code/modules/mechs/equipment/medical.dm index 7c39947f122..4aadea64370 100644 --- a/code/modules/mechs/equipment/medical.dm +++ b/code/modules/mechs/equipment/medical.dm @@ -22,7 +22,7 @@ /obj/item/mech_equipment/sleeper/uninstalled() . = ..() - sleeper.go_out() + sleeper?.go_out() /obj/item/mech_equipment/sleeper/attack_self(var/mob/user) . = ..() diff --git a/code/modules/mechs/mech.dm b/code/modules/mechs/mech.dm index 03469507760..809f205d2bd 100644 --- a/code/modules/mechs/mech.dm +++ b/code/modules/mechs/mech.dm @@ -157,10 +157,14 @@ hud_elements.Cut() for(var/hardpoint in hardpoints) - qdel(hardpoints[hardpoint]) + var/obj/item/mech_equipment/equipment = hardpoints[hardpoint] + if(istype(equipment)) + equipment.uninstalled() + QDEL_NULL(equipment) hardpoints.Cut() QDEL_NULL(access_card) + QDEL_NULL(radio) QDEL_NULL(arms) QDEL_NULL(legs) QDEL_NULL(head) diff --git a/code/modules/mechs/mech_wreckage.dm b/code/modules/mechs/mech_wreckage.dm index b22b3923107..a78f4c7596c 100644 --- a/code/modules/mechs/mech_wreckage.dm +++ b/code/modules/mechs/mech_wreckage.dm @@ -12,7 +12,7 @@ /obj/structure/mech_wreckage/Initialize(mapload, var/mob/living/exosuit/exosuit, var/gibbed) . = ..(mapload) if(exosuit) - name = "wreckage of \the [exosuit.name]" + name = "wreckage of \the [exosuit]" loot_pool = list() if(!gibbed) for(var/obj/item/thing in list(exosuit.arms, exosuit.legs, exosuit.head, exosuit.body)) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index a74526afdd4..4cacbc889af 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -189,7 +189,7 @@ visible_message(SPAN_NOTICE("\The [O] misses \the [src] narrowly!")) return FALSE - visible_message(SPAN_DANGER("\The [src] is hit [affecting ? "in \the [affecting.name] " : ""]by \the [O]!")) + visible_message(SPAN_DANGER("\The [src] is hit [affecting ? "in \the [affecting] " : ""]by \the [O]!")) if(TT?.thrower?.client) admin_attack_log(TT.thrower, src, "Threw \an [O] at the victim.", "Had \an [O] thrown at them.", "threw \an [O] at") try_embed_in_mob(TT.thrower, O, zone, throw_damage, dtype, null, affecting, direction = TT.init_dir) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm index e3f5b4fe9ff..c8de00b79d2 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/drone.dm @@ -82,6 +82,10 @@ ion_trail.set_up(src) ion_trail.start() +/mob/living/simple_animal/hostile/malf_drone/Destroy() + QDEL_NULL(ion_trail) + return ..() + /mob/living/simple_animal/hostile/malf_drone/Process_Spacemove() return 1 diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index a3009921249..585f57d0d7e 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -225,7 +225,7 @@ for(var/mob/living/M in landing.contents) if(M == src) continue - visible_message("\The [src] hits \the [M.name]!") + visible_message("\The [src] hits \the [M]!") M.take_overall_damage(fall_damage) return TRUE return FALSE diff --git a/code/modules/overmap/ships/landable.dm b/code/modules/overmap/ships/landable.dm index 375e4253ec2..24f4d999c0a 100644 --- a/code/modules/overmap/ships/landable.dm +++ b/code/modules/overmap/ships/landable.dm @@ -231,14 +231,14 @@ if(SHIP_STATUS_LANDED) var/obj/effect/overmap/visitable/location = loc if(istype(loc, /obj/effect/overmap/visitable/sector)) - return "Landed on \the [location.name]. Use secondary thrust to get clear before activating primary engines." + return "Landed on \the [location]. Use secondary thrust to get clear before activating primary engines." if(istype(loc, /obj/effect/overmap/visitable/ship)) - return "Docked with \the [location.name]. Use secondary thrust to get clear before activating primary engines." + return "Docked with \the [location]. Use secondary thrust to get clear before activating primary engines." return "Docked with an unknown object." if(SHIP_STATUS_ENCOUNTER) var/datum/shuttle/autodock/overmap/child_shuttle = SSshuttle.shuttles[shuttle] var/obj/effect/overmap/visitable/location = global.overmap_sectors[num2text(child_shuttle.current_location.z)] - return "Maneuvering nearby \the [location.name]." + return "Maneuvering nearby \the [location]." if(SHIP_STATUS_TRANSIT) return "Maneuvering under secondary thrust." if(SHIP_STATUS_OVERMAP) diff --git a/code/modules/overmap/ships/ship_physics.dm b/code/modules/overmap/ships/ship_physics.dm index 66e85a3f0b9..28e71cf1f5b 100644 --- a/code/modules/overmap/ships/ship_physics.dm +++ b/code/modules/overmap/ships/ship_physics.dm @@ -36,31 +36,43 @@ /obj/effect/overmap/visitable/ship/proc/recalculate_vessel_mass() var/list/zones = list() + // for(var/turf/tile in area) is an implied in-world loop + // an in-world loop per area is very bad, so instead + // we do one in-world loop and check area + var/list/areas = list() + // create an associative list of area -> TRUE so that lookup is faster for(var/area/A in get_areas()) - - // Do not include space please - if(istype(A, world.area)) + if(istype(A, world.area)) // exclude the base area continue + areas[A] = TRUE + var/start_z = min(map_z) + var/end_z = max(map_z) + if(!start_z || !end_z) + return initial(vessel_mass) // This shouldn't happen ideally so just go with the initial vessel mass + for(var/z_level in start_z to end_z) + var/datum/level_data/z_data = SSmapping.levels_by_z[z_level] + for(var/turf/tile in block(z_data.level_inner_min_x, z_data.level_inner_min_y, z_level, z_data.level_inner_max_x, z_data.level_inner_max_y)) + var/area/tile_area = tile.loc + if(!tile_area || !areas[tile_area]) + continue - for(var/turf/T in A) - - if(!T.simulated || T.is_open()) + if(!tile.simulated || tile.is_open()) continue . += DEFAULT_TURF_MASS - if(istype(T, /turf/wall)) - var/turf/wall/W = T - if(W.material) - . += W.material.weight * 5 - if(W.reinf_material) - . += W.reinf_material.weight * 5 - if(W.girder_material) - . += W.girder_material.weight * 5 + if(istype(tile, /turf/wall)) + var/turf/wall/wall_tile = tile + if(wall_tile.material) + . += wall_tile.material.weight * 5 + if(wall_tile.reinf_material) + . += wall_tile.reinf_material.weight * 5 + if(wall_tile.girder_material) + . += wall_tile.girder_material.weight * 5 - if(T.zone) - zones |= T.zone + if(tile.zone) + zones[tile.zone] = TRUE // assoc list for fast deduplication - for(var/atom/movable/C in T) + for(var/atom/movable/C as anything in tile) // as anything is safe here since only movables can be in turf contents if(!C.simulated) continue . += C.get_mass() @@ -69,8 +81,9 @@ continue . += C2.get_mass() - for(var/zone/Z in zones) - . += Z.air.get_mass() + // loop over keys of all zones in the list + for(var/zone/zone as anything in zones) + . += zone.air.get_mass() // Convert kilograms to metric tonnes. . = . / 1000 \ No newline at end of file diff --git a/code/modules/paperwork/paper_bundle.dm b/code/modules/paperwork/paper_bundle.dm index bf75bc60846..b01ab304426 100644 --- a/code/modules/paperwork/paper_bundle.dm +++ b/code/modules/paperwork/paper_bundle.dm @@ -40,7 +40,7 @@ // merging bundles else if(istype(W, /obj/item/paper_bundle) && merge(W, user, cur_page)) - to_chat(user, SPAN_NOTICE("You add \the [W.name] to \the [name].")) + to_chat(user, SPAN_NOTICE("You add \the [W] to \the [name].")) return TRUE // burning diff --git a/code/modules/persistence/graffiti.dm b/code/modules/persistence/graffiti.dm index e9afbbad8d8..1ed8a188417 100644 --- a/code/modules/persistence/graffiti.dm +++ b/code/modules/persistence/graffiti.dm @@ -14,10 +14,10 @@ var/author = "unknown" /obj/effect/decal/writing/Initialize(mapload, var/_age, var/_message, var/_author) - var/list/random_icon_states = icon_states(icon) + var/list/random_icon_states = get_states_in_icon(icon) for(var/obj/effect/decal/writing/W in loc) - random_icon_states.Remove(W.icon_state) - if(random_icon_states.len) + random_icon_states -= W.icon_state + if(length(random_icon_states)) icon_state = pick(random_icon_states) SSpersistence.track_value(src, /decl/persistence_handler/graffiti) . = ..(mapload) diff --git a/code/modules/power/geothermal/_geothermal.dm b/code/modules/power/geothermal/_geothermal.dm index 5f76fa6dd0f..3425a37cd2c 100644 --- a/code/modules/power/geothermal/_geothermal.dm +++ b/code/modules/power/geothermal/_geothermal.dm @@ -148,6 +148,7 @@ var/global/const/MAX_GEOTHERMAL_PRESSURE = 12000 /obj/machinery/geothermal/Destroy() var/atom/last_loc = loc unset_vent() + connector = null . = ..() if(istype(last_loc)) propagate_refresh_neighbors(last_loc) diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index f163d0f64c7..9230d872fd5 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -39,6 +39,10 @@ pixel_y = rand(-randpixel, randpixel) . = ..() +/obj/item/ammo_casing/Destroy() + QDEL_NULL(BB) + return ..() + //removes the projectile from the ammo casing /obj/item/ammo_casing/proc/expend() . = BB @@ -270,10 +274,9 @@ var/global/list/magazine_icondata_states = list() /proc/magazine_icondata_cache_add(var/obj/item/ammo_magazine/M) var/list/icon_keys = list() var/list/ammo_states = list() - var/list/states = icon_states(M.icon) for(var/i = 0, i <= M.max_ammo, i++) var/ammo_state = "[M.icon_state]-[i]" - if(ammo_state in states) + if(check_state_in_icon(ammo_state, M.icon)) icon_keys += i ammo_states += ammo_state diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm index 1466e138166..8534b7ddc7a 100644 --- a/code/modules/projectiles/guns/projectile.dm +++ b/code/modules/projectiles/guns/projectile.dm @@ -49,6 +49,12 @@ ammo_magazine = new magazine_type(src) update_icon() +/obj/item/gun/projectile/Destroy() + chambered = null + loaded.Cut() + ammo_magazine = null + return ..() + /obj/item/gun/projectile/consume_next_projectile() if(!is_jammed && prob(jam_chance)) src.visible_message("\The [src] jams!") diff --git a/code/modules/sprite_accessories/metadata/accessory_metadata_gradient.dm b/code/modules/sprite_accessories/metadata/accessory_metadata_gradient.dm index 8b5d7b48734..d134b6782f8 100644 --- a/code/modules/sprite_accessories/metadata/accessory_metadata_gradient.dm +++ b/code/modules/sprite_accessories/metadata/accessory_metadata_gradient.dm @@ -19,8 +19,7 @@ var/list/selectable_labels_to_states = list() /decl/sprite_accessory_metadata/gradient/Initialize() - var/list/selectable = icon_states(icon) - for(var/state in selectable) + for(var/state in get_states_in_icon_cached(icon)) if(!selectable_states_to_labels[state]) selectable_states_to_labels[state] = capitalize(state) for(var/state in selectable_states_to_labels) diff --git a/code/modules/surgery/necrotic.dm b/code/modules/surgery/necrotic.dm index 14b39b1f739..6f41080ae0d 100644 --- a/code/modules/surgery/necrotic.dm +++ b/code/modules/surgery/necrotic.dm @@ -133,15 +133,15 @@ var/list/dead_organs if(E.status & ORGAN_DEAD) var/image/radial_button = image(icon = E.icon, icon_state = E.icon_state) - radial_button.name = "Regenerate \the [E.name]" + radial_button.name = "Regenerate \the [E]" LAZYSET(dead_organs, E.organ_tag, radial_button) for(var/obj/item/organ/I in target.get_internal_organs()) if(I && (I.status & ORGAN_DEAD) && I.parent_organ == target_zone) if(!I.can_recover()) - to_chat(user, SPAN_WARNING("\The [I.name] is beyond saving.")) + to_chat(user, SPAN_WARNING("\The [I] is beyond saving.")) var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state) - radial_button.name = "Regenerate \the [I.name]" + radial_button.name = "Regenerate \the [I]" LAZYSET(dead_organs, I.organ_tag, radial_button) if(!LAZYLEN(dead_organs)) diff --git a/code/modules/surgery/organs_internal.dm b/code/modules/surgery/organs_internal.dm index f6b9bffb28e..916db17c1d2 100644 --- a/code/modules/surgery/organs_internal.dm +++ b/code/modules/surgery/organs_internal.dm @@ -104,7 +104,7 @@ for(var/obj/item/organ/I in target.get_internal_organs()) if(I && !(I.status & ORGAN_CUT_AWAY) && I.parent_organ == target_zone) var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state) - radial_button.name = "Detach \the [I.name]" + radial_button.name = "Detach \the [I]" LAZYSET(attached_organs, I.organ_tag, radial_button) if(!LAZYLEN(attached_organs)) to_chat(user, SPAN_WARNING("You can't find any organs to separate.")) @@ -158,7 +158,7 @@ for(var/obj/item/organ/internal/I in affected.implants) if(I.status & ORGAN_CUT_AWAY) var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state) - radial_button.name = "Remove \the [I.name]" + radial_button.name = "Remove \the [I]" LAZYSET(removable_organs, I, radial_button) if(!LAZYLEN(removable_organs)) to_chat(user, SPAN_WARNING("You can't find any removable organs.")) @@ -336,7 +336,7 @@ for(var/obj/item/organ/I in (affected.implants|affected.internal_organs)) if(I.status & ORGAN_CUT_AWAY) var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state) - radial_button.name = "Attach \the [I.name]" + radial_button.name = "Attach \the [I]" LAZYSET(attachable_organs, I, radial_button) if(!LAZYLEN(attachable_organs)) diff --git a/code/modules/surgery/robotics.dm b/code/modules/surgery/robotics.dm index 44020605dee..8695ab98499 100644 --- a/code/modules/surgery/robotics.dm +++ b/code/modules/surgery/robotics.dm @@ -416,7 +416,7 @@ for(var/obj/item/organ/I in target.get_internal_organs()) if(I && !(I.status & ORGAN_CUT_AWAY) && !BP_IS_CRYSTAL(I) && I.parent_organ == target_zone) var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state) - radial_button.name = "Detach \the [I.name]" + radial_button.name = "Detach \the [I]" LAZYSET(attached_organs, I.organ_tag, radial_button) if(!LAZYLEN(attached_organs)) to_chat(user, SPAN_WARNING("There are no appropriate internal components to decouple.")) @@ -461,7 +461,7 @@ for(var/obj/item/organ/I in affected.implants) if ((I.status & ORGAN_CUT_AWAY) && BP_IS_PROSTHETIC(I) && !BP_IS_CRYSTAL(I) && (I.parent_organ == target_zone)) var/image/radial_button = image(icon = I.icon, icon_state = I.icon_state) - radial_button.name = "Reattach \the [I.name]" + radial_button.name = "Reattach \the [I]" LAZYSET(removable_organs, I.organ_tag, radial_button) var/organ_to_replace = show_radial_menu(user, tool, removable_organs, radius = 42, require_near = TRUE, use_labels = RADIAL_LABELS_OFFSET, check_locs = list(tool)) if(!organ_to_replace) diff --git a/code/unit_tests/alt_appearances_test.dm b/code/unit_tests/alt_appearances_test.dm index 1bfd0ed325e..d1c4b22a8e8 100644 --- a/code/unit_tests/alt_appearances_test.dm +++ b/code/unit_tests/alt_appearances_test.dm @@ -20,9 +20,8 @@ for(var/ca_type in decls_repository.get_decl_paths_of_subtype(/decl/cardborg_appearance)) var/decl/cardborg_appearance/ca = ca_type - var/list/existing_icon_states = icon_states(initial(ca.icon)) var/icon_state = initial(ca.icon_state) - if(!(icon_state in existing_icon_states)) + if(!check_state_in_icon(icon_state, initial(ca.icon))) log_unit_test("Icon state [icon_state] is missing.") failed = TRUE if(failed) diff --git a/code/unit_tests/closets.dm b/code/unit_tests/closets.dm index 9bc236f8ff9..10688fd7231 100644 --- a/code/unit_tests/closets.dm +++ b/code/unit_tests/closets.dm @@ -29,18 +29,18 @@ if(!closet.base_icon) LAZYADD(bad_base_icon, "[closet.type]") else - var/list/base_states = icon_states(closet.base_icon) + var/list/base_states = get_states_in_icon_cached(closet.base_icon) for(var/thing in check_base_states) - if(!(thing in base_states)) + if(!base_states[thing]) LAZYADD(bad_base_state, "[closet.type] - [thing] - [closet.base_icon]") if(LAZYLEN(closet.decals) && !closet.decal_icon) LAZYADD(bad_decal_icon, "[closet.type]") else - var/list/decal_states = icon_states(closet.decal_icon) + var/list/decal_states = get_states_in_icon_cached(closet.decal_icon) for(var/thing in closet.decals) if(isnull(closet.decals[thing])) LAZYADD(bad_decal_colour, "[check_appearance] - [thing]") - if(!(thing in decal_states)) + if(!decal_states[thing]) LAZYADD(bad_decal_state, "[check_appearance] - [thing] - [closet.decal_icon]") if( \ diff --git a/code/unit_tests/clothing.dm b/code/unit_tests/clothing.dm index 2b3889695c3..8219e6d0418 100644 --- a/code/unit_tests/clothing.dm +++ b/code/unit_tests/clothing.dm @@ -99,7 +99,7 @@ generated_tokens += "[token][clothes.markings_state_modifier]" // Keep track of which states we've looked for or otherwise evaluated for later state checking. - var/list/check_states = icon_states(clothes.icon) + var/list/check_states = get_states_in_icon(clothes.icon) // Validate against the list of generated tokens. for(var/gen_token in generated_tokens) diff --git a/code/unit_tests/del_the_world.dm b/code/unit_tests/del_the_world.dm index 3a9c575e699..0ceff60ef7d 100644 --- a/code/unit_tests/del_the_world.dm +++ b/code/unit_tests/del_the_world.dm @@ -7,15 +7,6 @@ var/turf/spawn_loc = get_safe_turf() var/list/cached_contents = spawn_loc.contents.Copy() - /// Types to except from GC checking tests. - var/list/gc_exceptions = list( - // I hate doing this, but until the graph tests are fixed by someone who actually understands them, - // this is the best I can do without breaking other stuff. - /datum/node/physical, - // Randomly fails to GC during CI, cause unclear. Remove this if the root cause is identified. - /obj/item/organ/external/chest - ) - var/list/ignore = typesof( // will error if the area already has one /obj/machinery/power/apc, @@ -64,8 +55,9 @@ if(!length(filter_queue)) filter_queue_finished = TRUE break - var/oldest_item = filter_queue[1] - var/qdel_time = filter_queue[oldest_item] + var/list/oldest_packet = filter_queue[1] + //Pull out the time we inserted at + var/qdel_time = oldest_packet[GC_QUEUE_ITEM_GCD_DESTROYED] if(qdel_time > start_time) // Everything is in the check queue now! filter_queue_finished = TRUE break @@ -89,9 +81,9 @@ garbage_queue_processed = TRUE break - var/oldest_packet = check_queue[1] - //Pull out the time we deld at - var/qdeld_at = check_queue[oldest_packet] + var/list/oldest_packet = filter_queue[1] + //Pull out the time we inserted at + var/qdeld_at = oldest_packet[GC_QUEUE_ITEM_GCD_DESTROYED] //If we've found a packet that got del'd later then we finished, then all our shit has been processed if(qdeld_at > start_time) garbage_queue_processed = TRUE @@ -109,8 +101,6 @@ //Alright, time to see if anything messed up var/list/cache_for_sonic_speed = SSgarbage.items for(var/path in cache_for_sonic_speed) - if(path in gc_exceptions) - continue var/datum/qdel_item/item = cache_for_sonic_speed[path] if(item.failures) failures += "[item.name] hard deleted [item.failures] times out of a total del count of [item.qdels]" diff --git a/code/unit_tests/graph_tests.dm b/code/unit_tests/graph_tests.dm index 66a939e3a8f..1d1cd69b621 100644 --- a/code/unit_tests/graph_tests.dm +++ b/code/unit_tests/graph_tests.dm @@ -480,7 +480,7 @@ /atom/movable/graph_test is_spawnable_type = FALSE var/datum/node/physical/node - var/list/neighoursByDirection = list() + var/list/neighboursByDirection = list() /atom/movable/graph_test/Initialize() . = ..() @@ -497,20 +497,21 @@ /atom/movable/graph_test/proc/Connect(atom/movable/graph_test/neighbour) var/direction = get_dir(src, neighbour) - neighoursByDirection[num2text(direction)] = neighbour - neighbour.neighoursByDirection[num2text(global.flip_dir[direction])] = src + neighboursByDirection[num2text(direction)] = neighbour + neighbour.neighboursByDirection[num2text(global.flip_dir[direction])] = src node.Connect(neighbour.node) /atom/movable/graph_test/CheckNodeNeighbours() // This is a lazy setup for ease of debugging // In a practical setup you'd preferably gather a list of neighbours to be disconnected and pass them in a single Disconnect-call // You'd possibly also verify the dir of this and neighbour nodes, to ensure that they're still facing each other properly - for(var/direction in neighoursByDirection) - var/atom/movable/graph_test/neighbour = neighoursByDirection[direction] + for(var/direction in neighboursByDirection) + var/atom/movable/graph_test/neighbour = neighboursByDirection[direction] var/turf/expected_loc = get_step(src, text2num(direction)) - if(neighbour.loc != expected_loc) + // can't connect in nullspace + if(isnull(neighbour.loc) || neighbour.loc != expected_loc) node.Disconnect(neighbour.node) - neighoursByDirection -= direction + neighboursByDirection -= direction return TRUE /datum/graph/testing diff --git a/code/unit_tests/job_tests.dm b/code/unit_tests/job_tests.dm index b1295165734..858063c9bad 100644 --- a/code/unit_tests/job_tests.dm +++ b/code/unit_tests/job_tests.dm @@ -42,20 +42,20 @@ var/failed_jobs = 0 var/failed_sanity_checks = 0 - var/job_huds = icon_states(global.using_map.id_hud_icons) + var/list/job_huds = get_states_in_icon_cached(global.using_map.id_hud_icons) - if(!("" in job_huds)) + if(!job_huds[""]) log_bad("Sanity Check - Missing default/unnamed HUD icon") failed_sanity_checks++ - if(!("hudunknown" in job_huds)) + if(!job_huds["hudunknown"]) log_bad("Sanity Check - Missing HUD icon: hudunknown") failed_sanity_checks++ for(var/job_name in SSjobs.titles_to_datums) var/datum/job/J = SSjobs.titles_to_datums[job_name] var/hud_icon_state = J.hud_icon - if(!(hud_icon_state in job_huds)) + if(!job_huds[hud_icon_state]) log_bad("[J.title] - Missing HUD icon: [hud_icon_state]") failed_jobs++ diff --git a/code/unit_tests/mob_tests.dm b/code/unit_tests/mob_tests.dm index 5867dd2ca90..0228ea80341 100644 --- a/code/unit_tests/mob_tests.dm +++ b/code/unit_tests/mob_tests.dm @@ -260,15 +260,15 @@ var/global/default_mobloc = null fail("[icon_file] is not a valid icon file.") return 1 - var/list/valid_states = icon_states(icon_file) + var/list/valid_states = get_states_in_icon_cached(icon_file) - if(!valid_states.len) + if(!length(valid_states)) return 1 for(var/i=1, i<=SSrobots.all_module_names.len, i++) var/modname = lowertext(SSrobots.all_module_names[i]) var/bad_msg = "[ascii_red]--------------- [modname]" - if(!(modname in valid_states)) + if(!valid_states[modname]) log_unit_test("[bad_msg] does not contain a valid icon state in [icon_file][ascii_reset]") failed=1