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