diff --git a/code/__DEFINES/callbacks.dm b/code/__DEFINES/callbacks.dm index 126ef1ea1c..f25dfdf150 100644 --- a/code/__DEFINES/callbacks.dm +++ b/code/__DEFINES/callbacks.dm @@ -1,4 +1,5 @@ #define GLOBAL_PROC "some_magic_bullshit" - +/// A shorthand for the callback datum, [documented here](datum/callback.html) #define CALLBACK new /datum/callback #define INVOKE_ASYNC world.ImmediateInvokeAsync +#define CALLBACK_NEW(typepath, args) CALLBACK(GLOBAL_PROC, /proc/___callbacknew, typepath, args) diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm index 71dbf82734..4296e3c2e9 100644 --- a/code/__DEFINES/qdel.dm +++ b/code/__DEFINES/qdel.dm @@ -11,14 +11,12 @@ #define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails. //defines for the gc_destroyed var -#define GC_QUEUE_PREQUEUE 1 -#define GC_QUEUE_CHECK 2 -#define GC_QUEUE_HARDDELETE 3 -#define GC_QUEUE_COUNT 3 //increase this when adding more steps. +#define GC_QUEUE_CHECK 1 +#define GC_QUEUE_HARDDELETE 2 +#define GC_QUEUE_COUNT 2 //increase this when adding more steps. #define GC_QUEUED_FOR_QUEUING -1 -#define GC_QUEUED_FOR_HARD_DEL -2 -#define GC_CURRENTLY_BEING_QDELETED -3 +#define GC_CURRENTLY_BEING_QDELETED -2 #define QDELING(X) (X.gc_destroyed) #define QDELETED(X) (!X || QDELING(X)) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 74c32dd52f..e64ed8fd55 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1452,6 +1452,37 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) else D.vars[var_name] = var_value +#define TRAIT_CALLBACK_ADD(target, trait, source) CALLBACK(GLOBAL_PROC, /proc/___TraitAdd, ##target, ##trait, ##source) +#define TRAIT_CALLBACK_REMOVE(target, trait, source) CALLBACK(GLOBAL_PROC, /proc/___TraitRemove, ##target, ##trait, ##source) + +///DO NOT USE ___TraitAdd OR ___TraitRemove as a replacement for ADD_TRAIT / REMOVE_TRAIT defines. To be used explicitly for callback. +/proc/___TraitAdd(target,trait,source) + if(!target || !trait || !source) + return + if(islist(target)) + for(var/i in target) + if(!isatom(i)) + continue + var/atom/the_atom = i + ADD_TRAIT(the_atom,trait,source) + else if(isatom(target)) + var/atom/the_atom2 = target + ADD_TRAIT(the_atom2,trait,source) + +///DO NOT USE ___TraitAdd OR ___TraitRemove as a replacement for ADD_TRAIT / REMOVE_TRAIT defines. To be used explicitly for callback. +/proc/___TraitRemove(target,trait,source) + if(!target || !trait || !source) + return + if(islist(target)) + for(var/i in target) + if(!isatom(i)) + continue + var/atom/the_atom = i + REMOVE_TRAIT(the_atom,trait,source) + else if(isatom(target)) + var/atom/the_atom2 = target + REMOVE_TRAIT(the_atom2,trait,source) + /proc/get_random_food() var/list/blocked = list(/obj/item/reagent_containers/food/snacks, /obj/item/reagent_containers/food/snacks/store/bread, diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 125da84a30..8772fac5af 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -8,6 +8,7 @@ **/ //This is the ABSOLUTE ONLY THING that should init globally like this +//2019 update: the failsafe,config and Global controllers also do it GLOBAL_REAL(Master, /datum/controller/master) = new //THIS IS THE INIT ORDER @@ -54,7 +55,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/static/restart_clear = 0 var/static/restart_timeout = 0 var/static/restart_count = 0 - + var/static/random_seed //current tick limit, assigned before running a subsystem. @@ -69,7 +70,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(!random_seed) random_seed = (TEST_RUN_PARAMETER in world.params) ? 29051994 : rand(1, 1e9) rand_seed(random_seed) - + var/list/_subsystems = list() subsystems = _subsystems if (Master != src) @@ -614,4 +615,4 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if (client_count < CONFIG_GET(number/mc_tick_rate/disable_high_pop_mc_mode_amount)) processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate) else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount)) - processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate) \ No newline at end of file + processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate) diff --git a/code/controllers/subsystem/server_maint.dm b/code/controllers/subsystem/server_maint.dm index 15d3e17d29..9e926b29a1 100644 --- a/code/controllers/subsystem/server_maint.dm +++ b/code/controllers/subsystem/server_maint.dm @@ -8,6 +8,10 @@ SUBSYSTEM_DEF(server_maint) init_order = INIT_ORDER_SERVER_MAINT runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT var/list/currentrun + var/cleanup_ticker = 0 + +/datum/controller/subsystem/server_maint/PreInit() + world.hub_password = "" //quickly! before the hubbies see us. /datum/controller/subsystem/server_maint/Initialize(timeofday) if (CONFIG_GET(flag/hub)) @@ -18,10 +22,30 @@ SUBSYSTEM_DEF(server_maint) if(!resumed) if(listclearnulls(GLOB.clients)) log_world("Found a null in clients list!") - if(listclearnulls(GLOB.player_list)) - log_world("Found a null in player list!") src.currentrun = GLOB.clients.Copy() + switch (cleanup_ticker) // do only one of these at a time, once per 5 fires + if (0) + if(listclearnulls(GLOB.player_list)) + log_world("Found a null in player_list!") + cleanup_ticker++ + if (5) + if(listclearnulls(GLOB.mob_list)) + log_world("Found a null in mob_list!") + cleanup_ticker++ + if (10) + if(listclearnulls(GLOB.alive_mob_list)) + log_world("Found a null in alive_mob_list!") + cleanup_ticker++ + if (15) + if(listclearnulls(GLOB.dead_mob_list)) + log_world("Found a null in dead_mob_list!") + cleanup_ticker++ + if (20) + cleanup_ticker = 0 + else + cleanup_ticker++ + var/list/currentrun = src.currentrun var/round_started = SSticker.HasRoundStarted() diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index d0eb0b9ce2..bd2fb854af 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -1,5 +1,5 @@ #define BUCKET_LEN (world.fps*1*60) //how many ticks should we keep in the bucket. (1 minutes worth) -#define BUCKET_POS(timer) ((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag) % BUCKET_LEN)||BUCKET_LEN) +#define BUCKET_POS(timer) (((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag)+1) % BUCKET_LEN)||BUCKET_LEN) #define TIMER_MAX (world.time + TICKS2DS(min(BUCKET_LEN-(SStimer.practical_offset-DS2TICKS(world.time - SStimer.head_offset))-1, BUCKET_LEN-1))) #define TIMER_ID_MAX (2**24) //max float with integer precision @@ -38,15 +38,15 @@ SUBSYSTEM_DEF(timer) /datum/controller/subsystem/timer/fire(resumed = FALSE) var/lit = last_invoke_tick - var/last_check = world.time - TIMER_NO_INVOKE_WARNING + var/last_check = world.time - TICKS2DS(BUCKET_LEN*1.5) var/list/bucket_list = src.bucket_list if(!bucket_count) last_invoke_tick = world.time - if(lit && lit < last_check && last_invoke_warning < last_check) + if(lit && lit < last_check && head_offset < last_check && last_invoke_warning < last_check) last_invoke_warning = world.time - var/msg = "No regular timers processed in the last [TIMER_NO_INVOKE_WARNING] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!" + var/msg = "No regular timers processed in the last [BUCKET_LEN*1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!" message_admins(msg) WARNING(msg) if(bucket_auto_reset) @@ -121,7 +121,7 @@ SUBSYSTEM_DEF(timer) if (!resumed) timer = null - while (practical_offset <= BUCKET_LEN && head_offset + (practical_offset*world.tick_lag) <= world.time) + while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset-1)*world.tick_lag) <= world.time) var/datum/timedevent/head = bucket_list[practical_offset] if (!timer || !head || timer == head) head = bucket_list[practical_offset] @@ -159,7 +159,7 @@ SUBSYSTEM_DEF(timer) if (timer.timeToRun < head_offset) bucket_resolution = null //force bucket recreation - CRASH("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + stack_trace("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") if (timer.callBack && !timer.spent) timer.callBack.InvokeAsync() @@ -169,9 +169,9 @@ SUBSYSTEM_DEF(timer) qdel(timer) continue - if (timer.timeToRun < head_offset + TICKS2DS(practical_offset)) + if (timer.timeToRun < head_offset + TICKS2DS(practical_offset-1)) bucket_resolution = null //force bucket recreation - CRASH("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + stack_trace("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") if (timer.callBack && !timer.spent) timer.callBack.InvokeAsync() spent += timer @@ -447,6 +447,7 @@ SUBSYSTEM_DEF(timer) next.prev = src prev.next = src +///Returns a string of the type of the callback for this timer /datum/timedevent/proc/getcallingtype() . = "ERROR" if (callBack.object == GLOBAL_PROC) @@ -454,6 +455,14 @@ SUBSYSTEM_DEF(timer) else . = "[callBack.object.type]" +/** + * Create a new timer and insert it in the queue + * + * Arguments: + * * callback the callback to call on timer finish + * * wait deciseconds to run the timer for + * * flags flags for this timer, see: code\__DEFINES\subsystems.dm + */ /proc/addtimer(datum/callback/callback, wait = 0, flags = 0) if (!callback) CRASH("addtimer called without a callback") @@ -498,6 +507,12 @@ SUBSYSTEM_DEF(timer) var/datum/timedevent/timer = new(callback, wait, flags, hash) return timer.id +/** + * Delete a timer + * + * Arguments: + * * id a timerid or a /datum/timedevent + */ /proc/deltimer(id) if (!id) return FALSE @@ -514,6 +529,26 @@ SUBSYSTEM_DEF(timer) return TRUE return FALSE +/** + * Get the remaining deciseconds on a timer + * + * Arguments: + * * id a timerid or a /datum/timedevent + */ +/proc/timeleft(id) + if (!id) + return null + if (id == TIMER_ID_NULL) + CRASH("Tried to get timeleft of a null timerid. Use TIMER_STOPPABLE flag") + if (!istext(id)) + if (istype(id, /datum/timedevent)) + var/datum/timedevent/timer = id + return timer.timeToRun - world.time + //id is string + var/datum/timedevent/timer = SStimer.timer_id_dict[id] + if (timer && !timer.spent) + return timer.timeToRun - world.time + return null #undef BUCKET_LEN #undef BUCKET_POS diff --git a/code/datums/callback.dm b/code/datums/callback.dm index 1a26052cc9..62e10922f3 100644 --- a/code/datums/callback.dm +++ b/code/datums/callback.dm @@ -1,55 +1,72 @@ -/* - 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. - OR - INVOKE_ASYNC() to immediately create and call InvokeAsync - - PROC TYPEPATH SHORTCUTS (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...) - - global proc while in another global proc: - .procname - Example: - CALLBACK(GLOBAL_PROC, .some_proc_here) - - proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents: - .procname - Example: - CALLBACK(src, .some_proc_here) - - - when the above doesn't apply: - .proc/procname - Example: - CALLBACK(src, .proc/some_proc_here) - - proc defined on a parent of a some type: - /some/type/.proc/some_proc_here - - - - Other wise you will have to do the full typepath of the proc (/type/of/thing/proc/procname) - -*/ - +/** + *# 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...) + * + * ### global proc while in another global proc: + * .procname + * + * `CALLBACK(GLOBAL_PROC, .some_proc_here)` + * + * ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents: + * .procname + * + * `CALLBACK(src, .some_proc_here)` + * + * ### when the above doesn't apply: + *.proc/procname + * + * `CALLBACK(src, .proc/some_proc_here)` + * + * + * proc defined on a parent of a some type + * + * `/some/type/.proc/some_proc_here` + * + * Otherwise you must always provide the full typepath of the proc (/type/of/thing/proc/procname) + */ /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 @@ -58,7 +75,14 @@ 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 @@ -72,6 +96,14 @@ 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 @@ -97,7 +129,14 @@ return call(delegate)(arglist(calling_arguments)) return call(object, delegate)(arglist(calling_arguments)) -//copy and pasted because fuck proc overhead +/** + * 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 @@ -125,7 +164,9 @@ 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 @@ -150,15 +191,17 @@ if (savereturn) finished[index] = rtn - - - -//runs a list of callbacks asynchronously, returning once all of them return. -//callbacks can be repeated. -//callbacks-args is an optional list of argument lists, in the same order as the callbacks, -// the inner lists will be sent to the callbacks when invoked() as additional args. -//can optionly save and return a list of return values, in the same order as the original list of callbacks -//resolution is the number of byond ticks between checks. +/** + * 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 @@ -178,3 +221,5 @@ sleep(resolution*world.tick_lag) return CS.finished +/proc/___callbacknew(typepath, arguments) + new typepath(arglist(arguments))