ID:2191707
 
Making a callback system mainly because having to lose out on default args to have callbacks was lame.

Figured I'd pop it in here. (also works with verbs as long as it likes the args you gave it)
#define GLOBAL_PROC "THIS_IS_A_GLOBAL_PROC_CALLBACK" //used instead of null because clients can be callback targets and then go null from disconnect before invoked, and we need to be able to differentiate when that happens or when it's just a global proc.
#define CALLBACK new /datum/callback //not a macro to make it 510 compatible

/*
USAGE:

var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn)
var/timerid = addtimer(C, time, timertype)
OR
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
See the note on proc typepath shortcuts

INVOKING THE CALLBACK:
var/result = C.Invoke(args, to, add) //additional args are added after the ones given when the callback was created
OR
var/result = C.InvokeAsync(args, to, add) //Sleeps will not block, 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...)

proc defined on current(src) object OR overridden at src or any of it's parents:
.procname
Example:
CALLBACK(src, .some_proc_here)

proc defined on parent(of src) object (when the above doesn't apply):
.proc/procname
Example:
CALLBACK(src, .proc/some_proc_here)

global proc while in another global proc:
.procname
Example:
CALLBACK(GLOBAL_PROC, .some_proc_here)

Other wise you will have to do the full typepath of the proc (/type/of/thing/proc/procname)

*/


/datum/callback
var/datum/object = GLOBAL_PROC
var/delegate
var/list/arguments

/datum/callback/New(thingtocall, proctocall, ...)
if (thingtocall)
object = thingtocall
delegate = proctocall
if (length(args) > 2)
arguments = args.Copy(3)


/datum/callback/proc/Invoke(...)
if (!object)
CRASH("Cannot call null.[delegate]")

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 (object == GLOBAL_PROC)
return call(delegate)(arglist(calling_arguments))
return call(object, delegate)(arglist(calling_arguments))

//copy and pasted because of the overhead required in wrapping this as a layer around the above proc
/datum/callback/proc/InvokeAsync(...)
set waitfor = 0
if (!object)
CRASH("Cannot call null.[delegate]")

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 (object == GLOBAL_PROC)
return call(delegate)(arglist(calling_arguments))
return call(object, delegate)(arglist(calling_arguments))
you changed my global proc define!
Cmon, "some_magic_bullshit" was a perfect value for it!
yes, it was, it was also agpl. my code i can put up here under public domain, but if i hadn't changed that then it would technically have agpl code.
You might want to make it clear that InvokeAsync() will always return null if you are calling a proc that waits, it returns InvokeAsync's . - not the . of the function you are invoking.