Files
Yogstation/code/datums/callback.dm
adamsong 5a43d8e4ba Port codebase to 515 (#18669)
* Set max version

* Updates most references to .proc (Leaves a couple to check check_grep)

* Actually add check

* Oops

* Hopefully exclude the one place we do want .proc

* AAAAAAA

* Trying this instead

* Hopefully checks go green

* Switch to NAMEOF_STATIC

* Makes 515 acutally build

* LIBCALL
2023-05-08 17:01:37 -05:00

217 lines
6.7 KiB
Plaintext

/**
*# Callback Datums
*A datum that holds a proc to be called on another object, used to track proccalls to other objects
*
* ## USAGE
*
* ```
* var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn)
* var/timerid = addtimer(C, time, timertype)
* you can also use the compiler define shorthand
* var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype)
* ```
*
* Note: proc strings can only be given for datum proc calls, global procs must be proc paths
*
* Also proc strings are strongly advised against because they don't compile error if the proc stops existing
*
* In some cases you can provide a shortform of the procname, see the proc typepath shortcuts documentation below
*
* ## INVOKING THE CALLBACK
*`var/result = C.Invoke(args, to, add)` additional args are added after the ones given when the callback was created
*
* `var/result = C.InvokeAsync(args, to, add)` Asyncronous - returns . on the first sleep then continues on in the background
* after the sleep/block ends, otherwise operates normally.
*
* ## PROC TYPEPATH SHORTCUTS
* (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...)
*
* ### Calling a global proc (/proc/procname):
* GLOBAL_PROC_REF(procname)
*
* `CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(some_proc_here))`
*
* ### proc defined on current(src) object or global:
* PROC_REF(procname)
*
* `CALLBACK(src, PROC_REF(some_proc_here))`
*
* ### Callback on a type different from source:
* TYPED_PROC_REF(type, procname)
*
* `CALLBACK(other, TYPED_PROC_REF(some_type_here, some_proc_here))`
*/
/datum/callback
///The object we will be calling the proc on
var/datum/object = GLOBAL_PROC
///The proc we will be calling on the object
var/delegate
///A list of arguments to pass into the proc
var/list/arguments
///A weak reference to the user who triggered this callback
var/datum/weakref/user
/**
* Create a new callback datum
*
* Arguments
* * thingtocall the object to call the proc on
* * proctocall the proc to call on the target object
* * ... an optional list of extra arguments to pass to the proc
*/
/datum/callback/New(thingtocall, proctocall, ...)
if (thingtocall)
object = thingtocall
delegate = proctocall
if (length(args) > 2)
arguments = args.Copy(3)
if(usr)
user = WEAKREF(usr)
/**
* Immediately Invoke proctocall on thingtocall, with waitfor set to false
*
* Arguments:
* * thingtocall Object to call on
* * proctocall Proc to call on that object
* * ... optional list of arguments to pass as arguments to the proc being called
*/
/world/proc/ImmediateInvokeAsync(thingtocall, proctocall, ...)
set waitfor = FALSE
if (!thingtocall)
return
var/list/calling_arguments = length(args) > 2 ? args.Copy(3) : null
if (thingtocall == GLOBAL_PROC)
call(proctocall)(arglist(calling_arguments))
else
call(thingtocall, proctocall)(arglist(calling_arguments))
/**
* Invoke this callback
*
* Calls the registered proc on the registered object, if the user ref
* can be resolved it also inclues that as an arg
*
* If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
*/
/datum/callback/proc/Invoke(...)
if(!usr)
var/datum/weakref/W = user
if(W)
var/mob/M = W.resolve()
if(M)
if (length(args))
return world.PushUsr(arglist(list(M, src) + args))
return world.PushUsr(M, src)
if (!object)
return
var/list/calling_arguments = arguments
if (length(args))
if (length(arguments))
calling_arguments = calling_arguments + args //not += so that it creates a new list so the arguments list stays clean
else
calling_arguments = args
if(datum_flags & DF_VAR_EDITED)
return WrapAdminProcCall(object, delegate, calling_arguments)
if (object == GLOBAL_PROC)
return call(delegate)(arglist(calling_arguments))
return call(object, delegate)(arglist(calling_arguments))
/**
* Invoke this callback async (waitfor=false)
*
* Calls the registered proc on the registered object, if the user ref
* can be resolved it also inclues that as an arg
*
* If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
*/
/datum/callback/proc/InvokeAsync(...)
set waitfor = FALSE
if(!usr)
var/datum/weakref/W = user
if(W)
var/mob/M = W.resolve()
if(M)
if (length(args))
return world.PushUsr(arglist(list(M, src) + args))
return world.PushUsr(M, src)
if (!object)
return
var/list/calling_arguments = arguments
if (length(args))
if (length(arguments))
calling_arguments = calling_arguments + args //not += so that it creates a new list so the arguments list stays clean
else
calling_arguments = args
if(datum_flags & DF_VAR_EDITED)
return WrapAdminProcCall(object, delegate, calling_arguments)
if (object == GLOBAL_PROC)
return call(delegate)(arglist(calling_arguments))
return call(object, delegate)(arglist(calling_arguments))
/**
Helper datum for the select callbacks proc
*/
/datum/callback_select
var/list/finished
var/pendingcount
var/total
/datum/callback_select/New(count, savereturns)
total = count
if (savereturns)
finished = new(count)
/datum/callback_select/proc/invoke_callback(index, datum/callback/callback, list/callback_args, savereturn = TRUE)
set waitfor = FALSE
if (!callback || !istype(callback))
//This check only exists because the alternative is callback_select would block forever if given invalid data
CRASH("invalid callback passed to invoke_callback")
if (!length(callback_args))
callback_args = list()
pendingcount++
var/rtn = callback.Invoke(arglist(callback_args))
pendingcount--
if (savereturn)
finished[index] = rtn
/**
* Runs a list of callbacks asyncronously, returning only when all have finished
*
* Callbacks can be repeated, to call it multiple times
*
* Arguments:
* * list/callbacks the list of callbacks to be called
* * list/callback_args the list of lists of arguments to pass into each callback
* * savereturns Optionally save and return the list of returned values from each of the callbacks
* * resolution The number of byond ticks between each time you check if all callbacks are complete
*/
/proc/callback_select(list/callbacks, list/callback_args, savereturns = TRUE, resolution = 1)
if (!callbacks)
return
var/count = length(callbacks)
if (!count)
return
if (!callback_args)
callback_args = list()
callback_args.len = count
var/datum/callback_select/CS = new(count, savereturns)
for (var/i in 1 to count)
CS.invoke_callback(i, callbacks[i], callback_args[i], savereturns)
while(CS.pendingcount)
sleep(resolution*world.tick_lag)
return CS.finished