Adds looping timers

and a requested binary search insert macro
This commit is contained in:
ninjanomnom
2018-07-08 17:52:38 -04:00
parent 4c6f2fad26
commit d32d2869c0
5 changed files with 139 additions and 106 deletions

View File

@@ -18,6 +18,9 @@
//To be used with TIMER_UNIQUE //To be used with TIMER_UNIQUE
//prevents distinguishing identical timers with the wait variable //prevents distinguishing identical timers with the wait variable
#define TIMER_NO_HASH_WAIT (1<<4) #define TIMER_NO_HASH_WAIT (1<<4)
//Loops the timer repeatedly until qdeleted
//In most cases you want a subsystem instead
#define TIMER_LOOP (1<<5)
#define TIMER_NO_INVOKE_WARNING 600 //number of byond ticks that are allowed to pass before the timer subsystem thinks it hung on something #define TIMER_NO_INVOKE_WARNING 600 //number of byond ticks that are allowed to pass before the timer subsystem thinks it hung on something

View File

@@ -21,6 +21,34 @@
#define LAZYCLEARLIST(L) if(L) L.Cut() #define LAZYCLEARLIST(L) if(L) L.Cut()
#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) #define SANITIZE_LIST(L) ( islist(L) ? L : list() )
// binary search sorted insert
// IN: Object to be inserted
// LIST: List to insert object into
// TYPECONT: The typepath of the contents of the list
// COMPARE: The variable on the objects to compare
#define BINARY_INSERT(IN, LIST, TYPECONT, COMPARE) \
var/__BIN_CTTL = length(LIST);\
if(!__BIN_CTTL) {\
LIST += IN;\
} else {\
var/__BIN_LEFT = 1;\
var/__BIN_RIGHT = __BIN_CTTL;\
var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
var/##TYPECONT/__BIN_ITEM;\
while(__BIN_LEFT < __BIN_RIGHT) {\
__BIN_ITEM = LIST[__BIN_MID];\
if(__BIN_ITEM.##COMPARE <= IN.##COMPARE) {\
__BIN_LEFT = __BIN_MID + 1;\
} else {\
__BIN_RIGHT = __BIN_MID;\
};\
__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
};\
__BIN_ITEM = LIST[__BIN_MID];\
__BIN_MID = __BIN_ITEM.##COMPARE > IN.##COMPARE ? __BIN_MID : __BIN_MID + 1;\
LIST.Insert(__BIN_MID, IN);\
}
//Returns a list in plain english as a string //Returns a list in plain english as a string
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) /proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
var/total = input.len var/total = input.len

View File

@@ -71,6 +71,7 @@ SUBSYSTEM_DEF(timer)
for(var/I in second_queue) for(var/I in second_queue)
log_world(get_timer_debug_string(I)) log_world(get_timer_debug_string(I))
var/cut_start_index = 1
var/next_clienttime_timer_index = 0 var/next_clienttime_timer_index = 0
var/len = length(clienttime_timers) var/len = length(clienttime_timers)
@@ -90,11 +91,17 @@ SUBSYSTEM_DEF(timer)
ctime_timer.spent = REALTIMEOFDAY ctime_timer.spent = REALTIMEOFDAY
callBack.InvokeAsync() callBack.InvokeAsync()
if(ctime_timer.flags & TIMER_LOOP)
ctime_timer.spent = 0
clienttime_timers.Insert(ctime_timer, 1)
cut_start_index++
else
qdel(ctime_timer) qdel(ctime_timer)
if (next_clienttime_timer_index) if (next_clienttime_timer_index)
clienttime_timers.Cut(1,next_clienttime_timer_index+1) clienttime_timers.Cut(cut_start_index,next_clienttime_timer_index+1)
if (MC_TICK_CHECK) if (MC_TICK_CHECK)
return return
@@ -197,8 +204,18 @@ SUBSYSTEM_DEF(timer)
bucket_count -= length(spent) bucket_count -= length(spent)
for (var/spent_timer in spent) for (var/i in spent)
qdel(spent_timer) var/datum/timedevent/qtimer = i
if(!(qtimer.flags & TIMER_LOOP))
qdel(qtimer)
else
qtimer.spent = 0
qtimer.bucketEject()
if(qtimer.flags & TIMER_CLIENT_TIME)
qtimer.timeToRun = REALTIMEOFDAY
else
qtimer.timeToRun = world.time + qtimer.wait
qtimer.bucketJoin()
spent.len = 0 spent.len = 0
@@ -294,6 +311,7 @@ SUBSYSTEM_DEF(timer)
var/id var/id
var/datum/callback/callBack var/datum/callback/callBack
var/timeToRun var/timeToRun
var/wait
var/hash var/hash
var/list/flags var/list/flags
var/spent = 0 //time we ran the timer. var/spent = 0 //time we ran the timer.
@@ -302,14 +320,19 @@ SUBSYSTEM_DEF(timer)
var/datum/timedevent/next var/datum/timedevent/next
var/datum/timedevent/prev var/datum/timedevent/prev
/datum/timedevent/New(datum/callback/callBack, timeToRun, flags, hash) /datum/timedevent/New(datum/callback/callBack, wait, flags, hash)
var/static/nextid = 1 var/static/nextid = 1
id = TIMER_ID_NULL id = TIMER_ID_NULL
src.callBack = callBack src.callBack = callBack
src.timeToRun = timeToRun src.wait = wait
src.flags = flags src.flags = flags
src.hash = hash src.hash = hash
if (flags & TIMER_CLIENT_TIME)
timeToRun = REALTIMEOFDAY + wait
else
timeToRun = world.time + wait
if (flags & TIMER_UNIQUE) if (flags & TIMER_UNIQUE)
SStimer.hashes[hash] = src SStimer.hashes[hash] = src
@@ -321,7 +344,7 @@ SUBSYSTEM_DEF(timer)
nextid++ nextid++
SStimer.timer_id_dict[id] = src SStimer.timer_id_dict[id] = src
name = "Timer: [id] (\ref[src]), 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(", ") : ""])" name = "Timer: [id] (\ref[src]), TTR: [timeToRun], Flags: [jointext(bitfield2list(flags, list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")), ", ")], callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])"
if ((timeToRun < world.time || timeToRun < SStimer.head_offset) && !(flags & TIMER_CLIENT_TIME)) if ((timeToRun < world.time || timeToRun < SStimer.head_offset) && !(flags & TIMER_CLIENT_TIME))
CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]") CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]")
@@ -329,60 +352,7 @@ SUBSYSTEM_DEF(timer)
if (callBack.object != GLOBAL_PROC && !QDESTROYING(callBack.object)) if (callBack.object != GLOBAL_PROC && !QDESTROYING(callBack.object))
LAZYADD(callBack.object.active_timers, src) LAZYADD(callBack.object.active_timers, src)
bucketJoin()
var/list/L
if (flags & TIMER_CLIENT_TIME)
L = SStimer.clienttime_timers
else if (timeToRun >= TIMER_MAX)
L = SStimer.second_queue
if (L)
//binary search sorted insert
var/cttl = length(L)
if(cttl)
var/left = 1
var/right = cttl
var/mid = (left+right) >> 1 //rounded divide by two for hedgehogs
var/datum/timedevent/item
while (left < right)
item = L[mid]
if (item.timeToRun <= timeToRun)
left = mid+1
else
right = mid
mid = (left+right) >> 1
item = L[mid]
mid = item.timeToRun > timeToRun ? mid : mid+1
L.Insert(mid, src)
else
L += 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)
//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
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() /datum/timedevent/Destroy()
..() ..()
@@ -406,31 +376,7 @@ SUBSYSTEM_DEF(timer)
if (!spent) if (!spent)
spent = world.time spent = world.time
var/bucketpos = BUCKET_POS(src) bucketEject()
var/datum/timedevent/buckethead
var/list/bucket_list = SStimer.bucket_list
if (bucketpos > 0)
buckethead = bucket_list[bucketpos]
if (buckethead == src)
bucket_list[bucketpos] = next
SStimer.bucket_count--
else if (timeToRun < TIMER_MAX || next || prev)
SStimer.bucket_count--
else
var/l = length(SStimer.second_queue)
SStimer.second_queue -= src
if (l == length(SStimer.second_queue))
SStimer.bucket_count--
if (prev == next && next)
next.prev = null
prev.next = null
else
if (prev)
prev.next = next
if (next)
next.prev = prev
else else
if (prev && prev.next == src) if (prev && prev.next == src)
prev.next = next prev.next = next
@@ -440,6 +386,64 @@ SUBSYSTEM_DEF(timer)
prev = null prev = null
return QDEL_HINT_IWILLGC return QDEL_HINT_IWILLGC
/datum/timedevent/proc/bucketEject()
var/bucketpos = BUCKET_POS(src)
var/list/bucket_list = SStimer.bucket_list
var/list/second_queue = SStimer.second_queue
var/datum/timedevent/buckethead
if(bucketpos > 0)
buckethead = bucket_list[bucketpos]
if(buckethead == src)
bucket_list[bucketpos] = next
SStimer.bucket_count--
else if(timeToRun < TIMER_MAX || next || prev)
SStimer.bucket_count--
else
var/l = length(second_queue)
second_queue -= src
if(l == length(second_queue))
SStimer.bucket_count--
if(prev != next)
prev.next = next
next.prev = prev
else
prev?.next = null
next?.prev = null
prev = next = null
/datum/timedevent/proc/bucketJoin()
var/list/L
if (flags & TIMER_CLIENT_TIME)
L = SStimer.clienttime_timers
else if (timeToRun >= TIMER_MAX)
L = SStimer.second_queue
if(L)
BINARY_INSERT(src, L, datum/timedevent, timeToRun)
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)
//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
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/proc/getcallingtype() /datum/timedevent/proc/getcallingtype()
. = "ERROR" . = "ERROR"
if (callBack.object == GLOBAL_PROC) if (callBack.object == GLOBAL_PROC)
@@ -488,12 +492,7 @@ SUBSYSTEM_DEF(timer)
else if(flags & TIMER_OVERRIDE) else if(flags & TIMER_OVERRIDE)
stack_trace("TIMER_OVERRIDE used without TIMER_UNIQUE") stack_trace("TIMER_OVERRIDE used without TIMER_UNIQUE")
var/datum/timedevent/timer = new(callback, wait, flags, hash)
var/timeToRun = world.time + wait
if (flags & TIMER_CLIENT_TIME)
timeToRun = REALTIMEOFDAY + wait
var/datum/timedevent/timer = new(callback, timeToRun, flags, hash)
return timer.id return timer.id
/proc/deltimer(id) /proc/deltimer(id)

View File

@@ -13,7 +13,7 @@
volume = 25 volume = 25
var/last_radiation var/last_radiation
/datum/looping_sound/geiger/get_sound(looped) /datum/looping_sound/geiger/get_sound(starttime)
var/danger var/danger
switch(last_radiation) switch(last_radiation)
if(RAD_BACKGROUND_RADIATION to RAD_GEIGER_LOW) if(RAD_BACKGROUND_RADIATION to RAD_GEIGER_LOW)
@@ -26,7 +26,7 @@
danger = 4 danger = 4
else else
return null return null
return ..(looped, mid_sounds[danger]) return ..(starttime, mid_sounds[danger])
/datum/looping_sound/geiger/stop() /datum/looping_sound/geiger/stop()
. = ..() . = ..()

View File

@@ -24,10 +24,11 @@
var/end_sound var/end_sound
var/chance var/chance
var/volume = 100 var/volume = 100
var/muted = TRUE
var/max_loops var/max_loops
var/direct var/direct
var/timerid
/datum/looping_sound/New(list/_output_atoms=list(), start_immediately=FALSE, _direct=FALSE) /datum/looping_sound/New(list/_output_atoms=list(), start_immediately=FALSE, _direct=FALSE)
if(!mid_sounds) if(!mid_sounds)
WARNING("A looping sound datum was created without sounds to play.") WARNING("A looping sound datum was created without sounds to play.")
@@ -47,25 +48,27 @@
/datum/looping_sound/proc/start(atom/add_thing) /datum/looping_sound/proc/start(atom/add_thing)
if(add_thing) if(add_thing)
output_atoms |= add_thing output_atoms |= add_thing
if(!muted) if(timerid)
return return
muted = FALSE
on_start() on_start()
/datum/looping_sound/proc/stop(atom/remove_thing) /datum/looping_sound/proc/stop(atom/remove_thing)
if(remove_thing) if(remove_thing)
output_atoms -= remove_thing output_atoms -= remove_thing
if(muted) if(!timerid)
return return
muted = TRUE on_stop()
deltimer(timerid)
timerid = null
/datum/looping_sound/proc/sound_loop(looped=0) /datum/looping_sound/proc/sound_loop(starttime)
if(muted || (max_loops && looped > max_loops)) if(max_loops && world.time >= starttime + mid_length * max_loops)
on_stop(looped) stop()
return return
if(!chance || prob(chance)) if(!chance || prob(chance))
play(get_sound(looped)) play(get_sound(starttime))
addtimer(CALLBACK(src, .proc/sound_loop, ++looped), mid_length) if(!timerid)
timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_STOPPABLE | TIMER_LOOP)
/datum/looping_sound/proc/play(soundfile) /datum/looping_sound/proc/play(soundfile)
var/list/atoms_cache = output_atoms var/list/atoms_cache = output_atoms
@@ -80,7 +83,7 @@
else else
playsound(thing, S, volume) playsound(thing, S, volume)
/datum/looping_sound/proc/get_sound(looped, _mid_sounds) /datum/looping_sound/proc/get_sound(starttime, _mid_sounds)
if(!_mid_sounds) if(!_mid_sounds)
. = mid_sounds . = mid_sounds
else else
@@ -95,6 +98,6 @@
start_wait = start_length start_wait = start_length
addtimer(CALLBACK(src, .proc/sound_loop), start_wait) addtimer(CALLBACK(src, .proc/sound_loop), start_wait)
/datum/looping_sound/proc/on_stop(looped) /datum/looping_sound/proc/on_stop()
if(end_sound) if(end_sound)
play(end_sound) play(end_sound)