diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 48b3260dd70..c2ffd331969 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -18,6 +18,9 @@ //To be used with TIMER_UNIQUE //prevents distinguishing identical timers with the wait variable #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 diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 54dd2a982cb..b25d1e58d5f 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -21,6 +21,34 @@ #define LAZYCLEARLIST(L) if(L) L.Cut() #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 /proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) var/total = input.len diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 89534f007d2..c504c382e57 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -71,6 +71,7 @@ SUBSYSTEM_DEF(timer) for(var/I in second_queue) log_world(get_timer_debug_string(I)) + var/cut_start_index = 1 var/next_clienttime_timer_index = 0 var/len = length(clienttime_timers) @@ -90,11 +91,17 @@ SUBSYSTEM_DEF(timer) ctime_timer.spent = REALTIMEOFDAY callBack.InvokeAsync() - qdel(ctime_timer) + + if(ctime_timer.flags & TIMER_LOOP) + ctime_timer.spent = 0 + clienttime_timers.Insert(ctime_timer, 1) + cut_start_index++ + else + qdel(ctime_timer) 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) return @@ -197,8 +204,18 @@ SUBSYSTEM_DEF(timer) bucket_count -= length(spent) - for (var/spent_timer in spent) - qdel(spent_timer) + for (var/i in spent) + 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 @@ -294,6 +311,7 @@ SUBSYSTEM_DEF(timer) var/id var/datum/callback/callBack var/timeToRun + var/wait var/hash var/list/flags var/spent = 0 //time we ran the timer. @@ -302,14 +320,19 @@ SUBSYSTEM_DEF(timer) var/datum/timedevent/next 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 id = TIMER_ID_NULL src.callBack = callBack - src.timeToRun = timeToRun + src.wait = wait src.flags = flags src.hash = hash + if (flags & TIMER_CLIENT_TIME) + timeToRun = REALTIMEOFDAY + wait + else + timeToRun = world.time + wait + if (flags & TIMER_UNIQUE) SStimer.hashes[hash] = src @@ -321,7 +344,7 @@ SUBSYSTEM_DEF(timer) nextid++ 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)) 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)) LAZYADD(callBack.object.active_timers, src) - - 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 + bucketJoin() /datum/timedevent/Destroy() ..() @@ -406,31 +376,7 @@ SUBSYSTEM_DEF(timer) if (!spent) spent = world.time - var/bucketpos = BUCKET_POS(src) - 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 + bucketEject() else if (prev && prev.next == src) prev.next = next @@ -440,6 +386,64 @@ SUBSYSTEM_DEF(timer) prev = null 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() . = "ERROR" if (callBack.object == GLOBAL_PROC) @@ -488,12 +492,7 @@ SUBSYSTEM_DEF(timer) else if(flags & TIMER_OVERRIDE) stack_trace("TIMER_OVERRIDE used without TIMER_UNIQUE") - - var/timeToRun = world.time + wait - if (flags & TIMER_CLIENT_TIME) - timeToRun = REALTIMEOFDAY + wait - - var/datum/timedevent/timer = new(callback, timeToRun, flags, hash) + var/datum/timedevent/timer = new(callback, wait, flags, hash) return timer.id /proc/deltimer(id) diff --git a/code/datums/looping_sounds/item_sounds.dm b/code/datums/looping_sounds/item_sounds.dm index d650f7294a8..e729ed22dd4 100644 --- a/code/datums/looping_sounds/item_sounds.dm +++ b/code/datums/looping_sounds/item_sounds.dm @@ -13,7 +13,7 @@ volume = 25 var/last_radiation -/datum/looping_sound/geiger/get_sound(looped) +/datum/looping_sound/geiger/get_sound(starttime) var/danger switch(last_radiation) if(RAD_BACKGROUND_RADIATION to RAD_GEIGER_LOW) @@ -26,7 +26,7 @@ danger = 4 else return null - return ..(looped, mid_sounds[danger]) + return ..(starttime, mid_sounds[danger]) /datum/looping_sound/geiger/stop() . = ..() diff --git a/code/datums/looping_sounds/looping_sound.dm b/code/datums/looping_sounds/looping_sound.dm index aed7c3ace89..2eba36dc90d 100644 --- a/code/datums/looping_sounds/looping_sound.dm +++ b/code/datums/looping_sounds/looping_sound.dm @@ -24,10 +24,11 @@ var/end_sound var/chance var/volume = 100 - var/muted = TRUE var/max_loops var/direct + var/timerid + /datum/looping_sound/New(list/_output_atoms=list(), start_immediately=FALSE, _direct=FALSE) if(!mid_sounds) WARNING("A looping sound datum was created without sounds to play.") @@ -47,25 +48,27 @@ /datum/looping_sound/proc/start(atom/add_thing) if(add_thing) output_atoms |= add_thing - if(!muted) + if(timerid) return - muted = FALSE on_start() /datum/looping_sound/proc/stop(atom/remove_thing) if(remove_thing) output_atoms -= remove_thing - if(muted) + if(!timerid) return - muted = TRUE + on_stop() + deltimer(timerid) + timerid = null -/datum/looping_sound/proc/sound_loop(looped=0) - if(muted || (max_loops && looped > max_loops)) - on_stop(looped) +/datum/looping_sound/proc/sound_loop(starttime) + if(max_loops && world.time >= starttime + mid_length * max_loops) + stop() return if(!chance || prob(chance)) - play(get_sound(looped)) - addtimer(CALLBACK(src, .proc/sound_loop, ++looped), mid_length) + play(get_sound(starttime)) + if(!timerid) + timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_STOPPABLE | TIMER_LOOP) /datum/looping_sound/proc/play(soundfile) var/list/atoms_cache = output_atoms @@ -80,7 +83,7 @@ else 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) . = mid_sounds else @@ -95,6 +98,6 @@ start_wait = start_length addtimer(CALLBACK(src, .proc/sound_loop), start_wait) -/datum/looping_sound/proc/on_stop(looped) +/datum/looping_sound/proc/on_stop() if(end_sound) play(end_sound) \ No newline at end of file