diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index f9dbff89d76..3d56972b9f9 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -14,6 +14,10 @@ //prevents distinguishing identical timers with the wait variable #define TIMER_NO_HASH_WAIT 16 +//Loops the timer repeatedly until qdeleted +//In most cases you want a subsystem instead +#define TIMER_LOOP 32 + #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_ID_NULL -1 diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 071c1afa202..e5c5cc68b49 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -9,6 +9,35 @@ * Misc */ + // 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(var/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 21fea2ee419..e2539ebdf06 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) + 1) +#define BUCKET_POS(timer) ((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag) % 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 @@ -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,22 @@ 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(QDELETED(qtimer)) + bucket_count++ + continue + if(!(qtimer.flags & TIMER_LOOP)) + qdel(qtimer) + else + bucket_count++ + qtimer.spent = 0 + qtimer.bucketEject() + if(qtimer.flags & TIMER_CLIENT_TIME) + qtimer.timeToRun = REALTIMEOFDAY + qtimer.wait + else + qtimer.timeToRun = world.time + qtimer.wait + qtimer.bucketJoin() spent.len = 0 @@ -294,6 +315,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 +324,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,68 +348,15 @@ 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)]") - if(callBack.object != GLOBAL_PROC) + 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 +380,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 +390,66 @@ 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 + if(prev) + prev.next = null + if(next) + 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) @@ -452,13 +462,12 @@ SUBSYSTEM_DEF(timer) CRASH("addtimer called without a callback") if(wait < 0) - stack_trace("addtimer called with a negative wait. Converting to 0") + stack_trace("addtimer called with a negative wait. Converting to [world.tick_lag]") - //alot of things add short timers on themselves in their destroy, we ignore those cases - if(wait >= 1 && callback && callback.object && callback.object != GLOBAL_PROC && QDELETED(callback.object)) - stack_trace("addtimer called with a callback assigned to a qdeleted object") + if(callback.object != GLOBAL_PROC && QDELETED(callback.object) && !QDESTROYING(callback.object)) + stack_trace("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) + wait = max(CEILING(wait, world.tick_lag), world.tick_lag) if(wait >= INFINITY) CRASH("Attempted to create timer with INFINITY delay") @@ -468,9 +477,9 @@ SUBSYSTEM_DEF(timer) if(flags & TIMER_UNIQUE) var/list/hashlist if(flags & TIMER_NO_HASH_WAIT) - hashlist = list(callback.object, "([callback.object.UID()])", callback.delegate, flags & TIMER_CLIENT_TIME) + hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, flags & TIMER_CLIENT_TIME) else - hashlist = list(callback.object, "([callback.object.UID()])", callback.delegate, wait, flags & TIMER_CLIENT_TIME) + hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, wait, flags & TIMER_CLIENT_TIME) hashlist += callback.arguments hash = hashlist.Join("|||||||") @@ -486,13 +495,10 @@ SUBSYSTEM_DEF(timer) if(hash_timer.flags & TIMER_STOPPABLE) . = hash_timer.id return + 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) @@ -515,4 +521,4 @@ SUBSYSTEM_DEF(timer) #undef BUCKET_LEN #undef BUCKET_POS #undef TIMER_MAX -#undef TIMER_ID_MAX +#undef TIMER_ID_MAX \ No newline at end of file