Critical Fixes

- Fixes HREF Exploit
- Replaces Timer Subsystem with a Bay port in a desperate attempt to fix the timer SS crashes
- Port of https://github.com/PolarisSS13/Polaris/pull/6180 and https://github.com/PolarisSS13/Polaris/pull/6178
- Fixes #5272
This commit is contained in:
Unknown
2019-05-26 23:58:56 -04:00
parent 9f1d75cdd0
commit d13e8ee784
3 changed files with 202 additions and 277 deletions

View File

@@ -1,4 +1,5 @@
#define MC_TICK_CHECK ( ( TICK_USAGE > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 ) #define MC_TICK_CHECK ( ( TICK_USAGE > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 )
#define GAME_STATE 2 ** (Master.current_runlevel - 1)
#define MC_SPLIT_TICK_INIT(phase_count) var/original_tick_limit = Master.current_ticklimit; var/split_tick_phases = ##phase_count #define MC_SPLIT_TICK_INIT(phase_count) var/original_tick_limit = Master.current_ticklimit; var/split_tick_phases = ##phase_count
#define MC_SPLIT_TICK \ #define MC_SPLIT_TICK \
@@ -55,6 +56,30 @@
/// This flag overrides SS_KEEP_TIMING /// This flag overrides SS_KEEP_TIMING
#define SS_POST_FIRE_TIMING 64 #define SS_POST_FIRE_TIMING 64
// -- SStimer stuff --
//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
//Timer can be stopped using deltimer()
#define TIMER_STOPPABLE 0x8
//To be used with TIMER_UNIQUE
//prevents distinguishing identical timers with the wait variable
#define TIMER_NO_HASH_WAIT 0x10
//number of byond ticks that are allowed to pass before the timer subsystem thinks it hung on something
#define TIMER_NO_INVOKE_WARNING 600
#define TIMER_ID_NULL -1
//! SUBSYSTEM STATES //! SUBSYSTEM STATES
#define SS_IDLE 0 /// aint doing shit. #define SS_IDLE 0 /// aint doing shit.
#define SS_QUEUED 1 /// queued to run #define SS_QUEUED 1 /// queued to run

View File

@@ -1,21 +1,19 @@
#define BUCKET_LEN (world.fps*1*60) //how many ticks should we keep in the bucket. (1 minutes worth) #define BUCKET_LEN (round(10*(60/world.tick_lag), 1)) //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)||BUCKET_LEN) #define BUCKET_POS(timer) (round((timer.timeToRun - SStimer.head_offset) / world.tick_lag) + 1)
#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 #define TIMER_ID_MAX (2**24) //max float with integer precision
SUBSYSTEM_DEF(timer) SUBSYSTEM_DEF(timer)
name = "Timer" name = "Timer"
wait = 1 //SS_TICKER subsystem, so wait is in ticks wait = 1 //SS_TICKER subsystem, so wait is in ticks
priority = FIRE_PRIORITY_TIMERS //VOREStation Emergency Edit init_order = 1
init_order = INIT_ORDER_TIMER
flags = SS_TICKER|SS_NO_INIT flags = SS_TICKER|SS_NO_INIT
var/list/datum/timedevent/second_queue = list() //awe, yes, you've had first queue, but what about second queue? var/list/datum/timedevent/processing = list()
var/list/hashes = list() var/list/hashes = list()
var/head_offset = 0 //world.time of the first entry in the the bucket. var/head_offset = 0 //world.time of the first entry in the the bucket.
var/practical_offset = 1 //index of the first non-empty item in 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_resolution = 0 //world.tick_lag the bucket was designed for
var/bucket_count = 0 //how many timers are in the buckets var/bucket_count = 0 //how many timers are in the buckets
@@ -29,31 +27,31 @@ SUBSYSTEM_DEF(timer)
var/static/last_invoke_warning = 0 var/static/last_invoke_warning = 0
var/static/bucket_auto_reset = TRUE var/static/bucket_auto_reset = TRUE
/datum/controller/subsystem/timer/PreInit() var/static/times_flushed = 0
bucket_list.len = BUCKET_LEN var/static/times_crashed = 0
head_offset = world.time
bucket_resolution = world.tick_lag
/datum/controller/subsystem/timer/stat_entry(msg) /datum/controller/subsystem/timer/stat_entry(msg)
..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)]") ..("B:[bucket_count] P:[length(processing)] H:[length(hashes)] C:[length(clienttime_timers)][times_crashed ? " F:[times_crashed]" : ""]")
/datum/controller/subsystem/timer/fire(resumed = FALSE) /datum/controller/subsystem/timer/fire(resumed = FALSE)
var/lit = last_invoke_tick var/lit = last_invoke_tick
var/last_check = world.time - TICKS2DS(BUCKET_LEN*1.5) var/last_check = world.time - TIMER_NO_INVOKE_WARNING
var/list/bucket_list = src.bucket_list var/list/bucket_list = src.bucket_list
var/static/list/spent = list()
if(!bucket_count) if(!bucket_count)
last_invoke_tick = world.time last_invoke_tick = world.time
if(lit && lit < last_check && head_offset < last_check && last_invoke_warning < last_check) if(lit && lit < last_check && last_invoke_warning < last_check)
last_invoke_warning = world.time last_invoke_warning = world.time
var/msg = "No regular timers processed in the last [BUCKET_LEN*1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!" var/msg = "No regular timers processed in the last [TIMER_NO_INVOKE_WARNING] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!"
times_crashed++
message_admins(msg) message_admins(msg)
WARNING(msg) WARNING(msg)
if(bucket_auto_reset) if(bucket_auto_reset)
bucket_resolution = 0 bucket_resolution = 0
log_world("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") log_world("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset], times_flushed: [times_flushed], length(spent): [length(spent)]")
for (var/i in 1 to length(bucket_list)) for (var/i in 1 to length(bucket_list))
var/datum/timedevent/bucket_head = bucket_list[i] var/datum/timedevent/bucket_head = bucket_list[i]
if (!bucket_head) if (!bucket_head)
@@ -68,174 +66,85 @@ SUBSYSTEM_DEF(timer)
bucket_node = bucket_node.next bucket_node = bucket_node.next
anti_loop_check-- anti_loop_check--
while(bucket_node && bucket_node != bucket_head && anti_loop_check) while(bucket_node && bucket_node != bucket_head && anti_loop_check)
log_world("Active timers in the second_queue queue:") log_world("Active timers in the processing queue:")
for(var/I in second_queue) for(var/I in processing)
log_world(get_timer_debug_string(I)) log_world(get_timer_debug_string(I))
var/cut_start_index = 1 while(length(clienttime_timers))
var/next_clienttime_timer_index = 0 var/datum/timedevent/ctime_timer = clienttime_timers[clienttime_timers.len]
var/len = length(clienttime_timers) if (ctime_timer.timeToRun <= REALTIMEOFDAY)
--clienttime_timers.len
for (next_clienttime_timer_index in 1 to len) var/datum/callback/callBack = ctime_timer.callBack
if (MC_TICK_CHECK) ctime_timer.spent = TRUE
next_clienttime_timer_index-- callBack.InvokeAsync()
break
var/datum/timedevent/ctime_timer = clienttime_timers[next_clienttime_timer_index]
if (ctime_timer.timeToRun > REALTIMEOFDAY)
next_clienttime_timer_index--
break
var/datum/callback/callBack = ctime_timer.callBack
if (!callBack)
clienttime_timers.Cut(next_clienttime_timer_index,next_clienttime_timer_index+1)
CRASH("Invalid timer: [get_timer_debug_string(ctime_timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset], REALTIMEOFDAY: [REALTIMEOFDAY]")
ctime_timer.spent = REALTIMEOFDAY
callBack.InvokeAsync()
if(ctime_timer.flags & TIMER_LOOP)
ctime_timer.spent = 0
clienttime_timers.Insert(ctime_timer, 1)
cut_start_index++
else
qdel(ctime_timer) qdel(ctime_timer)
else
break //None of the rest are ready to run
if (MC_TICK_CHECK)
return
if (next_clienttime_timer_index)
clienttime_timers.Cut(cut_start_index,next_clienttime_timer_index+1)
if (MC_TICK_CHECK)
return
var/static/list/spent = list()
var/static/datum/timedevent/timer var/static/datum/timedevent/timer
if (practical_offset > BUCKET_LEN) var/static/datum/timedevent/head
head_offset += TICKS2DS(BUCKET_LEN)
practical_offset = 1
resumed = FALSE
if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution)) if (practical_offset > BUCKET_LEN || (!resumed && length(bucket_list) != BUCKET_LEN || world.tick_lag != bucket_resolution))
reset_buckets() shift_buckets()
bucket_list = src.bucket_list bucket_list = src.bucket_list
resumed = FALSE resumed = FALSE
if (!resumed) if (!resumed)
timer = null timer = null
head = null
while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset-1)*world.tick_lag) <= world.time) 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 (!timer || !head || timer == head) if (!timer || !head || timer == head)
head = bucket_list[practical_offset] head = bucket_list[practical_offset]
if (!head)
practical_offset++
if (MC_TICK_CHECK)
break
continue
timer = head timer = head
while (timer) do
var/datum/callback/callBack = timer.callBack var/datum/callback/callBack = timer.callBack
if (!callBack) if (!callBack)
qdel(timer)
bucket_resolution = null //force bucket recreation bucket_resolution = null //force bucket recreation
CRASH("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") CRASH("Invalid timer: [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]")
if (!timer.spent) if (!timer.spent)
spent += timer spent += timer
timer.spent = world.time timer.spent = TRUE
callBack.InvokeAsync() callBack.InvokeAsync()
last_invoke_tick = world.time last_invoke_tick = world.time
timer = timer.next
if (MC_TICK_CHECK) if (MC_TICK_CHECK)
return return
while (timer && timer != head)
timer = timer.next
if (timer == head)
break
bucket_list[practical_offset++] = null
//we freed up a bucket, lets see if anything in second_queue needs to be shifted to that bucket.
var/i = 0
var/L = length(second_queue)
for (i in 1 to L)
timer = second_queue[i]
if (timer.timeToRun >= TIMER_MAX)
i--
break
if (timer.timeToRun < head_offset)
bucket_resolution = null //force bucket recreation
CRASH("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
if (timer.callBack && !timer.spent)
timer.callBack.InvokeAsync()
spent += timer
bucket_count++
else if(!QDELETED(timer))
qdel(timer)
continue
if (timer.timeToRun < head_offset + TICKS2DS(practical_offset-1))
bucket_resolution = null //force bucket recreation
CRASH("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
if (timer.callBack && !timer.spent)
timer.callBack.InvokeAsync()
spent += timer
bucket_count++
else if(!QDELETED(timer))
qdel(timer)
continue
bucket_count++
var/bucket_pos = max(1, BUCKET_POS(timer))
var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
if (!bucket_head)
bucket_list[bucket_pos] = timer
timer.next = null
timer.prev = null
continue
if (!bucket_head.prev)
bucket_head.prev = bucket_head
timer.next = bucket_head
timer.prev = bucket_head.prev
timer.next.prev = timer
timer.prev.next = timer
if (i)
second_queue.Cut(1, i+1)
timer = null timer = null
bucket_list[practical_offset++] = null
if (MC_TICK_CHECK)
return
times_flushed++
bucket_count -= length(spent) bucket_count -= length(spent)
for (var/i in spent) for (var/spent_timer in spent)
var/datum/timedevent/qtimer = i qdel(spent_timer)
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 spent.len = 0
//formated this way to be runtime resistant
/datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE) /datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE)
. = "Timer: [TE]" . = "Timer: [TE]"
. += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]" . += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]"
if(TE.spent) if(TE.spent)
. += ", SPENT([TE.spent])" . += ", SPENT"
if(QDELETED(TE)) if(QDELETED(TE))
. += ", QDELETED" . += ", QDELETED"
if(!TE.callBack)
. += ", NO CALLBACK"
/datum/controller/subsystem/timer/proc/reset_buckets() /datum/controller/subsystem/timer/proc/shift_buckets()
var/list/bucket_list = src.bucket_list var/list/bucket_list = src.bucket_list
var/list/alltimers = list() var/list/alltimers = list()
//collect the timers currently in the bucket //collect the timers currently in the bucket
@@ -256,37 +165,33 @@ SUBSYSTEM_DEF(timer)
head_offset = world.time head_offset = world.time
bucket_resolution = world.tick_lag bucket_resolution = world.tick_lag
alltimers += second_queue alltimers += processing
if (!length(alltimers)) if (!length(alltimers))
return return
sortTim(alltimers, .proc/cmp_timer) sortTim(alltimers, /proc/cmp_timer)
var/datum/timedevent/head = alltimers[1] var/datum/timedevent/head = alltimers[1]
if (head.timeToRun < head_offset) if (head.timeToRun < head_offset)
head_offset = head.timeToRun head_offset = head.timeToRun
var/new_bucket_count var/list/timers_to_remove = list()
var/i = 1
for (i in 1 to length(alltimers)) for (var/thing in alltimers)
var/datum/timedevent/timer = alltimers[1] var/datum/timedevent/timer = thing
if (!timer) if (!timer)
timers_to_remove += timer
continue continue
var/bucket_pos = BUCKET_POS(timer) var/bucket_pos = BUCKET_POS(timer)
if (timer.timeToRun >= TIMER_MAX) if (bucket_pos > BUCKET_LEN)
i--
break break
timers_to_remove += timer //remove it from the big list once we are done
if (!timer.callBack || timer.spent) if (!timer.callBack || timer.spent)
WARNING("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
if (timer.callBack)
qdel(timer)
continue continue
bucket_count++
new_bucket_count++
var/datum/timedevent/bucket_head = bucket_list[bucket_pos] var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
if (!bucket_head) if (!bucket_head)
bucket_list[bucket_pos] = timer bucket_list[bucket_pos] = timer
@@ -300,14 +205,12 @@ SUBSYSTEM_DEF(timer)
timer.prev = bucket_head.prev timer.prev = bucket_head.prev
timer.next.prev = timer timer.next.prev = timer
timer.prev.next = timer timer.prev.next = timer
if (i)
alltimers.Cut(1, i+1) processing = (alltimers - timers_to_remove)
second_queue = alltimers
bucket_count = new_bucket_count
/datum/controller/subsystem/timer/Recover() /datum/controller/subsystem/timer/Recover()
second_queue |= SStimer.second_queue processing |= SStimer.processing
hashes |= SStimer.hashes hashes |= SStimer.hashes
timer_id_dict |= SStimer.timer_id_dict timer_id_dict |= SStimer.timer_id_dict
bucket_list |= SStimer.bucket_list bucket_list |= SStimer.bucket_list
@@ -316,130 +219,73 @@ SUBSYSTEM_DEF(timer)
var/id var/id
var/datum/callback/callBack var/datum/callback/callBack
var/timeToRun var/timeToRun
var/wait
var/hash var/hash
var/list/flags var/list/flags
var/spent = 0 //time we ran the timer. var/spent = FALSE //set to true right before running.
var/name //for easy debugging. var/name //for easy debugging.
//cicular doublely linked list //cicular doublely linked list
var/datum/timedevent/next var/datum/timedevent/next
var/datum/timedevent/prev var/datum/timedevent/prev
/datum/timedevent/New(datum/callback/callBack, wait, flags, hash)
var/static/nextid = 1 var/static/nextid = 1
/datum/timedevent/New(datum/callback/callBack, timeToRun, flags, hash)
id = TIMER_ID_NULL id = TIMER_ID_NULL
src.callBack = callBack src.callBack = callBack
src.wait = wait src.timeToRun = timeToRun
src.flags = flags src.flags = flags
src.hash = hash src.hash = hash
if (flags & TIMER_CLIENT_TIME)
timeToRun = REALTIMEOFDAY + wait
else
timeToRun = world.time + wait
if (flags & TIMER_UNIQUE) if (flags & TIMER_UNIQUE)
SStimer.hashes[hash] = src SStimer.hashes[hash] = src
if (flags & TIMER_STOPPABLE) if (flags & TIMER_STOPPABLE)
id = num2text(nextid, 100) do
if (nextid >= SHORT_REAL_LIMIT) if (nextid >= TIMER_ID_MAX)
nextid += min(1, 2**round(nextid/SHORT_REAL_LIMIT)) nextid = 1
else id = nextid++
nextid++ while(SStimer.timer_id_dict["timerid" + num2text(id, 8)])
SStimer.timer_id_dict[id] = src SStimer.timer_id_dict["timerid" + num2text(id, 8)] = 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", "TIMER_LOOP")), ", ")], callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])" name = "Timer: " + num2text(id, 8) + ", 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(", ") : ""])"
if ((timeToRun < world.time || timeToRun < SStimer.head_offset) && !(flags & TIMER_CLIENT_TIME)) if (callBack.object != GLOBAL_PROC)
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 && !QDESTROYING(callBack.object))
LAZYADD(callBack.object.active_timers, src) LAZYADD(callBack.object.active_timers, src)
bucketJoin()
/datum/timedevent/Destroy()
..()
if (flags & TIMER_UNIQUE && hash)
SStimer.hashes -= hash
if (callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers)
callBack.object.active_timers -= src
UNSETEMPTY(callBack.object.active_timers)
callBack = null
if (flags & TIMER_STOPPABLE)
SStimer.timer_id_dict -= id
if (flags & TIMER_CLIENT_TIME) if (flags & TIMER_CLIENT_TIME)
if (!spent) //sorted insert
spent = world.time var/list/ctts = SStimer.clienttime_timers
SStimer.clienttime_timers -= src var/cttl = length(ctts)
return QDEL_HINT_IWILLGC if(cttl)
var/datum/timedevent/Last = ctts[cttl]
if (!spent) if(Last.timeToRun >= timeToRun)
spent = world.time ctts += src
bucketEject() else if(cttl > 1)
else for(var/I in cttl to 1)
if (prev && prev.next == src) var/datum/timedevent/E = ctts[I]
prev.next = next if(E.timeToRun <= timeToRun)
if (next && next.prev == src) ctts.Insert(src, I)
next.prev = prev break
next = null else
prev = null ctts += src
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 return
//get the list of buckets //get the list of buckets
var/list/bucket_list = SStimer.bucket_list var/list/bucket_list = SStimer.bucket_list
//calculate our place in the bucket list //calculate our place in the bucket list
var/bucket_pos = BUCKET_POS(src) var/bucket_pos = BUCKET_POS(src)
//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 //get the bucket for our tick
var/datum/timedevent/bucket_head = bucket_list[bucket_pos] var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
SStimer.bucket_count++ SStimer.bucket_count++
//empty bucket, we will just add ourselves //empty bucket, we will just add ourselves
if (!bucket_head) if (!bucket_head)
bucket_list[bucket_pos] = src bucket_list[bucket_pos] = src
if (bucket_pos < SStimer.practical_offset)
SStimer.practical_offset = bucket_pos
return return
//other wise, lets do a simplified linked list add. //other wise, lets do a simplified linked list add.
if (!bucket_head.prev) if (!bucket_head.prev)
@@ -449,27 +295,68 @@ SUBSYSTEM_DEF(timer)
next.prev = src next.prev = src
prev.next = src prev.next = src
/datum/timedevent/Destroy()
..()
if (flags & TIMER_UNIQUE)
SStimer.hashes -= hash
if (callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers)
callBack.object.active_timers -= src
UNSETEMPTY(callBack.object.active_timers)
callBack = null
if (flags & TIMER_STOPPABLE)
SStimer.timer_id_dict -= "timerid[id]"
if (flags & TIMER_CLIENT_TIME)
SStimer.clienttime_timers -= src
return QDEL_HINT_IWILLGC
if (!spent)
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/bucket_list = SStimer.bucket_list
if (bucketpos > 0 && bucketpos <= length(bucket_list))
buckethead = bucket_list[bucketpos]
SStimer.bucket_count--
else
SStimer.processing -= src
if (buckethead == src)
bucket_list[bucketpos] = next
else
if (prev && prev.next == src)
prev.next = next
if (next && next.prev == src)
next.prev = prev
next = null
prev = null
return QDEL_HINT_IWILLGC
/datum/timedevent/proc/getcallingtype() /datum/timedevent/proc/getcallingtype()
. = "ERROR" . = "ERROR"
if (callBack.object == GLOBAL_PROC) if (callBack.object == GLOBAL_PROC)
. = "GLOBAL_PROC" . = "GLOBAL PROC"
else else
. = "[callBack.object.type]" . = "[callBack.object.type]"
/proc/addtimer(datum/callback/callback, wait = 0, flags = 0) /proc/addtimer(datum/callback/callback, wait, flags)
if (!callback) if (!callback)
CRASH("addtimer called without a callback") return
if (wait < 0) wait = max(wait, 0)
crash_with("addtimer called with a negative wait. Converting to [world.tick_lag]")
if (callback.object != GLOBAL_PROC && QDELETED(callback.object) && !QDESTROYING(callback.object))
crash_with("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(CEILING(wait, world.tick_lag), world.tick_lag)
if(wait >= INFINITY)
CRASH("Attempted to create timer with INFINITY delay")
var/hash var/hash
@@ -485,32 +372,36 @@ SUBSYSTEM_DEF(timer)
var/datum/timedevent/hash_timer = SStimer.hashes[hash] var/datum/timedevent/hash_timer = SStimer.hashes[hash]
if(hash_timer) if(hash_timer)
if (hash_timer.spent) //it's pending deletion, pretend it doesn't exist. if (hash_timer.spent) //it's pending deletion, pretend it doesn't exist.
hash_timer.hash = null //but keep it from accidentally deleting us hash_timer.hash = null
SStimer.hashes -= hash
else else
if (flags & TIMER_OVERRIDE) if (flags & TIMER_OVERRIDE)
hash_timer.hash = null //no need having it delete it's hash if we are going to replace it
qdel(hash_timer) qdel(hash_timer)
else else
if (hash_timer.flags & TIMER_STOPPABLE) if (hash_timer.flags & TIMER_STOPPABLE)
. = hash_timer.id . = hash_timer.id
return return
else if(flags & TIMER_OVERRIDE)
crash_with("TIMER_OVERRIDE used without TIMER_UNIQUE")
var/datum/timedevent/timer = new(callback, wait, flags, hash)
return timer.id var/timeToRun = world.time + wait
if (flags & TIMER_CLIENT_TIME)
timeToRun = REALTIMEOFDAY + wait
var/datum/timedevent/timer = new(callback, timeToRun, flags, hash)
if (flags & TIMER_STOPPABLE)
return timer.id
/proc/deltimer(id) /proc/deltimer(id)
if (!id) if (!id)
return FALSE return FALSE
if (id == TIMER_ID_NULL) if (id == TIMER_ID_NULL)
CRASH("Tried to delete a null timerid. Use TIMER_STOPPABLE flag") CRASH("Tried to delete a null timerid. Use the TIMER_STOPPABLE flag.")
if (!istext(id)) if (!istext(id))
if (istype(id, /datum/timedevent)) if (istype(id, /datum/timedevent))
qdel(id) qdel(id)
return TRUE return TRUE
//id is string var/datum/timedevent/timer = SStimer.timer_id_dict["timerid[id]"]
var/datum/timedevent/timer = SStimer.timer_id_dict[id]
if (timer && !timer.spent) if (timer && !timer.spent)
qdel(timer) qdel(timer)
return TRUE return TRUE
@@ -519,5 +410,4 @@ SUBSYSTEM_DEF(timer)
#undef BUCKET_LEN #undef BUCKET_LEN
#undef BUCKET_POS #undef BUCKET_POS
#undef TIMER_MAX
#undef TIMER_ID_MAX #undef TIMER_ID_MAX

View File

@@ -383,6 +383,16 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
if(!pref.species_preview || !(pref.species_preview in all_species)) if(!pref.species_preview || !(pref.species_preview in all_species))
return TOPIC_NOACTION return TOPIC_NOACTION
var/datum/species/setting_species
if(all_species[href_list["set_species"]])
setting_species = all_species[href_list["set_species"]]
else
return TOPIC_NOACTION
if(((!(setting_species.spawn_flags & SPECIES_CAN_JOIN)) || (!is_alien_whitelisted(preference_mob(),setting_species))) && !check_rights(R_ADMIN, 0))
return TOPIC_NOACTION
var/prev_species = pref.species var/prev_species = pref.species
pref.species = href_list["set_species"] pref.species = href_list["set_species"]
if(prev_species != pref.species) if(prev_species != pref.species)