Skip to content

Commit

Permalink
Implements Extools + Maptick (#4574)
Browse files Browse the repository at this point in the history
  • Loading branch information
AffectedArc07 authored and ACCount12 committed Jan 28, 2020
1 parent 9c3a5b6 commit bee93c9
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 10 deletions.
Binary file added byond-extools.dll
Binary file not shown.
1 change: 1 addition & 0 deletions cev_eris.dme
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
153 changes: 153 additions & 0 deletions code/__DEFINES/_extools_api.dm
Original file line number Diff line number Diff line change
@@ -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 << "<b>TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly.</b>"
world.log << "<b>TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly.</b>"
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
2 changes: 1 addition & 1 deletion code/__DEFINES/callbacks.dm
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 5 additions & 1 deletion code/__DEFINES/tick.dm
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 10 additions & 0 deletions code/_global_vars/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions code/controllers/master.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions code/world.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Binary file added libbyond-extools.so
Binary file not shown.

0 comments on commit bee93c9

Please sign in to comment.