Addtimer refactor: Officially faster then byond's internal sleep system

This commit is contained in:
MrStonedOne
2017-01-16 12:56:48 -08:00
parent 1620bec15d
commit 3f75fdb42d
6 changed files with 239 additions and 46 deletions
+9 -2
View File
@@ -45,5 +45,12 @@
#define SS_POST_FIRE_TIMING 128
//Timing subsystem
#define TIMER_NORMAL "normal"
#define TIMER_UNIQUE "unique"
//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
+5
View File
@@ -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 )
+5 -1
View File
@@ -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)
return initial(A.sort_priority) - initial(B.sort_priority)
+10 -1
View File
@@ -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")
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
+2
View File
@@ -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.
+208 -42
View File
@@ -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