diff --git a/byond-extools.dll b/byond-extools.dll index 65be0856e095..dc90fd1ecc4f 100644 Binary files a/byond-extools.dll and b/byond-extools.dll differ diff --git a/byond-extools.pdb b/byond-extools.pdb index be8b64d780b0..42a16cd58fc1 100644 Binary files a/byond-extools.pdb and b/byond-extools.pdb differ diff --git a/code/__DEFINES/_globals.dm b/code/__DEFINES/_globals.dm index 8c0c99eefda5..873ed7eaea77 100644 --- a/code/__DEFINES/_globals.dm +++ b/code/__DEFINES/_globals.dm @@ -57,4 +57,4 @@ #define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_UNMANAGED(X) //Create an typed null global -#define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_UNMANAGED(X) +#define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_UNMANAGED(X) \ No newline at end of file diff --git a/code/__DEFINES/extools.dm b/code/__DEFINES/extools.dm new file mode 100644 index 000000000000..cb2bd56ee8d9 --- /dev/null +++ b/code/__DEFINES/extools.dm @@ -0,0 +1,143 @@ +#define EXTOOLS_SUCCESS "SUCCESS" +#define EXTOOLS_FAILED "FAIL" + +/* + Core - Provides necessary functionality for other modules. + Initializing any other modules also initializes this so it shouldn't be necessary to call this. +*/ + +/proc/extools_initialize() + return call(EXTOOLS, "core_initialize")() == EXTOOLS_SUCCESS + +/* + TFFI - Threaded FFI + All DLL calls are automatically threaded off. + Black magic is used to suspend (sleep) the currently executing proc, allowing non-blocking FFI. + You may call a DLL function and sleep until it returns, pass a callback to be called with the result, + or call resolve() on the /datum/promise to receive the return value at any time. + Example: + var/x = call_wait("sample.dll", "do_work", "arg1", "arg2", "arg3") + - Calls the do_work function from sample.dll with 3 arguments. The proc sleeps until do_work returns. + var/datum/promise/P = call_async("sample.dll", "do_work", "arg1") + ... do something else ... + var/result = P.resolve() + - Calls do_work with 1 argument. Returns a promise object. Runs some other code before calling P.resolve() to obtain the result. + /proc/print_result(result) + world << result + call_cb("sample.dll", "do_work", /proc/print_result, "arg1", "arg2") + - Calls do_work with 2 arguments. The callback is invoked with the result as the single argument. Execution resumes immediately. +*/ + +#if 0 + +/proc/tffi_initialize() + return call(EXTOOLS, "tffi_initialize")() == EXTOOLS_SUCCESS + +GLOBAL_VAR_INIT(fallback_alerted, FALSE) +GLOBAL_VAR_INIT(next_promise_id, 0) + +/datum/promise + var/completed = FALSE + var/result = "" + var/callback_context = GLOBAL_PROC + var/callback_proc = null + var/__id = 0 + +/datum/promise/New() + __id = next_promise_id++ //please don't create more than 10^38 promises in a single tick + +//This proc's bytecode is overwritten to allow suspending and resuming on demand. +//None of the code here should run. +/datum/promise/proc/__internal_resolve(ref, id) + if(!fallback_alerted && world.system_type != UNIX) // the rewriting is currently broken on Linux. + world << "TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly." + world.log << "TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly." + fallback_alerted = TRUE + while(!completed) + sleep(1) + //It might be better to just fail and notify the user that something went wrong. + +/datum/promise/proc/__resolve_callback() + __internal_resolve("\ref[src]", __id) + if(callback_context == GLOBAL_PROC) + call(callback_proc)(result) + else + call(callback_context, callback_proc)(result) + +/datum/promise/proc/resolve() + __internal_resolve("\ref[src]", __id) + return result + +/proc/call_async() + var/list/arguments = args.Copy() + var/datum/promise/P = new + arguments.Insert(1, "\ref[P]") + call(EXTOOLS, "call_async")(arglist(arguments)) + return P + +/proc/call_cb() + var/list/arguments = args.Copy() + var/context = arguments[3] + var/callback = arguments[4] + arguments.Cut(3, 5) + var/datum/promise/P = new + P.callback_context = context + P.callback_proc = callback + arguments.Insert(1, "\ref[P]") + call(EXTOOLS, "call_async")(arglist(arguments)) + spawn(0) + P.__resolve_callback() + +/proc/call_wait() + return call_async(arglist(args)).resolve() + +#endif + +/* + Extended Profiling - High precision in-depth performance profiling. + Turning on extended profiling for a proc will cause each execution of it to generate a file in the ./profiles directory + containing a breakdown of time spent executing the proc and each sub-proc it calls. Import the file into https://www.speedscope.app/ to + view a good visual representation. + Be aware that sleeping counts as stopping and restarting the execution of the proc, which will generate multiple files, one between each sleep. + For large procs the profiles may become unusably large. Optimizations pending. + Example: + start_profiling(/datum/explosion/New) + - Enables profiling for /datum/explosion/New(), which will produce a detailed breakdown of each explosion that occurs afterwards. + stop_profiling(/datum/explosion/New) + - Disables profiling for explosions. Any currently running profiles will stop when the proc finishes executing or enters a sleep. +*/ + +/proc/profiling_initialize() + return call(EXTOOLS, "extended_profiling_initialize")() == EXTOOLS_SUCCESS + +/proc/start_profiling(procpath) + call(EXTOOLS, "enable_extended_profiling")("[procpath]") + +/proc/stop_profiling(procpath) + call(EXTOOLS, "disable_extended_profiling")("[procpath]") + +/* + Debug Server - High and low level debugging of DM code. + Calling debugger_initialize will start a debug server that allows connections from frontends, + such as SpaceManiac's VSCode extension for line-by-line debugging (and more), or Steamport's + Somnium for bytecode inspection. + Call with pause = TRUE to wait until the debugger connected and immediately break on the next instruction after the call. +*/ + +/proc/debugger_initialize(pause = FALSE) + return call(EXTOOLS, "debug_initialize")(pause ? "pause" : "") == EXTOOLS_SUCCESS + +/* + Misc +*/ + +//Programatically enable and disable the built-in byond profiler. Useful if you want to, for example, profile subsystem initializations. +/proc/enable_profiling() + return call(EXTOOLS, "enable_profiling")() == EXTOOLS_SUCCESS + +/proc/disable_profiling() + return call(EXTOOLS, "disable_profiling")() == EXTOOLS_SUCCESS + +// Will dump the server's in-depth memory profile into the file specified. +/proc/dump_memory_profile(file_name) + return call(EXTOOLS, "dump_memory_usage")(file_name) == EXTOOLS_SUCCESS diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index d3cb7b7c9258..18ba7eacc6a5 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -3,7 +3,7 @@ for(var/file in args) src << browse_rsc(file) -/client/proc/browse_files(root="data/logs/", max_iterations=10, list/valid_extensions=list("txt","log","htm", "html")) +/client/proc/browse_files(root="data/logs/", max_iterations=10, list/valid_extensions=list("txt","log","htm", "html", "json")) if(IsAdminAdvancedProcCall()) log_admin_private("BROWSEFILES: Admin proc call blocked") message_admins("BROWSEFILES: Admin proc call blocked") diff --git a/code/_compile_options.dm b/code/_compile_options.dm index d126453c807f..7dfc3773a0d3 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -56,4 +56,4 @@ #define TESTING #endif -#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so") +#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so") \ No newline at end of file diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index e7a919f32332..e15dcd79a5fd 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -147,7 +147,8 @@ GLOBAL_PROTECT(admin_verbs_server) /client/proc/adminchangemap, /client/proc/panicbunker, /client/proc/toggle_hub, - /client/proc/mentor_memo, /* YOGS - something stupid about "Mentor memos" */ + /client/proc/mentor_memo, // YOGS - something stupid about "Mentor memos" + ///client/proc/dump_memory_usage, /client/proc/release_queue // Yogs -- Adds some queue-manipulation verbs ) GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug()) @@ -778,3 +779,30 @@ GLOBAL_PROTECT(admin_verbs_hideable) log_admin("[key_name(usr)] has [AI_Interact ? "activated" : "deactivated"] Admin AI Interact") message_admins("[key_name_admin(usr)] has [AI_Interact ? "activated" : "deactivated"] their AI interaction") + +/*/client/proc/dump_memory_usage() + set name = "Dump Server Memory Usage" + set category = "Server" + + if(!check_rights(R_SERVER)) + return + + if(alert(usr, "This will dump memory usage and potentially lag the server. Proceed?", "Alert", "Yes", "No") != "Yes") + return + + var/fname = "[GLOB.round_id ? GLOB.round_id : "NULL"]-[time2text(world.timeofday, "MM-DD-hhmm")].json" + + to_chat(world, "Performing a memory dump!") + log_admin("[key_name_admin(usr)] has initiated a memory dump into \"[fname]\".") + message_admins("[key_name_admin(usr)] has initiated a memory dump into \"[fname]\".") + + sleep(20) + + if(!dump_memory_profile("data/logs/memory/[fname]")) + to_chat(usr, "Dumping memory failed at dll call.") + return + + if(!fexists("data/logs/memory/[fname]")) + to_chat(usr, "File creation failed. Please check to see if the data/logs/memory folder actually exists.") + else + to_chat(usr, "Memory dump completed.")*/ diff --git a/libbyond-extools.so b/libbyond-extools.so index 0f3247dbb1e2..8de3f23fa9fe 100755 Binary files a/libbyond-extools.so and b/libbyond-extools.so differ diff --git a/yogstation.dme b/yogstation.dme index 3b73ccce1344..460293a400f8 100644 --- a/yogstation.dme +++ b/yogstation.dme @@ -43,6 +43,7 @@ #include "code\__DEFINES\economy.dm" #include "code\__DEFINES\events.dm" #include "code\__DEFINES\exports.dm" +#include "code\__DEFINES\extools.dm" #include "code\__DEFINES\fantasy_affixes.dm" #include "code\__DEFINES\fastdmm2.dm" #include "code\__DEFINES\flags.dm"