diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 1ce68f5a65c..50a6892bd79 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -226,8 +226,10 @@ var/datum/subsystem/garbage_collector/SSgarbage // Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE. /datum/proc/Destroy(force=FALSE) tag = null - for(var/thing in SStimer.timer_src_dict[src]) - qdel(thing) + var/list/timers = active_timers + active_timers = null + for(var/timer in timers) + qdel(timer) return QDEL_HINT_QUEUE /datum/var/gc_destroyed //Time when this object was destroyed. diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 3f345cad328..6a80c6bfc78 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -1,4 +1,4 @@ -#define BUCKET_LEN (world.fps*10*60) //how many ticks should we keep in the bucket. (10 minutes worth) +#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) + 1) var/datum/subsystem/timer/SStimer @@ -16,13 +16,12 @@ var/datum/subsystem/timer/SStimer 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/buckets //list of buckets, each bucket holds every timer that has to run that byond tick. + var/list/bucket_list //list of buckets, each bucket holds every timer that has to run that byond tick. var/list/timer_id_dict //list of all active timers assoicated to their timer id (for easy lookup) - var/list/timer_src_dict //list of everything with an active timer attached to it as key with a list of those timers as the value (for destroy) - var/list/clienttime_timers //special snowflake timers that run on fancy pansy "client time" @@ -31,7 +30,6 @@ var/datum/subsystem/timer/SStimer hashes = list() timer_id_dict = list() - timer_src_dict = list() clienttime_timers = list() @@ -39,83 +37,155 @@ var/datum/subsystem/timer/SStimer /datum/subsystem/timer/stat_entry(msg) - ..("P:[length(processing)] H:[length(hashes)] C:[length(clienttime_timers)]") + ..("B:[bucket_count] P:[length(processing)] H:[length(hashes)] C:[length(clienttime_timers)]") /datum/subsystem/timer/fire() if (length(clienttime_timers)) for (var/thing in clienttime_timers) - var/datum/timedevent/event = thing - if (event.timeToRun <= REALTIMEOFDAY) - var/datum/callback/callback = event.callback - qdel(event) //this is right, we delete the event before running it so that stack overflows don't cause us to repeatively run the same event - callback.InvokeAsync() + var/datum/timedevent/timer = thing + if (timer.spent) + qdel(timer) + continue + if (timer.timeToRun <= REALTIMEOFDAY) + var/datum/callback/callBack = timer.callBack + timer.spent = TRUE + callBack.InvokeAsync() + qdel(timer) if (MC_TICK_CHECK) return - if (head_offset + (world.tick_lag * BUCKET_LEN) < world.time || length(src.buckets) != BUCKET_LEN || world.tick_lag != bucket_resolution) - refill_buckets() + if (head_offset + (world.tick_lag * BUCKET_LEN) < world.time || length(src.bucket_list) != BUCKET_LEN || world.tick_lag != bucket_resolution) + shift_buckets() - var/list/buckets = src.buckets - while (practical_offset <= BUCKET_LEN && head_offset + (practical_offset*world.tick_lag) <= world.time && !MC_TICK_CHECK) - var/datum/timedevent/event = buckets[practical_offset] - while (event) - var/datum/callback/callback = event.callback - if (!callback) - CRASH("Invalid event: event.timeToRun=[event.timeToRun]||qdeleted(event)=[qdeleted(event)]||world.time=[world.time]||head_offset=[head_offset]||practical_offset=[practical_offset]") - qdel(event) //same as above - callback.InvokeAsync() + var/list/bucket_list = src.bucket_list + var/static/list/spent = list() + LOOP_OUTER + while (practical_offset <= BUCKET_LEN && head_offset + (practical_offset*world.tick_lag) <= world.time && !MC_TICK_CHECK) + var/datum/timedevent/head = bucket_list[practical_offset] + if (!head) + practical_offset++ + if (MC_TICK_CHECK) + break + continue + var/datum/timedevent/timer = head + do + var/datum/callback/callBack = timer.callBack + if (!callBack || timer.spent) + qdel(timer) + bucket_resolution = null //force bucket recreation + CRASH("Invalid 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]") + + spent += timer + timer.spent = TRUE + + callBack.InvokeAsync() + + timer = timer.next + + if (MC_TICK_CHECK) + if (!timer || timer == head) + break + if (head.prev) + head.prev.next = timer + if (timer) + if (timer.prev) + timer.prev.next = head + timer.prev = head.prev + bucket_list[practical_offset] = timer + break LOOP_OUTER + while (timer && timer != head) + + bucket_list[practical_offset++] = null if (MC_TICK_CHECK) - return - event = buckets[practical_offset] + break - practical_offset++ + timer_id_dict -= spent + bucket_count -= length(spent) + for (var/timer in spent) + qdel(timer) + spent.len = 0 -/datum/subsystem/timer/proc/refill_buckets() - sortTim(processing, /proc/cmp_timer) - src.buckets = new(BUCKET_LEN) + +/datum/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 = new(BUCKET_LEN) + src.bucket_list = bucket_list //cache update practical_offset = 1 + bucket_count = 0 + head_offset = world.time bucket_resolution = world.tick_lag - var/list/buckets = src.buckets - var/new_offset - for (var/thing in processing) - var/datum/timedevent/event = thing - if (!event) - processing -= event + + 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 - if (isnull(new_offset)) - new_offset = event.timeToRun - - var/bucket_pos = round((event.timeToRun - new_offset) / world.tick_lag) + 1 + var/bucket_pos = BUCKET_POS(timer) if (bucket_pos > BUCKET_LEN) break - var/datum/timedevent/bucket_head = buckets[bucket_pos] + 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) - buckets[bucket_pos] = event + bucket_list[bucket_pos] = timer + timer.next = null + timer.prev = null continue if (!bucket_head.prev) bucket_head.prev = bucket_head - event.next = bucket_head - event.prev = bucket_head.prev - event.next.prev = event - event.prev.next = event - head_offset = new_offset + timer.next = bucket_head + timer.prev = bucket_head.prev + timer.next.prev = timer + timer.prev.next = timer + + processing = (alltimers - timers_to_remove) /datum/subsystem/timer/Recover() processing |= SStimer.processing hashes |= SStimer.hashes + timer_id_dict |= SStimer.timer_id_dict + bucket_list |= SStimer.bucket_list +/datum/var/list/active_timers /datum/timedevent var/id - var/datum/callback/callback + var/datum/callback/callBack var/timeToRun - var/datum/timerid var/hash var/list/flags + var/spent = FALSE //set to true right before running. //cicular doublely linked list var/datum/timedevent/next @@ -123,9 +193,9 @@ var/datum/subsystem/timer/SStimer var/static/nextid = 1 -/datum/timedevent/New(datum/callback/callback, timeToRun, flags, hash) +/datum/timedevent/New(datum/callback/callBack, timeToRun, flags, hash) id = nextid++ - src.callback = callback + src.callBack = callBack src.timeToRun = timeToRun src.flags = flags @@ -135,36 +205,36 @@ var/datum/subsystem/timer/SStimer SStimer.timer_id_dict["timerid[id]"] = src - if (callback.object != GLOBAL_PROC) - SStimer.timer_src_dict[callback.object] = src + if (callBack.object != GLOBAL_PROC) + LAZYINITLIST(callBack.object.active_timers) + callBack.object.active_timers += src if (flags & TIMER_CLIENT_TIME) SStimer.clienttime_timers += src return - SStimer.processing += src - //get the list of buckets - var/list/buckets = SStimer.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, refill_buckets() will handle us. - if (bucket_pos > length(buckets)) + //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/event = buckets[bucket_pos] - //empty bucket, we will just add ourself - if (!event) - buckets[bucket_pos] = src + 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 (!event.prev) - event.prev = event - - next = event - prev = event.prev + if (!bucket_head.prev) + bucket_head.prev = bucket_head + next = bucket_head + prev = bucket_head.prev next.prev = src prev.next = src @@ -173,37 +243,45 @@ var/datum/subsystem/timer/SStimer if (hash) SStimer.hashes -= hash - SStimer.timer_id_dict -= "timerid[id]" - if (callback && callback.object != GLOBAL_PROC) - SStimer.timer_src_dict -= callback.object + if (callBack && callBack.object != GLOBAL_PROC && callBack.object.active_timers) + callBack.object.active_timers -= src + UNSETEMPTY(callBack.object.active_timers) - callback = null + callBack = null if (flags & TIMER_CLIENT_TIME) SStimer.clienttime_timers -= src return QDEL_HINT_IWILLGC - SStimer.processing -= src + if (!spent) + if (prev == next && next) + next.prev = null + prev.next = null + else + if (prev) + prev.next = next + if (next) + next.prev = prev - if (prev == next && next) - next.prev = null - prev.next = null + SStimer.timer_id_dict -= "timerid[id]" + + 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] + else + SStimer.processing -= src + SStimer.bucket_count-- + if (buckethead == src) + bucket_list[bucketpos] = next else - if (prev) - prev.next = next - - if (next) - next.prev = prev - - var/bucketpos = BUCKET_POS(src) - var/datum/timedevent/buckethead - var/list/buckets = SStimer.buckets - - if (bucketpos > 0 && bucketpos <= length(buckets)) - buckethead = buckets[bucketpos] - if (buckethead == src) - buckets[bucketpos] = next + if (prev && prev.next == src) + prev.next = null + if (next && next.prev == src) + next.prev = null prev = null next = null @@ -226,28 +304,34 @@ var/datum/subsystem/timer/SStimer hashlist += callback.arguments hash = hashlist.Join("|||||||") - var/datum/timedevent/hash_event = SStimer.hashes[hash] - if(hash_event) + var/datum/timedevent/hash_timer = SStimer.hashes[hash] + if(hash_timer) if (flags & TIMER_OVERRIDE) - qdel(hash_event) + qdel(hash_timer) else - return hash_event.id + return hash_timer.id var/timeToRun = world.time + wait if (flags & TIMER_CLIENT_TIME) timeToRun = REALTIMEOFDAY + wait - var/datum/timedevent/event = new(callback, timeToRun, flags, hash) - - - return event.id + var/datum/timedevent/timer = new(callback, timeToRun, flags, hash) + return timer.id /proc/deltimer(id) if (!id) return FALSE - var/datum/timedevent/event = SStimer.timer_id_dict["timerid[id]"] - if (event) - qdel(event) + 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 + + +#undef BUCKET_LEN +#undef BUCKET_POS \ No newline at end of file