From 3f75fdb42df661f0e94f85885ec3d4fde5dfcd0f Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Wed, 11 Jan 2017 08:16:26 -0800 Subject: [PATCH 1/5] Addtimer refactor: Officially faster then byond's internal sleep system --- code/__DEFINES/MC.dm | 11 +- code/__DEFINES/math.dm | 5 + code/__HELPERS/cmp.dm | 6 +- code/__HELPERS/time.dm | 11 +- code/controllers/subsystem/garbage.dm | 2 + code/controllers/subsystem/timer.dm | 250 +++++++++++++++++++++----- 6 files changed, 239 insertions(+), 46 deletions(-) diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm index 2ea7258816b..87374b6672c 100644 --- a/code/__DEFINES/MC.dm +++ b/code/__DEFINES/MC.dm @@ -45,5 +45,12 @@ #define SS_POST_FIRE_TIMING 128 //Timing subsystem -#define TIMER_NORMAL "normal" -#define TIMER_UNIQUE "unique" \ No newline at end of file +//Don't run if there is an identical unique timer active +#define TIMER_UNIQUE 0x1 +//For unique timers: Replace the old timer rather then not start this one +#define TIMER_OVERRIDE 0x2 +//Timing should be based on how timing progresses on clients, not the sever. +// tracking this is more expensive, +// should only be used in conjuction with things that have to progress client side, such as animate() or sound() +#define TIMER_CLIENT_TIME 0x4 + diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm index d454c0a0676..4726d210012 100644 --- a/code/__DEFINES/math.dm +++ b/code/__DEFINES/math.dm @@ -17,3 +17,8 @@ #define TICK_USAGE_TO_MS(starting_tickusage) (TICK_DELTA_TO_MS(world.tick_usage-starting_tickusage)) #define PERCENT(val) (round(val*100, 0.1)) + +//time of day but automatically adjusts to the server going into the next day within the same round. +//for when you need a reliable time number that doesn't depend on byond time. +#define REALTIMEOFDAY (world.timeofday + (MIDNIGHT_ROLLOVER * MIDNIGHT_ROLLOVER_CHECK)) +#define MIDNIGHT_ROLLOVER_CHECK ( rollovercheck_last_timeofday != world.timeofday ? update_midnight_rollover() : midnight_rollovers ) diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index 1cad2a5e5e4..f963f09a35c 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -40,8 +40,12 @@ var/cmp_field = "name" /proc/cmp_subsystem_priority(datum/subsystem/a, datum/subsystem/b) return a.priority - b.priority +/proc/cmp_timer(datum/timedevent/a, datum/timedevent/b) + return a.timeToRun - b.timeToRun + /proc/cmp_clientcolour_priority(datum/client_colour/A, datum/client_colour/B) return B.priority - A.priority /proc/cmp_clockscripture_priority(datum/clockwork_scripture/A, datum/clockwork_scripture/B) - return initial(A.sort_priority) - initial(B.sort_priority) \ No newline at end of file + return initial(A.sort_priority) - initial(B.sort_priority) + diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index 8d000ce6a57..22ff1c2bef9 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -22,4 +22,13 @@ //returns timestamp in a sql and ISO 8601 friendly format /proc/SQLtime() - return time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") \ No newline at end of file + return time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") + + +/var/midnight_rollovers = 0 +/var/rollovercheck_last_timeofday = 0 +/proc/update_midnight_rollover() + if (world.timeofday < rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! + return midnight_rollovers++ + return midnight_rollovers + diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 73de5173c60..6d24e127294 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -224,6 +224,8 @@ 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) 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 11edde6a064..2313bbac961 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -1,90 +1,256 @@ +#define BUCKET_LEN (world.fps*10*60) //how many ticks should we keep in the bucket. (10 minutes worth) +#define BUCKET_POS(timer) (round((timer.timeToRun - SStimer.head_offset) / world.tick_lag) + 1) var/datum/subsystem/timer/SStimer /datum/subsystem/timer name = "Timer" - wait = 2 //SS_TICKER subsystem, so wait is in ticks + wait = 1 //SS_TICKER subsystem, so wait is in ticks init_order = 1 display_order = 3 - can_fire = 0 //start disabled - flags = SS_FIRE_IN_LOBBY|SS_TICKER|SS_POST_FIRE_TIMING|SS_NO_INIT + + flags = SS_FIRE_IN_LOBBY|SS_TICKER|SS_NO_INIT var/list/datum/timedevent/processing var/list/hashes + 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/list/buckets //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" + /datum/subsystem/timer/New() processing = list() hashes = list() + + timer_id_dict = list() + timer_src_dict = list() + + clienttime_timers = list() + NEW_SS_GLOBAL(SStimer) /datum/subsystem/timer/stat_entry(msg) - ..("P:[processing.len]") + ..("P:[length(processing)] H:[length(hashes)] C:[length(clienttime_timers)]") /datum/subsystem/timer/fire() - if(!processing.len) - can_fire = 0 //nothing to do, lets stop firing. - return - for(var/datum/timedevent/event in processing) - if(event.timeToRun <= world.time) - event.callback.InvokeAsync() - qdel(event) - if (MC_TICK_CHECK) - return + 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() + + 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() + + 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) + stack_trace("FAILURE! [event]||[event.timeToRun]|[qdeleted(event)]|||[world.time]||[head_offset]||[practical_offset]") + if (event.timeToRun > world.time) + stack_trace("HOLY JESUS! SHIT BE FUCKED [event]||[event.timeToRun]||[world.time]||[event.callback.object]||[event.callback.delegate]||[head_offset]||[practical_offset]") + qdel(event) //same as above + callback.InvokeAsync() + if (MC_TICK_CHECK) + return + event = buckets[practical_offset] + + practical_offset++ + +/datum/subsystem/timer/proc/refill_buckets() + sortTim(processing, /proc/cmp_timer) + src.buckets = new(BUCKET_LEN) + practical_offset = 1 + 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 + continue + + if (isnull(new_offset)) + new_offset = event.timeToRun + + var/bucket_pos = round((event.timeToRun - new_offset) / world.tick_lag) + 1 + if (bucket_pos > BUCKET_LEN) + break + + var/datum/timedevent/bucket_head = buckets[bucket_pos] + if (!bucket_head) + buckets[bucket_pos] = event + 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 + /datum/subsystem/timer/Recover() processing |= SStimer.processing hashes |= SStimer.hashes /datum/timedevent + var/id var/datum/callback/callback var/timeToRun - var/id + var/datum/timerid var/hash + var/list/flags + + //cicular doublely linked list + var/datum/timedevent/next + var/datum/timedevent/prev + var/static/nextid = 1 -/datum/timedevent/New() +/datum/timedevent/New(datum/callback/callback, timeToRun, flags, hash) id = nextid++ + src.callback = callback + src.timeToRun = timeToRun + src.flags = flags + + if (hash) + src.hash = hash + SStimer.hashes[hash] = src + + SStimer.timer_id_dict["timerid[id]"] = src + + if (callback.object != GLOBAL_PROC) + SStimer.timer_src_dict[callback.object] = src + + if (flags & TIMER_CLIENT_TIME) + SStimer.clienttime_timers += src + return + + SStimer.processing += src + + //get the list of buckets + var/list/buckets = SStimer.buckets + //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. + debug_admins("Timer insert: [bucket_pos] for [timeToRun] at [world.time] and [SStimer.head_offset]") + if (bucket_pos > length(buckets)) + 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 + 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 + next.prev = src + prev.next = src + /datum/timedevent/Destroy() + if (hash) + SStimer.hashes -= hash + + SStimer.timer_id_dict -= "timerid[id]" + + if (callback && callback.object != GLOBAL_PROC) + SStimer.timer_src_dict -= callback.object + + callback = null + + if (flags & TIMER_CLIENT_TIME) + SStimer.clienttime_timers -= src + return QDEL_HINT_IWILLGC + SStimer.processing -= src - SStimer.hashes -= hash + + 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/buckets = SStimer.buckets + + if (bucketpos > 0 && bucketpos <= length(buckets)) + buckethead = buckets[bucketpos] + if (buckethead == src) + buckets[bucketpos] = next + + prev = null + next = null + return QDEL_HINT_IWILLGC -/proc/addtimer(datum/callback/callback, wait, unique = TIMER_NORMAL) + +/proc/addtimer(datum/callback/callback, wait, flags) if (!callback) return - if (!SStimer.can_fire) - SStimer.can_fire = 1 - var/datum/timedevent/event = new() - event.callback = callback - event.timeToRun = world.time + wait - var/list/hashlist = args.Copy() - - hashlist[1] = "[callback.object](\ref[callback.object])" - hashlist.Insert(2, callback.delegate, callback.arguments) - event.hash = jointext(hashlist, null) - - if(unique == TIMER_UNIQUE) - var/datum/timedevent/hash_event = SStimer.hashes[event.hash] - if(hash_event) - return hash_event.id - - SStimer.hashes[event.hash] = event if (wait <= 0) callback.InvokeAsync() - SStimer.hashes -= event.hash return - // If we are unique (or we're not checking that), add the timer and return the id. - SStimer.processing += event + var/hash + + if (flags & TIMER_UNIQUE) + var/list/hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, wait, flags & TIMER_CLIENT_TIME) + hashlist += callback.arguments + hash = hashlist.Join("|||||||") + + var/datum/timedevent/hash_event = SStimer.hashes[hash] + if(hash_event) + if (flags & TIMER_OVERRIDE) + qdel(hash_event) + else + return hash_event.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 + /proc/deltimer(id) - for(var/datum/timedevent/event in SStimer.processing) - if(event.id == id) - qdel(event) - return 1 - return 0 + if (!id) + return FALSE + var/datum/timedevent/event = SStimer.timer_id_dict["timerid[id]"] + if (event) + qdel(event) + return TRUE + return FALSE From 4c55dbe2a62132c4bdccd2b8e8d32611a89e93d3 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Wed, 11 Jan 2017 08:20:52 -0800 Subject: [PATCH 2/5] Fixes bug with IWILLGC qdel hint This came up in testing, this doesn't set the object in such a way that qdeleted() returns true. --- code/controllers/subsystem/garbage.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 6d24e127294..1ce68f5a65c 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -170,7 +170,9 @@ var/datum/subsystem/garbage_collector/SSgarbage switch(hint) if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion. SSgarbage.QueueForQueuing(D) - if (QDEL_HINT_LETMELIVE, QDEL_HINT_IWILLGC) //qdel should let the object live after calling destory. + if (QDEL_HINT_IWILLGC) + return + if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory. if(!force) D.gc_destroyed = null //clear the gc variable (important!) return From 15e69deadcb239c4c769b13b8acd3154eef2a10a Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Wed, 11 Jan 2017 09:25:29 -0800 Subject: [PATCH 3/5] Remove a debug line and reword a crash --- code/controllers/subsystem/timer.dm | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 2313bbac961..3f345cad328 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -62,9 +62,7 @@ var/datum/subsystem/timer/SStimer while (event) var/datum/callback/callback = event.callback if (!callback) - stack_trace("FAILURE! [event]||[event.timeToRun]|[qdeleted(event)]|||[world.time]||[head_offset]||[practical_offset]") - if (event.timeToRun > world.time) - stack_trace("HOLY JESUS! SHIT BE FUCKED [event]||[event.timeToRun]||[world.time]||[event.callback.object]||[event.callback.delegate]||[head_offset]||[practical_offset]") + 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() if (MC_TICK_CHECK) @@ -151,7 +149,6 @@ var/datum/subsystem/timer/SStimer //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. - debug_admins("Timer insert: [bucket_pos] for [timeToRun] at [world.time] and [SStimer.head_offset]") if (bucket_pos > length(buckets)) return //get the bucket for our tick From c8d4ed5cfd8e2ba61b1826ddcedc57991b0620ed Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Mon, 16 Jan 2017 12:54:12 -0800 Subject: [PATCH 4/5] Some more improvements and overhead shaving. --- code/controllers/subsystem/garbage.dm | 6 +- code/controllers/subsystem/timer.dm | 280 +++++++++++++++++--------- 2 files changed, 186 insertions(+), 100 deletions(-) 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 From 01e8fc6fbc0d251d8e32420fa744fc7da114a7e3 Mon Sep 17 00:00:00 2001 From: MrStonedOne Date: Tue, 17 Jan 2017 00:12:29 -0800 Subject: [PATCH 5/5] Fixes timers cleaning up oddly in certain edge cases. --- code/controllers/subsystem/garbage.dm | 5 ++++- code/controllers/subsystem/timer.dm | 15 ++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 50a6892bd79..ece8d7c5678 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -228,7 +228,10 @@ var/datum/subsystem/garbage_collector/SSgarbage tag = null var/list/timers = active_timers active_timers = null - for(var/timer in timers) + for(var/thing in timers) + var/datum/timedevent/timer = thing + if (timer.spent) + continue qdel(timer) return QDEL_HINT_QUEUE diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 6a80c6bfc78..d8b283b0009 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -88,10 +88,9 @@ var/datum/subsystem/timer/SStimer break if (head.prev) head.prev.next = timer - if (timer) - if (timer.prev) - timer.prev.next = head - timer.prev = head.prev + if (timer.prev) + timer.prev.next = head + timer.prev = head.prev bucket_list[practical_offset] = timer break LOOP_OUTER while (timer && timer != head) @@ -244,7 +243,7 @@ var/datum/subsystem/timer/SStimer SStimer.hashes -= hash - if (callBack && callBack.object != GLOBAL_PROC && callBack.object.active_timers) + if (callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers) callBack.object.active_timers -= src UNSETEMPTY(callBack.object.active_timers) @@ -272,9 +271,10 @@ var/datum/subsystem/timer/SStimer if (bucketpos > 0 && bucketpos <= length(bucket_list)) buckethead = bucket_list[bucketpos] + SStimer.bucket_count-- else SStimer.processing -= src - SStimer.bucket_count-- + if (buckethead == src) bucket_list[bucketpos] = next else @@ -283,9 +283,6 @@ var/datum/subsystem/timer/SStimer if (next && next.prev == src) next.prev = null - prev = null - next = null - return QDEL_HINT_IWILLGC