There were [station_vault] credits collected by crew this shift.
"
+ parts += "
General Statistics:"
+ parts += "There were [station_vault] credits collected by crew this shift.
"
if(total_players > 0)
parts += "An average of [station_vault/total_players] credits were collected.
"
// log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]")
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 64b4129024..1aca8959c2 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -1,7 +1,7 @@
-//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this
+//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this
//uncommented, but not visible in the release version)
-//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed.
+//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed.
// Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error
#ifdef DEBUG
@@ -11,34 +11,44 @@
#ifdef TESTING
#define DATUMVAR_DEBUGGING_MODE
-/*
-* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete.
-*
-* * Requires TESTING to be defined to work.
-*/
+///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
+#ifdef REFERENCE_TRACKING
-///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools.
-//#define LEGACY_REFERENCE_TRACKING
-#ifdef LEGACY_REFERENCE_TRACKING
+///alternate to reftracking, extool variant
+//#define EXTOOLS_REFERENCE_TRACKING
-///Use the legacy reference on things hard deleting by default.
+///Should we be logging our findings or not
+#define REFERENCE_TRACKING_LOG
+
+///Used for doing dry runs of the reference finder, to test for feature completeness
+//#define REFERENCE_TRACKING_DEBUG
+
+///Run a lookup on things hard deleting by default.
//#define GC_FAILURE_HARD_LOOKUP
#ifdef GC_FAILURE_HARD_LOOKUP
#define FIND_REF_NO_CHECK_TICK
#endif //ifdef GC_FAILURE_HARD_LOOKUP
-#endif //ifdef LEGACY_REFERENCE_TRACKING
+#endif //ifdef REFERENCE_TRACKING
-//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
+/*
+* Enables debug messages for every single reaction step. This is 1 message per 0.5s for a SINGLE reaction. Useful for tracking down bugs/asking me for help in the main reaction handiler (equilibrium.dm).
+*
+* * Requires TESTING to be defined to work.
+*/
+//#define REAGENTS_TESTING
+// #define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
+// #define TRACK_MAX_SHARE //Allows max share tracking, for use in the atmos debugging ui
#endif //ifdef TESTING
-//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER
-#ifndef PRELOAD_RSC //set to:
-#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
-#endif // 1 to use the default behaviour;
- // 2 for preloading absolutely everything;
+//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
+
+#ifndef PRELOAD_RSC //set to:
+#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
+#endif // 1 to use the default behaviour;
+ // 2 for preloading absolutely everything;
#ifdef LOWMEMORYMODE
#define FORCE_MAP "_maps/runtimestation.json"
@@ -47,7 +57,7 @@
//Update this whenever you need to take advantage of more recent byond features
#define MIN_COMPILER_VERSION 513
#define MIN_COMPILER_BUILD 1514
-#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD
+#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM)
//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 513.1514 or higher
@@ -58,10 +68,6 @@
#warn compiling in TESTING mode. testing() debug messages will be visible.
#endif
-#ifdef GC_FAILURE_HARD_LOOKUP
-#define FIND_REF_NO_CHECK_TICK
-#endif
-
#ifdef CIBUILDING
#define UNIT_TESTS
#endif
@@ -70,6 +76,24 @@
#define TESTING
#endif
+#if defined(UNIT_TESTS)
+//Hard del testing defines
+#define REFERENCE_TRACKING
+#define REFERENCE_TRACKING_DEBUG
+#define FIND_REF_NO_CHECK_TICK
+#endif
+
+#ifdef TGS
+// TGS performs its own build of dm.exe, but includes a prepended TGS define.
+#define CBT
+#endif
+
// A reasonable number of maximum overlays an object needs
// If you think you need more, rethink it
#define MAX_ATOM_OVERLAYS 100
+
+#if !defined(CBT) && !defined(SPACEMAN_DMM)
+#warn Building with Dream Maker is no longer supported and will result in errors.
+#warn In order to build, run BUILD.bat in the root directory.
+#warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build.
+#endif
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index cf3f45e196..743d75b557 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -43,7 +43,7 @@
INVOKE_ASYNC(aicamera, /obj/item/camera.proc/captureimage, A, usr)
return
- var/obj/item/W = get_active_held_item()
+ var/obj/item/W = get_active_held_item(TRUE)
if(!W && A.Adjacent(src) && (isobj(A) || ismob(A)))
var/atom/movable/C = A
diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm
index 19fef28597..5c767ecb1b 100644
--- a/code/controllers/admin.dm
+++ b/code/controllers/admin.dm
@@ -1,14 +1,25 @@
// Clickable stat() button.
/obj/effect/statclick
name = "Initializing..."
+ // blocks_emissive = NONE
var/target
INITIALIZE_IMMEDIATE(/obj/effect/statclick)
-/obj/effect/statclick/Initialize(mapload, text, target) //Don't port this to Initialize it's too critical
+/obj/effect/statclick/Initialize(mapload, text, target)
. = ..()
name = text
src.target = target
+ if(istype(target, /datum)) //Harddel man bad
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/cleanup)
+
+/obj/effect/statclick/Destroy()
+ target = null
+ return ..()
+
+/obj/effect/statclick/proc/cleanup()
+ SIGNAL_HANDLER
+ qdel(src)
/obj/effect/statclick/proc/update(text)
name = text
@@ -51,3 +62,30 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller")
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
+
+/client/proc/debug_controller()
+ set category = "Debug"
+ set name = "Debug Controller"
+ set desc = "Debug the various periodic loop controllers for the game (be careful!)"
+
+ if(!holder)
+ return
+
+ var/list/controllers = list()
+ var/list/controller_choices = list()
+
+ for (var/datum/controller/controller in world)
+ if (istype(controller, /datum/controller/subsystem))
+ continue
+ controllers["[controller] (controller.type)"] = controller //we use an associated list to ensure clients can't hold references to controllers
+ controller_choices += "[controller] (controller.type)"
+
+ var/datum/controller/controller_string = input("Select controller to debug", "Debug Controller") as null|anything in controller_choices
+ var/datum/controller/controller = controllers[controller_string]
+
+ if (!istype(controller))
+ return
+ debug_variables(controller)
+
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller")
+ message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
diff --git a/code/controllers/configuration/entries/admin.dm b/code/controllers/configuration/entries/admin.dm
index 3b5b437742..1f9b5d460f 100644
--- a/code/controllers/configuration/entries/admin.dm
+++ b/code/controllers/configuration/entries/admin.dm
@@ -39,7 +39,9 @@
/datum/config_entry/flag/announce_admin_login
-/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
+/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
+
+/datum/config_entry/string/centcom_source_whitelist
/datum/config_entry/flag/autoadmin // if autoadmin is enabled
protection = CONFIG_ENTRY_LOCKED
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 8ecd7c0c60..d828457fa7 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -333,3 +333,12 @@
config_entry_value = 0.333
min_val = 0
integer = FALSE
+
+/datum/config_entry/number/hard_deletes_overrun_threshold
+ integer = FALSE
+ min_val = 0
+ default = 0.5
+
+/datum/config_entry/number/hard_deletes_overrun_limit
+ default = 0
+ min_val = 0
diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm
index a15056e442..04b88eb3be 100644
--- a/code/controllers/failsafe.dm
+++ b/code/controllers/failsafe.dm
@@ -15,7 +15,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
// The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC.
var/defcon = 5
//the world.time of the last check, so the mc can restart US if we hang.
- // (Real friends look out for *eachother*)
+ // (Real friends look out for *eachother*)
var/lasttick = 0
// Track the MC iteration to make sure its still on track.
@@ -33,6 +33,22 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/datum/controller/failsafe/Initialize()
set waitfor = FALSE
Failsafe.Loop()
+ if (!Master || defcon == 0) //Master is gone/not responding and Failsafe just exited its loop
+ defcon = 3 //Reset defcon level as its used inside the emergency loop
+ while (defcon > 0)
+ var/recovery_result = emergency_loop()
+ if (recovery_result == 1) //Exit emergency loop and delete self if it was able to recover MC
+ break
+ else if (defcon == 1) //Exit Failsafe if we weren't able to recover the MC in the last stage
+ log_game("FailSafe: Failed to recover MC while in emergency state. Failsafe exiting.")
+ message_admins(span_boldannounce("Failsafe failed criticaly while trying to recreate broken MC. Please manually fix the MC or reboot the server. Failsafe exiting now."))
+ message_admins(span_boldannounce("You can try manually calling these two procs:."))
+ message_admins(span_boldannounce("/proc/recover_all_SS_and_recreate_master: Most stuff should still function but expect instability/runtimes/broken stuff."))
+ message_admins(span_boldannounce("/proc/delete_all_SS_and_recreate_master: Most stuff will be broken but basic stuff like movement and chat should still work."))
+ else if (recovery_result == -1) //Failed to recreate MC
+ defcon--
+ sleep(initial(processing_interval)) //Wait a bit until the next try
+
if(!QDELETED(src))
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
@@ -45,42 +61,56 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
while(running)
lasttick = world.time
if(!Master)
- // Replace the missing Master! This should never, ever happen.
- new /datum/controller/master()
+ // Break out of the main loop so we go into emergency state
+ break
// Only poke it if overrides are not in effect.
if(processing_interval > 0)
if(Master.processing && Master.iteration)
+ if (defcon > 1 && (!Master.stack_end_detector || !Master.stack_end_detector.check()))
+
+ to_chat(GLOB.admins, span_boldannounce("ERROR: The Master Controller code stack has exited unexpectedly, Restarting..."))
+ defcon = 0
+ var/rtn = Recreate_MC()
+ if(rtn > 0)
+ master_iteration = 0
+ to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
+ else if(rtn < 0)
+ log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
+ to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
// Check if processing is done yet.
if(Master.iteration == master_iteration)
switch(defcon)
if(4,5)
--defcon
- if(3)
- message_admins("
Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.")
- --defcon
- if(2)
- to_chat(GLOB.admins, "
Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.")
- --defcon
- if(1)
- to_chat(GLOB.admins, "
Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...")
+ if(3)
+ message_admins(span_adminnotice("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks."))
+ --defcon
+
+ if(2)
+ to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks."))
+ --defcon
+
+ if(1)
+ to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting..."))
--defcon
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
- to_chat(GLOB.admins, "
MC restarted successfully")
+ to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
- to_chat(GLOB.admins, "
ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.")
+ to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
//if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again
//no need to handle that specially when defcon 0 can handle it
+
if(0) //DEFCON 0! (mc failed to restart)
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
- to_chat(GLOB.admins, "
MC restarted successfully")
+ to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
else
defcon = min(defcon + 1,5)
master_iteration = Master.iteration
@@ -92,6 +122,57 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
defcon = 5
sleep(initial(processing_interval))
+//Emergency loop used when Master got deleted or the main loop exited while Defcon == 0
+//Loop is driven externally so runtimes only cancel the current recovery attempt
+/datum/controller/failsafe/proc/emergency_loop()
+ //The code in this proc should be kept as simple as possible, anything complicated like to_chat might rely on master existing and runtime
+ //The goal should always be to get a new Master up and running before anything else
+ . = -1
+ switch (defcon) //The lower defcon goes the harder we try to fix the MC
+ if (2 to 3) //Try to normally recreate the MC two times
+ . = Recreate_MC()
+ if (1) //Delete the old MC first so we don't transfer any info, in case that caused any issues
+ del(Master)
+ . = Recreate_MC()
+
+ if (. == 1) //We were able to create a new master
+ master_iteration = 0
+ SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
+ Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
+ to_chat(GLOB.admins, span_adminnotice("Failsafe recovered MC while in emergency state [defcon_pretty()]"))
+ else
+ log_game("FailSafe: Failsafe in emergency state and was unable to recreate MC while in defcon state [defcon_pretty()].")
+ message_admins(span_boldannounce("Failsafe in emergency state and master down, trying to recreate MC while in defcon level [defcon_pretty()] failed."))
+
+///Recreate all SSs which will still cause data survive due to Recover(), the new Master will then find and take them from global.vars
+/proc/recover_all_SS_and_recreate_master()
+ del(Master)
+ var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
+ sortTim(subsytem_types, /proc/cmp_subsystem_init)
+ for(var/I in subsytem_types)
+ new I
+ . = Recreate_MC()
+ if (. == 1) //We were able to create a new master
+ SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
+ Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
+ to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after recovering all subsystems!"))
+ else
+ message_admins(span_boldannounce("Failed to create new MC!"))
+
+///Delete all existing SS to basically start over
+/proc/delete_all_SS_and_recreate_master()
+ del(Master)
+ for(var/global_var in global.vars)
+ if (istype(global.vars[global_var], /datum/controller/subsystem))
+ del(global.vars[global_var])
+ . = Recreate_MC()
+ if (. == 1) //We were able to create a new master
+ SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set
+ Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this
+ to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after deleting and recreating all subsystems!"))
+ else
+ message_admins(span_boldannounce("Failed to create new MC!"))
+
/datum/controller/failsafe/proc/defcon_pretty()
return defcon
diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm
index 21f022acfd..7b5cc94d36 100644
--- a/code/controllers/globals.dm
+++ b/code/controllers/globals.dm
@@ -14,7 +14,7 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
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))
- QDEL_IN(exclude_these, 0) //signal logging isn't ready
+ QDEL_IN(exclude_these, 0) //signal logging isn't ready
log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 59ac68960c..055271fd86 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -18,15 +18,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master
name = "Master"
- // Are we processing (higher values increase the processing delay by n ticks)
+ /// Are we processing (higher values increase the processing delay by n ticks)
var/processing = TRUE
- // How many times have we ran
+ /// How many times have we ran
var/iteration = 0
+ /// Stack end detector to detect stack overflows that kill the mc's main loop
+ var/datum/stack_end_detector/stack_end_detector
- // world.time of last fire, for tracking lag outside of the mc
+ /// world.time of last fire, for tracking lag outside of the mc
var/last_run
- // List of subsystems to process().
+ /// List of subsystems to process().
var/list/subsystems
// Vars for keeping track of tick drift.
@@ -34,25 +36,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/init_time
var/tickdrift = 0
+ /// How long is the MC sleeping between runs, read only (set by Loop() based off of anti-tick-contention heuristics)
var/sleep_delta = 1
- ///Only run ticker subsystems for the next n ticks.
+ /// Only run ticker subsystems for the next n ticks.
var/skip_ticks = 0
+ /// makes the mc main loop runtime
var/make_runtime = FALSE
- var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
+ var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
- // The type of the last subsystem to be process()'d.
+ /// The type of the last subsystem to be fire()'d.
var/last_type_processed
- var/datum/controller/subsystem/queue_head //Start of queue linked list
- var/datum/controller/subsystem/queue_tail //End of queue linked list (used for appending to the list)
+ var/datum/controller/subsystem/queue_head //!Start of queue linked list
+ var/datum/controller/subsystem/queue_tail //!End of queue linked list (used for appending to the list)
var/queue_priority_count = 0 //Running total so that we don't have to loop thru the queue each run to split up the tick
var/queue_priority_count_bg = 0 //Same, but for background subsystems
- var/map_loading = FALSE //Are we loading in a new map?
+ var/map_loading = FALSE //!Are we loading in a new map?
- var/current_runlevel //for scheduling different subsystems for different stages of the round
+ var/current_runlevel //!for scheduling different subsystems for different stages of the round
var/sleep_offline_after_initializations = TRUE
var/static/restart_clear = 0
@@ -61,8 +65,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/static/random_seed
- //current tick limit, assigned before running a subsystem.
- //used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
+ ///current tick limit, assigned before running a subsystem.
+ ///used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
var/static/current_ticklimit = TICK_LIMIT_RUNNING
/datum/controller/master/New()
@@ -81,15 +85,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/list/_subsystems = list()
subsystems = _subsystems
if (Master != src)
- if (istype(Master))
+ if (istype(Master)) //If there is an existing MC take over his stuff and delete it
Recover()
qdel(Master)
+ Master = src
else
+ //Code used for first master on game boot or if existing master got deleted
+ Master = src
var/list/subsytem_types = subtypesof(/datum/controller/subsystem)
sortTim(subsytem_types, /proc/cmp_subsystem_init)
+ //Find any abandoned subsystem from the previous master (if there was any)
+ var/list/existing_subsystems = list()
+ for(var/global_var in global.vars)
+ if (istype(global.vars[global_var], /datum/controller/subsystem))
+ existing_subsystems += global.vars[global_var]
+ //Either init a new SS or if an existing one was found use that
for(var/I in subsytem_types)
- _subsystems += new I
- Master = src
+ var/datum/controller/subsystem/existing_subsystem = locate(I) in existing_subsystems
+ if (istype(existing_subsystem))
+ _subsystems += existing_subsystem
+ else
+ _subsystems += new I
if(!GLOB)
new /datum/controller/global_vars
@@ -109,7 +125,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
log_world("Shutdown complete")
// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart,
-// -1 if we encountered a runtime trying to recreate it
+// -1 if we encountered a runtime trying to recreate it
/proc/Recreate_MC()
. = -1 //so if we runtime, things know we failed
if (world.time < Master.restart_timeout)
@@ -120,7 +136,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/delay = 50 * ++Master.restart_count
Master.restart_timeout = world.time + delay
Master.restart_clear = world.time + (delay * 2)
- Master.processing = FALSE //stop ticking this one
+ if (Master) //Can only do this if master hasn't been deleted
+ Master.processing = FALSE //stop ticking this one
try
new/datum/controller/master()
catch
@@ -156,22 +173,22 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined."
BadBoy.flags |= SS_NO_FIRE
if(msg)
- to_chat(GLOB.admins, "
[msg]")
+ to_chat(GLOB.admins, span_boldannounce("[msg]"))
log_world(msg)
if (istype(Master.subsystems))
if(FireHim)
- Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
+ Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one
subsystems = Master.subsystems
current_runlevel = Master.current_runlevel
StartProcessing(10)
else
- to_chat(world, "
The Master Controller is having some issues, we will need to re-initialize EVERYTHING")
+ to_chat(world, span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING"))
Initialize(20, TRUE)
// Please don't stuff random bullshit here,
-// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
+// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
/datum/controller/master/Initialize(delay, init_sss, tgs_prime)
set waitfor = 0
@@ -181,7 +198,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
- to_chat(world, "
Initializing subsystems...")
+ to_chat(world, span_boldannounce("Initializing subsystems..."))
// Sort subsystems by init_order, so they initialize in the correct order.
sortTim(subsystems, /proc/cmp_subsystem_init)
@@ -190,7 +207,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Initialize subsystems.
current_ticklimit = CONFIG_GET(number/tick_limit_mc_init)
for (var/datum/controller/subsystem/SS in subsystems)
- if (SS.flags & SS_NO_INIT)
+ if (SS.flags & SS_NO_INIT || SS.initialized) //Don't init SSs with the correspondig flag or if they already are initialzized
continue
SS.Initialize(REALTIMEOFDAY)
CHECK_TICK
@@ -198,7 +215,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!"
- to_chat(world, "
[msg]")
+ to_chat(world, span_boldannounce("[msg]"))
log_world(msg)
if (!current_runlevel)
@@ -255,11 +272,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master/proc/Loop()
. = -1
//Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because
- // local vars rock
+ // local vars rock
//all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed)
var/list/tickersubsystems = list()
- var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
+ var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
var/timer = world.time
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
@@ -305,8 +322,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/error_level = 0
var/sleep_delta = 1
var/list/subsystems_to_check
- //the actual loop.
+ //setup the stack overflow detector
+ stack_end_detector = new()
+ var/datum/stack_canary/canary = stack_end_detector.prime_canary()
+ canary.use_variable()
+ //the actual loop.
while (1)
tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag)))
var/starting_tick_usage = TICK_USAGE
@@ -317,7 +338,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
//Anti-tick-contention heuristics:
//if there are mutiple sleeping procs running before us hogging the cpu, we have to run later.
- // (because sleeps are processed in the order received, longer sleeps are more likely to run first)
+ // (because sleeps are processed in the order received, longer sleeps are more likely to run first)
if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit.
sleep_delta *= 2
current_ticklimit = TICK_LIMIT_RUNNING * 0.5
@@ -423,6 +444,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
continue
if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time)
continue
+ if (SS.postponed_fires >= 1)
+ SS.postponed_fires--
+ SS.update_nextfire()
+ continue
SS.enqueue()
. = 1
@@ -439,12 +464,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/tick_precentage
var/tick_remaining
var/ran = TRUE //this is right
- var/ran_non_ticker = FALSE
var/bg_calc //have we swtiched current_tick_budget to background mode yet?
var/tick_usage
//keep running while we have stuff to run and we haven't gone over a tick
- // this is so subsystems paused eariler can use tick time that later subsystems never used
+ // this is so subsystems paused eariler can use tick time that later subsystems never used
while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC)
ran = FALSE
bg_calc = FALSE
@@ -459,20 +483,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if (!(queue_node_flags & SS_TICKER) && skip_ticks)
queue_node = queue_node.queue_next
continue
- //super special case, subsystems where we can't make them pause mid way through
- //if we can't run them this tick (without going over a tick)
- //we bump up their priority and attempt to run them next tick
- //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run
- // in those cases, so we just let them run)
- if (queue_node_flags & SS_NO_TICK_CHECK)
- if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker)
- if (!(queue_node_flags & SS_BACKGROUND))
- queue_node.queued_priority += queue_priority_count * 0.1
- queue_priority_count -= queue_node_priority
- queue_priority_count += queue_node.queued_priority
- current_tick_budget -= queue_node_priority
- queue_node = queue_node.queue_next
- continue
if (!bg_calc && (queue_node_flags & SS_BACKGROUND))
current_tick_budget = queue_priority_count_bg
@@ -481,7 +491,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE
if (current_tick_budget > 0 && queue_node_priority > 0)
- tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority)
+ //Give the subsystem a precentage of the remaining tick based on the remaning priority
+ tick_precentage = tick_remaining * (queue_node_priority / current_tick_budget)
else
tick_precentage = tick_remaining
@@ -489,8 +500,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
current_ticklimit = round(TICK_USAGE + tick_precentage)
- if (!(queue_node_flags & SS_TICKER))
- ran_non_ticker = TRUE
ran = TRUE
queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING)
@@ -535,14 +544,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node.last_fire = world.time
queue_node.times_fired++
- if (queue_node_flags & SS_TICKER)
- queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait)
- else if (queue_node_flags & SS_POST_FIRE_TIMING)
- queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
- else if (queue_node_flags & SS_KEEP_TIMING)
- queue_node.next_fire += queue_node.wait
- else
- queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100))
+ queue_node.update_nextfire()
queue_node.queued_time = 0
@@ -554,7 +556,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
. = 1
//resets the queue, and all subsystems, while filtering out the subsystem lists
-// called if any mc's queue procs runtime or exit improperly.
+// called if any mc's queue procs runtime or exit improperly.
/datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS)
. = 0
log_world("MC: SoftReset called, resetting MC queue state.")
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 12798f3863..db780ca05b 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -29,11 +29,11 @@
var/initialized = FALSE
/// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
- /// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
+ /// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
var/can_fire = TRUE
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
- var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
+ var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
/*
* The following variables are managed by the MC and should not be modified directly.
@@ -69,6 +69,9 @@
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
+ /// How many fires have we been requested to postpone
+ var/postponed_fires = 0
+
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
@@ -122,12 +125,38 @@
dequeue()
can_fire = 0
flags |= SS_NO_FIRE
- Master.subsystems -= src
+ if (Master)
+ Master.subsystems -= src
return ..()
+
+/** Update next_fire for the next run.
+ * reset_time (bool) - Ignore things that would normally alter the next fire, like tick_overrun, and last_fire. (also resets postpone)
+ */
+/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE)
+ var/queue_node_flags = flags
+
+ if (reset_time)
+ postponed_fires = 0
+ if (queue_node_flags & SS_TICKER)
+ next_fire = world.time + (world.tick_lag * wait)
+ else
+ next_fire = world.time + wait
+ return
+
+ if (queue_node_flags & SS_TICKER)
+ next_fire = world.time + (world.tick_lag * wait)
+ else if (queue_node_flags & SS_POST_FIRE_TIMING)
+ next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100))
+ else if (queue_node_flags & SS_KEEP_TIMING)
+ next_fire += wait
+ else
+ next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100))
+
+
//Queue it to run.
-// (we loop thru a linked list until we get to the end or find the right point)
-// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
+// (we loop thru a linked list until we get to the end or find the right point)
+// (this lets us sort our run order correctly without having to re-sort the entire already sorted list)
/datum/controller/subsystem/proc/enqueue()
var/SS_priority = priority
var/SS_flags = flags
@@ -191,9 +220,9 @@
queue_next.queue_prev = queue_prev
if (queue_prev)
queue_prev.queue_next = queue_next
- if (src == Master.queue_tail)
+ if (Master && (src == Master.queue_tail))
Master.queue_tail = queue_prev
- if (src == Master.queue_head)
+ if (Master && (src == Master.queue_head))
Master.queue_head = queue_next
queued_time = 0
if (state == SS_QUEUED)
@@ -217,10 +246,11 @@
//used to initialize the subsystem AFTER the map has loaded
/datum/controller/subsystem/Initialize(start_timeofday)
initialized = TRUE
+ // SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
- to_chat(world, "
[msg]")
- log_subsystem("INIT", msg)
+ to_chat(world, span_boldannounce("[msg]"))
+ log_subsystem(msg)
return time
/datum/controller/subsystem/stat_entry(msg)
@@ -243,11 +273,10 @@
if (SS_IDLE)
. = " "
-//could be used to postpone a costly subsystem for (default one) var/cycles, cycles
-//for instance, during cpu intensive operations like explosions
+/// Causes the next "cycle" fires to be missed. Effect is accumulative but can reset by calling update_nextfire(reset_time = TRUE)
/datum/controller/subsystem/proc/postpone(cycles = 1)
- if(next_fire - world.time < wait)
- next_fire += (wait*cycles)
+ if (can_fire && cycles >= 1)
+ postponed_fires += cycles
//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
@@ -258,7 +287,7 @@
if (NAMEOF(src, can_fire))
//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
if (var_value)
- next_fire = world.time + wait
+ update_nextfire(reset_time = TRUE)
if (NAMEOF(src, queued_priority)) //editing this breaks things.
return FALSE
. = ..()
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 2d2fac1d13..7f7d301a1d 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -29,37 +29,35 @@ SUBSYSTEM_DEF(garbage)
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
- var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
+ var/list/collection_timeout = list(GC_FILTER_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/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/pass_counts
var/list/fail_counts
- var/list/items = list() // Holds our qdel_item statistics datums
+ var/list/items = list() // Holds our qdel_item statistics datums
//Queue
var/list/queues
- #ifdef LEGACY_REFERENCE_TRACKING
+ #ifdef REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
+ #ifdef REFERENCE_TRACKING_DEBUG
+ //Should we save found refs. Used for unit testing
+ var/should_save_refs = FALSE
+ #endif
#endif
/datum/controller/subsystem/garbage/PreInit()
- 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
+ InitQueues()
/datum/controller/subsystem/garbage/stat_entry(msg)
var/list/counts = list()
@@ -90,13 +88,18 @@ SUBSYSTEM_DEF(garbage)
for(var/path in items)
var/datum/qdel_item/I = items[path]
dellog += "Path: [path]"
+ if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
+ dellog += "\tSUSPENDED FOR LAG"
if (I.failures)
dellog += "\tFailures: [I.failures]"
dellog += "\tqdel() Count: [I.qdels]"
dellog += "\tDestroy() Cost: [I.destroy_time]ms"
if (I.hard_deletes)
- dellog += "\tTotal Hard Deletes [I.hard_deletes]"
+ dellog += "\tTotal Hard Deletes: [I.hard_deletes]"
dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
+ dellog += "\tHighest Time Spent Hard Deleting: [I.hard_delete_max]ms"
+ if (I.hard_deletes_over_threshold)
+ dellog += "\tHard Deletes Over Threshold: [I.hard_deletes_over_threshold]"
if (I.slept_destroy)
dellog += "\tSleeps: [I.slept_destroy]"
if (I.no_respect_force)
@@ -122,6 +125,15 @@ SUBSYSTEM_DEF(garbage)
+/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_CHECK)
if (level == GC_QUEUE_CHECK)
@@ -153,7 +165,6 @@ SUBSYSTEM_DEF(garbage)
if(GCd_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
-
var/refID = L[2]
var/datum/D
D = locate(refID)
@@ -162,8 +173,8 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
- #ifdef LEGACY_REFERENCE_TRACKING
- reference_find_on_fail -= refID //It's deleted we don't care anymore.
+ #ifdef REFERENCE_TRACKING
+ reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -171,35 +182,43 @@ SUBSYSTEM_DEF(garbage)
// Something's still referring to the qdel'd object.
fail_counts[level]++
+
+ #ifdef REFERENCE_TRACKING
+ var/ref_searching = FALSE
+ #endif
+
switch (level)
if (GC_QUEUE_CHECK)
#ifdef REFERENCE_TRACKING
- D.find_references()
- #elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
- D.find_references_legacy()
+ INVOKE_ASYNC(D, /datum/proc/find_references)
+ ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
- D.find_references_legacy()
+ INVOKE_ASYNC(D, /datum/proc/find_references)
+ ref_searching = TRUE
#endif
reference_find_on_fail -= refID
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
- #ifdef TESTING
+
log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ #ifdef TESTING
for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
var/client/admin = c
if(!check_rights_for(admin, R_ADMIN))
continue
to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
- testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
- #endif
- #ifdef REFERENCE_TRACKING
- GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
- continue
#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)
@@ -208,27 +227,17 @@ SUBSYSTEM_DEF(garbage)
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
-#ifdef LEGACY_REFERENCE_TRACKING
-/datum/controller/subsystem/garbage/proc/add_type_to_findref(type)
- if(!ispath(type))
- return "NOT A VAILD PATH"
- reference_find_on_fail_types |= typecacheof(type)
-
-/datum/controller/subsystem/garbage/proc/remove_type_from_findref(type)
- if(!ispath(type))
- return "NOT A VALID PATH"
- reference_find_on_fail_types -= typesof(type)
-
-/datum/controller/subsystem/garbage/proc/clear_findref_types()
- reference_find_on_fail_types = list()
-#endif
-
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
if (isnull(D))
return
@@ -238,63 +247,66 @@ SUBSYSTEM_DEF(garbage)
var/gctime = world.time
var/refid = "\ref[D]"
-#ifdef LEGACY_REFERENCE_TRACKING
- if(reference_find_on_fail_types[D.type])
- SSgarbage.reference_find_on_fail[REF(D)] = TRUE
-#endif
-
D.gc_destroyed = gctime
var/list/queue = queues[level]
+
queue[++queue.len] = list(gctime, refid) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
- var/time = world.timeofday
- var/tick = TICK_USAGE
- var/ticktime = world.time
++delslasttick
++totaldels
var/type = D.type
var/refID = "\ref[D]"
+ var/tick_usage = TICK_USAGE
del(D)
-
- tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100))
+ tick_usage = TICK_USAGE_TO_MS(tick_usage)
var/datum/qdel_item/I = items[type]
-
I.hard_deletes++
- I.hard_delete_time += TICK_DELTA_TO_MS(tick)
+ I.hard_delete_time += tick_usage
+ if (tick_usage > I.hard_delete_max)
+ I.hard_delete_max = tick_usage
+ if (tick_usage > highest_del_ms)
+ highest_del_ms = tick_usage
+ highest_del_type_string = "[type]"
+ var/time = MS2DS(tick_usage)
- 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_game("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete)")
- message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).")
+ if (time > 0.1 SECONDS)
postpone(time)
+ var/threshold = CONFIG_GET(number/hard_deletes_overrun_threshold)
+ if (threshold && (time > threshold SECONDS))
+ if (!(I.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
+ log_game("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)")
+ message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete).")
+ I.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
+ I.hard_deletes_over_threshold++
+ var/overrun_limit = CONFIG_GET(number/hard_deletes_overrun_limit)
+ if (overrun_limit && I.hard_deletes_over_threshold >= overrun_limit)
+ I.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
/datum/controller/subsystem/garbage/Recover()
+ 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]
-
+/// Qdel Item: Holds statistics on each type that passes thru qdel
/datum/qdel_item
- var/name = ""
- 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/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/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.
/datum/qdel_item/New(mytype)
name = "[mytype]"
@@ -307,12 +319,12 @@ SUBSYSTEM_DEF(garbage)
if(!istype(D))
del(D)
return
+
var/datum/qdel_item/I = SSgarbage.items[D.type]
if (!I)
I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
I.qdels++
-
if(isnull(D.gc_destroyed))
if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
return
@@ -328,12 +340,12 @@ SUBSYSTEM_DEF(garbage)
if(!D)
return
switch(hint)
- if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
+ if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
SSgarbage.Queue(D)
if (QDEL_HINT_IWILLGC)
D.gc_destroyed = world.time
return
- if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
+ if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
if(!force)
D.gc_destroyed = null //clear the gc variable (important!)
return
@@ -350,17 +362,17 @@ SUBSYSTEM_DEF(garbage)
I.no_respect_force++
SSgarbage.Queue(D)
- if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
+ if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
- if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
+ if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
- #ifdef LEGACY_REFERENCE_TRACKING
- if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
+ #ifdef REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D)
- D.find_references_legacy()
- if (QDEL_HINT_IFFAIL_FINDREFERENCE)
+ D.find_references()
+ if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
SSgarbage.Queue(D)
- SSgarbage.reference_find_on_fail[REF(D)] = TRUE
+ SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
#endif
else
#ifdef TESTING
@@ -371,18 +383,3 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
-
-#ifdef TESTING
-/proc/writeDatumCount()
- var/list/datums = list()
- for(var/datum/D in world)
- datums[D.type] += 1
- for(var/datum/D)
- datums[D.type] += 1
- datums = sortTim(datums, /proc/cmp_numeric_dsc, associative = TRUE)
- if(fexists("data/DATUMCOUNT.txt"))
- fdel("data/DATUMCOUNT.txt")
- var/outfile = file("data/DATUMCOUNT.txt")
- for(var/path in datums)
- outfile << "[datums[path]]\t\t\t\t\t[path]"
-#endif
diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm
index 946980389c..410e21d419 100644
--- a/code/controllers/subsystem/research.dm
+++ b/code/controllers/subsystem/research.dm
@@ -366,10 +366,16 @@ SUBSYSTEM_DEF(research)
techweb_categories[I.category] = list(I.id = TRUE)
/datum/controller/subsystem/research/proc/techweb_node_by_id(id)
- return techweb_nodes[id] || error_node
+ if(techweb_nodes[id])
+ return techweb_nodes[id]
+ stack_trace("Attempted to access node ID [id] which didn't exist")
+ return error_node
/datum/controller/subsystem/research/proc/techweb_design_by_id(id)
- return techweb_designs[id] || error_design
+ if(techweb_designs[id])
+ return techweb_designs[id]
+ stack_trace("Attempted to access design ID [id] which didn't exist")
+ return error_design
/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D)
for(var/i in techweb_nodes)
diff --git a/code/datums/components/armor_plate.dm b/code/datums/components/armor_plate.dm
index f026c89c60..763aef6a70 100644
--- a/code/datums/components/armor_plate.dm
+++ b/code/datums/components/armor_plate.dm
@@ -71,6 +71,7 @@
R.update_icon()
to_chat(user, "
You strengthen [R], improving its resistance against melee, bullet and laser damage.")
else
+ SEND_SIGNAL(O, COMSIG_ARMOR_PLATED, amount, maxamount)
to_chat(user, "
You strengthen [O], improving its resistance against melee attacks.")
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 42580425ce..164cda63e0 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -57,9 +57,13 @@
*/
var/list/cooldowns
-#ifdef TESTING
+#ifdef REFERENCE_TRACKING
var/running_find_references
var/last_find_references = 0
+ #ifdef REFERENCE_TRACKING_DEBUG
+ ///Stores info about where refs are found, used for sanity checks and testing
+ var/list/found_refs
+ #endif
#endif
#ifdef DATUMVAR_DEBUGGING_MODE
diff --git a/code/datums/helper_datums/stack_end_detector.dm b/code/datums/helper_datums/stack_end_detector.dm
new file mode 100644
index 0000000000..0d621f51b3
--- /dev/null
+++ b/code/datums/helper_datums/stack_end_detector.dm
@@ -0,0 +1,32 @@
+/**
+ Stack End Detector.
+ Can detect if a given code stack has exited, used by the mc for stack overflow detection.
+
+ **/
+/datum/stack_end_detector
+ var/datum/weakref/_WF
+ var/datum/stack_canary/_canary
+
+/datum/stack_end_detector/New()
+ _canary = new()
+ _WF = WEAKREF(_canary)
+
+/** Prime the stack overflow detector.
+ Store the return value of this proc call in a proc level var.
+ Can only be called once.
+**/
+/datum/stack_end_detector/proc/prime_canary()
+ if (!_canary)
+ CRASH("Prime_canary called twice")
+ . = _canary
+ _canary = null
+
+/// Returns true if the stack is still going. Calling before the canary has been primed also returns true
+/datum/stack_end_detector/proc/check()
+ return !!_WF.resolve()
+
+/// Stack canary. Will go away if the stack it was primed by is ended by byond for return or stack overflow reasons.
+/datum/stack_canary
+
+/// empty proc to avoid warnings about unused variables. Call this proc on your canary in the stack it's watching.
+/datum/stack_canary/proc/use_variable()
diff --git a/code/datums/http.dm b/code/datums/http.dm
index 2a9b53f131..49b183fde6 100644
--- a/code/datums/http.dm
+++ b/code/datums/http.dm
@@ -6,10 +6,12 @@
var/body
var/headers
var/url
+ /// If present response body will be saved to this file.
+ var/output_file
var/_raw_response
-/datum/http_request/proc/prepare(method, url, body = "", list/headers)
+/datum/http_request/proc/prepare(method, url, body = "", list/headers, output_file)
if (!length(headers))
headers = ""
else
@@ -19,15 +21,16 @@
src.url = url
src.body = body
src.headers = headers
+ src.output_file = output_file
/datum/http_request/proc/execute_blocking()
- _raw_response = rustg_http_request_blocking(method, url, body, headers)
+ _raw_response = rustg_http_request_blocking(method, url, body, headers, build_options())
/datum/http_request/proc/begin_async()
if (in_progress)
CRASH("Attempted to re-use a request object.")
- id = rustg_http_request_async(method, url, body, headers)
+ id = rustg_http_request_async(method, url, body, headers, build_options())
if (isnull(text2num(id)))
stack_trace("Proc error: [id]")
@@ -35,6 +38,11 @@
else
in_progress = TRUE
+/datum/http_request/proc/build_options()
+ if(output_file)
+ return json_encode(list("output_filename"=output_file,"body_filename"=null))
+ return null
+
/datum/http_request/proc/is_complete()
if (isnull(id))
return TRUE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 3dbeda5ff5..7177249dbc 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -568,6 +568,12 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
if(usr.incapacitated() || !Adjacent(usr) || usr.lying)
return
+ if(iscyborg(usr))
+ var/obj/item/gripper/gripper = usr.get_active_held_item(TRUE)
+ if(istype(gripper))
+ gripper.pre_attack(src, usr, get_dist(src, usr))
+ return
+
if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota
usr.UnarmedAttack(src)
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 6513b53e1d..cbfdc85f6d 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -745,9 +745,9 @@
Grippers oh god oh fuck
***********************************************************************/
-/obj/item/weapon/gripper
+/obj/item/gripper
name = "engineering gripper"
- desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more. Alt click to drop instead of use."
+ desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more."
icon = 'icons/obj/device.dmi'
icon_state = "gripper"
@@ -776,18 +776,29 @@
var/obj/item/wrapped = null // Item currently being held.
-//Used to interact with UI's of held items, such as gas tanks and airlock electronics.
-/obj/item/weapon/gripper/AltClick(mob/user)
+// Used to drop whatever's in the gripper.
+/obj/item/gripper/proc/drop_held(silent = FALSE)
if(wrapped)
wrapped.forceMove(get_turf(wrapped))
- to_chat(user, "
You drop the [wrapped].")
+ if(!silent)
+ to_chat(usr, "
You drop the [wrapped].")
+ modify_appearance(wrapped, FALSE)
wrapped = null
- return ..()
+ update_appearance()
+ return TRUE
+ return FALSE
-/obj/item/weapon/gripper/pre_attack(var/atom/target, var/mob/living/silicon/robot/user, proximity, params)
+/obj/item/gripper/proc/takeitem(obj/item/item, silent = FALSE)
+ if(!silent)
+ to_chat(usr, "
You collect \the [item].")
+ item.loc = src
+ wrapped = item
+ update_appearance()
- if(!proximity)
- return
+/obj/item/gripper/pre_attack(atom/target, mob/living/silicon/robot/user, params)
+ var/proximity = get_dist(user, target)
+ if(proximity > 1)
+ return STOP_ATTACK_PROC_CHAIN
if(!wrapped)
for(var/obj/item/thing in src.contents)
@@ -795,21 +806,24 @@
break
if(wrapped) //Already have an item.
+ var/obj/item/item = wrapped
+ drop_held(TRUE)
//Temporary put wrapped into user so target's attackby() checks pass.
- wrapped.loc = user
+ item.loc = user
//Pass the attack on to the target. This might delete/relocate wrapped.
- var/resolved = target.attackby(wrapped,user)
- if(!resolved && wrapped && target)
- wrapped.afterattack(target,user,1)
+ var/resolved = target.attackby(item, user, params)
+ if(!resolved && item && target)
+ item.afterattack(target, user, proximity, params)
//If wrapped was neither deleted nor put into target, put it back into the gripper.
- if(wrapped && user && (wrapped.loc == user))
- wrapped.loc = src
- else
- wrapped = null
+ if(item && user && (item.loc == user))
+ takeitem(item, TRUE)
return
+ else
+ item = null
+ return STOP_ATTACK_PROC_CHAIN
- else if(istype(target,/obj/item))
+ else if(isitem(target))
var/obj/item/I = target
var/grab = 0
@@ -824,24 +838,94 @@
//We can grab the item, finally.
if(grab)
- to_chat(user, "
You collect \the [I].")
- I.loc = src
- wrapped = I
+ takeitem(I)
return
else
to_chat(user, "
Your gripper cannot hold \the [target].")
-/obj/item/weapon/gripper/mining
+// Rare cases - meant to be handled by code\modules\mob\living\silicon\robot\robot.dm:584 and the weirdness of get_active_held_item() of borgs.
+/obj/item/gripper/attack_self(mob/user)
+ if(wrapped)
+ wrapped.attack_self(user)
+ return
+ . = ..()
+
+// Splitable items
+/obj/item/gripper/AltClick(mob/user)
+ if(wrapped)
+ wrapped.AltClick(user)
+ return
+ . = ..()
+
+// Even rarer cases
+/obj/item/gripper/CtrlClick(mob/user)
+ if(wrapped)
+ wrapped.CtrlClick(user)
+ return
+ . = ..()
+
+// At this point you're just kidding me, but have this one as well.
+/obj/item/gripper/CtrlShiftClick(mob/user)
+ if(wrapped)
+ wrapped.CtrlShiftClick(user)
+ return
+ . = ..()
+
+// Make it clear what we can do with it.
+/obj/item/gripper/examine(mob/user)
+ . = ..()
+ if(wrapped)
+ . += "
It is holding [icon2html(wrapped, user)] [wrapped]."
+ . += "
Examine the little preview to examine it."
+ . += "
Attempting to drop the gripper will only drop [wrapped]."
+
+// Resets vis_contents and if holding something, add it to vis_contents.
+/obj/item/gripper/update_appearance(updates)
+ . = ..()
+ vis_contents = list()
+ if(wrapped)
+ modify_appearance(wrapped, TRUE)
+ vis_contents += wrapped
+
+// Generates the "minified" version of the item being held and adjust it's position.
+/obj/item/gripper/proc/modify_appearance(obj/item, minify = FALSE)
+ if(minify)
+ var/matrix/new_transform = new
+ new_transform.Scale(0.5, 0.5)
+ item.transform = new_transform
+ item.pixel_x = 8
+ item.pixel_y = -8
+ else
+ item.pixel_x = initial(pixel_x)
+ item.pixel_y = initial(pixel_y)
+ item.transform = new
+
+// I kind of wanted the item to be held in the gripper when stored as well, but i realized "store" is just drop as well, so i'll do this for now.
+// This will handle cases where the borg runs out of power or is damaged enough so the module is forcefully stored.
+/obj/item/gripper/cyborg_unequip(mob/user)
+ . = ..()
+ if(wrapped)
+ drop_held()
+
+// Clear references on being destroyed
+/obj/item/Destroy()
+ for(var/obj/item/gripper/gripper in vis_locs)
+ if(gripper.wrapped == src)
+ gripper.wrapped = null
+ gripper.update_appearance()
+ . = ..()
+
+/obj/item/gripper/mining
name = "shelter capsule deployer"
- desc = "A simple grasping tool for carrying and deploying shelter capsules. Alt click to drop instead of use."
+ desc = "A simple grasping tool for carrying and deploying shelter capsules."
icon_state = "gripper_mining"
can_hold = list(
/obj/item/survivalcapsule
)
-/obj/item/weapon/gripper/medical
+/obj/item/gripper/medical
name = "medical gripper"
- desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more. Alt click to drop instead of use."
+ desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more."
icon_state = "gripper_medical"
can_hold = list(
/obj/item/storage/bag/bio,
diff --git a/code/game/world.dm b/code/game/world.dm
index f466913346..14a85bb619 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -16,7 +16,7 @@ GLOBAL_LIST(topic_status_cache)
call(debug_server, "auxtools_init")()
enable_debugging()
AUXTOOLS_CHECK(AUXMOS)
-#ifdef REFERENCE_TRACKING
+#ifdef EXTOOLS_REFERENCE_TRACKING
enable_reference_tracking()
#endif
world.Profile(PROFILE_START)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 22bbf03014..437c74991c 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -195,7 +195,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
// #endif
/datum/admins/proc/create_or_modify_area,
/datum/admins/proc/fixcorruption,
-#ifdef REFERENCE_TRACKING
+#ifdef EXTOOLS_REFERENCE_TRACKING
/datum/admins/proc/view_refs,
/datum/admins/proc/view_del_failures,
#endif
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 953835bff0..4224b406b3 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2862,7 +2862,7 @@
return
if(!CONFIG_GET(string/centcom_ban_db))
- to_chat(usr, "
Centcom Galactic Ban DB is disabled!")
+ to_chat(usr, span_warning("Centcom Galactic Ban DB is disabled!"))
return
var/ckey = href_list["centcomlookup"]
@@ -2889,8 +2889,19 @@
dat += "
0 bans detected for [ckey]"
else
bans = json_decode(response["body"])
- dat += "
[bans.len] ban\s detected for [ckey]"
+
+ //Ignore bans from non-whitelisted sources, if a whitelist exists
+ var/list/valid_sources
+ if(CONFIG_GET(string/centcom_source_whitelist))
+ valid_sources = splittext(CONFIG_GET(string/centcom_source_whitelist), ",")
+ dat += "
Bans detected for [ckey]"
+ else
+ //Ban count is potentially inaccurate if they're using a whitelist
+ dat += "
[bans.len] ban\s detected for [ckey]"
+
for(var/list/ban in bans)
+ if(valid_sources && !(ban["sourceName"] in valid_sources))
+ continue
dat += "
Server: [sanitize(ban["sourceName"])]
"
dat += "
RP Level: [sanitize(ban["sourceRoleplayLevel"])]
"
dat += "
Type: [sanitize(ban["type"])]
"
diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm
index 279654eec1..714b54cd45 100644
--- a/code/modules/admin/view_variables/reference_tracking.dm
+++ b/code/modules/admin/view_variables/reference_tracking.dm
@@ -1,4 +1,4 @@
-#ifdef REFERENCE_TRACKING
+#ifdef EXTOOLS_REFERENCE_TRACKING
GLOBAL_LIST_EMPTY(deletion_failures)
@@ -102,29 +102,21 @@ GLOBAL_LIST_EMPTY(deletion_failures)
#endif
-#ifdef LEGACY_REFERENCE_TRACKING
+#ifdef REFERENCE_TRACKING
-/datum/verb/legacy_find_refs()
- set category = "Debug"
- set name = "Find References"
- set src in world
-
- find_references_legacy(FALSE)
-
-
-/datum/proc/find_references_legacy(skip_alert)
+/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].")
+ log_reftracker("CANCELLED search for references to a [usr.client.running_find_references].")
usr.client.running_find_references = null
running_find_references = null
//restart the garbage collector
SSgarbage.can_fire = TRUE
- SSgarbage.next_fire = world.time + world.tick_lag
+ SSgarbage.update_nextfire(reset_time = TRUE)
return
- if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes")
+ 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
@@ -134,92 +126,122 @@ GLOBAL_LIST_EMPTY(deletion_failures)
if(usr?.client)
usr.client.running_find_references = type
- testing("Beginning search for references to a [type].")
- last_find_references = world.time
+ log_reftracker("Beginning search for references to a [type].")
+
+ var/starting_time = world.time
+
+ //Time to search the whole game for our ref
+ DoSearchVar(GLOB, "GLOB") //globals
+ log_reftracker("Finished searching globals")
- DoSearchVar(GLOB) //globals
for(var/datum/thing in world) //atoms (don't beleive its lies)
- DoSearchVar(thing, "World -> [thing]")
+ DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time)
+ log_reftracker("Finished searching atoms")
for(var/datum/thing) //datums
- DoSearchVar(thing, "World -> [thing]")
+ DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time)
+ log_reftracker("Finished searching datums")
+ //Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
for(var/client/thing) //clients
- DoSearchVar(thing, "World -> [thing]")
+ DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time)
+ log_reftracker("Finished searching clients")
+
+ log_reftracker("Completed search for references to a [type].")
- testing("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.next_fire = world.time + world.tick_lag
+ 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(!found_refs && SSgarbage.should_save_refs)
+ found_refs = list()
+ #endif
-/datum/verb/qdel_then_find_references()
- set category = "Debug"
- set name = "qdel() then Find References"
- set src in world
-
- qdel(src, TRUE) //force a qdel
- if(!running_find_references)
- find_references_legacy(TRUE)
-
-
-/datum/verb/qdel_then_if_fail_find_references()
- set category = "Debug"
- set name = "qdel() then Find References if GC failure"
- set src in world
-
- qdel_and_find_ref_if_fail(src, TRUE)
-
-
-/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64)
if(usr?.client && !usr.client.running_find_references)
return
if(!recursive_limit)
+ log_reftracker("Recursion limit reached. [container_name]")
return
- if(istype(potential_container, /datum))
- var/datum/datum_container = potential_container
- if(datum_container.last_find_references == last_find_references)
- return
-
- datum_container.last_find_references = last_find_references
- var/list/vars_list = datum_container.vars
-
- for(var/varname in vars_list)
- if (varname == "vars")
- continue
- var/variable = vars_list[varname]
-
- if(variable == src)
- testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]")
-
- else if(islist(variable))
- DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1)
-
- else if(islist(potential_container))
- var/normal = IS_NORMAL_LIST(potential_container)
- for(var/element_in_list in potential_container)
- if(element_in_list == src)
- testing("Found [type] \ref[src] in list [container_name].")
-
- else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src)
- testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
-
- else if(islist(element_in_list))
- DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1)
-
+ //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(istype(potential_container, /datum))
+ 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
+ #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
+ #endif
+ log_reftracker("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]")
+ continue
+
+ if(islist(variable))
+ DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time)
+
+ else if(islist(potential_container))
+ var/normal = IS_NORMAL_LIST(potential_container)
+ var/list/potential_cache = potential_container
+ for(var/element_in_list in potential_cache)
+ #ifndef FIND_REF_NO_CHECK_TICK
+ CHECK_TICK
+ #endif
+ //Check normal entrys
+ if(element_in_list == src)
+ #ifdef REFERENCE_TRACKING_DEBUG
+ if(SSgarbage.should_save_refs)
+ found_refs[potential_cache] = TRUE
+ #endif
+ log_reftracker("Found [type] \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
+ #endif
+ log_reftracker("Found [type] \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)
- SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE
- qdel(thing_to_del, force)
+ 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["\ref[src]"] = TRUE
+ qdel(src, force)
#endif
diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm
index 01b5536948..043c50173d 100644
--- a/code/modules/admin/view_variables/topic_basic.dm
+++ b/code/modules/admin/view_variables/topic_basic.dm
@@ -47,7 +47,7 @@
usr.client.debug_variables(src)
return
- #ifdef REFERENCE_TRACKING
+ #ifdef EXTOOLS_REFERENCE_TRACKING
if(href_list[VV_HK_VIEW_REFERENCES])
var/datum/D = locate(href_list[VV_HK_TARGET])
if(!D)
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
index 973b615f4a..6204871a77 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
@@ -253,7 +253,7 @@
purpose_fulfilled = TRUE
make_glow()
animate(glow, transform = matrix() * 1.5, alpha = 255, time = 125)
- sound_to_playing_players(volume = 100, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/ratvar_rises.ogg')) //End the sounds
+ sound_to_playing_players('sound/effects/ratvar_rises.ogg', 90, FALSE, channel = CHANNEL_JUSTICAR_ARK)
sleep(125)
make_glow()
animate(glow, transform = matrix() * 3, alpha = 0, time = 5)
@@ -318,19 +318,19 @@
if(-INFINITY to GATEWAY_REEBE_FOUND)
if(!second_sound_played)
sound_to_playing_players('sound/magic/clockwork/invoke_general.ogg', 30, FALSE)
- sound_to_playing_players(volume = 10, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
+ sound_to_playing_players('sound/effects/clockcult_gateway_charging.ogg', 10, FALSE, channel = CHANNEL_JUSTICAR_ARK)
second_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_charging"
if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING)
if(!third_sound_played)
- sound_to_playing_players(volume = 30, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_active.ogg', TRUE))
+ sound_to_playing_players('sound/effects/clockcult_gateway_active.ogg', 30, FALSE, channel = CHANNEL_JUSTICAR_ARK)
third_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_active"
if(GATEWAY_RATVAR_COMING to GATEWAY_RATVAR_ARRIVAL)
if(!fourth_sound_played)
- sound_to_playing_players(volume = 70, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_closing.ogg', TRUE))
+ sound_to_playing_players('sound/effects/clockcult_gateway_closing.ogg', 70, FALSE, channel = CHANNEL_JUSTICAR_ARK)
fourth_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_closing"
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index 6a49addc13..0af024985e 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -3,21 +3,21 @@
/datum/asset/simple/tgui_common
keep_local_name = TRUE
assets = list(
- "tgui-common.bundle.js" = 'tgui/public/tgui-common.bundle.js',
+ "tgui-common.bundle.js" = file("tgui/public/tgui-common.bundle.js"),
)
/datum/asset/simple/tgui
keep_local_name = TRUE
assets = list(
- "tgui.bundle.js" = 'tgui/public/tgui.bundle.js',
- "tgui.bundle.css" = 'tgui/public/tgui.bundle.css',
+ "tgui.bundle.js" = file("tgui/public/tgui.bundle.js"),
+ "tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
)
/datum/asset/simple/tgui_panel
keep_local_name = TRUE
assets = list(
- "tgui-panel.bundle.js" = 'tgui/public/tgui-panel.bundle.js',
- "tgui-panel.bundle.css" = 'tgui/public/tgui-panel.bundle.css',
+ "tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"),
+ "tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"),
)
/datum/asset/simple/headers
@@ -168,10 +168,12 @@
/datum/asset/simple/namespaced/tgfont
assets = list(
- "tgfont.eot" = 'tgui/packages/tgfont/dist/tgfont.eot',
- "tgfont.woff2" = 'tgui/packages/tgfont/dist/tgfont.woff2',
+ "tgfont.eot" = file("tgui/packages/tgfont/dist/tgfont.eot"),
+ "tgfont.woff2" = file("tgui/packages/tgfont/dist/tgfont.woff2"),
+ )
+ parents = list(
+ "tgfont.css" = file("tgui/packages/tgfont/dist/tgfont.css"),
)
- parents = list("tgfont.css" = 'tgui/packages/tgfont/dist/tgfont.css')
/datum/asset/spritesheet/chat
name = "chat"
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index 4af9e7387d..cd48a81350 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -237,12 +237,28 @@
/obj/item/clothing/head/helmet/space/hardsuit/mining/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
+ RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
+
+/obj/item/clothing/head/helmet/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount)
+ SIGNAL_HANDLER
+
+ if(amount)
+ name = "reinforced [initial(name)]"
+ hardsuit_type = "mining_goliath"
+ if(amount == maxamount)
+ hardsuit_type = "mining_goliath_full"
+ icon_state = "hardsuit[on]-[hardsuit_type]"
+ if(ishuman(loc))
+ var/mob/living/carbon/human/wearer = loc
+ if(wearer.head == src)
+ wearer.update_inv_head()
/obj/item/clothing/suit/space/hardsuit/mining
icon_state = "hardsuit-mining"
name = "mining hardsuit"
desc = "A special suit that protects against hazardous, low pressure environments. Has reinforced plating for wildlife encounters."
item_state = "mining_hardsuit"
+ hardsuit_type = "mining"
max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF
armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75, "wound" = 15)
@@ -254,6 +270,21 @@
/obj/item/clothing/suit/space/hardsuit/mining/Initialize()
. = ..()
AddComponent(/datum/component/armor_plate)
+ RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon)
+
+/obj/item/clothing/suit/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount)
+ SIGNAL_HANDLER
+
+ if(amount)
+ name = "reinforced [initial(name)]"
+ hardsuit_type = "mining_goliath"
+ if(amount == maxamount)
+ hardsuit_type = "mining_goliath_full"
+ icon_state = "hardsuit-[hardsuit_type]"
+ if(ishuman(loc))
+ var/mob/living/carbon/human/wearer = loc
+ if(wearer.wear_suit == src)
+ wearer.update_inv_wear_suit()
//Syndicate hardsuit
/obj/item/clothing/head/helmet/space/hardsuit/syndi
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index c245a30b92..90fd0b2812 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -152,8 +152,8 @@
/obj/item/clothing/suit/armor/riot
name = "riot suit"
desc = "A suit of semi-flexible polycarbonate body armor with heavy padding to protect against melee attacks. Helps the wearer resist shoving in close quarters."
- icon_state = "swat"
- item_state = "swat_suit"
+ icon_state = "riot"
+ item_state = "riot"
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm
index 4e29483846..78708f54bb 100644
--- a/code/modules/clothing/suits/toggles.dm
+++ b/code/modules/clothing/suits/toggles.dm
@@ -6,6 +6,7 @@
var/hoodtype = /obj/item/clothing/head/hooded/winterhood //so the chaplain hoodie or other hoodies can override this
///Alternative mode for hiding the hood, instead of storing the hood in the suit it qdels it, useful for when you deal with hooded suit with storage.
var/alternative_mode = FALSE
+ var/no_t //do not update sprites when pulling up hood so we can avoid oddities with certain mechanics
/obj/item/clothing/suit/hooded/Initialize()
. = ..()
@@ -51,6 +52,8 @@
update_icon()
/obj/item/clothing/suit/hooded/update_icon_state()
+ if(no_t)
+ return
icon_state = "[initial(icon_state)]"
if(ishuman(hood?.loc))
var/mob/living/carbon/human/H = hood.loc
diff --git a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
index 4fc55ba4c3..303d18b4a4 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
@@ -8,8 +8,6 @@
#define CONE_WAFFLE 8
#define CONE_CHOC 9
-
-
/obj/machinery/icecream_vat
name = "ice cream vat"
desc = "Ding-aling ding dong. Get your Nanotrasen-approved ice cream!"
@@ -35,6 +33,8 @@
/datum/reagent/consumable/ethanol/singulo = 6,
/datum/reagent/consumable/peachjuice = 6,
/datum/reagent/consumable/grapejuice = 6)
+ var/custom_taste
+ var/custom_color
/obj/machinery/icecream_vat/proc/get_ingredient_list(type)
switch(type)
@@ -99,7 +99,10 @@
dat += "
Peach ice cream: Select Make x5 [product_types[ICECREAM_PEACH]] dollops left. (Ingredients: milk, ice, peach juice)
"
dat += "
Grape ice cream: Select Make x5 [product_types[ICECREAM_GRAPE]] dollops left. (Ingredients: milk, ice, grape juice)
"
dat += "
Blue ice cream: Select Make x5 [product_types[ICECREAM_BLUE]] dollops left. (Ingredients: milk, ice, singulo)
"
- dat += "
Custom ice cream: Select Make x5 [product_types[ICECREAM_CUSTOM]] dollops left. (Ingredients: milk, ice, optional flavoring)
[product_types[ICECREAM_CUSTOM]] dollops left. (Ingredients: milk, ice, optional flavoring)