diff --git a/aurorastation.dme b/aurorastation.dme index 45d49c2b155..90dfd6cd067 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -22,6 +22,7 @@ #include "code\__DEFINES\_common.dm" #include "code\__DEFINES\_compile_helpers.dm" #include "code\__DEFINES\_flags.dm" +#include "code\__DEFINES\_helpers.dm" #include "code\__DEFINES\_layers.dm" #include "code\__DEFINES\_macros.dm" #include "code\__DEFINES\_protect.dm" @@ -73,7 +74,6 @@ #include "code\__DEFINES\lists.dm" #include "code\__DEFINES\logging.dm" #include "code\__DEFINES\machinery.dm" -#include "code\__DEFINES\manual_unit_testing.dm" #include "code\__DEFINES\materials.dm" #include "code\__DEFINES\math_physics.dm" #include "code\__DEFINES\MC.dm" @@ -133,6 +133,7 @@ #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals.dm" +#include "code\__DEFINES\dcs\signals\signals_datum.dm" #include "code\__DEFINES\dcs\signals\signals_global.dm" #include "code\__DEFINES\dcs\signals\signals_record.dm" #include "code\__DEFINES\dcs\signals\signals_subsystem.dm" @@ -163,6 +164,7 @@ #include "code\__HELPERS\names.dm" #include "code\__HELPERS\overlay.dm" #include "code\__HELPERS\overmap.dm" +#include "code\__HELPERS\qdel.dm" #include "code\__HELPERS\sanitize_values.dm" #include "code\__HELPERS\shell.dm" #include "code\__HELPERS\smart_token_bucket.dm" @@ -376,7 +378,7 @@ #include "code\datums\statistic.dm" #include "code\datums\tgs_event_handler.dm" #include "code\datums\tgui_module.dm" -#include "code\datums\weakref.dm" +#include "code\datums\weakrefs.dm" #include "code\datums\components\_component.dm" #include "code\datums\components\connect_mob_behalf.dm" #include "code\datums\components\local_network.dm" @@ -418,6 +420,7 @@ #include "code\datums\observation\shuttle_moved.dm" #include "code\datums\observation\sight_set.dm" #include "code\datums\observation\stat_set.dm" +#include "code\datums\observation\~cleanup.dm" #include "code\datums\outfits\outfit.dm" #include "code\datums\outfits\outfit_admin.dm" #include "code\datums\outfits\outfit_antag.dm" @@ -1466,6 +1469,7 @@ #include "code\modules\admin\verbs\viewlist.dm" #include "code\modules\admin\verbs\warning.dm" #include "code\modules\admin\view_variables\helpers.dm" +#include "code\modules\admin\view_variables\reference_tracking.dm" #include "code\modules\admin\view_variables\topic.dm" #include "code\modules\admin\view_variables\view_variables.dm" #include "code\modules\alarm\alarm.dm" diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm new file mode 100644 index 00000000000..998075b6a03 --- /dev/null +++ b/code/__DEFINES/_helpers.dm @@ -0,0 +1,15 @@ +// Stuff that is relatively "core" and is used in other defines/helpers + +//Returns the hex value of a decimal number +//len == length of returned string +#define num2hex(X, len) num2text(X, len, 16) + +//Returns an integer given a hex input, supports negative values "-ff" +//skips preceding invalid characters +#define hex2num(X) text2num(X, 16) + +/// subtypesof(), typesof() without the parent path +#define subtypesof(typepath) ( typesof(typepath) - typepath ) + +/// Takes a datum as input, returns its ref string +#define text_ref(datum) ref(datum) diff --git a/code/__DEFINES/_macros.dm b/code/__DEFINES/_macros.dm index 37f78355a80..351ac9aed1e 100644 --- a/code/__DEFINES/_macros.dm +++ b/code/__DEFINES/_macros.dm @@ -2,9 +2,6 @@ #define CLAMP01(x) (Clamp(x, 0, 1)) #define JOINTEXT(X) jointext(X, null) #define list_find(L, needle, LIMITS...) L.Find(needle, LIMITS) -#define hex2num(hex) text2num(hex, 16) -#define num2hex(num, pad) num2text(num, pad, 16) -#define text_ref(datum) (isdatum(datum) ? (datum:cached_ref ||= "\ref[datum]") : ("\ref[datum]")) #define span(class, text) ("" + text + "") #define SPAN_NOTICE(X) ("" + X + "") diff --git a/code/__DEFINES/auxtools.dm b/code/__DEFINES/auxtools.dm index 885b57c8baa..1325c614072 100644 --- a/code/__DEFINES/auxtools.dm +++ b/code/__DEFINES/auxtools.dm @@ -8,8 +8,7 @@ CRASH("auxtools not loaded") /world/Del() - if(byond_version < 515) - var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") - if (debug_server) - call(debug_server, "auxtools_shutdown")() + var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") + if (debug_server) + call_ext(debug_server, "auxtools_shutdown")() . = ..() diff --git a/code/__DEFINES/byond_compat.dm b/code/__DEFINES/byond_compat.dm index 21fe5b62a2c..ccb88af21c5 100644 --- a/code/__DEFINES/byond_compat.dm +++ b/code/__DEFINES/byond_compat.dm @@ -1,57 +1,27 @@ -// 515 split call for external libraries into call_ext -#if DM_VERSION < 515 -#define LIBCALL call -#else -#define LIBCALL call_ext +// This file contains defines allowing targeting byond versions newer than the supported + +//Update this whenever you need to take advantage of more recent byond features +#define MIN_COMPILER_VERSION 515 +#define MIN_COMPILER_BUILD 1609 +#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM) && !defined(OPENDREAM) +//Don't forget to update this part +#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update. +#error You need version 515.1609 or higher #endif -// So we want to have compile time guarantees these procs exist on local type, unfortunately 515 killed the .proc/procname syntax so we have to use nameof() -#if DM_VERSION < 515 +// So we want to have compile time guarantees these methods exist on local type +// We use wrappers for this in case some part of the api ever changes, and to make their function more clear +// For the record: GLOBAL_VERB_REF would be useless as verbs can't be global. - /** - * Call by name proc reference, checks if the proc exists on this type or as a global proc - * - * * X - The proc name - */ - #define PROC_REF(X) (.proc/##X) +/// Call by name proc references, checks if the proc exists on either this type or as a global proc. +#define PROC_REF(X) (nameof(.proc/##X)) +/// Call by name verb references, checks if the verb exists on either this type or as a global verb. +#define VERB_REF(X) (nameof(.verb/##X)) - /** - * Call by name proc reference, checks if the proc exists on given type or as a global proc - * - * * TYPE - The type (eg. `/datum/something` or `/atom`), without trailing slash - * * X - The proc name - */ - #define TYPE_PROC_REF(TYPE, X) (##TYPE.proc/##X) +/// Call by name proc reference, checks if the proc exists on either the given type or as a global proc +#define TYPE_PROC_REF(TYPE, X) (nameof(##TYPE.proc/##X)) +/// Call by name verb reference, checks if the verb exists on either the given type or as a global verb +#define TYPE_VERB_REF(TYPE, X) (nameof(##TYPE.verb/##X)) - /** - * Call by name proc reference, checks if the proc is existing global proc - * - * * X - The proc name - */ - #define GLOBAL_PROC_REF(X) (/proc/##X) - -#else - - /** - * Call by name proc reference, checks if the proc exists on this type or as a global proc - * - * * X - The proc name - */ - #define PROC_REF(X) (nameof(.proc/##X)) - - /** - * Call by name proc reference, checks if the proc exists on given type or as a global proc - * - * * TYPE - The type (eg. `/datum/something` or `/atom`), without trailing slash - * * X - The proc name - */ - #define TYPE_PROC_REF(TYPE, X) (nameof(##TYPE.proc/##X)) - - /** - * Call by name proc reference, checks if the proc is existing global proc - * - * * X - The proc name - */ - #define GLOBAL_PROC_REF(X) (/proc/##X) - -#endif +/// Call by name proc reference, checks if the proc is an existing global proc +#define GLOBAL_PROC_REF(X) (/proc/##X) diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 7b2e2dd1101..54c77295201 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -12,22 +12,6 @@ ////////////////////////////////////////////////////////////////// -// /datum signals -/// when a component is added to a datum: (/datum/component) -#define COMSIG_COMPONENT_ADDED "component_added" -/// before a component is removed from a datum because of RemoveComponent: (/datum/component) -#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" -/// 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_QDELETING "parent_qdeleting" -/// from datum ui_act (usr, action) -#define COMSIG_UI_ACT "COMSIG_UI_ACT" -/// fires on the target datum when an element is attached to it (/datum/element) -#define COMSIG_ELEMENT_ATTACH "element_attach" -/// fires on the target datum when an element is attached to it (/datum/element) -#define COMSIG_ELEMENT_DETACH_ON_HOST_DESTROY "ELEMENT_DETACH_ON_HOST_DESTROY" - // /atom signals diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm new file mode 100644 index 00000000000..9ac01a252c0 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_datum.dm @@ -0,0 +1,22 @@ +// Datum signals. Format: +// When the signal is called: (signal arguments) +// All signals send the source datum of the signal as the first argument + +// /datum signals +/// when a component is added to a datum: (/datum/component) +#define COMSIG_COMPONENT_ADDED "component_added" +/// before a component is removed from a datum because of RemoveComponent: (/datum/component) +#define COMSIG_COMPONENT_REMOVING "component_removing" + +/// before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation +/// you should only be using this if you want to block deletion +/// that's the only functional difference between it and COMSIG_QDELETING, outside setting QDELETING to detect +#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_QDELETING "parent_qdeleting" +/// from datum ui_act (usr, action) +#define COMSIG_UI_ACT "COMSIG_UI_ACT" +/// fires on the target datum when an element is attached to it (/datum/element) +#define COMSIG_ELEMENT_ATTACH "element_attach" +/// fires on the target datum when an element is attached to it (/datum/element) +#define COMSIG_ELEMENT_DETACH_ON_HOST_DESTROY "ELEMENT_DETACH_ON_HOST_DESTROY" diff --git a/code/__DEFINES/global.dm b/code/__DEFINES/global.dm index bb521a48320..4f3c913b040 100644 --- a/code/__DEFINES/global.dm +++ b/code/__DEFINES/global.dm @@ -1,6 +1,6 @@ //#define TESTING -#if DM_VERSION < 513 -#error Your version of BYOND is too old to compile the code. At least BYOND 513 is required. +#if DM_VERSION < 515 && !defined(OPENDREAM) +#error Your version of BYOND is too old to compile the code. At least BYOND 515 is required. #endif diff --git a/code/__DEFINES/manual_unit_testing.dm b/code/__DEFINES/manual_unit_testing.dm deleted file mode 100644 index f3dcdcb031e..00000000000 --- a/code/__DEFINES/manual_unit_testing.dm +++ /dev/null @@ -1,7 +0,0 @@ -// !!! For manual use only, remember to recomment before PRing !!! -// #define UNIT_TEST -// #define MANUAL_UNIT_TEST - -#if defined(MANUAL_UNIT_TEST) && !defined(SPACEMAN_DMM) && !defined(OPENDREAM) - #warn Manual unit test is defined, remember to recomment it before PRing! -#endif // MANUAL_UNIT_TEST diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index e001d1c02f9..650180ff72b 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -310,9 +310,6 @@ // Used for creating soft references to objects. A manner of storing an item reference #define SOFTREF(A) ref(A) -// This only works on 511 because it relies on 511's `var/something = foo = bar` syntax. -#define WEAKREF(D) (istype(D, /datum) && !D:gcDestroyed ? (D:weakref || (D:weakref = new/datum/weakref(D))) : null) - #define ADD_VERB_IN(the_atom,time,verb) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(add_verb), the_atom, verb), time, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_NO_HASH_WAIT) #define ADD_VERB_IN_IF(the_atom,time,verb,callback) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(add_verb), the_atom, verb, callback), time, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_NO_HASH_WAIT) diff --git a/code/__DEFINES/profiler_tracy.dm b/code/__DEFINES/profiler_tracy.dm index 33699d14ce5..8559b39833c 100644 --- a/code/__DEFINES/profiler_tracy.dm +++ b/code/__DEFINES/profiler_tracy.dm @@ -39,7 +39,7 @@ var/byond_tracy_running_v = null // Which version of byond-tracy is currently ru if("tracy") lib = "tracy.dll" if("tracy_disk") lib = "tracy-disk.dll" - var/init = LIBCALL(lib, "init")() + var/init = call_ext(lib, "init")() if("0" != init) CRASH("[lib] init error: [init]") byond_tracy_running = 1 @@ -56,7 +56,7 @@ var/byond_tracy_running_v = null // Which version of byond-tracy is currently ru if("tracy") lib = "tracy.dll" if("tracy_disk") lib = "tracy-disk.dll" - LIBCALL(lib, "destroy")() + call_ext(lib, "destroy")() byond_tracy_running = 0 byond_tracy_running_v = null diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm index 5cce66345db..a1ffd5c3699 100644 --- a/code/__DEFINES/qdel.dm +++ b/code/__DEFINES/qdel.dm @@ -1,36 +1,88 @@ +/** + * THIS IS A TEMPORARY WORKAROUND WHILE WE RESOLVE HARDDELS WITH THE NEW GC + * + * YOU ARE ONLY ALLOWED TO REMOVE THE AMOUNT OF THEM, NOT ADD MORE + * + * AND FOR WHAT MATTERS, THE SAME GOES FOR QDEL_HINT_HARDDEL, + * I DO NOT CARE HOW MANY REWRITES AND REFACTORS YOU HAVE TO DO, WHATEVER YOU ADD NEEDS TO BE GARGAGE COLLECTABLE, OR HAVE AN EXPLAINED, CLEAR, UNFIXABLE REASON WHY IT IS NOT + * + */ +#define GC_TEMPORARY_HARDDEL return QDEL_HINT_HARDDEL + //! Defines that give qdel hints. //! //! These can be given as a return in [/atom/proc/Destroy] or by calling [/proc/qdel]. /// `qdel` should queue the object for deletion. -#define QDEL_HINT_QUEUE 0 -/// `qdel` should let the object live after calling [/datum/proc/Destroy]. -#define QDEL_HINT_LETMELIVE 1 -/// Functionally the same as above. `qdel` should assume the object will gc on its own, and not check it. -#define QDEL_HINT_IWILLGC 2 -/// `qdel` should assume this object won't GC, and queue a hard delete using a hardref. -#define QDEL_HINT_HARDDEL 3 -/// `qdel` should assume this object won't GC, and hard delete it immediately. -#define QDEL_HINT_HARDDEL_NOW 4 +#define QDEL_HINT_QUEUE 0 +/// `qdel` should let the object live after calling [/atom/proc/Destroy]. +#define QDEL_HINT_LETMELIVE 1 +/// Functionally the same as the above. `qdel` should assume the object will gc on its own, and not check it. +#define QDEL_HINT_IWILLGC 2 +/// Qdel should assume this object won't GC, and queue a hard delete using a hard reference. +#define QDEL_HINT_HARDDEL 3 +// Qdel should assume this object won't gc, and hard delete it posthaste. +#define QDEL_HINT_HARDDEL_NOW 4 -/* -* If REFERENCE_TRACKING and [GC_FAILURE_HARD_LOOKUP] are not enabled, these are functionally identical to [QDEL_HINT_QUEUE]. -* If they are enabled, qdel will call this object's find_references() verb. -*/ -#define QDEL_HINT_FINDREFERENCE 5 -/// Same behavior as [QDEL_HINT_FINDREFERENCE], but only if GC fails and a hard delete is forced. + +#ifdef REFERENCE_TRACKING + + //A warning on compile is treated as an error in the CI, therefore unlike TG we must avoid the warn if it's running in the CI + #if defined(TESTING) && !defined(CIBUILDING) && !defined(OPENDREAM) && !defined(SPACEMAN_DMM) + /* If REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb. + + Functionally identical to [QDEL_HINT_QUEUE] if [GC_FAILURE_HARD_LOOKUP] is not enabled in _compiler_options.dm. + */ + #warn TG0001 qdel REFERENCE_TRACKING enabled + #endif + +#define QDEL_HINT_FINDREFERENCE 5 +/// Behavior as [QDEL_HINT_FINDREFERENCE], but only if the GC fails and a hard delete is forced. #define QDEL_HINT_IFFAIL_FINDREFERENCE 6 -//defines for the gcDestroyed var +#endif -#define GC_QUEUED_FOR_QUEUING -1 -#define GC_QUEUED_FOR_HARD_DEL -2 -#define GC_CURRENTLY_BEING_QDELETED -3 +// Defines for the ssgarbage queues +#define GC_QUEUE_FILTER 1 //! short queue to filter out quick gc successes so they don't hang around in the main queue for 5 minutes +#define GC_QUEUE_CHECK 2 //! main queue that waits 5 minutes because thats the longest byond can hold a reference to our shit. +#define GC_QUEUE_HARDDELETE 3 //! short queue for things that hard delete instead of going thru the gc subsystem, this is purely so if they *can* softdelete, they will soft delete rather then wasting time with a hard delete. +#define GC_QUEUE_COUNT 3 //! Number of queues, used for allocating the nested lists. Don't forget to increase this if you add a new queue stage -#define QDELING(X) (X.gcDestroyed) -#define QDELETED(X) (!X || X.gcDestroyed) -#define QDESTROYING(X) (!X || X.gcDestroyed == GC_CURRENTLY_BEING_QDELETED) -#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE) -#define QDEL_NULL(item) qdel(item); item = null -#define QDEL_NULL_LIST(x) if(x) { for(var/y in x) { qdel(y) }}; if(x) {x.Cut(); x = null } // Second x check to handle items that LAZYREMOVE on qdel. -#define QDEL_NULL_ASSOC(x) if(x) { for(var/y in x) { qdel(x[y])}}; if(x) {x.Cut(); x = null } // As above, but qdels the value rather than the key +// 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 + +// Defines for the time an item has to get its reference cleaned before it fails the queue and moves to the next. +#define GC_FILTER_QUEUE (1 SECONDS) +#define GC_CHECK_QUEUE (5 MINUTES) +#define GC_DEL_QUEUE (10 SECONDS) + + +#define QDEL_ITEM_ADMINS_WARNED (1<<0) //! Set when admins are told about lag causing qdels in this type. +#define QDEL_ITEM_SUSPENDED_FOR_LAG (1<<1) //! Set when a type can no longer be hard deleted on failure because of lag it causes while this happens. + +// Defines for the [gc_destroyed][/datum/var/gc_destroyed] var. +#define GC_CURRENTLY_BEING_QDELETED -2 + +#define QDELING(X) (X.gc_destroyed) +#define QDELETED(X) (isnull(X) || QDELING(X)) +#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) + +// This is a bit hacky, we do it to avoid people relying on a return value for the macro +// If you need that you should use QDEL_IN_STOPPABLE instead +#define QDEL_IN(item, time) ; \ + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), (time) > GC_FILTER_QUEUE ? WEAKREF(item) : item), time); +#define QDEL_IN_STOPPABLE(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), (time) > GC_FILTER_QUEUE ? WEAKREF(item) : item), time, TIMER_STOPPABLE) +#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME) + +//Bay uses the check before deleting something, to ensure it's not null, TG does not, and since I don't want to go hunt down 3 billion runtimes, we keep bay's version + +///Delete and null the reference, if the object isn't null +#define QDEL_NULL(item) if(item) { qdel(item) ; item = null } + +#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); } +#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(______qdel_list_wrapper), L), time, TIMER_STOPPABLE) +#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); } +#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); } diff --git a/code/__DEFINES/subsystem-priority.dm b/code/__DEFINES/subsystem-priority.dm index 5dce9d45ce2..15de111c58a 100644 --- a/code/__DEFINES/subsystem-priority.dm +++ b/code/__DEFINES/subsystem-priority.dm @@ -1,4 +1,9 @@ -#define INIT_ORDER_PROFILER 101 +/* + THIS IS A LEGACY FILE, DO NOT ADD ONTOP OF THIS + USE code\__DEFINES\subsystems.dm AND THE APPROPRIATE DEF NAMES + THAT ARE PRESENT THERE +*/ + #define SS_INIT_PERSISTENT_CONFIG 26 #define SS_INIT_MISC_FIRST 25 #define SS_INIT_SEEDS 24 // Plant controller setup. diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 42d02f92aee..08180ee8bbe 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -191,10 +191,19 @@ /// The timer key used to know how long subsystem initialization takes #define SS_INIT_TIMER_KEY "ss_init" +//! ### SS initialization load orders +// Subsystem init_order, from highest priority to lowest priority +// Subsystems shutdown in the reverse of the order they initialize in +// The numbers just define the ordering, they are meaningless otherwise. + +#define INIT_ORDER_PROFILER 101 +#define INIT_ORDER_GARBAGE 99 + // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) #define FIRE_PRIORITY_DEFAULT 50 +#define FIRE_PRIORITY_GARBAGE 15 /* AURORA SHIT */ diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm index 73eb12b0f91..5a201855611 100644 --- a/code/__DEFINES/time.dm +++ b/code/__DEFINES/time.dm @@ -1,3 +1,7 @@ +#define MILLISECONDS *0.01 + +#define DECISECONDS *1 //the base unit all of these defines are scaled by, because byond uses that as a unit of measurement for some fucking reason + #define SECOND *10 #define SECONDS *10 @@ -16,3 +20,7 @@ #define DS2TICKS(DS) ((DS)/world.tick_lag) #define TICKS2DS(T) ((T) TICKS) + +#define MS2DS(T) ((T) MILLISECONDS) + +#define DS2MS(T) ((T) * 100) diff --git a/code/__HELPERS/dll_call.dm b/code/__HELPERS/dll_call.dm index aea6734bb61..2d174d216d8 100644 --- a/code/__HELPERS/dll_call.dm +++ b/code/__HELPERS/dll_call.dm @@ -8,7 +8,7 @@ var/list/calling_arguments = length(args) > 2 ? args.Copy(3) : null - . = LIBCALL(dll, func)(arglist(calling_arguments)) + . = call_ext(dll, func)(arglist(calling_arguments)) if (world.timeofday - start > 10 SECONDS) crash_with("DLL call took longer than 10 seconds: [func]") diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 4162c84bd7f..1bc0957e571 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -544,9 +544,6 @@ /proc/is_list_containing_type(var/list/L, type) return count_by_type(L, type) == L.len -/proc/subtypesof(prototype) - return (typesof(prototype) - prototype) - //creates every subtype of prototype (excluding prototype) and adds it to list L. //if no list/L is provided, one is created. /proc/init_subtypes(prototype, list/L) diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 98f6272052c..110c94eb3a7 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -68,6 +68,8 @@ return current_species ? current_species.sanitize_name(name) : sanitizeName(name) /proc/random_name(gender, species = SPECIES_HUMAN) + SHOULD_NOT_SLEEP(TRUE) + var/datum/species/current_species if(species) current_species = GLOB.all_species[species] diff --git a/code/__HELPERS/nameof.dm b/code/__HELPERS/nameof.dm index 7cd5777f465..5a2fd60e710 100644 --- a/code/__HELPERS/nameof.dm +++ b/code/__HELPERS/nameof.dm @@ -8,8 +8,4 @@ /** * NAMEOF that actually works in static definitions because src::type requires src to be defined */ -#if DM_VERSION >= 515 #define NAMEOF_STATIC(datum, X) (nameof(type::##X)) -#else -#define NAMEOF_STATIC(datum, X) (#X || ##datum.##X) -#endif diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm new file mode 100644 index 00000000000..bbbf1827db5 --- /dev/null +++ b/code/__HELPERS/qdel.dm @@ -0,0 +1,4 @@ + +///the underscores are to encourage people not to use this directly. +/proc/______qdel_list_wrapper(list/L) + QDEL_LIST(L) diff --git a/code/__HELPERS/sorting/cmp.dm b/code/__HELPERS/sorting/cmp.dm index 7baad507053..331863a31ce 100644 --- a/code/__HELPERS/sorting/cmp.dm +++ b/code/__HELPERS/sorting/cmp.dm @@ -40,6 +40,15 @@ /proc/cmp_timer(datum/timedevent/a, datum/timedevent/b) return a.timeToRun - b.timeToRun +/proc/cmp_qdel_item_time(datum/qdel_item/A, datum/qdel_item/B) + . = B.hard_delete_time - A.hard_delete_time + if (!.) + . = B.destroy_time - A.destroy_time + if (!.) + . = B.failures - A.failures + if (!.) + . = B.qdels - A.qdels + /proc/cmp_camera(obj/machinery/camera/a, obj/machinery/camera/b) if (a.c_tag_order != b.c_tag_order) return b.c_tag_order - a.c_tag_order diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 382160b23bb..25fefdf36f9 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -30,10 +30,18 @@ Manual runs area, uncomment the appropriate defines depending on what you want to do */ -//#define TESTING // Creates debug feedback messages and enables many optional testing procs/checks +// !!! For manual use only, remember to recomment before PRing !!! +// #define TESTING // Creates debug feedback messages and enables many optional testing procs/checks +// #define UNIT_TEST +// #define MANUAL_UNIT_TEST + +#if defined(MANUAL_UNIT_TEST) && !defined(SPACEMAN_DMM) && !defined(OPENDREAM) + #warn Manual unit test is defined, remember to recomment it before PRing! +#endif // MANUAL_UNIT_TEST + #ifdef TESTING ///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while - //#define REFERENCE_TRACKING + // #define REFERENCE_TRACKING #ifdef REFERENCE_TRACKING //Run a lookup on things hard deleting by default. @@ -77,7 +85,8 @@ //Test at full capacity, the extra cost doesn't matter #define TIMER_DEBUG - #define ZASDBG + //If you really are lost with atmos issues + // #define ZASDBG #define SQL_PREF_DEBUG #endif //UNIT_TEST diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm index 5b97cf46c0c..1e9d6c5e4b5 100644 --- a/code/_onclick/hud/ability_screen_objects.dm +++ b/code/_onclick/hud/ability_screen_objects.dm @@ -19,7 +19,6 @@ update_abilities(0, owner) /obj/screen/movable/ability_master/Destroy() - . = ..() //Get rid of the ability objects. remove_all_abilities() ability_objects.Cut() @@ -31,6 +30,8 @@ my_mob.client.screen -= src my_mob = null + . = ..() + /obj/screen/movable/ability_master/MouseDrop() if(showing) return diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index e6b00d9b296..64a20ad5629 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -149,7 +149,6 @@ var/list/global_huds ..() /datum/hud/Destroy() - . = ..() grab_intent = null hurt_intent = null disarm_intent = null @@ -167,6 +166,8 @@ var/list/global_huds // item_action_list = null // ? mymob = null + . = ..() + /datum/hud/proc/hidden_inventory_update() if(!mymob) return if(ishuman(mymob)) diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index b051a645f8c..a9ac5b43900 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -9,7 +9,8 @@ GLOBAL_LIST_EMPTY(radial_menus) var/datum/radial_menu/parent /obj/screen/radial/Destroy() - qdel(parent) + QDEL_NULL(parent) + return ..() /obj/screen/radial/slice diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 52229d0a001..f92a2f2bb09 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -86,8 +86,8 @@ var/obj/item/owner /obj/screen/item_action/Destroy() - . = ..() owner = null + . = ..() /obj/screen/item_action/Click() if(!usr || !owner) diff --git a/code/_onclick/hud/spell_screen_objects.dm b/code/_onclick/hud/spell_screen_objects.dm index 886d55853d5..87d626a90e7 100644 --- a/code/_onclick/hud/spell_screen_objects.dm +++ b/code/_onclick/hud/spell_screen_objects.dm @@ -13,7 +13,6 @@ var/mob/spell_holder /obj/screen/movable/spell_master/Destroy() - . = ..() for(var/obj/screen/spell/spells in spell_objects) spells.spellmaster = null spell_objects.Cut() @@ -23,6 +22,8 @@ spell_holder.client.screen -= src spell_holder = null + . = ..() + /obj/screen/movable/spell_master/MouseDrop() if(showing) return @@ -157,7 +158,6 @@ var/icon/last_charged_icon /obj/screen/spell/Destroy() - . = ..() spell = null last_charged_icon = null if(spellmaster) @@ -168,6 +168,8 @@ qdel(spellmaster) spellmaster = null + . = ..() + /obj/screen/spell/proc/update_charge(var/forced_update = 0) if(!spell) qdel(src) diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm index 0fc64b901fd..e8411701644 100644 --- a/code/controllers/globals.dm +++ b/code/controllers/globals.dm @@ -13,7 +13,14 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars) GLOB = src var/datum/controller/exclude_these = new - gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) + // I know this is dumb but the nested vars list hangs a ref to the datum. This fixes that + var/list/controller_vars = exclude_these.vars.Copy() + controller_vars["vars"] = null + gvars_datum_in_built_vars = controller_vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) + +#if MIN_COMPILER_VERSION >= 515 && MIN_COMPILER_BUILD > 1620 + #warn datum.vars hanging a ref should now be fixed, there should be no reason to remove the vars list from our controller's vars list anymore +#endif QDEL_IN(exclude_these, 0) //signal logging isn't ready Initialize() diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index 451a0e16671..17eee1f305c 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -1,39 +1,53 @@ +/*! +## Debugging GC issues + +In order to debug `qdel()` failures, there are several tools available. +To enable these tools, define `TESTING` in [_compile_options.dm](https://github.com/tgstation/-tg-station/blob/master/code/_compile_options.dm). + +First is a verb called "Find References", which lists **every** refererence to an object in the world. This allows you to track down any indirect or obfuscated references that you might have missed. + +Complementing this is another verb, "qdel() then Find References". +This does exactly what you'd expect; it calls `qdel()` on the object and then it finds all references remaining. +This is great, because it means that `Destroy()` will have been called before it starts to find references, +so the only references you'll find will be the ones preventing the object from `qdel()`ing gracefully. + +If you have a datum or something you are not destroying directly (say via the singulo), +the next tool is `QDEL_HINT_FINDREFERENCE`. You can return this in `Destroy()` (where you would normally `return ..()`), +to print a list of references once it enters the GC queue. + +Finally is a verb, "Show qdel() Log", which shows the deletion log that the garbage subsystem keeps. This is helpful if you are having race conditions or need to review the order of deletions. + +Note that for any of these tools to work `TESTING` must be defined. +By using these methods of finding references, you can make your life far, far easier when dealing with `qdel()` failures. +*/ + SUBSYSTEM_DEF(garbage) name = "Garbage" - priority = SS_PRIORITY_GARBAGE + priority = FIRE_PRIORITY_GARBAGE wait = 2 SECONDS flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY + init_order = INIT_ORDER_GARBAGE init_stage = INITSTAGE_EARLY - var/collection_timeout = 3000// deciseconds to wait to let running procs finish before we just say fuck it and force del() the object - var/delslasttick = 0 // number of del()'s we've done this tick - var/gcedlasttick = 0 // number of things that gc'ed last tick + var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level + + //Stat tracking + var/delslasttick = 0 // number of del()'s we've done this tick + var/gcedlasttick = 0 // number of things that gc'ed last tick var/totaldels = 0 var/totalgcs = 0 - var/highest_del_time = 0 - var/highest_del_tickusage = 0 + var/highest_del_ms = 0 + var/highest_del_type_string = "" - var/list/queue = list() // list of refID's of things that should be garbage collected - // refID's are associated with the time at which they time out and need to be manually del() - // we do this so we aren't constantly locating them and preventing them from being gc'd + var/list/pass_counts + var/list/fail_counts - var/list/tobequeued = list() //We store the references of things to be added to the queue seperately so we can spread out GC overhead over a few ticks - - var/list/didntgc = list() // list of all types that have failed to GC associated with the number of times that's happened. - // the types are stored as strings - var/list/sleptDestroy = list() //Same as above but these are paths that slept during their Destroy call - - var/list/noqdelhint = list()// list of all types that do not return a QDEL_HINT - // all types that did not respect qdel(A, force=TRUE) and returned one - // of the immortality qdel hints - var/list/noforcerespect = list() - - #ifdef REFERENCE_TRACKING - var/list/qdel_list = list() // list of all types that have been qdel()eted - #endif + var/list/items = list() // Holds our qdel_item statistics datums + //Queue + var/list/queues #ifdef REFERENCE_TRACKING var/list/reference_find_on_fail = list() #ifdef REFERENCE_TRACKING_DEBUG @@ -42,8 +56,15 @@ SUBSYSTEM_DEF(garbage) #endif #endif + +/datum/controller/subsystem/garbage/PreInit() + InitQueues() + /datum/controller/subsystem/garbage/stat_entry(msg) - msg = "W:[tobequeued.len]|Q:[queue.len]|D:[delslasttick]|G:[gcedlasttick]|" + var/list/counts = list() + for (var/list/L in queues) + counts += length(L) + msg += "Q:[counts.Join(",")]|D:[delslasttick]|G:[gcedlasttick]|" msg += "GR:" if (!(delslasttick+gcedlasttick)) msg += "n/a|" @@ -55,405 +76,339 @@ SUBSYSTEM_DEF(garbage) msg += "n/a|" else msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%" + msg += " P:[pass_counts.Join(",")]" + msg += "|F:[fail_counts.Join(",")]" return ..() +/datum/controller/subsystem/garbage/Shutdown() + //Adds the del() log to the qdel log file + 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] + var/list/entry = list() + del_log[path] = entry + + if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG) + entry["SUSPENDED FOR LAG"] = TRUE + if (I.failures) + entry["Failures"] = I.failures + entry["qdel() Count"] = I.qdels + entry["Destroy() Cost (ms)"] = I.destroy_time + + if (I.hard_deletes) + 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) + entry["Hard Deletes Over Threshold"] = I.hard_deletes_over_threshold + if (I.slept_destroy) + entry["Total Sleeps"] = I.slept_destroy + if (I.no_respect_force) + entry["Total Ignored Force"] = I.no_respect_force + if (I.no_hint) + entry["Total No Hint"] = I.no_hint + if(LAZYLEN(I.extra_details)) + entry["Deleted Metadata"] = I.extra_details + + log_subsystem_garbage(json_encode(del_log)) + /datum/controller/subsystem/garbage/fire() - HandleToBeQueued() - if(state == SS_RUNNING) - HandleQueue() + //the fact that this resets its processing each fire (rather then resume where it left off) is intentional. + var/queue = GC_QUEUE_FILTER - if (state == SS_PAUSED) - state = SS_RUNNING + while (state == SS_RUNNING) + switch (queue) + if (GC_QUEUE_FILTER) + HandleQueue(GC_QUEUE_FILTER) + queue = GC_QUEUE_FILTER+1 + if (GC_QUEUE_CHECK) + HandleQueue(GC_QUEUE_CHECK) + queue = GC_QUEUE_CHECK+1 + if (GC_QUEUE_HARDDELETE) + HandleQueue(GC_QUEUE_HARDDELETE) + if (state == SS_PAUSED) //make us wait again before the next run. + state = SS_RUNNING + break -//If you see this proc high on the profile, what you are really seeing is the garbage collection/soft delete overhead in byond. -//Don't attempt to optimize, not worth the effort. -/datum/controller/subsystem/garbage/proc/HandleToBeQueued() - var/list/tobequeued = src.tobequeued - var/starttime = world.time - var/starttimeofday = world.timeofday - var/idex = 1 - while((tobequeued.len - (idex - 1)) && starttime == world.time && starttimeofday == world.timeofday) - if (MC_TICK_CHECK) - break - var/ref = tobequeued[idex] - tobequeued[idex++] = null // Clear this ref to assist hard deletes in Queue(). - Queue(ref) - if (idex > 1) - tobequeued.Cut(1, idex) -/datum/controller/subsystem/garbage/proc/HandleQueue() - delslasttick = 0 - gcedlasttick = 0 - var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del() - var/list/queue = src.queue - var/starttime = world.time - var/starttimeofday = world.timeofday - var/idex = 1 - #ifdef REFERENCE_TRACKING - var/ref_searching = FALSE - #endif - while((queue.len - (idex - 1)) && starttime == world.time && starttimeofday == world.timeofday) - if (MC_TICK_CHECK) - break - #ifdef REFERENCE_TRACKING - if (ref_searching) - break - #endif - var/refID = queue[idex] - if (!refID) - idex++ +/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) + pass_counts = new(GC_QUEUE_COUNT) + fail_counts = new(GC_QUEUE_COUNT) + for(var/i in 1 to GC_QUEUE_COUNT) + queues[i] = list() + pass_counts[i] = 0 + fail_counts[i] = 0 + + +/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_FILTER) + if (level == GC_QUEUE_FILTER) + delslasttick = 0 + gcedlasttick = 0 + var/cut_off_time = world.time - collection_timeout[level] //ignore entries newer then this + var/list/queue = queues[level] + var/static/lastlevel + var/static/count = 0 + if (count) //runtime last run before we could do this. + var/c = count + count = 0 //so if we runtime on the Cut, we don't try again. + var/list/lastqueue = queues[lastlevel] + lastqueue.Cut(1, c+1) + + lastlevel = level + + //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] + 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 > time_to_kill) + var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME] + if(queued_at_time > cut_off_time) break // Everything else is newer, skip them - idex++ - var/datum/A - A = locate(refID) - if (A && A.gcDestroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake - #ifdef REFERENCE_TRACKING - if(reference_find_on_fail[text_ref(A)]) - INVOKE_ASYNC(A, TYPE_PROC_REF(/datum, find_references)) - ref_searching = TRUE - #ifdef GC_FAILURE_HARD_LOOKUP - else - INVOKE_ASYNC(A, TYPE_PROC_REF(/datum, find_references)) - ref_searching = TRUE - #endif - reference_find_on_fail -= text_ref(A) - #endif + count++ - // Something's still referring to the qdel'd object. Kill it. - var/type = A.type - log_subsystem_garbage_harddel("-- \ref[A] | [type] was unable to be GC'd and was deleted --", type) - didntgc["[type]"]++ + var/datum/D = L[GC_QUEUE_ITEM_REF] - #ifdef REFERENCE_TRACKING - if(ref_searching) - continue //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 - - HardDelete(A) - - ++delslasttick - ++totaldels - else + // 1 from the hard reference in the queue, and 1 from the variable used before this + // If that's all we've got, send er off + if (refcount(D) == 2) ++gcedlasttick ++totalgcs + pass_counts[level]++ #ifdef REFERENCE_TRACKING - reference_find_on_fail -= text_ref(A) + reference_find_on_fail -= text_ref(D) //It's deleted we don't care anymore. #endif + if (MC_TICK_CHECK) + return + continue - if (idex > 1) - queue.Cut(1, idex) + // Something's still referring to the qdel'd object. + fail_counts[level]++ -/datum/controller/subsystem/garbage/proc/QueueForQueuing(datum/A) - if (istype(A) && A.gcDestroyed == GC_CURRENTLY_BEING_QDELETED) - tobequeued += A - A.gcDestroyed = GC_QUEUED_FOR_QUEUING + #ifdef REFERENCE_TRACKING + var/ref_searching = FALSE + #endif -/datum/controller/subsystem/garbage/proc/Queue(datum/A) - if (!istype(A) || (!isnull(A.gcDestroyed) && A.gcDestroyed >= 0)) + switch (level) + if (GC_QUEUE_CHECK) + #ifdef REFERENCE_TRACKING + if(reference_find_on_fail[text_ref(D)]) + INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references)) + ref_searching = TRUE + #ifdef GC_FAILURE_HARD_LOOKUP + else + INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references)) + ref_searching = TRUE + #endif + reference_find_on_fail -= text_ref(D) + #endif + var/type = D.type + var/datum/qdel_item/I = items[type] + + 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.staff) //Using testing() here would fill the logs with ADMIN_VV garbage + var/client/admin = c + if(!check_rights(R_ADMIN, user = admin?.mob)) + continue + // Used to be to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --") but we do not have that macro + to_chat(admin, "## TESTING: GC: -- VV | [type] was unable to be GC'd --") + #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) + return + continue + + Queue(D, level+1) + + #ifdef REFERENCE_TRACKING + if(ref_searching) + return + #endif + + if (MC_TICK_CHECK) + return + if (count) + queue.Cut(1,count+1) + count = 0 + +/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_FILTER) + if (isnull(D)) return - if (A.gcDestroyed == GC_QUEUED_FOR_HARD_DEL) - HardDelete(A) + if (level > GC_QUEUE_COUNT) + HardDelete(D) return - var/gctime = world.time - var/refid = "\ref[A]" + var/queue_time = world.time - A.gcDestroyed = gctime + if (D.gc_destroyed <= 0) + D.gc_destroyed = queue_time - if (queue[refid]) - queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list. + var/list/queue = queues[level] + queue[++queue.len] = list(queue_time, D, D.gc_destroyed) // not += for byond reasons - queue[refid] = gctime +//this is mainly to separate things profile wise. +/datum/controller/subsystem/garbage/proc/HardDelete(datum/D) + ++delslasttick + ++totaldels + var/type = D.type + var/refID = text_ref(D) + var/datum/qdel_item/type_info = items[type] + var/detail = D.dump_harddel_info() + if(detail) + LAZYADD(type_info.extra_details, detail) -// For profiling. -/datum/controller/subsystem/garbage/proc/HardDelete(datum/A) - var/time = world.timeofday - var/tick = world.tick_usage - var/ticktime = world.time + var/tick_usage = TICK_USAGE + del(D) + tick_usage = TICK_USAGE_TO_MS(tick_usage) - var/type = A.type - var/refID = "\ref[A]" + 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]" - del(A) + var/time = MS2DS(tick_usage) - tick = (world.tick_usage-tick+((world.time-ticktime)/world.tick_lag*100)) - if (tick > highest_del_tickusage) - highest_del_tickusage = tick - time = world.timeofday - time - if (!time && TICK_DELTA_TO_MS(tick) > 1) - time = TICK_DELTA_TO_MS(tick)/100 - if (time > highest_del_time) - highest_del_time = time - if (time > 10) - log_subsystem_garbage_error("Error: [type]([refID]) took longer then 1 second to delete (took [time/10] seconds to delete)", type, TRUE) - message_admins("Error: [type]([refID]) took longer then 1 second to delete (took [time/10] seconds to delete).") - postpone(time/5) - -/datum/controller/subsystem/garbage/proc/HardQueue(datum/A) - if (istype(A) && A.gcDestroyed == GC_CURRENTLY_BEING_QDELETED) - tobequeued += A - A.gcDestroyed = GC_QUEUED_FOR_HARD_DEL + if (time > 0.1 SECONDS) + postpone(time) + var/threshold = 0.5 // Used to be CONFIG_GET(number/hard_deletes_overrun_threshold) + if (threshold && (time > threshold SECONDS)) + 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).") + type_info.qdel_flags |= QDEL_ITEM_ADMINS_WARNED + type_info.hard_deletes_over_threshold++ + var/overrun_limit = 0 // Used to be CONFIG_GET(number/hard_deletes_overrun_limit) + 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() - if (istype(SSgarbage.queue)) - queue |= SSgarbage.queue - if (istype(SSgarbage.tobequeued)) - tobequeued |= SSgarbage.tobequeued + InitQueues() //We first need to create the queues before recovering data + if (istype(SSgarbage.queues)) + for (var/i in 1 to SSgarbage.queues.len) + queues[i] |= SSgarbage.queues[i] -// 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(!D) +/// Qdel Item: Holds statistics on each type that passes thru qdel +/datum/qdel_item + var/name = "" //!Holds the type as a string for this type + var/qdels = 0 //!Total number of times it's passed thru qdel. + var/destroy_time = 0 //!Total amount of milliseconds spent processing this type's Destroy() + var/failures = 0 //!Times it was queued for soft deletion but failed to soft delete. + var/hard_deletes = 0 //!Different from failures because it also includes QDEL_HINT_HARDDEL deletions + var/hard_delete_time = 0 //!Total amount of milliseconds spent hard deleting this type. + var/hard_delete_max = 0 //!Highest time spent hard_deleting this in ms. + var/hard_deletes_over_threshold = 0 //!Number of times hard deletes took longer than the configured threshold + var/no_respect_force = 0 //!Number of times it's not respected force=TRUE + var/no_hint = 0 //!Number of times it's not even bother to give a qdel hint + var/slept_destroy = 0 //!Number of times it's slept in its destroy + 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/to_delete, force = FALSE) + if(!istype(to_delete)) + del(to_delete) return -#ifdef REFERENCE_TRACKING - SSgarbage.qdel_list += "[D.type]" -#endif - if(!istype(D)) - del(D) - else if(isnull(D.gcDestroyed)) - if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted + + 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(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_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 - // this SEND_SIGNAL should be above Destroy because Destroy sets signal_enabled to FALSE - SEND_SIGNAL(D, COMSIG_QDELETING, force) // Let the (remaining) components know about the result of Destroy - D.gcDestroyed = GC_CURRENTLY_BEING_QDELETED - var/start_time = world.time - var/hint = D.Destroy(force) // Let our friend know they're about to get fucked up. - if(world.time != start_time) - SSgarbage.sleptDestroy["[D.type]"]++ - if(!D) - return - switch(hint) - if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion. - SSgarbage.QueueForQueuing(D) - if (QDEL_HINT_IWILLGC) - D.gcDestroyed = 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.gcDestroyed = null //clear the gc variable (important!) - return - // Returning LETMELIVE after being told to force destroy - // indicates the objects Destroy() does not respect force - if(!SSgarbage.noforcerespect["[D.type]"]) - SSgarbage.noforcerespect["[D.type]"] = "[D.type]" - log_subsystem_garbage_warning("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.") - SSgarbage.QueueForQueuing(D) - if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate() - SSgarbage.HardQueue(D) - if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste. - SSgarbage.HardDelete(D) - if (QDEL_HINT_FINDREFERENCE)//qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion. - SSgarbage.QueueForQueuing(D) - #ifdef REFERENCE_TRACKING - INVOKE_ASYNC(D, TYPE_PROC_REF(/datum, find_references), TRUE) - #endif - 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.QueueForQueuing(D) - #ifdef REFERENCE_TRACKING - SSgarbage.reference_find_on_fail[text_ref(D)] = TRUE - #endif - else - if(!SSgarbage.noqdelhint["[D.type]"]) - SSgarbage.noqdelhint["[D.type]"] = "[D.type]" - log_subsystem_garbage_warning("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.") - SSgarbage.QueueForQueuing(D) - else if(D.gcDestroyed == GC_CURRENTLY_BEING_QDELETED) - CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic") - -/client/Destroy() - ..() - return QDEL_HINT_HARDDEL_NOW - - -// REFERENCE TRACKING (the old garbage-debug file) // -// Only present if the appropriate define is set // -#ifdef REFERENCE_TRACKING - -/datum/verb/find_refs() - set category = "Debug" - set name = "Find References" - set background = 1 - set src in world - - find_references(FALSE) - -/client/verb/show_qdeleted() - set category = "Debug" - set name = "Show qdel() Log" - set desc = "Render the qdel() log and display it" - - var/dat = "List of things that have been qdel()eted this round

" - - var/tmplist = list() - for(var/elem in SSgarbage.qdel_list) - if(!(elem in tmplist)) - tmplist[elem] = 0 - tmplist[elem]++ - - sortTim(tmplist, GLOBAL_PROC_REF(cmp_numeric_dsc), TRUE) - - for(var/path in tmplist) - dat += "[path] - [tmplist[path]] times
" - - usr << browse(dat, "window=qdeletedlog") - -/datum/proc/find_references(skip_alert) - running_find_references = type - if(usr?.client) - if(usr.client.running_find_references) - testing("CANCELLED search for references to a [usr.client.running_find_references].") - usr.client.running_find_references = null - running_find_references = null - SSgarbage.can_fire = TRUE - SSgarbage.update_nextfire(reset_time = TRUE) - return - - if(!skip_alert && alert(usr, "Running this will lock everything up for 5+ minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes") - running_find_references = null - return - - SSgarbage.can_fire = FALSE // Keeps the GC from failing to collect objects being searched for here - - if(usr?.client) - usr.client.running_find_references = type - - //Time to search the whole game for our ref - testing("Beginning search for references to a [type].") - var/starting_time = world.time - - //Yes we do actually need to do this. The searcher refuses to read weird lists - //And global.vars is a really weird list - var/global_vars = list() - for(var/key in global.vars) - global_vars[key] = global.vars[key] - - search_var(global_vars, "Native Global", search_time = starting_time) - testing("Finished searching native globals") - - for(var/datum/thing in world) // atoms (don't believe its lies) - search_var(thing, "World -> [thing.type]", search_time = starting_time) - testing("Finished searching atoms") - - for(var/datum/thing) // datums - search_var(thing, "Datums -> [thing.type]", search_time = starting_time) - testing("Finished searching datums") - - //Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself - for(var/client/thing) // clients - search_var(thing, "Clients -> [thing.type]", search_time = starting_time) - testing("Finished searching clients") - - testing("Completed all searches for references to a [type].") - - if(usr?.client) - usr.client.running_find_references = null - running_find_references = null - - SSgarbage.can_fire = TRUE //restart the garbage collector - SSgarbage.update_nextfire(reset_time = TRUE) //restart the garbage collector - -/datum/proc/search_var(potential_container, container_name, recursive_limit = 64, search_time = world.time) - //If we are performing a search without a check tick, we should avoid sleeping - #if defined(FIND_REF_NO_CHECK_TICK) - SHOULD_NOT_SLEEP(TRUE) - #endif - - #ifdef REFERENCE_TRACKING_DEBUG - if(SSgarbage.should_save_refs && !found_refs) - found_refs = list() - #endif - - if(usr?.client && !usr.client.running_find_references) - return - - if(!recursive_limit) - testing("Recursion limit reached. [container_name]") - 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 - #ifndef FIND_REF_NO_CHECK_TICK - CHECK_TICK - #endif - - if(isdatum(potential_container)) - var/datum/datum_container = potential_container - if(datum_container.last_find_references == search_time) - return - - datum_container.last_find_references = search_time - var/list/vars_list = datum_container.vars - - for(var/varname in vars_list) - #ifndef FIND_REF_NO_CHECK_TICK - CHECK_TICK + // 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 - if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references - continue - var/variable = vars_list[varname] + trash.no_respect_force++ - if(variable == src) - #ifdef REFERENCE_TRACKING_DEBUG - if(SSgarbage.should_save_refs) - found_refs[varname] = TRUE - continue //End early, don't want these logging - #endif - testing("Found [type] [text_ref(src)] in [datum_container.type]'s [text_ref(datum_container)] [varname] var. [container_name]") - continue - - if(islist(variable)) - search_var(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 + 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 - // Check normal entries - if(element_in_list == src) - #ifdef REFERENCE_TRACKING_DEBUG - if(SSgarbage.should_save_refs) - found_refs[potential_cache] = TRUE - continue //End early, don't want these logging - #endif - testing("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]") - continue - - var/assoc_val = null - if(!isnum(element_in_list) && normal) - assoc_val = potential_cache[element_in_list] - // Check assoc entries - 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 - testing("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)) - search_var(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time) - // Check assoc sublists - if(islist(assoc_val)) - search_var(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_qdel, force = FALSE) - thing_to_qdel.qdel_and_find_ref_if_fail(force) - -/datum/proc/qdel_and_find_ref_if_fail(force = FALSE) - SSgarbage.reference_find_on_fail[text_ref(src)] = TRUE - qdel(src, force) - -#endif + trash.no_hint++ + SSgarbage.Queue(to_delete) diff --git a/code/controllers/subsystems/radio.dm b/code/controllers/subsystems/radio.dm index b5d664472d7..5940e18a1ee 100644 --- a/code/controllers/subsystems/radio.dm +++ b/code/controllers/subsystems/radio.dm @@ -108,6 +108,8 @@ SUBSYSTEM_DEF(radio) return ..() /datum/controller/subsystem/radio/proc/add_object(obj/device, new_frequency, filter = null) + SHOULD_NOT_SLEEP(TRUE) + var/f_text = num2text(new_frequency) var/datum/radio_frequency/frequency = frequencies[f_text] @@ -120,6 +122,8 @@ SUBSYSTEM_DEF(radio) return frequency /datum/controller/subsystem/radio/proc/remove_object(obj/device, old_frequency) + SHOULD_NOT_SLEEP(TRUE) + var/f_text = num2text(old_frequency) var/datum/radio_frequency/frequency = frequencies[f_text] @@ -127,10 +131,14 @@ SUBSYSTEM_DEF(radio) frequency.remove_listener(device) /datum/controller/subsystem/radio/proc/remove_object_all(obj/device) + SHOULD_NOT_SLEEP(TRUE) + for(var/freq in frequencies) SSradio.remove_object(device, text2num(freq)) /datum/controller/subsystem/radio/proc/get_devices(freq, filter = RADIO_DEFAULT) + SHOULD_NOT_SLEEP(TRUE) + var/datum/radio_frequency/frequency = frequencies[num2text(freq)] if(!frequency) return @@ -150,6 +158,8 @@ SUBSYSTEM_DEF(radio) // Used to test connectivity to the telecomms network. /datum/controller/subsystem/radio/proc/telecomms_ping(obj/O, test_freq = PUB_FREQ) + SHOULD_NOT_SLEEP(TRUE) + var/datum/signal/subspace/testsig = new(O, test_freq) for (var/obj/machinery/telecomms/R in SSmachinery.all_receivers) if(R.receive_range(testsig) >= 0) @@ -159,6 +169,8 @@ SUBSYSTEM_DEF(radio) //callback used by objects to react to incoming radio signals /obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param) + SHOULD_NOT_SLEEP(TRUE) + return null /proc/frequency_span_class(var/frequency) diff --git a/code/controllers/subsystems/spatial_gridmap.dm b/code/controllers/subsystems/spatial_gridmap.dm index 15b3bcf012e..00a101c62ff 100644 --- a/code/controllers/subsystems/spatial_gridmap.dm +++ b/code/controllers/subsystems/spatial_gridmap.dm @@ -131,7 +131,7 @@ SUBSYSTEM_DEF(spatial_grid) if(movable_turf) enter_cell(movable, movable_turf) - UnregisterSignal(movable, COMSIG_PARENT_PREQDELETED) + UnregisterSignal(movable, COMSIG_PREQDELETED) waiting_to_add_by_type[channel_type] -= movable pregenerate_more_oranges_ears(NUMBER_OF_PREGENERATED_ORANGES_EARS) @@ -143,7 +143,7 @@ SUBSYSTEM_DEF(spatial_grid) ///add a movable to the pre init queue for whichever type is specified so that when the subsystem initializes they get added to the grid /datum/controller/subsystem/spatial_grid/proc/enter_pre_init_queue(atom/movable/waiting_movable, type) - RegisterSignal(waiting_movable, COMSIG_PARENT_PREQDELETED, PROC_REF(queued_item_deleted), override = TRUE) + RegisterSignal(waiting_movable, COMSIG_PREQDELETED, PROC_REF(queued_item_deleted), override = TRUE) //override because something can enter the queue for two different types but that is done through unrelated procs that shouldnt know about eachother waiting_to_add_by_type[type] += waiting_movable @@ -158,11 +158,11 @@ SUBSYSTEM_DEF(spatial_grid) waiting_movable_is_in_other_queues = TRUE if(!waiting_movable_is_in_other_queues) - UnregisterSignal(movable_to_remove, COMSIG_PARENT_PREQDELETED) + UnregisterSignal(movable_to_remove, COMSIG_PREQDELETED) return - UnregisterSignal(movable_to_remove, COMSIG_PARENT_PREQDELETED) + UnregisterSignal(movable_to_remove, COMSIG_PREQDELETED) for(var/type in waiting_to_add_by_type) waiting_to_add_by_type[type] -= movable_to_remove diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 35ecdd5e24c..eb0e9e9a28e 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -1,15 +1,14 @@ /datum - var/tmp/list/active_timers - var/tmp/datum/weakref/weakref - var/tmp/isprocessing = 0 - /** * Tick count time when this object was destroyed. * * If this is non zero then the object has been garbage collected and is awaiting either * a hard del by the GC subsystme, or to be autocollected (if it has no references) */ - var/tmp/gcDestroyed + var/gc_destroyed + + var/tmp/list/active_timers + var/tmp/isprocessing = 0 /// Status traits attached to this datum. associative list of the form: list(trait name (string) = list(source1, source2, source3,...)) var/list/status_traits @@ -24,10 +23,9 @@ /// Is this datum capable of sending signals? /// Set to true when a signal has been registered var/signal_enabled = FALSE - /// A cached version of our \ref - /// The brunt of \ref costs are in creating entries in the string tree (a tree of immutable strings) - /// This avoids doing that more then once per datum by ensuring ref strings always have a reference to them after they're first pulled - var/cached_ref + + /// A weak reference to another datum + var/datum/weakref/weak_reference #ifdef REFERENCE_TRACKING var/running_find_references @@ -38,29 +36,29 @@ #endif #endif + // If we have called dump_harddel_info already. Used to avoid duped calls (since we call it immediately in some cases on failure to process) + // Create and destroy is weird and I wanna cover my bases + var/harddel_deets_dumped = FALSE + // Default implementation of clean-up code. // This should be overridden to remove all references pointing to the object being destroyed. // Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE. /datum/proc/Destroy(force=FALSE) SHOULD_CALL_PARENT(TRUE) + //SHOULD_NOT_SLEEP(TRUE) //Soon my friend, soon... - weakref = null - GLOB.destroyed_event.raise_event(src) - var/ui_key = SOFTREF(src) - if(LAZYISIN(SSnanoui.open_uis, ui_key)) - SSnanoui.close_uis(src) tag = null - var/list/timers = active_timers - active_timers = null - if (timers) - for (var/thing in timers) - var/datum/timedevent/timer = thing - if (timer.spent) - continue - qdel(timer) + weak_reference = null //ensure prompt GCing of weakref. - // Handle components & signals - signal_enabled = FALSE + if(active_timers) + var/list/timers = active_timers + active_timers = null + if (timers) + for (var/thing in timers) + var/datum/timedevent/timer = thing + if (timer.spent) + continue + qdel(timer) #ifdef REFERENCE_TRACKING #ifdef REFERENCE_TRACKING_DEBUG @@ -68,6 +66,19 @@ #endif #endif + GLOB.destroyed_event.raise_event(src) + if (!isturf(src)) + cleanup_events(src) + + var/ui_key = SOFTREF(src) + if(LAZYISIN(SSnanoui.open_uis, ui_key)) + SSnanoui.close_uis(src) + + + // Handle components & signals + signal_enabled = FALSE + + //BEGIN: ECS SHIT var/list/dc = datum_components if(dc) var/all_components = dc[/datum/component] @@ -95,6 +106,7 @@ for(var/target in signal_procs) UnregisterSignal(target, signal_procs[target]) + //END: ECS SHIT return QDEL_HINT_QUEUE @@ -125,3 +137,17 @@ return FALSE vars[var_name] = var_value return TRUE + + +/// 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 + +///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/helper_datums/construction_datum.dm b/code/datums/helper_datums/construction_datum.dm index 08e5e55efab..25204fad7de 100644 --- a/code/datums/helper_datums/construction_datum.dm +++ b/code/datums/helper_datums/construction_datum.dm @@ -2,17 +2,22 @@ var/list/steps var/atom/holder var/result - var/list/steps_desc var/current_desc = null /datum/construction/New(atom) ..() holder = atom if(!holder) //don't want this without a holder - spawn - qdel(src) + qdel(src) set_desc(steps.len) +/datum/construction/Destroy(force) + holder = null + + steps = null + + . = ..() + /datum/construction/proc/next_step() steps.len-- if(!steps.len) @@ -60,8 +65,7 @@ /datum/construction/proc/spawn_result() if(result) new result(get_turf(holder)) - spawn() - qdel(holder) + QDEL_NULL(holder) /datum/construction/proc/set_desc(index as num) var/list/step = steps[index] diff --git a/code/datums/observation/_debug.dm b/code/datums/observation/_debug.dm index 02b4a31e66f..b45a4fbcfb2 100644 --- a/code/datums/observation/_debug.dm +++ b/code/datums/observation/_debug.dm @@ -1,4 +1 @@ -/**************** -* Debug Support * -****************/ GLOBAL_LIST_EMPTY(all_observable_events) diff --git a/code/datums/observation/~cleanup.dm b/code/datums/observation/~cleanup.dm new file mode 100644 index 00000000000..01fc7192d3b --- /dev/null +++ b/code/datums/observation/~cleanup.dm @@ -0,0 +1,71 @@ +GLOBAL_LIST_EMPTY(global_listen_count) +GLOBAL_LIST_EMPTY(event_sources_count) +GLOBAL_LIST_EMPTY(event_listen_count) + +/proc/cleanup_events(source) + if(GLOB.global_listen_count && GLOB.global_listen_count[source]) + cleanup_global_listener(source, GLOB.global_listen_count[source]) + if(GLOB.event_sources_count && GLOB.event_sources_count[source]) + cleanup_source_listeners(source, GLOB.event_sources_count[source]) + if(GLOB.event_listen_count && GLOB.event_listen_count[source]) + cleanup_event_listener(source, GLOB.event_listen_count[source]) + +/singleton/observ/register(datum/event_source, datum/listener, proc_call) + . = ..() + if(.) + GLOB.event_sources_count[event_source] += 1 + GLOB.event_listen_count[listener] += 1 + +/singleton/observ/unregister(datum/event_source, datum/listener, proc_call) + . = ..() + if(.) + GLOB.event_sources_count[event_source] -= 1 + GLOB.event_listen_count[listener] -= 1 + + if(GLOB.event_sources_count[event_source] <= 0) + GLOB.event_sources_count -= event_source + if(GLOB.event_listen_count[listener] <= 0) + GLOB.event_listen_count -= listener + +/singleton/observ/register_global(datum/listener, proc_call) + . = ..() + if(.) + GLOB.global_listen_count[listener] += 1 + +/singleton/observ/unregister_global(datum/listener, proc_call) + . = ..() + if(.) + GLOB.global_listen_count[listener] -= 1 + if(GLOB.global_listen_count[listener] <= 0) + GLOB.global_listen_count -= listener + +/proc/cleanup_global_listener(listener, listen_count) + GLOB.global_listen_count -= listener + for(var/entry in GLOB.all_observable_events) + var/singleton/observ/event = entry + if(event.unregister_global(listener)) + log_debug("[event] - [listener] was deleted while still registered to global events.") + if(!(--listen_count)) + return + +/proc/cleanup_source_listeners(event_source, source_listener_count) + GLOB.event_sources_count -= event_source + for(var/entry in GLOB.all_observable_events) + var/singleton/observ/event = entry + var/proc_owners = event.event_sources[event_source] + if(proc_owners) + for(var/proc_owner in proc_owners) + if(event.unregister(event_source, proc_owner)) + log_debug("[event] - [event_source] was deleted while still being listened to by [proc_owner].") + if(!(--source_listener_count)) + return + +/proc/cleanup_event_listener(listener, listener_count) + GLOB.event_listen_count -= listener + for(var/entry in GLOB.all_observable_events) + var/singleton/observ/event = entry + for(var/event_source in event.event_sources) + if(event.unregister(event_source, listener)) + log_debug("[event] - [listener] was deleted while still listening to [event_source].") + if(!(--listener_count)) + return diff --git a/code/datums/weakref.dm b/code/datums/weakref.dm deleted file mode 100644 index a60ee9f1d07..00000000000 --- a/code/datums/weakref.dm +++ /dev/null @@ -1,17 +0,0 @@ -/datum/weakref - var/ref - -/datum/weakref/New(datum/D) - ref = SOFTREF(D) - -/datum/weakref/Destroy(force = 0) - crash_with("Some fuck is trying to [force ? "force-" : ""]delete a weakref!") - if (!force) - return QDEL_HINT_LETMELIVE // feck off - - return ..() - -/datum/weakref/proc/resolve() - var/datum/D = locate(ref) - if (!QDELETED(D) && D.weakref == src) - . = D diff --git a/code/datums/weakrefs.dm b/code/datums/weakrefs.dm new file mode 100644 index 00000000000..911623ff4c2 --- /dev/null +++ b/code/datums/weakrefs.dm @@ -0,0 +1,95 @@ +/// Creates a weakref to the given input. +/// See /datum/weakref's documentation for more information. +/proc/WEAKREF(datum/input) + if(istype(input) && !QDELETED(input)) + if(isweakref(input)) + return input + + if(!input.weak_reference) + input.weak_reference = new /datum/weakref(input) + return input.weak_reference + +/datum/proc/create_weakref() //Forced creation for admin proccalls + return WEAKREF(src) + +/** + * A weakref holds a non-owning reference to a datum. + * The datum can be referenced again using `resolve()`. + * + * To figure out why this is important, you must understand how deletion in + * BYOND works. + * + * Imagine a datum as a TV in a living room. When one person enters to watch + * TV, they turn it on. Others can come into the room and watch the TV. + * When the last person leaves the room, they turn off the TV because it's + * no longer being used. + * + * A datum being deleted tells everyone who's watching the TV to stop. + * If everyone leaves properly (AKA cleaning up their references), then the + * last person will turn off the TV, and everything is well. + * However, if someone is resistant (holds a hard reference after deletion), + * then someone has to walk in, drag them away, and turn off the TV forecefully. + * This process is very slow, and it's known as hard deletion. + * + * This is where weak references come in. Weak references don't count as someone + * watching the TV. Thus, when what it's referencing is destroyed, it will + * hopefully clean up properly, and limit hard deletions. + * + * A common use case for weak references is holding onto what created itself. + * For example, if a machine wanted to know what its last user was, it might + * create a `var/mob/living/last_user`. However, this is a strong reference to + * the mob, and thus will force a hard deletion when that mob is deleted. + * It is often better in this case to instead create a weakref to the user, + * meaning this type definition becomes `var/datum/weakref/last_user`. + * + * A good rule of thumb is that you should hold strong references to things + * that you *own*. For example, a dog holding a chew toy would be the owner + * of that chew toy, and thus a `var/obj/item/chew_toy` reference is fine + * (as long as it is cleaned up properly). + * However, a chew toy does not own its dog, so a `var/mob/living/dog/owner` + * might be inferior to a weakref. + * This is also a good rule of thumb to avoid circular references, such as the + * chew toy example. A circular reference that doesn't clean itself up properly + * will always hard delete. + */ +/datum/weakref + var/reference + +/datum/weakref/New(datum/thing) + reference = text_ref(thing) + +/datum/weakref/Destroy(force) + var/datum/target = resolve() + qdel(target) + + if(!force) + return QDEL_HINT_LETMELIVE //Let BYOND autoGC thiswhen nothing is using it anymore. + target?.weak_reference = null + return ..() + +/** + * Retrieves the datum that this weakref is referencing. + * + * This will return `null` if the datum was deleted. This MUST be respected. + */ +/datum/weakref/proc/resolve() + var/datum/D = locate(reference) + return (!QDELETED(D) && D.weak_reference == src) ? D : null + +/** + * SERIOUSLY READ THE AUTODOC COMMENT FOR THIS PROC BEFORE EVEN THINKING ABOUT USING IT + * + * Like resolve, but doesn't care if the datum is being qdeleted but hasn't been deleted yet. + * + * The return value of this proc leaves hanging references if the datum is being qdeleted but hasn't been deleted yet. + * + * Do not do anything that would create a lasting reference to the return value, such as giving it a tag, putting it on the map, + * adding it to an atom's contents or vis_contents, giving it a key (if it's a mob), attaching it to an atom (if it's an image), + * or assigning it to a datum or list referenced somewhere other than a temporary value. + * + * Unless you're resolving a weakref to a datum in a COMSIG_QDELETING signal handler registered on that very same datum, + * just use resolve instead. + */ +/datum/weakref/proc/hard_resolve() + var/datum/D = locate(reference) + return (D?.weak_reference == src) ? D : null diff --git a/code/game/atom/atoms_initializing_EXPENSIVE.dm b/code/game/atom/atoms_initializing_EXPENSIVE.dm index 7189c7c670e..1e960128759 100644 --- a/code/game/atom/atoms_initializing_EXPENSIVE.dm +++ b/code/game/atom/atoms_initializing_EXPENSIVE.dm @@ -4,7 +4,7 @@ if(QDELING(A)) // Check init_start_time to not worry about atoms created before the atoms SS that are cleaned up before this - if (A.gcDestroyed > init_start_time) + if (A.gc_destroyed > init_start_time) BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE return TRUE diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm index 79b6b90f839..6f959a9375c 100644 --- a/code/game/dna/dna_modifier.dm +++ b/code/game/dna/dna_modifier.dm @@ -222,6 +222,29 @@ active_power_usage = 400 var/waiting_for_user_input=0 // Fix for #274 (Mash create block injector without answering dialog to make unlimited injectors) - N3X +/obj/machinery/computer/scan_consolenew/Initialize() + ..() + for(var/i=0;i<3;i++) + buffers[i+1]=new /datum/dna2/record + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/computer/scan_consolenew/LateInitialize() + . = ..() + for(dir in list(NORTH,EAST,SOUTH,WEST)) + connected = locate(/obj/machinery/dna_scannernew, get_step(src, dir)) + if(!isnull(connected)) + break + + src.injector_ready = 1 + +/obj/machinery/computer/scan_consolenew/Destroy() + connected = null + disk = null + + QDEL_LIST_ASSOC(buffers) + + . = ..() + /obj/machinery/computer/scan_consolenew/attackby(obj/item/I as obj, mob/user as mob) if (istype(I, /obj/item/disk/data)) //INSERT SOME diskS if (!src.disk) @@ -234,31 +257,14 @@ return ..() /obj/machinery/computer/scan_consolenew/ex_act(severity) - switch(severity) if(1.0) //SN src = null qdel(src) - return if(2.0) if (prob(50)) //SN src = null qdel(src) - return - return - -/obj/machinery/computer/scan_consolenew/New() - ..() - for(var/i=0;i<3;i++) - buffers[i+1]=new /datum/dna2/record - spawn(5) - for(dir in list(NORTH,EAST,SOUTH,WEST)) - connected = locate(/obj/machinery/dna_scannernew, get_step(src, dir)) - if(!isnull(connected)) - break - spawn(250) - src.injector_ready = 1 - return return /obj/machinery/computer/scan_consolenew/proc/all_dna_blocks(var/list/buffer) diff --git a/code/game/gamemodes/technomancer/spells/spawner/destablize.dm b/code/game/gamemodes/technomancer/spells/spawner/destablize.dm index a270897a56e..c44aa5e25aa 100644 --- a/code/game/gamemodes/technomancer/spells/spawner/destablize.dm +++ b/code/game/gamemodes/technomancer/spells/spawner/destablize.dm @@ -35,20 +35,31 @@ var/pulses_remaining = 40 // Lasts 20 seconds. var/instability_power = 5 var/instability_range = 6 + var/timer_id = null /obj/effect/temporary_effect/destabilize/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/effect/temporary_effect/destabilize/LateInitialize() + . = ..() + timer_id = addtimer(CALLBACK(src, PROC_REF(radiate_loop)), 5 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) + +/obj/effect/temporary_effect/destabilize/Destroy() + deltimer(timer_id) . = ..() - radiate_loop() /obj/effect/temporary_effect/destabilize/proc/radiate_loop() - set waitfor = FALSE - while(pulses_remaining) - sleep(5) + if(pulses_remaining && !QDELETED(src)) for(var/mob/living/L in range(src, instability_range) ) var/radius = max(get_dist(L, src), 1) // Being farther away lessens the amount of instability received. var/outgoing_instability = instability_power * ( 1 / (radius**2) ) L.receive_radiated_instability(outgoing_instability) pulses_remaining-- - qdel(src) + timer_id = addtimer(CALLBACK(src, PROC_REF(radiate_loop)), 5 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) + + else + qdel(src) + diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm index 30710ea7dd1..c4ce6189d9d 100644 --- a/code/game/jobs/job/job.dm +++ b/code/game/jobs/job/job.dm @@ -145,6 +145,8 @@ . = equip(H, TRUE, FALSE, alt_title=alt_title) /datum/job/proc/get_access(selected_title) + SHOULD_NOT_SLEEP(TRUE) + if(!GLOB.config || GLOB.config.jobs_have_minimal_access) . = minimal_access.Copy() else diff --git a/code/game/machinery/autolathe/autolathe.dm b/code/game/machinery/autolathe/autolathe.dm index 711ef592535..b7edb605c12 100644 --- a/code/game/machinery/autolathe/autolathe.dm +++ b/code/game/machinery/autolathe/autolathe.dm @@ -14,7 +14,7 @@ clicksound = /singleton/sound_category/keyboard_sound clickvol = 30 - var/print_loc + var/atom/print_loc var/list/stored_material = list(DEFAULT_WALL_MATERIAL = 0, MATERIAL_GLASS = 0) var/list/storage_capacity = list(DEFAULT_WALL_MATERIAL = 0, MATERIAL_GLASS = 0) @@ -62,8 +62,12 @@ populate_lathe_recipes() /obj/machinery/autolathe/Destroy() + print_loc = null + QDEL_NULL(currently_printing) QDEL_NULL(wires) - print_queue.Cut() + + QDEL_LIST(print_queue) + return ..() /obj/machinery/autolathe/proc/populate_lathe_recipes() diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index d84a16a0f0c..f6faeceb9d1 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -74,7 +74,8 @@ GLOB.cameranet.remove_source(src) GLOB.cameranet.cameras -= src - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /obj/machinery/camera/set_pixel_offsets() pixel_x = dir & (NORTH|SOUTH) ? 0 : (dir == EAST ? -13 : 13) diff --git a/code/game/machinery/computer/slotmachine.dm b/code/game/machinery/computer/slotmachine.dm index a3928e5371e..761950d9027 100644 --- a/code/game/machinery/computer/slotmachine.dm +++ b/code/game/machinery/computer/slotmachine.dm @@ -50,8 +50,6 @@ coinvalues["[cointype]"] = get_value(cointype) /obj/machinery/computer/slot_machine/Destroy() - if(balance) - give_payout(balance) return ..() /obj/machinery/computer/slot_machine/process(delta_time) diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm index 289a33ece3d..cecd3adf95a 100644 --- a/code/game/machinery/doors/blast_door.dm +++ b/code/game/machinery/doors/blast_door.dm @@ -48,9 +48,8 @@ else layer = open_layer -/obj/machinery/door/airlock/Destroy() - qdel(wifi_receiver) - wifi_receiver = null +/obj/machinery/door/blast/Destroy() + QDEL_NULL(wifi_receiver) return ..() // Proc: Bumped() diff --git a/code/game/machinery/doors/multi_tile.dm b/code/game/machinery/doors/multi_tile.dm index 3e3ead762af..d998cfd40ab 100644 --- a/code/game/machinery/doors/multi_tile.dm +++ b/code/game/machinery/doors/multi_tile.dm @@ -35,7 +35,7 @@ LAZYADD(vision_blockers, vision_blocker) /obj/machinery/door/airlock/multi_tile/Destroy() - QDEL_NULL_LIST(vision_blockers) + QDEL_LIST(vision_blockers) return ..() /obj/machinery/door/airlock/multi_tile/set_opacity(var/new_opacity) @@ -100,7 +100,7 @@ LAZYADD(vision_blockers, vision_blocker) /obj/machinery/door/firedoor/multi_tile/Destroy() - QDEL_NULL_LIST(vision_blockers) + QDEL_LIST(vision_blockers) return ..() /obj/machinery/door/firedoor/multi_tile/set_opacity(var/new_opacity) diff --git a/code/game/machinery/embedded_controller/airlock_controllers.dm b/code/game/machinery/embedded_controller/airlock_controllers.dm index b9fbab51797..d1179a14579 100644 --- a/code/game/machinery/embedded_controller/airlock_controllers.dm +++ b/code/game/machinery/embedded_controller/airlock_controllers.dm @@ -31,6 +31,10 @@ tag_chamber_sensor = given_tag_chamber_sensor program = new /datum/computer/file/embedded_program/airlock(src) +/obj/machinery/embedded_controller/radio/airlock/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + //Advanced airlock controller for when you want a more versatile airlock controller - useful for turning simple access control rooms into airlocks /obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller name = "Advanced Airlock Controller" diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm index 5947e964dd4..7dcea6efd5b 100644 --- a/code/game/machinery/embedded_controller/embedded_controller_base.dm +++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm @@ -13,7 +13,8 @@ /obj/machinery/embedded_controller/radio/Destroy() if(SSradio) SSradio.remove_object(src,frequency) - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /obj/machinery/embedded_controller/proc/post_signal(datum/signal/signal, comm_line) return 0 diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index c7e7d2b1338..30613523ce5 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -155,12 +155,15 @@ Class Procs: /obj/machinery/Destroy() STOP_PROCESSING_MACHINE(src, MACHINERY_PROCESS_ALL) SSmachinery.machinery -= src + + //Clear the component parts + //If the components are inside the machine, delete them, otherwise we assume they were dropped to the ground during deconstruction, + //and were not removed from the component_parts list by deconstruction code if(component_parts) for(var/atom/A in component_parts) - if(A.loc == src) // If the components are inside the machine, delete them. + if(A.loc == src) qdel(A) - else // Otherwise we assume they were dropped to the ground during deconstruction, and were not removed from the component_parts list by deconstruction code. - component_parts -= A + component_parts = null return ..() @@ -451,9 +454,13 @@ Class Procs: M.set_dir(src.dir) M.state = 3 M.icon_state = "blueprint_1" + for(var/obj/I in component_parts) I.forceMove(loc) + component_parts -= I + qdel(src) + return TRUE /obj/machinery/proc/print(var/obj/paper, var/play_sound = 1, var/print_sfx = /singleton/sound_category/print_sound, var/print_delay = 10, var/message, var/mob/user) diff --git a/code/game/machinery/telecomms/machines/message_server.dm b/code/game/machinery/telecomms/machines/message_server.dm index 0b67ef80b49..7c07eb95385 100644 --- a/code/game/machinery/telecomms/machines/message_server.dm +++ b/code/game/machinery/telecomms/machines/message_server.dm @@ -215,7 +215,7 @@ + "", "window=pdaphoto;size=192x192") onclose(M, "pdaphoto") -var/obj/machinery/blackbox_recorder/blackbox +GLOBAL_DATUM(blackbox, /obj/machinery/blackbox_recorder) /obj/machinery/blackbox_recorder icon = 'icons/obj/stationobjs.dmi' @@ -231,16 +231,20 @@ var/obj/machinery/blackbox_recorder/blackbox //Only one can exist in the world! /obj/machinery/blackbox_recorder/Initialize() . = ..() - if(blackbox) - if(istype(blackbox,/obj/machinery/blackbox_recorder)) + if(GLOB.blackbox) + if(istype(GLOB.blackbox,/obj/machinery/blackbox_recorder)) qdel(src) else - blackbox = src + GLOB.blackbox = src /obj/machinery/blackbox_recorder/Destroy() feedback_set_details("blackbox_destroyed","true") feedback_set("blackbox_destroyed",1) - return ..() + + if(GLOB.blackbox == src) + GLOB.blackbox = null + + . = ..() #undef MESSAGE_SERVER_SPAM_REJECT diff --git a/code/game/objects/effects/explosion_particles.dm b/code/game/objects/effects/explosion_particles.dm index 392fc8bf906..e90be5fbac0 100644 --- a/code/game/objects/effects/explosion_particles.dm +++ b/code/game/objects/effects/explosion_particles.dm @@ -15,6 +15,10 @@ ..() return +/obj/effect/expl_particles/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /datum/effect/system/expl_particles var/number = 10 var/turf/location diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index bf88ddfb27f..d2f2761cbc2 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -92,7 +92,8 @@ /obj/effect/landmark/Destroy() GLOB.landmarks_list -= src - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /obj/effect/landmark/start name = "start" diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index 919045b5158..83346ebecd5 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -242,7 +242,6 @@ for(var/thing in contents) var/obj/O = thing O.forceMove(get_turf(src)) - O.throw_at_random(FALSE, 3, THROWNOBJ_KNOCKBACK_SPEED) var/area/A = get_area(src) message_all_revenants(FONT_LARGE(SPAN_WARNING("The rift keeping us here has been destroyed in [A.name]!"))) return ..() diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index c7c7925e39d..722b9fd969a 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -70,9 +70,7 @@ if(!M.moved_recently && M.last_move) M.moved_recently = 1 step(M, M.last_move) - sleep(50) - if(M) - M.moved_recently = 0 + addtimer(CALLBACK(src, PROC_REF(update_move_recently)), 50 SECONDS) to_chat(M, "You feel a sharp shock!") spark(M, 3) @@ -82,6 +80,13 @@ master.receive_signal() return +/obj/item/device/radio/electropack/proc/update_move_recently() + var/mob/M = loc + + if(M) + M.moved_recently = 0 + + /obj/item/device/radio/electropack/attack_self(mob/user as mob, flag1) if(!istype(user, /mob/living/carbon/human)) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 0f21551d2db..f63ff4d2bd1 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -783,6 +783,10 @@ var/myAi = null // Atlantis: Reference back to the AI which has this radio. var/disabledAi = 0 // Atlantis: Used to manually disable AI's integrated radio via intellicard menu. +/obj/item/device/radio/headset/heads/ai_integrated/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/device/radio/headset/heads/ai_integrated/can_receive(input_frequency, level) return ..(input_frequency, level, !disabledAi) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 683f5af7c47..b03c594d48e 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -565,7 +565,8 @@ var/global/list/default_interrogation_channels = list( /obj/item/device/radio/borg/Destroy() myborg = null - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /obj/item/device/radio/borg/list_channels(var/mob/user) return list_secure_channels(user) diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 8a0aa8df538..1ca139b4c46 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -38,6 +38,10 @@ var/global/list/datum/stack_recipe/rod_recipes = list( stacktype = /obj/item/stack/rods icon_has_variants = TRUE +/obj/item/stack/rods/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/stack/rods/full/Initialize() . = ..() amount = max_amount diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index 2412a1f07b2..646f3854cb6 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -126,7 +126,8 @@ var/const/NO_EMAG_ACT = -50 /obj/item/card/id/Destroy() QDEL_NULL(chat_user) - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /obj/item/card/id/examine(mob/user, distance) . = ..() @@ -162,6 +163,8 @@ var/const/NO_EMAG_ACT = -50 side.Scale(128, 128) /mob/proc/set_id_info(var/obj/item/card/id/id_card) + SHOULD_NOT_SLEEP(TRUE) + id_card.age = 0 id_card.registered_name = real_name id_card.sex = capitalize(gender) diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index bc78df7a4e4..b9b7fd3752f 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -38,11 +38,15 @@ M.disable_cloaking_device() M.update_icon() -/obj/item/grenade/flashbang/clusterbang//Created by Polymorph, fixed by Sieve +/obj/item/grenade/flashbang/clusterbang //Created by Polymorph, fixed by Sieve name = "clusterbang" icon = 'icons/obj/grenade.dmi' icon_state = "clusterbang" +/obj/item/grenade/flashbang/clusterbang/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/grenade/flashbang/clusterbang/prime() var/numspawned = rand(4,8) var/again = 0 @@ -103,3 +107,7 @@ var/dettime = rand(15,60) addtimer(CALLBACK(src, PROC_REF(prime)), dettime) ..() + +/obj/item/grenade/flashbang/cluster/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL diff --git a/code/game/objects/items/weapons/implants/implants/explosive.dm b/code/game/objects/items/weapons/implants/implants/explosive.dm index 92921984c23..c8090966a46 100644 --- a/code/game/objects/items/weapons/implants/implants/explosive.dm +++ b/code/game/objects/items/weapons/implants/implants/explosive.dm @@ -21,10 +21,24 @@ var/list/possible_explosions = list("Localized Limb", "Destroy Body") var/warning_message = "Tampering detected. Tampering detected." +/obj/item/implant/explosive/New() + ..() + become_hearing_sensitive(ROUNDSTART_TRAIT) + /obj/item/implant/explosive/Initialize() . = ..() setFrequency(frequency) +/obj/item/implant/explosive/Destroy() + lose_hearing_sensitivity(ROUNDSTART_TRAIT) + + if(frequency) + SSradio.remove_object_all(src) + frequency = null + radio_connection = null + + . = ..() + /obj/item/implant/explosive/get_data() . = {" Implant Specifications:
@@ -180,7 +194,7 @@ M.gib() //Simple mobs just get got qdel(src) -/proc/explosion_spread(turf/epicenter, power, adminlog = 1, z_transfer = UP|DOWN) +/obj/item/implant/explosive/proc/explosion_spread(turf/epicenter, power, adminlog = 1, z_transfer = UP|DOWN) var/datum/explosiondata/data = new data.epicenter = epicenter data.rec_pow = power @@ -223,13 +237,6 @@ /obj/item/implant/explosive/isLegal() return FALSE -/obj/item/implant/explosive/New() - ..() - become_hearing_sensitive(ROUNDSTART_TRAIT) - -/obj/item/implant/explosive/Destroy() - return ..() - /obj/item/implant/explosive/full possible_explosions = list("Localized Limb", "Destroy Body", "Full Explosion") diff --git a/code/game/objects/items/weapons/material/shards.dm b/code/game/objects/items/weapons/material/shards.dm index 95830717079..705948dc580 100644 --- a/code/game/objects/items/weapons/material/shards.dm +++ b/code/game/objects/items/weapons/material/shards.dm @@ -20,6 +20,10 @@ drop_sound = 'sound/effects/glass_step.ogg' surgerysound = 'sound/items/surgery/scalpel.ogg' +/obj/item/material/shard/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/material/shard/set_material(var/new_material) ..(new_material) if(!istype(material)) diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm index 515d106b4c8..91787291946 100644 --- a/code/game/objects/items/weapons/storage/storage.dm +++ b/code/game/objects/items/weapons/storage/storage.dm @@ -90,7 +90,7 @@ QDEL_NULL(storage_start) QDEL_NULL(storage_continue) QDEL_NULL(storage_end) - QDEL_NULL_LIST(storage_screens) + QDEL_LIST(storage_screens) QDEL_NULL(closer) return ..() @@ -208,7 +208,7 @@ user.s_active = null if(!length(can_see_contents())) storage_start.vis_contents = list() - QDEL_NULL_LIST(storage_screens) + QDEL_LIST(storage_screens) storage_screens = list() /obj/item/storage/proc/close_all() @@ -309,7 +309,7 @@ var/endpoint = 1 storage_start.vis_contents = list() - QDEL_NULL_LIST(storage_screens) + QDEL_LIST(storage_screens) storage_screens = list() for(var/obj/item/O in contents) diff --git a/code/game/world.dm b/code/game/world.dm index 920b3186e1d..2090f2fd13e 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -82,7 +82,7 @@ GLOBAL_PROTECT(config) loop_checks = FALSE #endif -#define RECOMMENDED_VERSION 510 +#define RECOMMENDED_VERSION 515 /world/New() //logs GLOB.diary_date_string = time2text(world.realtime, "YYYY/MM/DD") @@ -509,14 +509,14 @@ var/list/world_api_rate_limit = list() else CRASH("Unsupported platform: [system_type]") - var/init_result = LIBCALL(library, "init")("block") + var/init_result = call_ext(library, "init")("block") if (init_result != "0") CRASH("Error initializing byond-tracy: [init_result]") /world/proc/init_debugger() var/dll = GetConfig("env", "AUXTOOLS_DEBUG_DLL") if (dll) - LIBCALL(dll, "auxtools_init")() + call_ext(dll, "auxtools_init")() enable_debugging() #undef FAILED_DB_CONNECTION_CUTOFF diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 238f7aafa8c..143da660c88 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -218,6 +218,7 @@ var/list/admin_verbs_debug = list( /client/proc/lighting_show_verbs, /client/proc/restart_controller, /client/proc/cmd_display_del_log, + /client/proc/cmd_display_harddel_log, /client/proc/cmd_display_init_log, /client/proc/cmd_ss_panic, /client/proc/reset_openturf, @@ -388,6 +389,7 @@ var/list/admin_verbs_hideable = list( /client/proc/kill_air, /client/proc/kill_airgroup, /client/proc/cmd_display_del_log, + /client/proc/cmd_display_harddel_log, /datum/admins/proc/ccannoucment, /client/proc/cmd_display_init_log, /client/proc/getruntimelog, @@ -476,6 +478,7 @@ var/list/admin_verbs_dev = list( //will need to be altered - Ryan784 /client/proc/cmd_dev_bst, /client/proc/lighting_show_verbs, /client/proc/cmd_display_del_log, + /client/proc/cmd_display_harddel_log, /client/proc/cmd_display_init_log, /client/proc/create_poll, //Allows to create polls /client/proc/profiler_start, diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 3e9fce14af9..94467981545 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -421,21 +421,80 @@ /client/proc/cmd_display_del_log() set category = "Debug" set name = "Display del() Log" - set desc = "Displays a list of things that have failed to GC this round" + set desc = "Display del's log of everything that's passed through it." - var/dat = "List of things that failed to GC this round

" - for(var/path in SSgarbage.didntgc) - dat += "[path] - [SSgarbage.didntgc[path]] times
" + var/list/dellog = list("List of things that have gone through qdel this round

    ") + sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE) + for(var/path in SSgarbage.items) + var/datum/qdel_item/I = SSgarbage.items[path] + dellog += "
  1. [path]
  2. " - dat += "List of paths that did not return a qdel hint in Destroy()

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

" - for(var/path in SSgarbage.sleptDestroy) - dat += "[path]
" + usr << browse(dellog.Join(), "window=dellog") - usr << browse(dat, "window=dellog") +/** + * Same as `cmd_display_del_log`, but only shows harddels + */ +/client/proc/cmd_display_harddel_log() + set category = "Debug" + set name = "Display harddel() Log" + set desc = "Display harddel's log." + + var/list/dellog = list("List of things that have harddel'd this round

    ") + sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE) + for(var/path in SSgarbage.items) + var/datum/qdel_item/I = SSgarbage.items[path] + if(I.hard_deletes) + dellog += "
  1. [path]
  2. " + + dellog += "
" + + usr << browse(dellog.Join(), "window=harddellog") /client/proc/cmd_display_init_log() set category = "Debug" diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm new file mode 100644 index 00000000000..1003778e610 --- /dev/null +++ b/code/modules/admin/view_variables/reference_tracking.dm @@ -0,0 +1,170 @@ +#ifdef REFERENCE_TRACKING + +/datum/proc/find_references(skip_alert) + running_find_references = type + if(usr?.client) + if(usr.client.running_find_references) + log_subsystem_garbage_harddel("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.update_nextfire(reset_time = TRUE) + return + + if(!skip_alert && 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") + running_find_references = null + return + + //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 + + log_subsystem_garbage_harddel("Beginning search for references to a [type].") + + var/starting_time = world.time + + log_subsystem_garbage_harddel("Refcount for [type]: [refcount(src)]") + + //Time to search the whole game for our ref + DoSearchVar(GLOB, "GLOB", search_time = starting_time) //globals + log_subsystem_garbage_harddel("Finished searching globals") + + //Yes we do actually need to do this. The searcher refuses to read weird lists + //And global.vars is a really weird list + var/global_vars = list() + for(var/key in global.vars) + global_vars[key] = global.vars[key] + + DoSearchVar(global_vars, "Native Global", search_time = starting_time) + log_subsystem_garbage_harddel("Finished searching native globals") + + for(var/datum/thing in world) //atoms (don't beleive its lies) + DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time) + log_subsystem_garbage_harddel("Finished searching atoms") + + for(var/datum/thing) //datums + DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time) + log_subsystem_garbage_harddel("Finished searching datums") + + //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) + log_subsystem_garbage_harddel("Finished searching clients") +#endif + + log_subsystem_garbage_harddel("Completed search for references to a [type].") + + if(usr?.client) + usr.client.running_find_references = null + running_find_references = null + + //restart the garbage collector + SSgarbage.can_fire = TRUE + SSgarbage.update_nextfire(reset_time = TRUE) + +/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 + + if(usr?.client && !usr.client.running_find_references) + return + + if(!recursive_limit) + log_subsystem_garbage_harddel("Recursion limit reached. [container_name]") + 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 + #ifndef FIND_REF_NO_CHECK_TICK + CHECK_TICK + #endif + + if(isdatum(potential_container)) + var/datum/datum_container = potential_container + if(datum_container.last_find_references == search_time) + return + + datum_container.last_find_references = search_time + var/container_print = datum_container.ref_search_details() + var/list/vars_list = datum_container.vars + + 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) + #ifdef REFERENCE_TRACKING_DEBUG + if(SSgarbage.should_save_refs) + found_refs[varname] = TRUE + continue //End early, don't want these logging + #endif + log_subsystem_garbage_harddel("Found [type] [text_ref(src)] in [datum_container.type]'s [container_print] [varname] var. [container_name]") + continue + + if(islist(variable)) + DoSearchVar(variable, "[container_name] [container_print] -> [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 entrys + if(element_in_list == 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_subsystem_garbage_harddel("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_subsystem_garbage_harddel("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) + +/datum/proc/qdel_and_find_ref_if_fail(force = FALSE) + SSgarbage.reference_find_on_fail[text_ref(src)] = TRUE + qdel(src, force) + +#endif + +// Kept outside the ifdef so overrides are easy to implement + +/// 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)]" // Used to include `user: [user?.resolve() || "null"])` but dreamchecker no happy with non static ref diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index 4ba725c792d..2ef1f7b0477 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -33,6 +33,15 @@ */ var/wires = WIRE_RECEIVE_ASSEMBLY | WIRE_PULSE_ASSEMBLY +/obj/item/device/assembly/Destroy() + STOP_PROCESSING(SSprocessing, src) + + holder = null + cut_overlays() + attached_overlays = null + + . = ..() + /obj/item/device/assembly/proc/holder_movement() return diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm index 79b48bc16aa..f71d2f7b123 100644 --- a/code/modules/assembly/bomb.dm +++ b/code/modules/assembly/bomb.dm @@ -64,8 +64,11 @@ /obj/item/device/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. visible_message("[icon2html(src, viewers(get_turf(src)))] *beep* *beep*", "*beep* *beep*") - sleep(10) - if(!src) + addtimer(CALLBACK(src, PROC_REF(delayed_explosion)), 10 SECONDS) + + +/obj/item/device/onetankbomb/proc/delayed_explosion() + if(QDELETED(src)) return if(status) bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index e3805b3cf87..33a5aee9c87 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -15,6 +15,19 @@ var/obj/item/device/assembly/a_right = null var/obj/special_assembly = null +/obj/item/device/assembly_holder/Initialize(mapload, ...) + . = ..() + become_hearing_sensitive() + +/obj/item/device/assembly_holder/Destroy() + lose_hearing_sensitivity() + + QDEL_NULL(a_left) + QDEL_NULL(a_right) + QDEL_NULL(special_assembly) + + . = ..() + /obj/item/device/assembly_holder/proc/detached() if(a_left) a_left.holder_movement() @@ -174,17 +187,6 @@ master.receive_signal() return TRUE -/obj/item/device/assembly_holder/Initialize(mapload, ...) - . = ..() - become_hearing_sensitive() - -/obj/item/device/assembly_holder/Destroy() - if(a_left) - a_left.holder = null - if(a_right) - a_right.holder = null - return ..() - /obj/item/device/assembly_holder/hear_talk(mob/living/M, msg, verb, datum/language/speaking) if(a_right) a_right.hear_talk(M, msg, verb, speaking) diff --git a/code/modules/atmospherics/components/omni_devices/omni_base.dm b/code/modules/atmospherics/components/omni_devices/omni_base.dm index ea46884c7e8..480a58d3592 100644 --- a/code/modules/atmospherics/components/omni_devices/omni_base.dm +++ b/code/modules/atmospherics/components/omni_devices/omni_base.dm @@ -218,7 +218,8 @@ qdel(P.network) P.node = null - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /obj/machinery/atmospherics/omni/atmos_init() for(var/datum/omni_port/P in ports) diff --git a/code/modules/cargo/delivery/backpack.dm b/code/modules/cargo/delivery/backpack.dm index 3ad5f8a2b3e..4635b85366b 100644 --- a/code/modules/cargo/delivery/backpack.dm +++ b/code/modules/cargo/delivery/backpack.dm @@ -14,7 +14,7 @@ var/list/contained_packages /obj/item/cargo_backpack/Destroy() - QDEL_NULL_LIST(contained_packages) + QDEL_LIST(contained_packages) return ..() /obj/item/cargo_backpack/examine(mob/user, distance) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 5d35c8763cd..91369e2217d 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -211,7 +211,7 @@ var/list/preferences_datums = list() /datum/preferences/Destroy() . = ..() - QDEL_NULL_LIST(char_render_holders) + QDEL_LIST(char_render_holders) /datum/preferences/proc/load_and_update_character(var/slot) load_character(slot) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 4d37bfb8bc1..60895022d65 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -50,7 +50,7 @@ /obj/item/clothing/Destroy() STOP_PROCESSING(SSprocessing, src) - QDEL_NULL_LIST(accessories) + QDEL_LIST(accessories) return ..() //Updates the icons of the mob wearing the clothing item, if any. diff --git a/code/modules/clothing/spacesuits/rig/modules/storage.dm b/code/modules/clothing/spacesuits/rig/modules/storage.dm index 612313fef65..a557ac549ad 100644 --- a/code/modules/clothing/spacesuits/rig/modules/storage.dm +++ b/code/modules/clothing/spacesuits/rig/modules/storage.dm @@ -19,6 +19,11 @@ pockets.max_w_class = storage_max_w_class pockets.max_storage_space = storage_max_storage_space +/obj/item/rig_module/storage/Destroy() + QDEL_NULL(pockets) + + . = ..() + /obj/item/rig/attack_hand(mob/user as mob) var/obj/item/rig_module/storage/storage = locate() in installed_modules diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm index 78be1215649..da49fe494f6 100644 --- a/code/modules/clothing/spacesuits/rig/modules/utility.dm +++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm @@ -30,6 +30,13 @@ var/device_type var/obj/item/device +/obj/item/rig_module/device/Destroy() + if(!ispath(src.device)) + QDEL_NULL(src.device) + src.device = null + + . = ..() + /obj/item/rig_module/device/healthscanner name = "health scanner module" desc = "A hardsuit-mounted health scanner." @@ -383,11 +390,17 @@ category = MODULE_SPECIAL -/obj/item/rig_module/voice/New() - ..() +/obj/item/rig_module/voice/Initialize(mapload, ...) + . = ..() + voice_holder = new(src) voice_holder.active = 0 +/obj/item/rig_module/voice/Destroy() + QDEL_NULL(voice_holder) + + . = ..() + /obj/item/rig_module/voice/installed() ..() holder.speech = src @@ -552,12 +565,19 @@ category = MODULE_GENERAL -/obj/item/rig_module/device/stamp/New() - ..() +/obj/item/rig_module/device/stamp/Initialize() + . = ..() + iastamp = new /obj/item/stamp/internalaffairs(src) deniedstamp = new /obj/item/stamp/denied(src) device = iastamp +/obj/item/rig_module/device/stamp/Destroy() + QDEL_NULL(iastamp) + QDEL_NULL(deniedstamp) + + . = ..() + /obj/item/rig_module/device/stamp/engage(atom/target, mob/user) if(!..() || !device) return FALSE diff --git a/code/modules/clothing/spacesuits/rig/rig_construction.dm b/code/modules/clothing/spacesuits/rig/rig_construction.dm index 0c00a7e6f8b..ecf19d138fc 100644 --- a/code/modules/clothing/spacesuits/rig/rig_construction.dm +++ b/code/modules/clothing/spacesuits/rig/rig_construction.dm @@ -4,15 +4,37 @@ icon = 'icons/obj/rig_modules.dmi' var/icon_base = null w_class = ITEMSIZE_LARGE - var/obj/item/circuitboard/board_type = null - var/obj/item/circuitboard/target_board_type = null - var/obj/item/rig/rig_type = /obj/item/rig + + ///The type of board, a path of `/obj/item/circuitboard` + var/board_type = null + + ///The type of target board, a path of `/obj/item/circuitboard` + var/target_board_type = null + + ///The type of rig, a path of `/obj/item/rig` + var/rig_type = /obj/item/rig obj_flags = OBJ_FLAG_CONDUCTABLE origin_tech = list(TECH_MATERIAL = 4, TECH_ENGINEERING = 3, TECH_MAGNET = 4, TECH_POWER = 4) + var/datum/construction/reversible/rig_assembly/construct + obj_flags = OBJ_FLAG_CONDUCTABLE +/obj/item/rig_assembly/Initialize(mapload, ...) + . = ..() + + construct = new /datum/construction/reversible/rig_assembly/civilian(src) + construct.board_type = board_type + construct.steps[5]["key"] = board_type // defining board in construction step + construct.result = "[rig_type]" + +/obj/item/rig_assembly/Destroy() + QDEL_NULL(construct) + + . = ..() + + /obj/item/rig_assembly/examine(mob/user, distance) . = ..() if(construct) @@ -35,15 +57,10 @@ ..() return -/obj/item/rig_assembly/New() - ..() - construct = new /datum/construction/reversible/rig_assembly/civilian(src) - construct.board_type = board_type - construct.steps[5]["key"] = board_type // defining board in construction step - construct.result = "[rig_type]" -/obj/item/rig_assembly/combat/New() - ..() +/obj/item/rig_assembly/combat/Initialize(mapload, ...) + . = ..() + construct = new /datum/construction/reversible/rig_assembly/combat(src) construct.board_type = board_type construct.target_board_type = target_board_type @@ -166,8 +183,12 @@ /datum/construction/reversible/rig_assembly result = null - var/obj/item/circuitboard/rig_assembly/board_type = null - var/obj/item/circuitboard/rig_assembly/target_board_type = null + + ///The type of board, a path of `/obj/item/circuitboard` + var/board_type = null + + ///The type of target board, a path of `/obj/item/circuitboard` + var/target_board_type = null /datum/construction/reversible/rig_assembly/custom_action(index as num, diff as num, atom/used_atom, mob/user as mob) var/obj/item/I = used_atom diff --git a/code/modules/compass/compass_holder.dm b/code/modules/compass/compass_holder.dm index 18504538e89..35fd9773a43 100644 --- a/code/modules/compass/compass_holder.dm +++ b/code/modules/compass/compass_holder.dm @@ -59,7 +59,7 @@ rebuild_overlay_lists(TRUE) /obj/compass_holder/Destroy() - QDEL_NULL_LIST(compass_waypoints) + QDEL_LIST(compass_waypoints) . = ..() /obj/compass_holder/proc/get_heading() diff --git a/code/modules/cooking/machinery/cooking_machines/container.dm b/code/modules/cooking/machinery/cooking_machines/container.dm index 6cd35c0e621..845338be40b 100644 --- a/code/modules/cooking/machinery/cooking_machines/container.dm +++ b/code/modules/cooking/machinery/cooking_machines/container.dm @@ -122,7 +122,7 @@ //Deletes contents of container. //Used when food is burned, before replacing it with a burned mess /obj/item/reagent_containers/cooking_container/proc/clear() - QDEL_NULL_LIST(contents) + QDEL_LIST(contents) reagents.clear_reagents() /obj/item/reagent_containers/cooking_container/proc/label(var/number, var/CT = null) diff --git a/code/modules/ghostroles/spawner/human/human.dm b/code/modules/ghostroles/spawner/human/human.dm index 75601a9c611..7b41bcf8117 100644 --- a/code/modules/ghostroles/spawner/human/human.dm +++ b/code/modules/ghostroles/spawner/human/human.dm @@ -183,4 +183,5 @@ /mob/living/carbon/human/Destroy() ghost_spawner = null - return ..() + . = ..() + GC_TEMPORARY_HARDDEL diff --git a/code/modules/heavy_vehicle/mech_construction.dm b/code/modules/heavy_vehicle/mech_construction.dm index 1d2206e0c2f..10b10dcbd3f 100644 --- a/code/modules/heavy_vehicle/mech_construction.dm +++ b/code/modules/heavy_vehicle/mech_construction.dm @@ -160,7 +160,7 @@ return 1 -/mob/living/heavy_vehicle/proc/remove_system(var/system_hardpoint, var/mob/user, var/force) +/mob/living/heavy_vehicle/proc/remove_system_interact(var/system_hardpoint, var/mob/user, var/force) if((hardpoints_locked && !force) || !hardpoints[system_hardpoint]) return 0 @@ -172,6 +172,19 @@ if(!do_after(user, delay, src) || hardpoints[system_hardpoint] != system) return FALSE + remove_system(system_hardpoint, force) + + if(user) + system.forceMove(get_turf(user)) + user.put_in_hands(system) + to_chat(user, "You remove \the [system] in \the [src]'s [system_hardpoint].") + playsound(user.loc, 'sound/items/Screwdriver.ogg', 100, 1) + +/mob/living/heavy_vehicle/proc/remove_system(var/system_hardpoint, var/force) + if((hardpoints_locked && !force) || !hardpoints[system_hardpoint]) + return 0 + + var/obj/item/system = hardpoints[system_hardpoint] hardpoints[system_hardpoint] = null if(system_hardpoint == selected_hardpoint) @@ -197,10 +210,4 @@ refresh_hud() queue_icon_update() - if(user) - system.forceMove(get_turf(user)) - user.put_in_hands(system) - to_chat(user, "You remove \the [system] in \the [src]'s [system_hardpoint].") - playsound(user.loc, 'sound/items/Screwdriver.ogg', 100, 1) - return system diff --git a/code/modules/heavy_vehicle/mech_interaction.dm b/code/modules/heavy_vehicle/mech_interaction.dm index 866b02c4066..0d3074b9502 100644 --- a/code/modules/heavy_vehicle/mech_interaction.dm +++ b/code/modules/heavy_vehicle/mech_interaction.dm @@ -386,7 +386,7 @@ var/to_remove = tgui_input_list(user, "Which component would you like to remove?", "Remove Component", parts) - if(remove_system(to_remove, user)) + if(remove_system_interact(to_remove, user)) return to_chat(user, "\The [src] has no hardpoint systems to remove.") return diff --git a/code/modules/heavy_vehicle/mecha.dm b/code/modules/heavy_vehicle/mecha.dm index 1d514e31de8..055afc12f63 100644 --- a/code/modules/heavy_vehicle/mecha.dm +++ b/code/modules/heavy_vehicle/mecha.dm @@ -94,7 +94,8 @@ for(var/hardpoint in hardpoints) var/obj/item/S = remove_system(hardpoint, force = 1) - qdel(S) + if(S) + QDEL_NULL(S) hardpoints = null @@ -106,7 +107,7 @@ pilot.forceMove(get_turf(src)) pilots = null - QDEL_NULL_LIST(hud_elements) + QDEL_LIST(hud_elements) if(remote_network) SSvirtualreality.remove_mech(src, remote_network) diff --git a/code/modules/integrated_electronics/passive/power.dm b/code/modules/integrated_electronics/passive/power.dm index 43142d499a6..c044138e899 100644 --- a/code/modules/integrated_electronics/passive/power.dm +++ b/code/modules/integrated_electronics/passive/power.dm @@ -184,7 +184,7 @@ return ..() /obj/item/integrated_circuit/passive/power/powernet/Destroy() - qdel(IO) + QDEL_NULL(IO) return ..() /obj/item/integrated_circuit/passive/power/powernet/on_anchored() diff --git a/code/modules/materials/material_sheets.dm b/code/modules/materials/material_sheets.dm index 1324de7cde6..2f5abae48e7 100644 --- a/code/modules/materials/material_sheets.dm +++ b/code/modules/materials/material_sheets.dm @@ -292,6 +292,10 @@ default_type = MATERIAL_PLASTEEL icon_has_variants = TRUE +/obj/item/stack/material/plasteel/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/stack/material/plasteel/full/Initialize() . = ..() amount = max_amount diff --git a/code/modules/mob/abstract/freelook/ai/update_triggers.dm b/code/modules/mob/abstract/freelook/ai/update_triggers.dm index 36a55ce6305..4519cb563c1 100644 --- a/code/modules/mob/abstract/freelook/ai/update_triggers.dm +++ b/code/modules/mob/abstract/freelook/ai/update_triggers.dm @@ -20,6 +20,7 @@ if(on_open_network) GLOB.cameranet.remove_source(src) . = ..() + GC_TEMPORARY_HARDDEL /obj/machinery/camera/proc/update_coverage(var/network_change = 0) if(network_change) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 4f0aa867a4e..5881f75f56a 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -45,6 +45,8 @@ //This is just a commonly used configuration for the equip_to_slot_if_possible() proc, used to equip people when the rounds tarts and when events happen and such. /mob/proc/equip_to_slot_or_del(obj/item/W as obj, slot) + SHOULD_NOT_SLEEP(TRUE) + . = equip_to_slot_if_possible(W, slot, TRUE, TRUE, FALSE, TRUE) // Convinience proc. Collects crap that fails to equip either onto the mob's back, or drops it. diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm index 65d45c108a6..ace42ab4e11 100644 --- a/code/modules/mob/living/bot/bot.dm +++ b/code/modules/mob/living/bot/bot.dm @@ -43,7 +43,6 @@ if(pAI) if(isturf(loc)) drop_from_inventory(pAI, get_turf(src)) - pAI.throw_at_random(FALSE, 3, 1) else drop_from_inventory(pAI, loc) pAI = null diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm index cceb5ae56cb..5f5b5eb5861 100644 --- a/code/modules/mob/living/bot/cleanbot.dm +++ b/code/modules/mob/living/bot/cleanbot.dm @@ -62,7 +62,11 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th patrol_path = null target = null ignorelist = null + next_dest_loc = null + QDEL_NULL(listener) + SSradio.remove_object(listener, beacon_freq) + GLOB.janitorial_supplies -= src return ..() @@ -353,6 +357,7 @@ var/list/cleanbot_types // Going to use this to generate a list of types once th /obj/cleanbot_listener/Destroy() cleanbot = null + SSradio.remove_object_all(src) return ..() /* Assembly */ diff --git a/code/modules/mob/living/carbon/alien/diona/diona_nymph.dm b/code/modules/mob/living/carbon/alien/diona/diona_nymph.dm index 1c498b13dc8..eb69aa9ce73 100644 --- a/code/modules/mob/living/carbon/alien/diona/diona_nymph.dm +++ b/code/modules/mob/living/carbon/alien/diona/diona_nymph.dm @@ -50,6 +50,37 @@ var/can_attach = TRUE // Whether they can attach to a host +/mob/living/carbon/alien/diona/Initialize(var/mapload, var/flower_chance = 5) + if(prob(flower_chance)) + flower_color = get_random_colour(1) + . = ..(mapload) + //species = GLOB.all_species[] + ingested = new /datum/reagents/metabolism(500, src, CHEM_INGEST) + reagents = ingested + set_species(SPECIES_DIONA) + setup_dionastats() + eat_types |= TYPE_ORGANIC + nutrition = 0 //We dont start with biomass + update_verbs() + +/mob/living/carbon/alien/diona/Destroy() + cleanupTransfer() + QDEL_NULL(ingested) + QDEL_NULL(vessel) + + QDEL_NULL(DS) + gestalt = null + master_nymph = null + + hat = null + + flower_color = null + flower_image = null + cut_overlays() + + . = ..() + GC_TEMPORARY_HARDDEL + /mob/living/carbon/alien/diona/get_ingested_reagents() return ingested @@ -92,20 +123,6 @@ else ..() -/mob/living/carbon/alien/diona/Initialize(var/mapload, var/flower_chance = 5) - if(prob(flower_chance)) - flower_color = get_random_colour(1) - . = ..(mapload) - //species = GLOB.all_species[] - ingested = new /datum/reagents/metabolism(500, src, CHEM_INGEST) - reagents = ingested - set_species(SPECIES_DIONA) - setup_dionastats() - eat_types |= TYPE_ORGANIC - nutrition = 0 //We dont start with biomass - update_verbs() - - /mob/living/carbon/alien/diona/verb/check_light() set category = "Abilities" set name = "Check light level" @@ -321,14 +338,6 @@ return TRUE -/mob/living/carbon/alien/diona/Destroy() - walk_to(src, 0) - cleanupTransfer() - QDEL_NULL(ingested) - QDEL_NULL(vessel) - QDEL_NULL(DS) - . = ..() - /mob/living/carbon/alien/diona/proc/wear_hat(var/obj/item/new_hat) if(hat) return diff --git a/code/modules/mob/living/carbon/brain/brain.dm b/code/modules/mob/living/carbon/brain/brain.dm index 1baed537051..e128026ef30 100644 --- a/code/modules/mob/living/carbon/brain/brain.dm +++ b/code/modules/mob/living/carbon/brain/brain.dm @@ -21,7 +21,8 @@ death(1) //Brains can die again. AND THEY SHOULD AHA HA HA HA HA HA ghostize() //Ghostize checks for key so nothing else is necessary. container = null - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /mob/living/carbon/brain/IsAdvancedToolUser() // to be able to use weapons when piloting a hardsuit return TRUE diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index d693e9d02ed..8b8a24e926c 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -9,8 +9,6 @@ for(var/obj/item/organ/external/E in src.organs) E.droplimb(0,DROPLIMB_EDGE,1) - sleep(1) - for(var/obj/item/I in src) drop_from_inventory(I) I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)), rand(1,3), round(30/I.w_class)) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index cba73950c42..006b7ade15c 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -104,7 +104,7 @@ /mob/living/carbon/human/Destroy() GLOB.human_mob_list -= src GLOB.intent_listener -= src - QDEL_NULL_LIST(organs) + QDEL_LIST(organs) internal_organs_by_name = null internal_organs = null organs_by_name = null @@ -130,7 +130,8 @@ // Do this last so the mob's stuff doesn't drop on del. QDEL_NULL(w_uniform) - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /mob/living/carbon/human/can_devour(atom/movable/victim, var/silent = FALSE) if(!should_have_organ(BP_STOMACH)) diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm index 69bedc99de4..edeba280bb9 100644 --- a/code/modules/mob/living/carbon/human/human_powers.dm +++ b/code/modules/mob/living/carbon/human/human_powers.dm @@ -801,7 +801,7 @@ A.ex_act(2) sleep(1) - if (A && !(A.gcDestroyed) && A.type == oldtype) + if (A && !(A.gc_destroyed) && A.type == oldtype) src.visible_message("[src.name] plows into \the [aname]!") return 0 diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 1129f22ab5b..a78363e93d8 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -52,7 +52,7 @@ friends = null target_mob = null targets = null - QDEL_NULL_ASSOC(target_type_validator_map) + QDEL_LIST_ASSOC_VAL(target_type_validator_map) return ..() /mob/living/simple_animal/hostile/can_name(var/mob/living/M) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 200346c89dc..514348ffc2f 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -187,6 +187,14 @@ if(dead_on_map) death() +/mob/living/simple_animal/Destroy() + cut_overlay(blood_overlay) + movement_target = null + QDEL_NULL(udder) + + . = ..() + GC_TEMPORARY_HARDDEL + /mob/living/simple_animal/Move(NewLoc, direct) . = ..() if(.) diff --git a/code/modules/modular_computers/computers/modular_computer/core.dm b/code/modules/modular_computers/computers/modular_computer/core.dm index a8d18dafd00..7cf4309f064 100644 --- a/code/modules/modular_computers/computers/modular_computer/core.dm +++ b/code/modules/modular_computers/computers/modular_computer/core.dm @@ -112,7 +112,7 @@ for(var/datum/computer_file/program/P in hard_drive.stored_files) P.event_unregistered() - QDEL_NULL_LIST(hard_drive.stored_files) + QDEL_LIST(hard_drive.stored_files) for(var/obj/item/computer_hardware/CH in src.get_all_components()) uninstall_component(null, CH) @@ -130,8 +130,8 @@ service.service_deactivate() service.service_state = PROGRAM_STATE_KILLED - QDEL_NULL_LIST(idle_threads) - QDEL_NULL_LIST(enabled_services) + QDEL_LIST(idle_threads) + QDEL_LIST(enabled_services) if(looping_sound) soundloop.stop(src) @@ -237,6 +237,17 @@ ui_interact(user) // Re-open the UI on this computer. It should show the main screen now. update_icon() +/** + * Same as `kill_program()` but does not try to reopen the window, also makes the linter happy as it does not sleep + */ +/obj/item/modular_computer/proc/kill_program_shutdown(forced = FALSE) + SHOULD_NOT_SLEEP(TRUE) + + if(active_program && active_program.kill_program(forced)) + active_program = null + else + return FALSE + // Returns 0 for No Signal, 1 for Low Signal and 2 for Good Signal. 3 is for wired connection (always-on) /obj/item/modular_computer/proc/get_ntnet_status(var/specific_action = 0) if(network_card) @@ -251,7 +262,7 @@ /obj/item/modular_computer/proc/shutdown_computer(var/loud = TRUE) SStgui.close_uis(active_program) - kill_program(TRUE) + kill_program_shutdown(TRUE) for(var/datum/computer_file/program/P in idle_threads) P.kill_program(TRUE) idle_threads.Remove(P) diff --git a/code/modules/modular_computers/computers/subtypes/dev_handheld.dm b/code/modules/modular_computers/computers/subtypes/dev_handheld.dm index 5a1872113cb..e16016d10b9 100644 --- a/code/modules/modular_computers/computers/subtypes/dev_handheld.dm +++ b/code/modules/modular_computers/computers/subtypes/dev_handheld.dm @@ -19,6 +19,10 @@ . = ..() set_icon() +/obj/item/modular_computer/handheld/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/modular_computer/handheld/proc/set_icon() icon_state_unpowered = icon_state icon_state_broken = icon_state diff --git a/code/modules/modular_computers/computers/subtypes/dev_silicon.dm b/code/modules/modular_computers/computers/subtypes/dev_silicon.dm index 5a1e5d72639..9587a59e0de 100644 --- a/code/modules/modular_computers/computers/subtypes/dev_silicon.dm +++ b/code/modules/modular_computers/computers/subtypes/dev_silicon.dm @@ -19,11 +19,16 @@ . = computer_host /obj/item/modular_computer/silicon/Initialize(mapload) + . = ..() if(istype(loc, /mob/living/silicon)) computer_host = loc else return INITIALIZE_HINT_QDEL + +/obj/item/modular_computer/silicon/Destroy() + computer_host = null . = ..() + GC_TEMPORARY_HARDDEL /obj/item/modular_computer/silicon/computer_use_power(power_usage) // If we have host like AI, borg or pAI we handle there power @@ -38,10 +43,6 @@ // If we don't have host, then we let regular computer code handle power - like batteries and tesla coils. return ..() -/obj/item/modular_computer/silicon/Destroy() - computer_host = null - return ..() - /obj/item/modular_computer/silicon/Click(location, control, params) return attack_self(usr) diff --git a/code/modules/modular_computers/computers/subtypes/preset_laptop.dm b/code/modules/modular_computers/computers/subtypes/preset_laptop.dm index a7772ee1306..f07e1c471e8 100644 --- a/code/modules/modular_computers/computers/subtypes/preset_laptop.dm +++ b/code/modules/modular_computers/computers/subtypes/preset_laptop.dm @@ -3,6 +3,10 @@ screen_on = FALSE icon_state = "laptop-closed" +/obj/item/modular_computer/laptop/preset/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/modular_computer/laptop/preset/install_default_hardware() ..() processor_unit = new /obj/item/computer_hardware/processor_unit(src) diff --git a/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm b/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm index fa5a1b49c8a..488aa5dffa9 100644 --- a/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm +++ b/code/modules/modular_computers/computers/subtypes/preset_telescreen.dm @@ -1,3 +1,7 @@ +/obj/item/modular_computer/telescreen/preset/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/modular_computer/telescreen/preset/install_default_hardware() ..() processor_unit = new/obj/item/computer_hardware/processor_unit(src) diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index cdc67e94a88..4f5c33fa72e 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -54,6 +54,7 @@ computer.enabled_services -= src computer = null . = ..() + GC_TEMPORARY_HARDDEL /datum/computer_file/program/ui_host() if(computer) diff --git a/code/modules/modular_computers/file_system/programs/generic/records.dm b/code/modules/modular_computers/file_system/programs/generic/records.dm index 8a04d00f5ab..1773c989cca 100644 --- a/code/modules/modular_computers/file_system/programs/generic/records.dm +++ b/code/modules/modular_computers/file_system/programs/generic/records.dm @@ -31,6 +31,16 @@ "medical" = list("A-", "B-", "AB-", "O-", "A+", "B+", "AB+", "O+") ) +/datum/computer_file/program/records/New() + . = ..() + listener = new(src) + +/datum/computer_file/program/records/Destroy() + active = null + QDEL_NULL(listener) + active_virus = null + . = ..() + /datum/computer_file/program/records/medical filename = "medrec" filedesc = "Medical Records" @@ -82,22 +92,16 @@ program_key_icon_state = "lightblue_key" color = LIGHT_COLOR_BLUE +/datum/computer_file/program/records/employment/Destroy() + . = ..() + + /datum/computer_file/program/records/pai available_on_ntnet = 1 extended_desc = "This program is used to view crew records." usage_flags = PROGRAM_SILICON_PAI edit_type = 0 -/datum/computer_file/program/records/New() - . = ..() - listener = new(src) - -/datum/computer_file/program/records/Destroy() - active = null - QDEL_NULL(listener) - active_virus = null - . = ..() - /datum/computer_file/program/records/ui_data(mob/user) var/list/data = list( "activeview" = "list", diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm index f9489cd5784..44a856df9ac 100644 --- a/code/modules/modular_computers/hardware/hard_drive.dm +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -156,7 +156,7 @@ /obj/item/computer_hardware/hard_drive/Destroy() if(parent_computer?.hard_drive == src) parent_computer.hard_drive = null - QDEL_NULL_LIST(stored_files) + QDEL_LIST(stored_files) return ..() /obj/item/computer_hardware/hard_drive/Initialize(mapload) diff --git a/code/modules/modular_computers/hardware/portable_hard_drive_presets.dm b/code/modules/modular_computers/hardware/portable_hard_drive_presets.dm index fa7accf1b3f..445b2b8da70 100644 --- a/code/modules/modular_computers/hardware/portable_hard_drive_presets.dm +++ b/code/modules/modular_computers/hardware/portable_hard_drive_presets.dm @@ -2,6 +2,10 @@ . = ..() add_programs() +/obj/item/computer_hardware/hard_drive/portable/super/preset/all/Destroy() + . = ..() + GC_TEMPORARY_HARDDEL + /obj/item/computer_hardware/hard_drive/portable/super/preset/all/proc/add_programs() for(var/F in typesof(/datum/computer_file/program)) var/datum/computer_file/program/prog = new F("Compless") diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm index 3cec6ca67d2..2dca5f6ebdc 100644 --- a/code/modules/organs/organ_external.dm +++ b/code/modules/organs/organ_external.dm @@ -197,10 +197,10 @@ cached_markings = null mob_icon = null - QDEL_NULL_LIST(children) - QDEL_NULL_LIST(internal_organs) - QDEL_NULL_LIST(wounds) - QDEL_NULL_LIST(implants) + QDEL_LIST(children) + QDEL_LIST(internal_organs) + QDEL_LIST(wounds) + QDEL_LIST(implants) infect_target_internal = null infect_target_external = null diff --git a/code/modules/overmap/contacts/_contacts.dm b/code/modules/overmap/contacts/_contacts.dm index 4132c8e9a65..58a43197167 100644 --- a/code/modules/overmap/contacts/_contacts.dm +++ b/code/modules/overmap/contacts/_contacts.dm @@ -109,5 +109,5 @@ marker = null radar = null - QDEL_NULL_LIST(images) + QDEL_LIST(images) . = ..() diff --git a/code/modules/overmap/ship_weaponry/_ship_gun.dm b/code/modules/overmap/ship_weaponry/_ship_gun.dm index 0038adab44e..762c8684489 100644 --- a/code/modules/overmap/ship_weaponry/_ship_gun.dm +++ b/code/modules/overmap/ship_weaponry/_ship_gun.dm @@ -49,6 +49,7 @@ destroy_dummies() ammunition.Cut() barrel = null + LAZYREMOVE(linked?.ship_weapons, src) return ..() /obj/machinery/ship_weapon/ex_act(severity) diff --git a/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm b/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm index 7bd6b9f98d0..9368412b9d7 100644 --- a/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm +++ b/code/modules/overmap/ship_weaponry/weaponry/leviathan.dm @@ -288,6 +288,27 @@ ..() return INITIALIZE_HINT_LATELOAD +/obj/machinery/leviathan_safeguard/LateInitialize() + if(current_map.use_overmap && !linked) + var/my_sector = GLOB.map_sectors["[z]"] + if (istype(my_sector, /obj/effect/overmap/visitable)) + attempt_hook_up(my_sector) + if(linked) + ASSERT(isnull(linked.levi_safeguard)) //There should only ever be one + linked.levi_safeguard = src + for(var/obj/machinery/leviathan_button/LB in range(3, src)) + if(istype(LB)) + button = LB + +/obj/machinery/leviathan_safeguard/Destroy() + if(linked) + linked.levi_safeguard = null + + QDEL_NULL(key) + button = null + + . = ..() + /obj/machinery/leviathan_safeguard/ex_act(severity) return @@ -296,22 +317,6 @@ return -/obj/machinery/leviathan_safeguard/Destroy() - QDEL_NULL(key) - button = null - return ..() - -/obj/machinery/leviathan_safeguard/LateInitialize() - if(current_map.use_overmap && !linked) - var/my_sector = GLOB.map_sectors["[z]"] - if (istype(my_sector, /obj/effect/overmap/visitable)) - attempt_hook_up(my_sector) - if(linked) - linked.levi_safeguard = src - for(var/obj/machinery/leviathan_button/LB in range(3, src)) - if(istype(LB)) - button = LB - /obj/machinery/leviathan_safeguard/proc/open() opened = TRUE flick("safeguard_opening", src) diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index 9e7aa598ba7..3e14216bf84 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -40,6 +40,12 @@ var/list/admin_departments alldepartments |= department destination = current_map.boss_name +/obj/machinery/photocopier/faxmachine/Destroy() + allfaxes -= src + QDEL_NULL(identification) + + . = ..() + /obj/machinery/photocopier/faxmachine/ui_data(mob/user) var/list/data = list() data["destination"] = destination diff --git a/code/modules/power/fusion/core/core_field.dm b/code/modules/power/fusion/core/core_field.dm index d02b8b49ec4..472c079a092 100644 --- a/code/modules/power/fusion/core/core_field.dm +++ b/code/modules/power/fusion/core/core_field.dm @@ -574,7 +574,7 @@ /obj/effect/fusion_em_field/Destroy() set_light(0) RadiateAll() - QDEL_NULL_LIST(particle_catchers) + QDEL_LIST(particle_catchers) QDEL_NULL(radio) if(owned_core) owned_core.owned_field = null diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index 5022c535edd..91da77cfec4 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -133,7 +133,7 @@ update_icon() /obj/item/ammo_magazine/Destroy() - QDEL_NULL_LIST(stored_ammo) + QDEL_LIST(stored_ammo) . = ..() /obj/item/ammo_magazine/attackby(obj/item/W as obj, mob/user as mob) diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm index e03563106b4..aeadfe86562 100644 --- a/code/modules/projectiles/guns/projectile.dm +++ b/code/modules/projectiles/guns/projectile.dm @@ -51,7 +51,7 @@ /obj/item/gun/projectile/Destroy() chambered = null QDEL_NULL(ammo_magazine) - QDEL_NULL_LIST(loaded) + QDEL_LIST(loaded) . = ..() /obj/item/gun/projectile/update_icon() diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm index bc2105f5389..9bc5803f4ea 100644 --- a/code/modules/projectiles/guns/projectile/automatic.dm +++ b/code/modules/projectiles/guns/projectile/automatic.dm @@ -371,6 +371,11 @@ . = ..() launcher = new(src) +/obj/item/gun/projectile/automatic/rifle/z8/Destroy() + QDEL_NULL(launcher) + + . = ..() + /obj/item/gun/projectile/automatic/rifle/z8/attackby(obj/item/I, mob/user) if((istype(I, /obj/item/grenade))) launcher.load(I, user) diff --git a/code/modules/projectiles/guns/projectile/revolver.dm b/code/modules/projectiles/guns/projectile/revolver.dm index ca88856f851..371ddcaba3c 100644 --- a/code/modules/projectiles/guns/projectile/revolver.dm +++ b/code/modules/projectiles/guns/projectile/revolver.dm @@ -191,6 +191,12 @@ for(var/i in 1 to secondary_max_shells) secondary_loaded += new secondary_ammo_type(src) +/obj/item/gun/projectile/revolver/lemat/Destroy() + QDEL_LIST(secondary_loaded) + QDEL_LIST(tertiary_loaded) + + . = ..() + /obj/item/gun/projectile/revolver/lemat/unique_action(mob/living/user) to_chat(user, "You change the firing mode on \the [src].") if(!flipped_firing) diff --git a/code/modules/projectiles/targeting/targeting_mob.dm b/code/modules/projectiles/targeting/targeting_mob.dm index 4f99daea250..4248e6230a1 100644 --- a/code/modules/projectiles/targeting/targeting_mob.dm +++ b/code/modules/projectiles/targeting/targeting_mob.dm @@ -45,7 +45,7 @@ qdel(aiming) aiming = null - QDEL_NULL_LIST(aimed_at_by) + QDEL_LIST(aimed_at_by) if(vr_mob) vr_mob = null diff --git a/code/modules/reagents/reagent_containers/food/sandwich.dm b/code/modules/reagents/reagent_containers/food/sandwich.dm index b9fe4c7c735..76a1f372e65 100644 --- a/code/modules/reagents/reagent_containers/food/sandwich.dm +++ b/code/modules/reagents/reagent_containers/food/sandwich.dm @@ -66,7 +66,7 @@ w_class = n_ceil(Clamp((ingredients.len/2),2,4)) /obj/item/reagent_containers/food/snacks/csandwich/Destroy() - QDEL_NULL_LIST(ingredients) + QDEL_LIST(ingredients) return ..() /obj/item/reagent_containers/food/snacks/csandwich/examine(mob/user) diff --git a/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm b/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm index 8db2a7d44b7..c66468cc8be 100644 --- a/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm +++ b/code/modules/research/xenoarchaeology/artifact/artifact_unknown.dm @@ -52,6 +52,12 @@ if(prob(75)) my_effect.trigger = rand(1,4) +/obj/machinery/artifact/Destroy() + QDEL_NULL(my_effect) + QDEL_NULL(secondary_effect) + + . = ..() + /obj/machinery/artifact/process() var/turf/L = loc diff --git a/code/modules/research/xenoarchaeology/artifact/effect.dm b/code/modules/research/xenoarchaeology/artifact/effect.dm index e23a8829ea1..cb064636c55 100644 --- a/code/modules/research/xenoarchaeology/artifact/effect.dm +++ b/code/modules/research/xenoarchaeology/artifact/effect.dm @@ -45,6 +45,11 @@ chargelevelmax = rand(20, 60) effectrange = rand(15, 20) +/datum/artifact_effect/Destroy(force) + holder = null + + . = ..() + /datum/artifact_effect/proc/ToggleActivate(var/reveal_toggle = 1) //so that other stuff happens first spawn(0) diff --git a/code/modules/shareddream/dream_entry.dm b/code/modules/shareddream/dream_entry.dm index 6250b748db1..9d05bbefde8 100644 --- a/code/modules/shareddream/dream_entry.dm +++ b/code/modules/shareddream/dream_entry.dm @@ -11,7 +11,8 @@ var/list/dream_entries = list() srom_pulled_by = null srom_pulling = null bg = null //Just to be sure. - return ..() + . = ..() + GC_TEMPORARY_HARDDEL /mob/living/carbon/human/proc/handle_shared_dreaming(var/force_wakeup = FALSE) // If they're an Unconsious person with the abillity to do Skrellepathy. diff --git a/code/modules/shuttles/landmarks.dm b/code/modules/shuttles/landmarks.dm index 71dcb9cde69..6853872b4b8 100644 --- a/code/modules/shuttles/landmarks.dm +++ b/code/modules/shuttles/landmarks.dm @@ -87,7 +87,7 @@ landing_indicators += new /obj/effect/shuttle_warning(target_turf) /obj/effect/shuttle_landmark/proc/clear_landing_indicators() - QDEL_NULL_LIST(landing_indicators) // lazyclear but we delete the effects as well + QDEL_LIST(landing_indicators) // lazyclear but we delete the effects as well /obj/effect/shuttle_landmark/proc/cannot_depart(datum/shuttle/shuttle) return FALSE diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index 694b470ac06..1875ee0f48e 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -182,3 +182,7 @@ var/head_reattach = 0 var/current_organ = "organ" var/list/in_progress = list() + +/datum/surgery_status/Destroy(force) + in_progress = null + . = ..() diff --git a/code/modules/synthesized_instruments/event_manager.dm b/code/modules/synthesized_instruments/event_manager.dm index 060e0c640db..ad01d54c087 100644 --- a/code/modules/synthesized_instruments/event_manager.dm +++ b/code/modules/synthesized_instruments/event_manager.dm @@ -46,7 +46,7 @@ /datum/musical_event_manager/Destroy(force) . = ..() deactivate() - QDEL_NULL_LIST(events) + QDEL_LIST(events) /datum/musical_event_manager/proc/push_event(datum/sound_player/source, datum/sound_token/token, time, volume) if (istype(source) && istype(token) && volume >= 0 && volume <= 100) diff --git a/code/unit_tests/create_and_destroy.dm b/code/unit_tests/create_and_destroy.dm index 05a98f91bbd..386b698975e 100644 --- a/code/unit_tests/create_and_destroy.dm +++ b/code/unit_tests/create_and_destroy.dm @@ -4,7 +4,7 @@ groups = list("create and destroy") var/result = null -// var/datum/running_create_and_destroy = FALSE +GLOBAL_VAR_INIT(running_create_and_destroy, FALSE) /datum/unit_test/create_and_destroy/start_test() //We'll spawn everything here var/turf/spawn_at = locate() @@ -177,7 +177,7 @@ var/original_baseturf = islist(spawn_at.baseturf) ? spawn_at.baseturf:Copy() : spawn_at.baseturf var/original_baseturf_count = length(original_baseturf) - // /datum/running_create_and_destroy = TRUE + GLOB.running_create_and_destroy = TRUE for(var/type_path in typesof(/atom/movable, /turf) - ignore) //No areas please TEST_DEBUG("[name]: now creating and destroying: [type_path]") @@ -188,7 +188,7 @@ spawn_at.ChangeTurf(original_turf_type) if(original_baseturf_count != length(spawn_at.baseturf)) TEST_FAIL("[type_path] changed the amount of baseturfs from [original_baseturf_count] to [length(spawn_at.baseturf)]; [english_list(original_baseturf)] to [islist(spawn_at.baseturf) ? english_list(spawn_at.baseturf) : spawn_at.baseturf]") - // //Warn if it changes again + //Warn if it changes again original_baseturf = islist(spawn_at.baseturf) ? spawn_at.baseturf:Copy() : spawn_at.baseturf original_baseturf_count = length(original_baseturf) else @@ -207,36 +207,54 @@ for(var/atom/to_kill in to_del) qdel(to_kill) + GLOB.running_create_and_destroy = FALSE + //Hell code, we're bound to have ended the round somehow so let's stop if from ending while we work SSticker.delay_end = TRUE + + // Drastically lower the amount of time it takes to GC, since we don't have clients that can hold it up. + SSgarbage.collection_timeout[GC_QUEUE_CHECK] = 10 SECONDS //Clear it, just in case cached_contents.Cut() + var/list/queues_we_care_about = list() + // All of em, I want hard deletes too, since we rely on the debug info from them + for(var/i in 1 to GC_QUEUE_HARDDELETE) + queues_we_care_about += i + //Now that we've qdel'd everything, let's sleep until the gc has processed all the shit we care about - var/time_needed = SSgarbage.collection_timeout + // + 2 seconds to ensure that everything gets in the queue. + var/time_needed = 2 SECONDS + for(var/index in queues_we_care_about) + time_needed += SSgarbage.collection_timeout[index] + var/start_time = world.time + var/real_start_time = REALTIMEOFDAY var/garbage_queue_processed = FALSE sleep(time_needed) while(!garbage_queue_processed) - var/list/queue_to_check = SSgarbage.queue - //How the hell did you manage to empty this? Good job! - if(!length(queue_to_check)) - garbage_queue_processed = TRUE - break + var/oldest_packet_creation = INFINITY + for(var/index in queues_we_care_about) + var/list/queue_to_check = SSgarbage.queues[index] + if(!length(queue_to_check)) + continue + + var/list/oldest_packet = queue_to_check[1] + //Pull out the time we inserted at + var/qdeld_at = oldest_packet[GC_QUEUE_ITEM_GCD_DESTROYED] + + oldest_packet_creation = min(qdeld_at, oldest_packet_creation) - //Pull out the time we deld at - var/qdeld_at = SSgarbage.queue[queue_to_check[1]] //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) + //That said, if there are any pending hard deletes you may NOT sleep, we gotta handle that shit + if(oldest_packet_creation > start_time && !length(SSgarbage.queues[GC_QUEUE_HARDDELETE])) garbage_queue_processed = TRUE break - #if !defined(REFERENCE_TRACKING) - if(world.time > start_time + time_needed + 30 MINUTES) //If this gets us gitbanned I'm going to laugh so hard - result = TEST_FAIL("Something has gone horribly wrong, the garbage queue has been processing for well over 30 minutes. What the hell did you do") + if(REALTIMEOFDAY > real_start_time + time_needed + 50 MINUTES) //If this gets us gitbanned I'm going to laugh so hard + TEST_FAIL("Something has gone horribly wrong, the garbage queue has been processing for well over 30 minutes. What the hell did you do") break - #endif //Immediately fire the gc right after SSgarbage.next_fire = 1 @@ -244,11 +262,18 @@ sleep(20 SECONDS) //Alright, time to see if anything messed up - var/list/cache_for_sonic_speed = SSgarbage.didntgc - for(var/path in typesof(/atom/movable, /turf) - ignore) - var/times = cache_for_sonic_speed[path] - if(times) - result = TEST_FAIL("[path] hard deleted [times] times.") + var/list/cache_for_sonic_speed = SSgarbage.items + for(var/path in cache_for_sonic_speed) + var/datum/qdel_item/item = cache_for_sonic_speed[path] + if(item.failures) + result = TEST_FAIL("[item.name] hard deleted [item.failures] times out of a total del count of [item.qdels]") + if(item.no_respect_force) + result = 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) + result = 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") + result = TEST_FAIL("[item.name] failed with extra info: \n[details]") cache_for_sonic_speed = SSatoms.BadInitializeCalls for(var/path in cache_for_sonic_speed) @@ -262,6 +287,6 @@ SSticker.delay_end = FALSE //This shouldn't be needed, but let's be polite - SSgarbage.collection_timeout = initial(SSgarbage.collection_timeout) + SSgarbage.collection_timeout[GC_QUEUE_CHECK] = GC_CHECK_QUEUE return result ? result : TEST_PASS("All paths are created and destroyed successfully, without hard deletions or other unwanted behaviors") diff --git a/code/unit_tests/ss_test.dm b/code/unit_tests/ss_test.dm index acc3be32c05..7553cd69008 100644 --- a/code/unit_tests/ss_test.dm +++ b/code/unit_tests/ss_test.dm @@ -32,12 +32,8 @@ SUBSYSTEM_DEF(unit_tests_config) var/retries = 0 /datum/controller/subsystem/unit_tests_config/PreInit() - . = ..() - UT = new - world.fps = 10 - //Acquire our identifier, or enter Hopper mode if failing to do so try src.identifier = rustg_file_read("config/unit_test/identifier.txt") diff --git a/html/changelogs/fluffyghost-updatessgarbage.yml b/html/changelogs/fluffyghost-updatessgarbage.yml new file mode 100644 index 00000000000..b65f143d131 --- /dev/null +++ b/html/changelogs/fluffyghost-updatessgarbage.yml @@ -0,0 +1,42 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: FluffyGhost + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - backend: "Updated SSgarbage to the latest TG version." + - rscdel: "Dropped compatibility to build on the 514 branch."