diff --git a/byond-extools.dll b/byond-extools.dll new file mode 100644 index 0000000000..3b3dd39be1 Binary files /dev/null and b/byond-extools.dll differ diff --git a/cev_eris.dme b/cev_eris.dme index d531c57519..f32c97f500 100644 --- a/cev_eris.dme +++ b/cev_eris.dme @@ -13,6 +13,7 @@ #include "code\stylesheet.dm" #include "code\world.dm" #include "code\__DEFINES\_compile_options.dm" +#include "code\__DEFINES\_extools_api.dm" #include "code\__DEFINES\_globals.dm" #include "code\__DEFINES\_hydro_setup.dm" #include "code\__DEFINES\_planes+layers.dm" diff --git a/code/__DEFINES/_extools_api.dm b/code/__DEFINES/_extools_api.dm new file mode 100644 index 0000000000..4e4258e6dd --- /dev/null +++ b/code/__DEFINES/_extools_api.dm @@ -0,0 +1,153 @@ +#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "./libbyond-extools.so") +#define EXTOOLS_SUCCESS "SUCCESS" +#define EXTOOLS_FAILED "FAIL" +#define GLOBAL_PROC "magic BS" + +/* + 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() + call(EXTOOLS, "cleanup")() + 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. +*/ + +/proc/tffi_initialize() + return call(EXTOOLS, "tffi_initialize")() == EXTOOLS_SUCCESS + +/datum/promise + var/completed = FALSE + var/result = "" + var/callback_context = GLOBAL_PROC + var/callback_proc = null + var/__id = 0 + +/datum/promise/New() + __id = GLOB.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(!GLOB.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." + GLOB.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() + +/* + 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) + if(world.system_type == MS_WINDOWS) + 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 + +// MAPTICK STUFF // +/proc/maptick_initialize() + return call(EXTOOLS, "maptick_initialize")() == EXTOOLS_SUCCESS \ No newline at end of file diff --git a/code/__DEFINES/callbacks.dm b/code/__DEFINES/callbacks.dm index 753b802a4d..86511832a7 100644 --- a/code/__DEFINES/callbacks.dm +++ b/code/__DEFINES/callbacks.dm @@ -1,4 +1,4 @@ -#define GLOBAL_PROC "some_magic_bullshit" +//#define GLOBAL_PROC "some_magic_bullshit" #define CALLBACK new /datum/callback #define INVOKE_ASYNC ImmediateInvokeAsync diff --git a/code/__DEFINES/tick.dm b/code/__DEFINES/tick.dm index c8ff66f41c..28425eb987 100644 --- a/code/__DEFINES/tick.dm +++ b/code/__DEFINES/tick.dm @@ -1,4 +1,8 @@ -#define TICK_LIMIT_RUNNING 80 +// Maptick stuff +#define MAPTICK_MC_MIN_RESERVE 20 //Percentage of tick to leave for master controller to run +#define MAPTICK_LAST_INTERNAL_TICK_USAGE ((GLOB.internal_tick_usage / world.tick_lag) * 100) //internal_tick_usage is updated every tick by extools +#define TICK_LIMIT_RUNNING (max(90 - MAPTICK_LAST_INTERNAL_TICK_USAGE, MAPTICK_MC_MIN_RESERVE)) + #define TICK_LIMIT_TO_RUN 78 #define TICK_LIMIT_MC 70 #define TICK_LIMIT_MC_INIT_DEFAULT 98 diff --git a/code/_global_vars/misc.dm b/code/_global_vars/misc.dm index d807e33380..eb361df2ed 100644 --- a/code/_global_vars/misc.dm +++ b/code/_global_vars/misc.dm @@ -11,3 +11,13 @@ GLOBAL_DATUM(lobbyScreen, /datum/lobbyscreen) // WORLD TOPIC CACHING // GLOBAL_VAR(topic_status_lastcache) GLOBAL_LIST(topic_status_cache) + +// Extools vars +GLOBAL_VAR_INIT(internal_tick_usage, 0.2 * world.tick_lag) //This var is updated every tick by a DLL if present, used to reduce lag +GLOBAL_PROTECT(internal_tick_usage) // NO TOUCHY + +GLOBAL_VAR_INIT(fallback_alerted, FALSE) +GLOBAL_PROTECT(fallback_alerted) // NO TOUCHY + +GLOBAL_VAR_INIT(next_promise_id, 0) +GLOBAL_PROTECT(next_promise_id) // NO TOUCHY \ No newline at end of file diff --git a/code/controllers/master.dm b/code/controllers/master.dm index e0bff9e58c..3064bddf0c 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -571,8 +571,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(!statclick) statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))") - stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")) + stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)") + stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)]%)")) /datum/controller/master/StartLoadingMap() //disallow more than one map to load at once, multithreading it will just cause race conditions diff --git a/code/world.dm b/code/world.dm index c6e3c2fd0e..40c1bd477b 100644 --- a/code/world.dm +++ b/code/world.dm @@ -61,13 +61,12 @@ var/game_id = null // Only runs if env var EXTOOLS_DLL is set otherwise it won't load the dll file. // Used with the Visual Studio Code debugger and DreamMaker Language Client extension from https://github.com/SpaceManiac/SpacemanDMM/wiki/Setting-up-Debugging -/world/proc/enable_debugger() - var/dll = world.GetConfig("env", "EXTOOLS_DLL") - if (dll) - call(dll, "debug_initialize")() - /world/New() - enable_debugger() + // Begin loading of extools DLL and components + extools_initialize() + maptick_initialize() + debugger_initialize() + // End extools //logs var/date_string = time2text(world.realtime, "YYYY/MM-Month/DD-Day") href_logfile = file("data/logs/[date_string] hrefs.htm") diff --git a/libbyond-extools.so b/libbyond-extools.so new file mode 100644 index 0000000000..7ae42f20e2 Binary files /dev/null and b/libbyond-extools.so differ