Updated SSgarbage (#18173)

* pain

* sadfas

* sdfa

* sdfasf

* sfa

* sdf

* might the lord have mercy on our soul

* i cri everidai

* adeste fideles

* sdf

* where will this lead, what's coming next, from your inventions

* dear lord

* gjvhk

* i cri everidai

* fsgf

* sdfa

* sdaf

* hiuhi
This commit is contained in:
Fluffy
2024-01-15 14:08:27 +00:00
committed by GitHub
parent 0c8bdbdd68
commit ca6b04e1d7
125 changed files with 1574 additions and 805 deletions
+6 -2
View File
@@ -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"
+15
View File
@@ -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)
-3
View File
@@ -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) ("<span class='[class]'>" + text + "</span>")
#define SPAN_NOTICE(X) ("<span class='notice'>" + X + "</span>")
+3 -4
View File
@@ -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")()
. = ..()
+22 -52
View File
@@ -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)
-16
View File
@@ -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
@@ -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"
+2 -2
View File
@@ -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
-7
View File
@@ -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
-3
View File
@@ -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)
+2 -2
View File
@@ -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
+78 -26
View File
@@ -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(); }
+6 -1
View File
@@ -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.
+9
View File
@@ -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 */
+8
View File
@@ -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)
+1 -1
View File
@@ -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]")
-3
View File
@@ -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)
+2
View File
@@ -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]
-4
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
///the underscores are to encourage people not to use this directly.
/proc/______qdel_list_wrapper(list/L)
QDEL_LIST(L)
+9
View File
@@ -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
+12 -3
View File
@@ -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
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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))
+2 -1
View File
@@ -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
+1 -1
View File
@@ -86,8 +86,8 @@
var/obj/item/owner
/obj/screen/item_action/Destroy()
. = ..()
owner = null
. = ..()
/obj/screen/item_action/Click()
if(!usr || !owner)
+4 -2
View File
@@ -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)
+8 -1
View File
@@ -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()
+344 -389
View File
@@ -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: -- <a href='?_src_=vars;Vars=\ref[D]'>VV</a> | [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 = "<B>List of things that have been qdel()eted this round</B><BR><BR>"
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<BR>"
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)
+12
View File
@@ -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)
@@ -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
+50 -24
View File
@@ -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])" : ""]"
@@ -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]
-3
View File
@@ -1,4 +1 @@
/****************
* Debug Support *
****************/
GLOBAL_LIST_EMPTY(all_observable_events)
+71
View File
@@ -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
-17
View File
@@ -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
+95
View File
@@ -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
@@ -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
+23 -17
View File
@@ -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)
@@ -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)
+2
View File
@@ -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
+6 -2
View File
@@ -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()
+2 -1
View File
@@ -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)
@@ -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)
+2 -3
View File
@@ -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()
+2 -2
View File
@@ -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)
@@ -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"
@@ -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
+10 -3
View File
@@ -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)
@@ -215,7 +215,7 @@
+ "</body></html>", "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
@@ -15,6 +15,10 @@
..()
return
/obj/effect/expl_particles/Destroy()
. = ..()
GC_TEMPORARY_HARDDEL
/datum/effect/system/expl_particles
var/number = 10
var/turf/location
+2 -1
View File
@@ -92,7 +92,8 @@
/obj/effect/landmark/Destroy()
GLOB.landmarks_list -= src
return ..()
. = ..()
GC_TEMPORARY_HARDDEL
/obj/effect/landmark/start
name = "start"
-1
View File
@@ -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 ..()
@@ -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, "<span class='danger'>You feel a sharp shock!</span>")
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))
@@ -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)
@@ -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)
+4
View File
@@ -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
+4 -1
View File
@@ -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)
@@ -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
@@ -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()
. = {"
<b>Implant Specifications:</b><BR>
@@ -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")
@@ -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))
@@ -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)
+3 -3
View File
@@ -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
+3
View File
@@ -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,
+70 -11
View File
@@ -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 = "<B>List of things that failed to GC this round</B><BR><BR>"
for(var/path in SSgarbage.didntgc)
dat += "[path] - [SSgarbage.didntgc[path]] times<BR>"
var/list/dellog = list("<B>List of things that have gone through qdel this round</B><BR><BR><ol>")
sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
for(var/path in SSgarbage.items)
var/datum/qdel_item/I = SSgarbage.items[path]
dellog += "<li><u>[path]</u><ul>"
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
dellog += "<li>SUSPENDED FOR LAG</li>"
if (I.failures)
dellog += "<li>Failures: [I.failures]</li>"
dellog += "<li>qdel() Count: [I.qdels]</li>"
dellog += "<li>Destroy() Cost: [I.destroy_time]ms</li>"
if (I.hard_deletes)
dellog += "<li>Total Hard Deletes [I.hard_deletes]</li>"
dellog += "<li>Time Spent Hard Deleting: [I.hard_delete_time]ms</li>"
dellog += "<li>Highest Time Spent Hard Deleting: [I.hard_delete_max]ms</li>"
if (I.hard_deletes_over_threshold)
dellog += "<li>Hard Deletes Over Threshold: [I.hard_deletes_over_threshold]</li>"
if (I.slept_destroy)
dellog += "<li>Sleeps: [I.slept_destroy]</li>"
if (I.no_respect_force)
dellog += "<li>Ignored force: [I.no_respect_force]</li>"
if (I.no_hint)
dellog += "<li>No hint: [I.no_hint]</li>"
if(LAZYLEN(I.extra_details))
var/details = I.extra_details.Join("</li><li>")
dellog += "<li>Extra Info: <ul><li>[details]</li></ul>"
dellog += "</ul></li>"
dat += "<B>List of paths that did not return a qdel hint in Destroy()</B><BR><BR>"
for(var/path in SSgarbage.noqdelhint)
dat += "[path]<BR>"
dellog += "</ol>"
dat += "<B>List of paths that slept in Destroy()</B><BR><BR>"
for(var/path in SSgarbage.sleptDestroy)
dat += "[path]<BR>"
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("<B>List of things that have harddel'd this round</B><BR><BR><ol>")
sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
for(var/path in SSgarbage.items)
var/datum/qdel_item/I = SSgarbage.items[path]
if(I.hard_deletes)
dellog += "<li><u>[path]</u><ul>"
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
dellog += "<li>SUSPENDED FOR LAG</li>"
if (I.failures)
dellog += "<li>Failures: [I.failures]</li>"
dellog += "<li>qdel() Count: [I.qdels]</li>"
dellog += "<li>Destroy() Cost: [I.destroy_time]ms</li>"
if (I.hard_deletes)
dellog += "<li>Total Hard Deletes [I.hard_deletes]</li>"
dellog += "<li>Time Spent Hard Deleting: [I.hard_delete_time]ms</li>"
dellog += "<li>Highest Time Spent Hard Deleting: [I.hard_delete_max]ms</li>"
if (I.hard_deletes_over_threshold)
dellog += "<li>Hard Deletes Over Threshold: [I.hard_deletes_over_threshold]</li>"
if (I.slept_destroy)
dellog += "<li>Sleeps: [I.slept_destroy]</li>"
if (I.no_respect_force)
dellog += "<li>Ignored force: [I.no_respect_force]</li>"
if (I.no_hint)
dellog += "<li>No hint: [I.no_hint]</li>"
if(LAZYLEN(I.extra_details))
var/details = I.extra_details.Join("</li><li>")
dellog += "<li>Extra Info: <ul><li>[details]</li></ul>"
dellog += "</ul></li>"
dellog += "</ol>"
usr << browse(dellog.Join(), "window=harddellog")
/client/proc/cmd_display_init_log()
set category = "Debug"
@@ -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
+9
View File
@@ -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
+5 -2
View File
@@ -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
+13 -11
View File
@@ -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)
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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.
@@ -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
@@ -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
@@ -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
+1 -1
View File
@@ -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()
@@ -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)
@@ -183,4 +183,5 @@
/mob/living/carbon/human/Destroy()
ghost_spawner = null
return ..()
. = ..()
GC_TEMPORARY_HARDDEL
@@ -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, "<span class='notice'>You remove \the [system] in \the [src]'s [system_hardpoint].</span>")
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, "<span class='notice'>You remove \the [system] in \the [src]'s [system_hardpoint].</span>")
playsound(user.loc, 'sound/items/Screwdriver.ogg', 100, 1)
return system
@@ -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, "<span class='warning'>\The [src] has no hardpoint systems to remove.</span>")
return
+3 -2
View File
@@ -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)
@@ -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()
@@ -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
@@ -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)
+2
View File
@@ -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.
-1
View File
@@ -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
+5
View File
@@ -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 */
@@ -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
@@ -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
@@ -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))
@@ -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))
@@ -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("<span class='danger'>[src.name] plows into \the [aname]!</span>")
return 0
@@ -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)
@@ -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(.)
@@ -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)
@@ -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
@@ -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)
@@ -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)
@@ -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)

Some files were not shown because too many files have changed in this diff Show More