mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2025-12-21 23:52:12 +00:00
Adds ability for Gestalt to detach their limb nymphs and use them for scouting or whatever.
Detached Nymph can switch back and forth between controlling Gestalt or nymph
Detached nymph can merge back into Gestalt, restoring lost limb instantly.
Makes initialization of Diona nymphs based on external organs it has, not a hardcoded number
No longer Nymphs spawn on turf and move into Gestalt during initial setup.
No longer removing internal limbs would cause spawn of nymph. Only external organs do that now.
Timers:
Adds new proc in timers that calls the callback and deletes timer afterwards. Used when you need to make active timer execute now.
454 lines
13 KiB
Plaintext
454 lines
13 KiB
Plaintext
#define BUCKET_LEN (round(10*(60/world.tick_lag), 1)) //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) + 1)
|
|
#define TIMER_ID_MAX (2**24) //max float with integer precision
|
|
|
|
var/datum/controller/subsystem/timer/SStimer
|
|
|
|
/datum/controller/subsystem/timer
|
|
name = "Timer"
|
|
wait = 1 //SS_TICKER subsystem, so wait is in ticks
|
|
priority = SS_PRIORITY_TIMER
|
|
|
|
flags = SS_FIRE_IN_LOBBY|SS_TICKER|SS_NO_INIT
|
|
|
|
var/list/datum/timedevent/processing = list()
|
|
var/list/hashes = list()
|
|
|
|
var/head_offset = 0 //world.time of the first entry in the the bucket.
|
|
var/practical_offset = 0 //index of the first non-empty item in the bucket.
|
|
var/bucket_resolution = 0 //world.tick_lag the bucket was designed for
|
|
var/bucket_count = 0 //how many timers are in the buckets
|
|
|
|
var/list/bucket_list = list() //list of buckets, each bucket holds every timer that has to run that byond tick.
|
|
|
|
var/list/timer_id_dict = list() //list of all active timers assoicated to their timer id (for easy lookup)
|
|
|
|
var/list/clienttime_timers = list() //special snowflake timers that run on fancy pansy "client time"
|
|
|
|
var/last_invoke_tick = 0
|
|
var/static/last_invoke_warning = 0
|
|
var/static/bucket_auto_reset = TRUE
|
|
|
|
var/static/times_flushed = 0
|
|
var/static/times_crashed = 0
|
|
|
|
/datum/controller/subsystem/timer/New()
|
|
NEW_SS_GLOBAL(SStimer)
|
|
|
|
/datum/controller/subsystem/timer/stat_entry(msg)
|
|
..("B:[bucket_count] P:[length(processing)] H:[length(hashes)] C:[length(clienttime_timers)][times_crashed ? " F:[times_crashed]" : ""]")
|
|
|
|
/datum/controller/subsystem/timer/fire(resumed = FALSE)
|
|
var/lit = last_invoke_tick
|
|
var/last_check = world.time - TIMER_NO_INVOKE_WARNING
|
|
var/list/bucket_list = src.bucket_list
|
|
var/static/list/spent = list()
|
|
|
|
if(!bucket_count)
|
|
last_invoke_tick = world.time
|
|
|
|
if(lit && lit < 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" : ""]!"
|
|
times_crashed++
|
|
message_admins(msg)
|
|
WARNING(msg)
|
|
if(bucket_auto_reset)
|
|
bucket_resolution = 0
|
|
|
|
log_ss(name, "Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset], times_flushed: [times_flushed], length(spent): [length(spent)]")
|
|
for (var/i in 1 to length(bucket_list))
|
|
var/datum/timedevent/bucket_head = bucket_list[i]
|
|
if (!bucket_head)
|
|
continue
|
|
|
|
log_ss(name, "Active timers at index [i]:")
|
|
|
|
var/datum/timedevent/bucket_node = bucket_head
|
|
var/anti_loop_check = 1000
|
|
do
|
|
log_ss(name, get_timer_debug_string(bucket_node))
|
|
bucket_node = bucket_node.next
|
|
anti_loop_check--
|
|
while(bucket_node && bucket_node != bucket_head && anti_loop_check)
|
|
log_ss(name, "Active timers in the processing queue:")
|
|
for(var/I in processing)
|
|
log_ss(name, get_timer_debug_string(I))
|
|
|
|
while(length(clienttime_timers))
|
|
var/datum/timedevent/ctime_timer = clienttime_timers[clienttime_timers.len]
|
|
if (ctime_timer.timeToRun <= REALTIMEOFDAY)
|
|
--clienttime_timers.len
|
|
var/datum/callback/callBack = ctime_timer.callBack
|
|
ctime_timer.spent = TRUE
|
|
callBack.InvokeAsync()
|
|
qdel(ctime_timer)
|
|
else
|
|
break //None of the rest are ready to run
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
var/static/datum/timedevent/timer
|
|
var/static/datum/timedevent/head
|
|
|
|
if (practical_offset > BUCKET_LEN || (!resumed && length(bucket_list) != BUCKET_LEN || world.tick_lag != bucket_resolution))
|
|
shift_buckets()
|
|
bucket_list = src.bucket_list
|
|
resumed = FALSE
|
|
|
|
if (!resumed)
|
|
timer = null
|
|
head = null
|
|
|
|
while (practical_offset <= BUCKET_LEN && head_offset + (practical_offset*world.tick_lag) <= world.time && !MC_TICK_CHECK)
|
|
if (!timer || !head || timer == head)
|
|
head = bucket_list[practical_offset]
|
|
if (!head)
|
|
practical_offset++
|
|
if (MC_TICK_CHECK)
|
|
break
|
|
continue
|
|
timer = head
|
|
do
|
|
var/datum/callback/callBack = timer.callBack
|
|
if (!callBack)
|
|
qdel(timer)
|
|
bucket_resolution = null //force bucket recreation
|
|
CRASH("Invalid timer: [timer] timer.timeToRun=[timer.timeToRun]||QDELETED(timer)=[QDELETED(timer)]||world.time=[world.time]||head_offset=[head_offset]||practical_offset=[practical_offset]||timer.spent=[timer.spent]")
|
|
|
|
if (!timer.spent)
|
|
spent += timer
|
|
timer.spent = TRUE
|
|
callBack.InvokeAsync()
|
|
last_invoke_tick = world.time
|
|
|
|
timer = timer.next
|
|
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
while (timer && timer != head)
|
|
timer = null
|
|
bucket_list[practical_offset++] = null
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
times_flushed++
|
|
|
|
bucket_count -= length(spent)
|
|
|
|
for (var/spent_timer in spent)
|
|
qdel(spent_timer)
|
|
|
|
spent.len = 0
|
|
|
|
/datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE)
|
|
. = "Timer: [TE]"
|
|
. += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]"
|
|
if(TE.spent)
|
|
. += ", SPENT"
|
|
if(QDELETED(TE))
|
|
. += ", QDELETED"
|
|
|
|
/datum/controller/subsystem/timer/proc/shift_buckets()
|
|
var/list/bucket_list = src.bucket_list
|
|
var/list/alltimers = list()
|
|
//collect the timers currently in the bucket
|
|
for (var/bucket_head in bucket_list)
|
|
if (!bucket_head)
|
|
continue
|
|
var/datum/timedevent/bucket_node = bucket_head
|
|
do
|
|
alltimers += bucket_node
|
|
bucket_node = bucket_node.next
|
|
while(bucket_node && bucket_node != bucket_head)
|
|
|
|
bucket_list.len = 0
|
|
bucket_list.len = BUCKET_LEN
|
|
|
|
practical_offset = 1
|
|
bucket_count = 0
|
|
head_offset = world.time
|
|
bucket_resolution = world.tick_lag
|
|
|
|
alltimers += processing
|
|
if (!length(alltimers))
|
|
return
|
|
|
|
sortTim(alltimers, .proc/cmp_timer)
|
|
|
|
var/datum/timedevent/head = alltimers[1]
|
|
|
|
if (head.timeToRun < head_offset)
|
|
head_offset = head.timeToRun
|
|
|
|
var/list/timers_to_remove = list()
|
|
|
|
for (var/thing in alltimers)
|
|
var/datum/timedevent/timer = thing
|
|
if (!timer)
|
|
timers_to_remove += timer
|
|
continue
|
|
|
|
var/bucket_pos = BUCKET_POS(timer)
|
|
if (bucket_pos > BUCKET_LEN)
|
|
break
|
|
|
|
timers_to_remove += timer //remove it from the big list once we are done
|
|
if (!timer.callBack || timer.spent)
|
|
continue
|
|
bucket_count++
|
|
var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
|
|
if (!bucket_head)
|
|
bucket_list[bucket_pos] = timer
|
|
timer.next = null
|
|
timer.prev = null
|
|
continue
|
|
|
|
if (!bucket_head.prev)
|
|
bucket_head.prev = bucket_head
|
|
timer.next = bucket_head
|
|
timer.prev = bucket_head.prev
|
|
timer.next.prev = timer
|
|
timer.prev.next = timer
|
|
|
|
processing = (alltimers - timers_to_remove)
|
|
|
|
|
|
/datum/controller/subsystem/timer/Recover()
|
|
processing |= SStimer.processing
|
|
hashes |= SStimer.hashes
|
|
timer_id_dict |= SStimer.timer_id_dict
|
|
bucket_list |= SStimer.bucket_list
|
|
|
|
/datum/timedevent
|
|
var/id
|
|
var/datum/callback/callBack
|
|
var/timeToRun
|
|
var/hash
|
|
var/list/flags
|
|
var/spent = FALSE //set to true right before running.
|
|
var/name //for easy debugging.
|
|
|
|
//cicular doublely linked list
|
|
var/datum/timedevent/next
|
|
var/datum/timedevent/prev
|
|
|
|
var/static/nextid = 1
|
|
|
|
/datum/timedevent/New(datum/callback/callBack, timeToRun, flags, hash)
|
|
id = TIMER_ID_NULL
|
|
src.callBack = callBack
|
|
src.timeToRun = timeToRun
|
|
src.flags = flags
|
|
src.hash = hash
|
|
|
|
if (flags & TIMER_UNIQUE)
|
|
SStimer.hashes[hash] = src
|
|
if (flags & TIMER_STOPPABLE)
|
|
do
|
|
if (nextid >= TIMER_ID_MAX)
|
|
nextid = 1
|
|
id = nextid++
|
|
while(SStimer.timer_id_dict["timerid" + num2text(id, 8)])
|
|
SStimer.timer_id_dict["timerid" + num2text(id, 8)] = src
|
|
|
|
name = "Timer: " + num2text(id, 8) + ", TTR: [timeToRun], Flags: [jointext(bitfield2list(flags, list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT")), ", ")], callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])"
|
|
|
|
if (callBack.object != GLOBAL_PROC)
|
|
LAZYADD(callBack.object.active_timers, src)
|
|
|
|
if (flags & TIMER_CLIENT_TIME)
|
|
//sorted insert
|
|
var/list/ctts = SStimer.clienttime_timers
|
|
var/cttl = length(ctts)
|
|
if(cttl)
|
|
var/datum/timedevent/Last = ctts[cttl]
|
|
if(Last.timeToRun >= timeToRun)
|
|
ctts += src
|
|
else if(cttl > 1)
|
|
for(var/I in cttl to 1)
|
|
var/datum/timedevent/E = ctts[I]
|
|
if(E.timeToRun <= timeToRun)
|
|
ctts.Insert(src, I)
|
|
break
|
|
else
|
|
ctts += src
|
|
return
|
|
|
|
//get the list of buckets
|
|
var/list/bucket_list = SStimer.bucket_list
|
|
//calculate our place in the bucket list
|
|
var/bucket_pos = BUCKET_POS(src)
|
|
//we are too far aways from needing to run to be in the bucket list, shift_buckets() will handle us.
|
|
if (bucket_pos > length(bucket_list))
|
|
SStimer.processing += src
|
|
return
|
|
//get the bucket for our tick
|
|
var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
|
|
SStimer.bucket_count++
|
|
//empty bucket, we will just add ourselves
|
|
if (!bucket_head)
|
|
bucket_list[bucket_pos] = src
|
|
if (bucket_pos < SStimer.practical_offset)
|
|
SStimer.practical_offset = bucket_pos
|
|
return
|
|
//other wise, lets do a simplified linked list add.
|
|
if (!bucket_head.prev)
|
|
bucket_head.prev = bucket_head
|
|
next = bucket_head
|
|
prev = bucket_head.prev
|
|
next.prev = src
|
|
prev.next = src
|
|
|
|
/datum/timedevent/Destroy()
|
|
..()
|
|
if (flags & TIMER_UNIQUE)
|
|
SStimer.hashes -= hash
|
|
|
|
|
|
if (callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers)
|
|
callBack.object.active_timers -= src
|
|
UNSETEMPTY(callBack.object.active_timers)
|
|
|
|
callBack = null
|
|
|
|
if (flags & TIMER_STOPPABLE)
|
|
SStimer.timer_id_dict -= "timerid[id]"
|
|
|
|
if (flags & TIMER_CLIENT_TIME)
|
|
SStimer.clienttime_timers -= src
|
|
return QDEL_HINT_IWILLGC
|
|
|
|
if (!spent)
|
|
if (prev == next && next)
|
|
next.prev = null
|
|
prev.next = null
|
|
else
|
|
if (prev)
|
|
prev.next = next
|
|
if (next)
|
|
next.prev = prev
|
|
|
|
var/bucketpos = BUCKET_POS(src)
|
|
var/datum/timedevent/buckethead
|
|
var/list/bucket_list = SStimer.bucket_list
|
|
|
|
if (bucketpos > 0 && bucketpos <= length(bucket_list))
|
|
buckethead = bucket_list[bucketpos]
|
|
SStimer.bucket_count--
|
|
else
|
|
SStimer.processing -= src
|
|
|
|
if (buckethead == src)
|
|
bucket_list[bucketpos] = next
|
|
else
|
|
if (prev && prev.next == src)
|
|
prev.next = next
|
|
if (next && next.prev == src)
|
|
next.prev = prev
|
|
next = null
|
|
prev = null
|
|
return QDEL_HINT_IWILLGC
|
|
|
|
/datum/timedevent/proc/getcallingtype()
|
|
. = "ERROR"
|
|
if (callBack.object == GLOBAL_PROC)
|
|
. = "GLOBAL PROC"
|
|
else
|
|
. = "[callBack.object.type]"
|
|
|
|
/proc/addtimer(datum/callback/callback, wait, flags)
|
|
if (!callback)
|
|
CRASH("addtimer called without a callback")
|
|
|
|
if (wait < 0)
|
|
crash_with("addtimer called with a negative wait. Converting to 0")
|
|
|
|
if (callback.object != GLOBAL_PROC && QDELETED(callback.object) && !QDESTROYING(callback.object))
|
|
crash_with("addtimer called with a callback assigned to a qdeleted object. In the future such timers will not be supported and may refuse to run or run with a 0 wait")
|
|
|
|
wait = max(wait, 0)
|
|
|
|
if(wait >= INFINITY)
|
|
CRASH("Attempted to create timer with INFINITY delay")
|
|
|
|
var/hash
|
|
|
|
if (flags & TIMER_UNIQUE)
|
|
var/list/hashlist
|
|
if(flags & TIMER_NO_HASH_WAIT)
|
|
hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, flags & TIMER_CLIENT_TIME)
|
|
else
|
|
hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, wait, flags & TIMER_CLIENT_TIME)
|
|
hashlist += callback.arguments
|
|
hash = hashlist.Join("|||||||")
|
|
|
|
var/datum/timedevent/hash_timer = SStimer.hashes[hash]
|
|
if(hash_timer)
|
|
if (hash_timer.spent) //it's pending deletion, pretend it doesn't exist.
|
|
hash_timer.hash = null
|
|
SStimer.hashes -= hash
|
|
else
|
|
|
|
if (flags & TIMER_OVERRIDE)
|
|
qdel(hash_timer)
|
|
else
|
|
if (hash_timer.flags & TIMER_STOPPABLE)
|
|
. = hash_timer.id
|
|
return
|
|
|
|
|
|
var/timeToRun = world.time + wait
|
|
if (flags & TIMER_CLIENT_TIME)
|
|
timeToRun = REALTIMEOFDAY + wait
|
|
|
|
var/datum/timedevent/timer = new(callback, timeToRun, flags, hash)
|
|
if (flags & TIMER_STOPPABLE)
|
|
return timer.id
|
|
|
|
/proc/deltimer(id)
|
|
if (!id)
|
|
return FALSE
|
|
|
|
if (id == TIMER_ID_NULL)
|
|
CRASH("Tried to delete a null timerid. Use the TIMER_STOPPABLE flag.")
|
|
|
|
if (!istext(id))
|
|
if (istype(id, /datum/timedevent))
|
|
qdel(id)
|
|
return TRUE
|
|
|
|
var/datum/timedevent/timer = SStimer.timer_id_dict["timerid[id]"]
|
|
if (timer && !timer.spent)
|
|
qdel(timer)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/proc/execute_and_deltimer(id)
|
|
if (!id)
|
|
return FALSE
|
|
|
|
if (id == TIMER_ID_NULL)
|
|
CRASH("Tried to delete a null timerid. Use the TIMER_STOPPABLE flag.")
|
|
|
|
var/datum/timedevent/timer = null
|
|
if (!istext(id) && istype(id, /datum/timedevent))
|
|
timer = id
|
|
else
|
|
timer = SStimer.timer_id_dict["timerid[id]"]
|
|
|
|
if(!timer)
|
|
return FALSE
|
|
|
|
var/datum/callback/callBack = timer.callBack
|
|
|
|
if (timer && !timer.spent)
|
|
callBack.Invoke()
|
|
timer.spent = TRUE
|
|
qdel(timer)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
#undef BUCKET_LEN
|
|
#undef BUCKET_POS
|