From d653870a91e529ccd712495142ace3265ef7aa76 Mon Sep 17 00:00:00 2001 From: Lucy Date: Wed, 2 Apr 2025 17:38:49 -0400 Subject: [PATCH] Refactor debugger and byond-tracy init (#90288) ## About The Pull Request This refactors code related to debugger and byond-tracy `/datum/debugger` and `/datum/tracy` - which live in the `GLOBAL_REAL` vars `Debugger` and `Tracy` respectively. This allows code related to those two to be grouped together in their own files, rather than mashed into `world.dm` with a bunch of other shit - while it still initializes during the same stages of init. In addition, this also ports https://github.com/BeeStation/BeeStation-Hornet/pull/8947, which prints runtime errors to chat when the debugger is enabled.

Proof of Testing

![2025-03-27 (1743094986) ~ Code](https://github.com/user-attachments/assets/2678542f-8338-4ca8-9435-e0376da9e80a) ![2025-03-27 (1743095425) ~ Code](https://github.com/user-attachments/assets/35a1c454-c176-4fb9-987b-82eb92f0fd31) ![2025-03-27 (1743095936) ~ dreamseeker](https://github.com/user-attachments/assets/49f607dc-c684-4fd9-8271-a287f2341c52) ![2025-03-27 (1743096001) ~ Code](https://github.com/user-attachments/assets/8cca692c-6253-48e8-9994-beff39211078) ![2025-03-27 (1743096351) ~ dreamseeker](https://github.com/user-attachments/assets/43a993dd-3884-4709-94fc-d072ff97a337)
## Why It's Good For The Game Reduces some `GLOB` pollution, and groups a bunch of related code into dedicated files and datums. It's simply cleaner. Printing runtime errors to chat is also very useful, as it allows you to see when shit is fuck, if you don't want a breakpoint pause for each error. ## Changelog :cl: refactor: Refactored some code related to initialization. code: Runtime errors will now print to the chat while debugging. /:cl: --- code/_compile_options.dm | 7 +- code/controllers/subsystem/statpanel.dm | 8 +- code/game/world.dm | 86 ++++----------------- code/modules/admin/verbs/debug.dm | 24 +++--- code/modules/debugging/debugger.dm | 54 +++++++++++++ code/modules/debugging/tracy.dm | 64 +++++++++++++++ code/modules/error_handler/error_handler.dm | 2 + tgstation.dme | 2 + 8 files changed, 160 insertions(+), 87 deletions(-) create mode 100644 code/modules/debugging/debugger.dm create mode 100644 code/modules/debugging/tracy.dm diff --git a/code/_compile_options.dm b/code/_compile_options.dm index ee53eb99b8d..507ff81e786 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -8,6 +8,11 @@ #define USE_CUSTOM_ERROR_HANDLER #endif +#if defined(OPENDREAM) && !defined(SPACEMAN_DMM) && !defined(CIBUILDING) +// The code is being compiled for OpenDream, and not just for the CI linting. +#define OPENDREAM_REAL +#endif + #ifdef TESTING #define DATUMVAR_DEBUGGING_MODE @@ -112,7 +117,7 @@ // OpenDream currently doesn't support byondapi, so automatically disable it on OD, // unless CIBUILDING is defined - we still want to lint dreamluau-related code. // Get rid of this whenever it does have support. -#if defined(OPENDREAM) && !defined(SPACEMAN_DMM) && !defined(CIBUILDING) +#ifdef OPENDREAM_REAL #define DISABLE_DREAMLUAU #endif diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 366ad006594..d2912cb7fe1 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -188,10 +188,10 @@ SUBSYSTEM_DEF(statpanels) tracy_dll = TRACY_DLL_PATH tracy_present = fexists(tracy_dll) if(tracy_present) - if(GLOB.tracy_initialized) - mc_data.Insert(2, list(list("byond-tracy:", "Active (reason: [GLOB.tracy_init_reason || "N/A"])"))) - else if(GLOB.tracy_init_error) - mc_data.Insert(2, list(list("byond-tracy:", "Errored ([GLOB.tracy_init_error])"))) + if(Tracy.enabled) + mc_data.Insert(2, list(list("byond-tracy:", "Active (reason: [Tracy.init_reason || "N/A"])"))) + else if(Tracy.error) + mc_data.Insert(2, list(list("byond-tracy:", "Errored ([Tracy.error])"))) else if(fexists(TRACY_ENABLE_PATH)) mc_data.Insert(2, list(list("byond-tracy:", "Queued for next round"))) else diff --git a/code/game/world.dm b/code/game/world.dm index dde76a948a7..f9167fed753 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -7,14 +7,6 @@ #define NO_INIT_PARAMETER "no-init" GLOBAL_VAR(restart_counter) -GLOBAL_VAR(tracy_log) -GLOBAL_PROTECT(tracy_log) -GLOBAL_VAR(tracy_initialized) -GLOBAL_PROTECT(tracy_initialized) -GLOBAL_VAR(tracy_init_error) -GLOBAL_PROTECT(tracy_init_error) -GLOBAL_VAR(tracy_init_reason) -GLOBAL_PROTECT(tracy_init_reason) /** * WORLD INITIALIZATION @@ -74,26 +66,20 @@ GLOBAL_PROTECT(tracy_init_reason) RETURN_TYPE(/datum/controller/master) if(!tracy_initialized) - GLOB.tracy_initialized = FALSE -#ifndef OPENDREAM - if(!tracy_initialized) + Tracy = new #ifdef USE_BYOND_TRACY -#warn USE_BYOND_TRACY is enabled - var/should_init_tracy = TRUE - GLOB.tracy_init_reason = "USE_BYOND_TRACY defined" + if(Tracy.enable("USE_BYOND_TRACY defined")) + Genesis(tracy_initialized = TRUE) + return #else - var/should_init_tracy = FALSE + var/tracy_enable_reason if(USE_TRACY_PARAMETER in params) - should_init_tracy = TRUE - GLOB.tracy_init_reason = "world.params" + tracy_enable_reason = "world.params" if(fexists(TRACY_ENABLE_PATH)) - GLOB.tracy_init_reason ||= "enabled for round" + tracy_enable_reason ||= "enabled for round" SEND_TEXT(world.log, "[TRACY_ENABLE_PATH] exists, initializing byond-tracy!") - should_init_tracy = TRUE fdel(TRACY_ENABLE_PATH) -#endif - if(should_init_tracy) - init_byond_tracy() + if(!isnull(tracy_enable_reason) && Tracy.enable(tracy_enable_reason)) Genesis(tracy_initialized = TRUE) return #endif @@ -105,7 +91,7 @@ GLOBAL_PROTECT(tracy_init_reason) _initialize_log_files("data/logs/config_error.[GUID()].log") // Init the debugger first so we can debug Master - init_debugger() + Debugger = new // Create the logger logger = new @@ -243,8 +229,8 @@ GLOBAL_PROTECT(tracy_init_reason) logger.init_logging() - if(GLOB.tracy_log) - rustg_file_write("[GLOB.tracy_log]", "[GLOB.log_directory]/tracy.loc") + if(Tracy.trace_path) + rustg_file_write("[Tracy.trace_path]", "[GLOB.log_directory]/tracy.loc") var/latest_changelog = file("[global.config.directory]/../html/changelogs/archive/" + time2text(world.timeofday, "YYYY-MM", TIMEZONE_UTC) + ".yml") GLOB.changelog_hash = fexists(latest_changelog) ? md5(latest_changelog) : 0 //for telling if the changelog has changed recently @@ -353,30 +339,25 @@ GLOBAL_PROTECT(tracy_init_reason) if(do_hard_reboot) log_world("World hard rebooted at [time_stamp()]") shutdown_logging() // See comment below. - shutdown_byond_tracy() - auxcleanup() + QDEL_NULL(Tracy) + QDEL_NULL(Debugger) TgsEndProcess() return ..() log_world("World rebooted at [time_stamp()]") shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss. - shutdown_byond_tracy() - auxcleanup() + QDEL_NULL(Tracy) + QDEL_NULL(Debugger) TgsReboot() // TGS can decide to kill us right here, so it's important to do it last ..() #endif -/world/proc/auxcleanup() - var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") - if (debug_server) - call_ext(debug_server, "auxtools_shutdown")() - /world/Del() - shutdown_byond_tracy() - auxcleanup() + QDEL_NULL(Tracy) + QDEL_NULL(Debugger) . = ..() /world/proc/update_status() @@ -509,39 +490,6 @@ GLOBAL_PROTECT(tracy_init_reason) DREAMLUAU_SET_EXECUTION_LIMIT_MILLIS(tick_lag * 100) #endif -/world/proc/init_byond_tracy() - if(!fexists(TRACY_DLL_PATH)) - SEND_TEXT(world.log, "Error initializing byond-tracy: [TRACY_DLL_PATH] not found!") - CRASH("Error initializing byond-tracy: [TRACY_DLL_PATH] not found!") - - var/init_result = call_ext(TRACY_DLL_PATH, "init")("block") - if(length(init_result) != 0 && init_result[1] == ".") // if first character is ., then it returned the output filename - SEND_TEXT(world.log, "byond-tracy initialized (logfile: [init_result])") - GLOB.tracy_initialized = TRUE - return GLOB.tracy_log = init_result - else if(init_result == "already initialized") // not gonna question it. - GLOB.tracy_initialized = TRUE - SEND_TEXT(world.log, "byond-tracy already initialized ([GLOB.tracy_log ? "logfile: [GLOB.tracy_log]" : "no logfile"])") - else if(init_result != "0") - GLOB.tracy_init_error = init_result - SEND_TEXT(world.log, "Error initializing byond-tracy: [init_result]") - CRASH("Error initializing byond-tracy: [init_result]") - else - GLOB.tracy_initialized = TRUE - SEND_TEXT(world.log, "byond-tracy initialized (no logfile)") - -/world/proc/shutdown_byond_tracy() - if(GLOB.tracy_initialized) - SEND_TEXT(world.log, "Shutting down byond-tracy") - GLOB.tracy_initialized = FALSE - call_ext(TRACY_DLL_PATH, "destroy")() - -/world/proc/init_debugger() - var/dll = GetConfig("env", "AUXTOOLS_DEBUG_DLL") - if (dll) - call_ext(dll, "auxtools_init")() - enable_debugging() - /world/Profile(command, type, format) if((command & PROFILE_STOP) || !global.config?.loaded || !CONFIG_GET(flag/forbid_all_profiling)) . = ..() diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index f23fbbd8629..98b2c87094e 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -881,29 +881,27 @@ ADMIN_VERB(check_missing_sprites, R_DEBUG, "Debug Worn Item Sprites", "We're can if(!icon_exists(actual_file_name, sprite.icon_state)) to_chat(user, span_warning("ERROR sprites for [sprite.type]. Suit Storage slot."), confidential = TRUE) -#ifndef OPENDREAM +#ifndef OPENDREAM_REAL ADMIN_VERB(start_tracy, R_DEBUG, "Run Tracy Now", "Start running the byond-tracy profiler immediately", ADMIN_CATEGORY_DEBUG) - if(GLOB.tracy_initialized) + if(Tracy.enabled) to_chat(user, span_warning("byond-tracy is already running!"), avoid_highlighting = TRUE, type = MESSAGE_TYPE_DEBUG, confidential = TRUE) return - else if(GLOB.tracy_init_error) - to_chat(user, span_danger("byond-tracy failed to initialize during an earlier attempt: [GLOB.tracy_init_error]"), avoid_highlighting = TRUE, type = MESSAGE_TYPE_DEBUG, confidential = TRUE) + else if(Tracy.error) + to_chat(user, span_danger("byond-tracy failed to initialize during an earlier attempt: [Tracy.error]"), avoid_highlighting = TRUE, type = MESSAGE_TYPE_DEBUG, confidential = TRUE) return message_admins(span_adminnotice("[key_name_admin(user)] is trying to start the byond-tracy profiler.")) log_admin("[key_name(user)] is trying to start the byond-tracy profiler.") - GLOB.tracy_initialized = FALSE - GLOB.tracy_init_reason = "[user.ckey]" - world.init_byond_tracy() - if(GLOB.tracy_init_error) - to_chat(user, span_danger("byond-tracy failed to initialize: [GLOB.tracy_init_error]"), avoid_highlighting = TRUE, type = MESSAGE_TYPE_DEBUG, confidential = TRUE) - message_admins(span_adminnotice("[key_name_admin(user)] tried to start the byond-tracy profiler, but it failed to initialize ([GLOB.tracy_init_error])")) - log_admin("[key_name(user)] tried to start the byond-tracy profiler, but it failed to initialize ([GLOB.tracy_init_error])") + if(!Tracy.enable("[user.ckey]")) + var/error = Tracy.error || "N/A" + to_chat(user, span_danger("byond-tracy failed to initialize: [error]"), avoid_highlighting = TRUE, type = MESSAGE_TYPE_DEBUG, confidential = TRUE) + message_admins(span_adminnotice("[key_name_admin(user)] tried to start the byond-tracy profiler, but it failed to initialize ([error])")) + log_admin("[key_name(user)] tried to start the byond-tracy profiler, but it failed to initialize ([error])") return to_chat(user, span_notice("byond-tracy successfully started!"), avoid_highlighting = TRUE, type = MESSAGE_TYPE_DEBUG, confidential = TRUE) message_admins(span_adminnotice("[key_name_admin(user)] started the byond-tracy profiler.")) log_admin("[key_name(user)] started the byond-tracy profiler.") - if(GLOB.tracy_log) - rustg_file_write("[GLOB.tracy_log]", "[GLOB.log_directory]/tracy.loc") + if(Tracy.trace_path) + rustg_file_write("[Tracy.trace_path]", "[GLOB.log_directory]/tracy.loc") ADMIN_VERB_CUSTOM_EXIST_CHECK(start_tracy) return CONFIG_GET(flag/allow_tracy_start) && fexists(TRACY_DLL_PATH) diff --git a/code/modules/debugging/debugger.dm b/code/modules/debugging/debugger.dm new file mode 100644 index 00000000000..6ae39399591 --- /dev/null +++ b/code/modules/debugging/debugger.dm @@ -0,0 +1,54 @@ +/// The debugger instance. +/// This is a GLOBAL_REAL because it initializes before the MC or GLOB. +/// Really only used to check to see if the debugger is enabled or not, +/// and to separate debugger-related code into its own thing. +GLOBAL_REAL(Debugger, /datum/debugger) + +/datum/debugger + /// Is the debugger enabled? + VAR_FINAL/enabled = FALSE + /// The error text, if initializing the debugger errored. + VAR_FINAL/error + /// The path to the auxtools debug DLL, if it sets. + /// Defaults to the environmental variable AUXTOOLS_DEBUG_DLL. + VAR_FINAL/dll_path + +/datum/debugger/New(dll_path) + if(!isnull(Debugger)) + CRASH("Attempted to initialize /datum/debugger when global.Debugger is already set!") + Debugger = src +#ifndef OPENDREAM_REAL + src.dll_path = dll_path || world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") + enable() +#endif + +/datum/debugger/Destroy() +#ifndef OPENDREAM_REAL + if(enabled) + call_ext(dll_path, "auxtools_shutdown")() +#endif + return ..() + +/// Attempt to enable the debugger. +/datum/debugger/proc/enable() +#ifndef OPENDREAM_REAL + if(enabled) + CRASH("Attempted to enable debugger while its already enabled, somehow.") + if(!dll_path) + return FALSE + var/result = call_ext(dll_path, "auxtools_init")() + if(result != "SUCCESS") + error = result + return FALSE + enable_debugging() + enabled = TRUE + return TRUE +#else + return FALSE +#endif + +/datum/debugger/vv_edit_var(var_name, var_value) + return FALSE // no. + +/datum/debugger/CanProcCall(procname) + return FALSE // double no. diff --git a/code/modules/debugging/tracy.dm b/code/modules/debugging/tracy.dm new file mode 100644 index 00000000000..9840a253fb9 --- /dev/null +++ b/code/modules/debugging/tracy.dm @@ -0,0 +1,64 @@ +/// The byond-tracy instance. +/// This is a GLOBAL_REAL because it is the VERY FIRST THING to initialize, even before the MC or GLOB. +GLOBAL_REAL(Tracy, /datum/tracy) + +/datum/tracy + /// Is byond-tracy enabled and running? + VAR_FINAL/enabled = FALSE + /// The error text, if initializing byond-tracy errored. + VAR_FINAL/error + /// A description of what / who enabled byond-tracy. + VAR_FINAL/init_reason + /// A path to the file containing the output trace, if any. + VAR_FINAL/trace_path + +/datum/tracy/New() + if(!isnull(Tracy)) + CRASH("Attempted to initialize /datum/tracy when global.Tracy is already set!") + Tracy = src + +/datum/tracy/Destroy() +#ifndef OPENDREAM_REAL + if(enabled) + call_ext(TRACY_DLL_PATH, "destroy")() +#endif + return ..() + +/// Tries to initialize byond-tracy. +/datum/tracy/proc/enable(init_reason) +#ifndef OPENDREAM_REAL + if(enabled) + return TRUE + src.init_reason = init_reason + if(!fexists(TRACY_DLL_PATH)) + error = "[TRACY_DLL_PATH] not found" + SEND_TEXT(world.log, "Error initializing byond-tracy: [error]") + return FALSE + + var/init_result = call_ext(TRACY_DLL_PATH, "init")("block") + if(length(init_result) != 0 && init_result[1] == ".") // if first character is ., then it returned the output filename + SEND_TEXT(world.log, "byond-tracy initialized (logfile: [init_result])") + enabled = TRUE + return TRUE + else if(init_result == "already initialized") // not gonna question it. + enabled = TRUE + SEND_TEXT(world.log, "byond-tracy already initialized ([trace_path ? "logfile: [trace_path]" : "no logfile"])") + return TRUE + else if(init_result != "0") + error = init_result + SEND_TEXT(world.log, "Error initializing byond-tracy: [init_result]") + return FALSE + else + enabled = TRUE + SEND_TEXT(world.log, "byond-tracy initialized (no logfile)") + return TRUE +#else + error = "OpenDream not supported" + return FALSE +#endif + +/datum/tracy/vv_edit_var(var_name, var_value) + return FALSE // no. + +/datum/tracy/CanProcCall(procname) + return FALSE // double no. diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm index 7cdc43317e7..0e9b4b54dbd 100644 --- a/code/modules/error_handler/error_handler.dm +++ b/code/modules/error_handler/error_handler.dm @@ -146,6 +146,8 @@ GLOBAL_VAR_INIT(total_runtimes_skipped, 0) GLOB.current_test.Fail("[main_line]\n[desclines.Join("\n")]", file = E.file, line = E.line) #endif + if(Debugger?.enabled) + to_chat(world, span_alertwarning("[main_line]"), type = MESSAGE_TYPE_DEBUG) // This writes the regular format (unwrapping newlines and inserting timestamps as needed). log_runtime("runtime error: [E.name]\n[E.desc]") diff --git a/tgstation.dme b/tgstation.dme index 6cb0fef25e2..4fd22f28eb7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4082,6 +4082,8 @@ #include "code\modules\deathmatch\deathmatch_mapping.dm" #include "code\modules\deathmatch\deathmatch_maps.dm" #include "code\modules\deathmatch\deathmatch_modifier.dm" +#include "code\modules\debugging\debugger.dm" +#include "code\modules\debugging\tracy.dm" #include "code\modules\detectivework\evidence.dm" #include "code\modules\detectivework\scanner.dm" #include "code\modules\discord\accountlink.dm"