diff --git a/code/__defines/lists.dm b/code/__defines/lists.dm index 2df9fdd950f..50f8a3be1f3 100644 --- a/code/__defines/lists.dm +++ b/code/__defines/lists.dm @@ -14,30 +14,42 @@ // Shims for some list procs in lists.dm. #define isemptylist(L) (!LAZYLEN(L)) -// 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;\ +/// Passed into BINARY_INSERT to compare keys +#define COMPARE_KEY __BIN_LIST[__BIN_MID] +/// Passed into BINARY_INSERT to compare values +#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]] + +/** + * Binary search sorted insert from TG + * INPUT: Object to be inserted + * LIST: List to insert object into + * TYPECONT: The typepath of the contents of the list + * COMPARE: The object to compare against, usualy the same as INPUT + * COMPARISON: The variable on the objects to compare + * COMPTYPE: How should the values be compared? Either COMPARE_KEY or COMPARE_VALUE. + */ +#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ + do {\ + var/list/__BIN_LIST = LIST;\ + var/__BIN_CTTL = length(__BIN_LIST);\ + if(!__BIN_CTTL) {\ + __BIN_LIST += INPUT;\ + } 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 = COMPTYPE;\ + if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ };\ - __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + __BIN_ITEM = COMPTYPE;\ + __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\ + __BIN_LIST.Insert(__BIN_MID, INPUT);\ };\ - __BIN_ITEM = LIST[__BIN_MID];\ - __BIN_MID = __BIN_ITEM.##COMPARE > IN.##COMPARE ? __BIN_MID : __BIN_MID + 1;\ - LIST.Insert(__BIN_MID, IN);\ - } + } while(FALSE) diff --git a/code/_helpers/maths.dm b/code/_helpers/maths.dm index 617d61b223b..890343a268b 100644 --- a/code/_helpers/maths.dm +++ b/code/_helpers/maths.dm @@ -167,4 +167,7 @@ // Will filter out extra rotations and negative rotations // E.g: 540 becomes 180. -180 becomes 180. -#define SIMPLIFY_DEGREES(degrees) (MODULUS_FLOAT((degrees), 360)) \ No newline at end of file +#define SIMPLIFY_DEGREES(degrees) (MODULUS_FLOAT((degrees), 360)) + +/// Value or the next multiple of divisor in a positive direction. Ceilm(-1.5, 0.3) = -1.5 , Ceilm(-1.5, 0.4) = -1.2 +#define Ceilm(value, divisor) ( -round(-(value) / (divisor)) * (divisor) ) diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm index 138d8b4d553..cff2b45eaae 100644 --- a/code/_onclick/hud/ability_screen_objects.dm +++ b/code/_onclick/hud/ability_screen_objects.dm @@ -144,13 +144,13 @@ return O return -/mob/Login() - ..() +/mob/living/LateLogin() + . = ..() if(ability_master) ability_master.toggle_open(1) client.screen -= ability_master -/mob/Initialize() +/mob/living/Initialize() . = ..() ability_master = new /obj/screen/movable/ability_master(FALSE, src) diff --git a/code/controllers/master/master.dm b/code/controllers/master/master.dm index f2320ed6fd3..11080572380 100644 --- a/code/controllers/master/master.dm +++ b/code/controllers/master/master.dm @@ -489,7 +489,7 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING queue_node.times_fired++ if (queue_node_flags & SS_TICKER) - queue_node.next_fire = world.time + (world.tick_lag * (queue_node.wait + (queue_node.tick_overrun/100))) + queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait) else if (queue_node_flags & SS_POST_FIRE_TIMING) queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) else if (queue_node_flags & SS_KEEP_TIMING) diff --git a/code/controllers/master/subsystem.dm b/code/controllers/master/subsystem.dm index 81a7ae49def..d9fd2826f76 100644 --- a/code/controllers/master/subsystem.dm +++ b/code/controllers/master/subsystem.dm @@ -104,7 +104,7 @@ else if (SS_flags & SS_BACKGROUND) continue - if (SS_flags & SS_TICKER) + if (SS_flags & (SS_TICKER|SS_BACKGROUND) == SS_TICKER) break if (queue_node_priority < SS_priority) break diff --git a/code/controllers/subsystems/processing/shuttle.dm b/code/controllers/subsystems/processing/shuttle.dm index c65fe577aab..ccd1d9f449f 100644 --- a/code/controllers/subsystems/processing/shuttle.dm +++ b/code/controllers/subsystems/processing/shuttle.dm @@ -1,9 +1,8 @@ -var/datum/controller/subsystem/processing/shuttle/SSshuttle +var/datum/controller/subsystem/shuttle/SSshuttle -/datum/controller/subsystem/processing/shuttle +/datum/controller/subsystem/shuttle name = "Shuttle" wait = 2 SECONDS - flags = 0 priority = SS_PRIORITY_SHUTTLE init_order = SS_INIT_MISC //Should be initialized after all maploading is over and atoms are initialized, to ensure that landmarks have been initialized. @@ -27,10 +26,10 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle var/tmp/list/working_shuttles -/datum/controller/subsystem/processing/shuttle/New() +/datum/controller/subsystem/shuttle/New() NEW_SS_GLOBAL(SSshuttle) -/datum/controller/subsystem/processing/shuttle/Initialize() +/datum/controller/subsystem/shuttle/Initialize() last_landmark_registration_time = world.time for(var/shuttle_type in subtypesof(/datum/shuttle)) // This accounts for most shuttles, though away maps can queue up more. var/datum/shuttle/shuttle = shuttle_type @@ -42,26 +41,26 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle clear_init_queue() . = ..() -/datum/controller/subsystem/processing/shuttle/fire(resumed = FALSE) +/datum/controller/subsystem/shuttle/fire(resumed = FALSE) if (!resumed) working_shuttles = process_shuttles.Copy() while(working_shuttles.len) var/datum/shuttle/shuttle = working_shuttles[working_shuttles.len] working_shuttles.len-- - if(shuttle.process_state && (shuttle.process(wait, times_fired, src) == PROCESS_KILL)) + if(shuttle.process_state && (shuttle.process() == PROCESS_KILL)) process_shuttles -= shuttle if(TICK_CHECK) return -/datum/controller/subsystem/processing/shuttle/proc/clear_init_queue() +/datum/controller/subsystem/shuttle/proc/clear_init_queue() if(block_queue) return initialize_shuttles() initialize_sectors() -/datum/controller/subsystem/processing/shuttle/proc/initialize_shuttles() +/datum/controller/subsystem/shuttle/proc/initialize_shuttles() var/list/shuttles_made = list() for(var/shuttle_type in shuttles_to_initialize) var/shuttle = initialize_shuttle(shuttle_type) @@ -70,12 +69,12 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle hook_up_motherships(shuttles_made) shuttles_to_initialize = null -/datum/controller/subsystem/processing/shuttle/proc/initialize_sectors() +/datum/controller/subsystem/shuttle/proc/initialize_sectors() for(var/sector in sectors_to_initialize) initialize_sector(sector) sectors_to_initialize = null -/datum/controller/subsystem/processing/shuttle/proc/register_landmark(shuttle_landmark_tag, obj/effect/shuttle_landmark/shuttle_landmark) +/datum/controller/subsystem/shuttle/proc/register_landmark(shuttle_landmark_tag, obj/effect/shuttle_landmark/shuttle_landmark) if (registered_shuttle_landmarks[shuttle_landmark_tag]) CRASH("Attempted to register shuttle landmark with tag [shuttle_landmark_tag], but it is already registered!") if (istype(shuttle_landmark)) @@ -93,12 +92,12 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle else landmarks_awaiting_sector += shuttle_landmark -/datum/controller/subsystem/processing/shuttle/proc/get_landmark(var/shuttle_landmark_tag) +/datum/controller/subsystem/shuttle/proc/get_landmark(var/shuttle_landmark_tag) return registered_shuttle_landmarks[shuttle_landmark_tag] //Checks if the given sector's landmarks have initialized; if so, registers them with the sector, if not, marks them for assignment after they come in. //Also adds automatic landmarks that were waiting on their sector to spawn. -/datum/controller/subsystem/processing/shuttle/proc/initialize_sector(obj/effect/overmap/visitable/given_sector) +/datum/controller/subsystem/shuttle/proc/initialize_sector(obj/effect/overmap/visitable/given_sector) given_sector.populate_sector_objects() // This is a late init operation that sets up the sector's map_z and does non-overmap-related init tasks. for(var/landmark_tag in given_sector.initial_generic_waypoints) @@ -119,7 +118,7 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle initialized_sectors |= given_sector -/datum/controller/subsystem/processing/shuttle/proc/try_add_landmark_tag(landmark_tag, obj/effect/overmap/visitable/given_sector) +/datum/controller/subsystem/shuttle/proc/try_add_landmark_tag(landmark_tag, obj/effect/overmap/visitable/given_sector) var/obj/effect/shuttle_landmark/landmark = get_landmark(landmark_tag) if(!landmark) return FALSE @@ -132,14 +131,14 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle given_sector.add_landmark(landmark, shuttle_name) . = TRUE -/datum/controller/subsystem/processing/shuttle/proc/initialize_shuttle(var/shuttle_type) +/datum/controller/subsystem/shuttle/proc/initialize_shuttle(var/shuttle_type) var/datum/shuttle/shuttle = shuttle_type if(initial(shuttle.category) != shuttle_type) shuttle = new shuttle() shuttle_areas |= shuttle.shuttle_area return shuttle -/datum/controller/subsystem/processing/shuttle/proc/hook_up_motherships(shuttles_list) +/datum/controller/subsystem/shuttle/proc/hook_up_motherships(shuttles_list) for(var/datum/shuttle/S in shuttles_list) if(S.mothershuttle && !S.motherdock) var/datum/shuttle/mothership = shuttles[S.mothershuttle] @@ -149,7 +148,7 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle else error("Shuttle [S] was unable to find mothership [mothership]!") -/datum/controller/subsystem/processing/shuttle/proc/toggle_overmap(new_setting) +/datum/controller/subsystem/shuttle/proc/toggle_overmap(new_setting) if(overmap_halted == new_setting) return overmap_halted = !overmap_halted @@ -157,5 +156,5 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle var/obj/effect/overmap/visitable/ship/ship_effect = ship overmap_halted ? ship_effect.halt() : ship_effect.unhalt() -/datum/controller/subsystem/processing/shuttle/stat_entry() +/datum/controller/subsystem/shuttle/stat_entry() ..("Shuttles:[shuttles.len], Ships:[ships.len], L:[registered_shuttle_landmarks.len][overmap_halted ? ", HALT" : ""]") diff --git a/code/controllers/subsystems/timer.dm b/code/controllers/subsystems/timer.dm index 1d98f87d84a..ea406f2add5 100644 --- a/code/controllers/subsystems/timer.dm +++ b/code/controllers/subsystems/timer.dm @@ -1,8 +1,21 @@ -#define BUCKET_LEN (round(10*(60/world.tick_lag), 1)) //how many ticks should we keep in the bucket. (1 minutes worth) +/// Controls how many buckets should be kept, each representing a tick. (1 minutes worth) +#define BUCKET_LEN (world.fps*1*60) +/// Helper for getting the correct bucket for a given timer #define BUCKET_POS(timer) (((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag)+1) % BUCKET_LEN)||BUCKET_LEN) -#define TIMER_ID_MAX (2**24) //max float with integer precision +/// Gets the maximum time at which timers will be invoked from buckets, used for deferring to secondary queue #define TIMER_MAX (world.time + TICKS2DS(min(BUCKET_LEN-(SStimer.practical_offset-DS2TICKS(world.time - SStimer.head_offset))-1, BUCKET_LEN-1))) +/// Max float with integer precision +#define TIMER_ID_MAX (2**24) +/** + * # Timer Subsystem + * + * Handles creation, callbacks, and destruction of timed events. + * + * It is important to understand the buckets used in the timer subsystem are just a series of circular doubly-linked + * lists. The object at a given index in bucket_list is a /datum/timedevent, the head of a circular list, which has prev + * and next references for the respective elements in that bucket's circular list. + */ var/datum/controller/subsystem/timer/SStimer /datum/controller/subsystem/timer @@ -10,72 +23,96 @@ var/datum/controller/subsystem/timer/SStimer wait = 1 //SS_TICKER subsystem, so wait is in ticks priority = SS_PRIORITY_TIMER - flags = SS_FIRE_IN_LOBBY|SS_TICKER|SS_NO_INIT + flags = SS_TICKER|SS_NO_INIT|SS_FIRE_IN_LOBBY - var/list/datum/timedevent/second_queue = list() //awe, yes, you've had first queue, but what about second queue? + /// Queue used for storing timers that do not fit into the current buckets + var/list/datum/timedevent/second_queue = list() + /// A hashlist dictionary used for storing unique timers var/list/hashes = list() - - 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/bucket_list = list() //list of buckets, each bucket holds every timer that has to run that byond tick. - - var/list/timer_id_dict = list() //list of all active timers assoicated to their timer id (for easy lookup) - - var/list/clienttime_timers = list() //special snowflake timers that run on fancy pansy "client time" - + /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets + var/head_offset = 0 + /// Index of the wrap around pivot for buckets. buckets before this are later running buckets wrapped around from the end of the bucket list. + var/practical_offset = 1 + /// world.tick_lag the bucket was designed for + var/bucket_resolution = 0 + /// How many timers are in the buckets + var/bucket_count = 0 + /// List of buckets, each bucket holds every timer that has to run that byond tick + var/list/bucket_list = list() + /// List of all active timers associated to their timer ID (for easy lookup) + var/list/timer_id_dict = list() + /// Special timers that run in real-time, not BYOND time; these are more expensive to run and maintain + var/list/clienttime_timers = list() + /// Contains the last time that a timer's callback was invoked, or the last tick the SS fired if no timers are being processed var/last_invoke_tick = 0 + /// Keeps track of the next index to work on for client timers + var/next_clienttime_timer_index = 0 + /// Contains the last time that a warning was issued for not invoking callbacks var/static/last_invoke_warning = 0 + /// Boolean operator controlling if the timer SS will automatically reset buckets if it fails to invoke callbacks for an extended period of time var/static/bucket_auto_reset = TRUE + /// How many times bucket was reset + var/bucket_reset_count = 0 /datum/controller/subsystem/timer/New() NEW_SS_GLOBAL(SStimer) bucket_list.len = BUCKET_LEN + head_offset = world.time + bucket_resolution = world.tick_lag /datum/controller/subsystem/timer/stat_entry(msg) - ..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)]") + ..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)] RST:[bucket_reset_count]") -/datum/controller/subsystem/timer/fire(resumed = FALSE) - var/lit = last_invoke_tick - var/last_check = world.time - TICKS2DS(BUCKET_LEN*1.5) - var/list/bucket_list = src.bucket_list - - if(!bucket_count) - last_invoke_tick = world.time - - if(lit && lit < last_check && head_offset < last_check && last_invoke_warning < last_check) - last_invoke_warning = world.time - var/msg = "No regular timers processed in the last [BUCKET_LEN*1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!" - message_admins(msg) - WARNING(msg) - if(bucket_auto_reset) - bucket_resolution = 0 - - log_ss(name, "Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") +/datum/controller/subsystem/timer/proc/dump_timer_buckets(full = TRUE) + var/list/to_log = list("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]") + if (full) for (var/i in 1 to length(bucket_list)) var/datum/timedevent/bucket_head = bucket_list[i] if (!bucket_head) continue - log_ss(name, "Active timers at index [i]:") - + to_log += "Active timers at index [i]:" var/datum/timedevent/bucket_node = bucket_head - var/anti_loop_check = 1000 + var/anti_loop_check = 1 do - log_ss(name, get_timer_debug_string(bucket_node)) + to_log += get_timer_debug_string(bucket_node) bucket_node = bucket_node.next anti_loop_check-- while(bucket_node && bucket_node != bucket_head && anti_loop_check) - log_ss(name, "Active timers in the second_queue queue:") + + to_log += "Active timers in the second_queue queue:" for(var/I in second_queue) - log_ss(name, get_timer_debug_string(I)) + to_log += get_timer_debug_string(I) - var/next_clienttime_timer_index = 0 - var/len = length(clienttime_timers) + // Dump all the logged data to the world log + log_ss("timers", to_log.Join("\n")) - for (next_clienttime_timer_index in 1 to len) +/datum/controller/subsystem/timer/fire(resumed = FALSE) + // Store local references to datum vars as it is faster to access them + var/lit = last_invoke_tick + var/list/bucket_list = src.bucket_list + var/last_check = world.time - TICKS2DS(BUCKET_LEN * 1.5) + + // If there are no timers being tracked, then consider now to be the last invoked time + if(!bucket_count) + last_invoke_tick = world.time + + // Check that we have invoked a callback in the last 1.5 minutes of BYOND time, + // and throw a warning and reset buckets if this is true + if(lit && lit < last_check && head_offset < last_check && last_invoke_warning < last_check) + last_invoke_warning = world.time + var/msg = "No regular timers processed in the last [BUCKET_LEN * 1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!" + message_admins(msg) + WARNING(msg) + if(bucket_auto_reset) + bucket_resolution = 0 + dump_timer_buckets() + + // Process client-time timers + if (next_clienttime_timer_index) + clienttime_timers.Cut(1, next_clienttime_timer_index+1) + next_clienttime_timer_index = 0 + for (next_clienttime_timer_index in 1 to length(clienttime_timers)) if (MC_TICK_CHECK) next_clienttime_timer_index-- break @@ -86,8 +123,8 @@ var/datum/controller/subsystem/timer/SStimer 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]") + 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() @@ -95,148 +132,115 @@ var/datum/controller/subsystem/timer/SStimer if(ctime_timer.flags & TIMER_LOOP) ctime_timer.spent = 0 ctime_timer.timeToRun = REALTIMEOFDAY + ctime_timer.wait - BINARY_INSERT(ctime_timer, clienttime_timers, datum/timedevent, timeToRun) + BINARY_INSERT(ctime_timer, clienttime_timers, /datum/timedevent, ctime_timer, timeToRun, COMPARE_KEY) else qdel(ctime_timer) - + // Remove invoked client-time timers if (next_clienttime_timer_index) clienttime_timers.Cut(1, next_clienttime_timer_index+1) + next_clienttime_timer_index = 0 - if (MC_TICK_CHECK) - return - - var/static/list/spent = list() - var/static/datum/timedevent/timer + // Check for when we need to loop the buckets, this occurs when + // the head_offset is approaching BUCKET_LEN ticks in the past if (practical_offset > BUCKET_LEN) head_offset += TICKS2DS(BUCKET_LEN) practical_offset = 1 resumed = FALSE + // Check for when we have to reset buckets, typically from auto-reset if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution)) reset_buckets() bucket_list = src.bucket_list resumed = FALSE - - if (!resumed) - timer = null - - while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset-1)*world.tick_lag) <= world.time) - var/datum/timedevent/head = bucket_list[practical_offset] - if (!timer || !head || timer == head) - head = bucket_list[practical_offset] - timer = head - while (timer) + // Iterate through each bucket starting from the practical offset + while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time) + var/datum/timedevent/timer + while ((timer = bucket_list[practical_offset])) var/datum/callback/callBack = timer.callBack if (!callBack) - 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_with("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], \ + head_offset: [head_offset], practical_offset: [practical_offset], bucket_joined: [timer.bucket_joined]") + if (!timer.spent) + bucket_resolution = null // force bucket recreation + return + timer.bucketEject() //pop the timer off of the bucket list. + + // Invoke callback if possible if (!timer.spent) - spent += timer timer.spent = world.time callBack.InvokeAsync() last_invoke_tick = world.time - if (MC_TICK_CHECK) - return - - 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_with("[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_with("[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 - - bucket_count -= length(spent) - - 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 + if (timer.flags & TIMER_LOOP) // Prepare looping timers to re-enter the queue + timer.spent = 0 + timer.timeToRun = world.time + timer.wait + timer.bucketJoin() else - qtimer.timeToRun = world.time + qtimer.wait - qtimer.bucketJoin() + qdel(timer) - spent.len = 0 + if (MC_TICK_CHECK) + break + if (!bucket_list[practical_offset]) + // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket + bucket_list[practical_offset] = null // Just in case + practical_offset++ + var/i = 0 + for (i in 1 to length(second_queue)) + timer = second_queue[i] + if (timer.timeToRun >= TIMER_MAX) + i-- + break + + // Check for timers that are scheduled to run in the past + if (timer.timeToRun < head_offset) + bucket_resolution = null // force bucket recreation + crash_with("[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]") + break + + // Check for timers that are not capable of being scheduled to run without rebuilding buckets + if (timer.timeToRun < head_offset + TICKS2DS(practical_offset - 1)) + bucket_resolution = null // force bucket recreation + crash_with("[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]") + break + + timer.bucketJoin() + if (i) + second_queue.Cut(1, i+1) + if (MC_TICK_CHECK) + break + +/** + * Generates a string with details about the timed event for debugging purposes + */ /datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE) . = "Timer: [TE]" . += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]" if(TE.spent) - . += ", SPENT" + . += ", SPENT([TE.spent])" if(QDELETED(TE)) . += ", QDELETED" + if(!TE.callBack) + . += ", NO CALLBACK" +/** + * Destroys the existing buckets and creates new buckets from the existing timed events + */ /datum/controller/subsystem/timer/proc/reset_buckets() - var/list/bucket_list = src.bucket_list + log_debug("Timer buckets have been reset, this may cause timers to lag") + bucket_reset_count++ + + var/list/bucket_list = src.bucket_list // Store local reference to datum var, this is faster var/list/alltimers = list() - //collect the timers currently in the bucket + + // Get all timers currently in the buckets for (var/bucket_head in bucket_list) - if (!bucket_head) + if (!bucket_head) // if bucket is empty for this tick continue var/datum/timedevent/bucket_node = bucket_head do @@ -244,25 +248,38 @@ var/datum/controller/subsystem/timer/SStimer bucket_node = bucket_node.next while(bucket_node && bucket_node != bucket_head) + // Empty the list by zeroing and re-assigning the length bucket_list.len = 0 bucket_list.len = BUCKET_LEN + // Reset values for the subsystem to their initial values practical_offset = 1 bucket_count = 0 head_offset = world.time bucket_resolution = world.tick_lag + // Add all timed events from the secondary queue as well alltimers += second_queue + + // If there are no timers being tracked by the subsystem, + // there is no need to do any further rebuilding if (!length(alltimers)) return - sortTim(alltimers, /proc/cmp_timer) + // Sort all timers by time to run + sortTim(alltimers, .proc/cmp_timer) + // Get the earliest timer, and if the TTR is earlier than the current world.time, + // then set the head offset appropriately to be the earliest time tracked by the + // current set of buckets var/datum/timedevent/head = alltimers[1] - if (head.timeToRun < head_offset) head_offset = head.timeToRun + // Iterate through each timed event and insert it into an appropriate bucket, + // up unto the point that we can no longer insert into buckets as the TTR + // is outside the range we are tracking, then insert the remainder into the + // secondary queue var/new_bucket_count var/i = 1 for (i in 1 to length(alltimers)) @@ -270,19 +287,24 @@ var/datum/controller/subsystem/timer/SStimer if (!timer) continue - var/bucket_pos = BUCKET_POS(timer) + // Check that the TTR is within the range covered by buckets, when exceeded we've finished if (timer.timeToRun >= TIMER_MAX) i-- break - + // Check that timer has a valid callback and hasn't been invoked 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]") + 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 + // Insert the timer into the bucket, and perform necessary circular doubly-linked list operations new_bucket_count++ + var/bucket_pos = BUCKET_POS(timer) + timer.bucket_pos = bucket_pos + var/datum/timedevent/bucket_head = bucket_list[bucket_pos] if (!bucket_head) bucket_list[bucket_pos] = timer @@ -290,14 +312,14 @@ var/datum/controller/subsystem/timer/SStimer timer.prev = null continue - if (!bucket_head.prev) - bucket_head.prev = bucket_head + bucket_head.prev = timer timer.next = bucket_head - timer.prev = bucket_head.prev - timer.next.prev = timer - timer.prev.next = timer + timer.prev = null + bucket_list[bucket_pos] = timer + + // Cut the timers that are tracked by the buckets from the secondary queue if (i) - alltimers.Cut(1, i+1) + alltimers.Cut(1, i + 1) second_queue = alltimers bucket_count = new_bucket_count @@ -308,46 +330,68 @@ var/datum/controller/subsystem/timer/SStimer timer_id_dict |= SStimer.timer_id_dict bucket_list |= SStimer.bucket_list +/** + * # Timed Event + * + * This is the actual timer, it contains the callback and necessary data to maintain + * the timer. + * + * See the documentation for the timer subsystem for an explanation of the buckets referenced + * below in next and prev + */ /datum/timedevent + /// ID used for timers when the TIMER_STOPPABLE flag is present var/id + /// The callback to invoke after the timer completes var/datum/callback/callBack + /// The time at which the callback should be invoked at var/timeToRun + /// The length of the timer var/wait + /// Unique hash generated when TIMER_UNIQUE flag is present var/hash + /// The source of the timedevent, whatever called addtimer + var/source + /// Flags associated with the timer, see _DEFINES/subsystems.dm var/list/flags - var/spent = FALSE //set to true right before running. - var/name //for easy debugging. - - //cicular doublely linked list + /// Time at which the timer was invoked or destroyed + var/spent = 0 + /// An informative name generated for the timer as its representation in strings, useful for debugging + var/name + /// Next timed event in the bucket var/datum/timedevent/next + /// Previous timed event in the bucket var/datum/timedevent/prev + /// Boolean indicating if timer joined into bucket + var/bucket_joined = FALSE + /// Initial bucket position + var/bucket_pos = -1 +/datum/timedevent/New(datum/callback/callBack, wait, flags, hash, source) var/static/nextid = 1 - -/datum/timedevent/New(datum/callback/callBack, wait, flags, hash) id = TIMER_ID_NULL src.callBack = callBack src.wait = wait src.flags = flags src.hash = hash + src.source = source - if (flags & TIMER_CLIENT_TIME) - timeToRun = REALTIMEOFDAY + wait - else - timeToRun = world.time + wait + // Determine time at which the timer's callback should be invoked + timeToRun = (flags & TIMER_CLIENT_TIME ? REALTIMEOFDAY : world.time) + wait + // Include the timer in the hash table if the timer is unique if (flags & TIMER_UNIQUE) SStimer.hashes[hash] = src + + // Generate ID for the timer if the timer is stoppable, include in the timer id dictionary if (flags & TIMER_STOPPABLE) id = num2text(nextid, 100) if (nextid >= TIMER_ID_MAX) - nextid += min(1, 2**round(nextid/TIMER_ID_MAX)) + nextid += min(1, 2 ** round(nextid / TIMER_ID_MAX)) else nextid++ SStimer.timer_id_dict[id] = src - 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", "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)]") @@ -388,64 +432,104 @@ var/datum/controller/subsystem/timer/SStimer prev = null return QDEL_HINT_IWILLGC +/** + * Removes this timed event from any relevant buckets, or the secondary queue + */ /datum/timedevent/proc/bucketEject() - var/bucketpos = BUCKET_POS(src) + // Store local references for the bucket list and secondary queue + // This is faster than referencing them from the datum itself var/list/bucket_list = SStimer.bucket_list var/list/second_queue = SStimer.second_queue + + // Attempt to get the head of the bucket var/datum/timedevent/buckethead - if(bucketpos > 0) - buckethead = bucket_list[bucketpos] + if(bucket_pos > 0) + buckethead = bucket_list[bucket_pos] + + // Decrement the number of timers in buckets if the timed event is + // the head of the bucket, or has a TTR less than TIMER_MAX implying it fits + // into an existing bucket, or is otherwise not present in the secondary queue if(buckethead == src) - bucket_list[bucketpos] = next + bucket_list[bucket_pos] = next SStimer.bucket_count-- - else if(timeToRun < TIMER_MAX || next || prev) + else if(timeToRun < TIMER_MAX) SStimer.bucket_count-- else var/l = length(second_queue) second_queue -= src if(l == length(second_queue)) SStimer.bucket_count-- - if(prev != next) + + // Remove the timed event from the bucket, ensuring to maintain + // the integrity of the bucket's list if relevant + if (prev && prev.next == src) prev.next = next + if (next && next.prev == src) next.prev = prev - else - prev?.next = null - next?.prev = null prev = next = null + bucket_pos = -1 + bucket_joined = FALSE +/** + * Attempts to add this timed event to a bucket, will enter the secondary queue + * if there are no appropriate buckets at this time. + * + * Secondary queueing of timed events will occur when the timespan covered by the existing + * buckets is exceeded by the time at which this timed event is scheduled to be invoked. + * If the timed event is tracking client time, it will be added to a special bucket. + */ /datum/timedevent/proc/bucketJoin() - var/list/L + // Generate debug-friendly name for timer + var/static/list/bitfield_flags = list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP") + name = "Timer: [id] (\ref[src]), TTR: [timeToRun], wait:[wait] Flags: [jointext(bitfield2list(flags, bitfield_flags), ", ")], \ + callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), \ + callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""]), source: [source]" + if (bucket_joined) + crash_with("Bucket already joined! [name]") + + // Check if this timed event should be diverted to the client time bucket, or the secondary queue + 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) + BINARY_INSERT(src, L, /datum/timedevent, src, timeToRun, COMPARE_KEY) return - //get the list of buckets + // Get a local reference to the bucket list, this is faster than referencing the datum var/list/bucket_list = SStimer.bucket_list - //calculate our place in the bucket list - var/bucket_pos = BUCKET_POS(src) + // Find the correct bucket for this timed event + bucket_pos = BUCKET_POS(src) + + if (bucket_pos < SStimer.practical_offset && timeToRun < (SStimer.head_offset + TICKS2DS(BUCKET_LEN))) + WARNING("Bucket pos in past: bucket_pos = [bucket_pos] < practical_offset = [SStimer.practical_offset] \ + && timeToRun = [timeToRun] < [SStimer.head_offset + TICKS2DS(BUCKET_LEN)], Timer: [name]") + bucket_pos = SStimer.practical_offset // Recover bucket_pos to avoid timer blocking queue - //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 there is no timed event at this position, then the bucket is 'empty' + // and we can just set this event to that position if (!bucket_head) + bucket_joined = TRUE 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 + // Otherwise, we merely add this timed event into the bucket, which is a + // doubly-linked list + bucket_joined = TRUE + bucket_head.prev = src + next = bucket_head + prev = null + bucket_list[bucket_pos] = src + +/** + * Returns a string of the type of the callback for this timer + */ /datum/timedevent/proc/getcallingtype() . = "ERROR" if (callBack.object == GLOBAL_PROC) @@ -453,39 +537,47 @@ var/datum/controller/subsystem/timer/SStimer else . = "[callBack.object.type]" +/** + * Create a new timer and insert it in the queue. + * You should not call this directly, and should instead use the addtimer macro, which includes source information. + * + * Arguments: + * * callback the callback to call on timer finish + * * wait deciseconds to run the timer for + * * flags flags for this timer, see: code\__DEFINES\subsystems.dm + */ /proc/addtimer(datum/callback/callback, wait, flags) if (!callback) CRASH("addtimer called without a callback") if (wait < 0) - crash_with("addtimer called with a negative wait. Converting to 0") + CRASH("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") + CRASH("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(Ceilm(wait, world.tick_lag), world.tick_lag) if(wait >= INFINITY) CRASH("Attempted to create timer with INFINITY delay") + // Generate hash if relevant for timed events with the TIMER_UNIQUE flag var/hash - if (flags & TIMER_UNIQUE) - var/list/hashlist - if(flags & TIMER_NO_HASH_WAIT) - hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, flags & TIMER_CLIENT_TIME) - else - hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, wait, flags & TIMER_CLIENT_TIME) + var/list/hashlist = list(callback.object, "(\ref[callback.object])", callback.delegate, flags & TIMER_CLIENT_TIME) + if(!(flags & TIMER_NO_HASH_WAIT)) + hashlist += wait hashlist += callback.arguments hash = hashlist.Join("|||||||") var/datum/timedevent/hash_timer = SStimer.hashes[hash] if(hash_timer) - if (hash_timer.spent) //it's pending deletion, pretend it doesn't exist. - hash_timer.hash = null - SStimer.hashes -= hash + if (hash_timer.spent) // it's pending deletion, pretend it doesn't exist. + hash_timer.hash = null // but keep it from accidentally deleting us else 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) else if (hash_timer.flags & TIMER_STOPPABLE) @@ -497,18 +589,22 @@ var/datum/controller/subsystem/timer/SStimer var/datum/timedevent/timer = new(callback, wait, flags, hash) return timer.id + +/** + * Delete a timer + * + * Arguments: + * * id a timerid or a /datum/timedevent + */ /proc/deltimer(id) if (!id) return FALSE - if (id == TIMER_ID_NULL) - CRASH("Tried to delete a null timerid. Use the TIMER_STOPPABLE flag.") - - if (!istext(id)) - if (istype(id, /datum/timedevent)) - qdel(id) - return TRUE - + CRASH("Tried to delete a null timerid. Use TIMER_STOPPABLE flag") + if (istype(id, /datum/timedevent)) + qdel(id) + return TRUE + //id is string var/datum/timedevent/timer = SStimer.timer_id_dict[id] if (timer && !timer.spent) qdel(timer) diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm index 18f9cf27991..b2c095285ab 100644 --- a/code/datums/looping_sounds/_looping_sound.dm +++ b/code/datums/looping_sounds/_looping_sound.dm @@ -68,7 +68,7 @@ if(!chance || prob(chance)) play(get_sound(starttime)) if(!timerid) - timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_LOOP) + timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_STOPPABLE | TIMER_LOOP) /datum/looping_sound/proc/play(soundfile) var/list/atoms_cache = output_atoms @@ -93,7 +93,7 @@ if(start_sound) play(start_sound) start_wait = start_length - addtimer(CALLBACK(src, .proc/sound_loop), start_wait, TIMER_CLIENT_TIME) + addtimer(CALLBACK(src, .proc/sound_loop), start_wait) /datum/looping_sound/proc/on_stop() if(end_sound) diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index d3f009039d6..d598c648f2b 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -201,7 +201,6 @@ var/last_color_level = 5 var/health_timer = 10 MINUTES // you need to reduce the health by standing near it with a neutralizer - var/datum/looping_sound/revenant_rift/soundloop /obj/effect/portal/revenant/Initialize(mapload) . = ..() @@ -210,8 +209,6 @@ var/turf/T = get_turf(src) log_and_message_admins("Revenant Bluespace Rift spawned at \the [get_area(T)]", null, T) revenants.revenant_rift = src - soundloop = new(list(src), FALSE) - soundloop.start() /obj/effect/portal/revenant/Destroy() revenants.destroyed_rift() @@ -224,7 +221,6 @@ O.throw_at_random(FALSE, 3, THROWNOBJ_KNOCKBACK_SPEED) var/area/A = get_area(src) message_all_revenants(FONT_LARGE(SPAN_WARNING("The rift keeping us here has been destroyed in [A.name]!"))) - QDEL_NULL(soundloop) return ..() /obj/effect/portal/revenant/attackby(obj/item/I, mob/user) diff --git a/code/game/objects/items/weapons/thermal_drill.dm b/code/game/objects/items/weapons/thermal_drill.dm index e4113a90430..cf095a4df35 100644 --- a/code/game/objects/items/weapons/thermal_drill.dm +++ b/code/game/objects/items/weapons/thermal_drill.dm @@ -6,14 +6,11 @@ w_class = ITEMSIZE_HUGE force = 12 var/time_multiplier = 1 - var/datum/looping_sound/thermal_drill/soundloop /obj/item/thermal_drill/Initialize() . = ..() - soundloop = new(list(src), FALSE) /obj/item/thermal_drill/Destroy() - QDEL_NULL(soundloop) return ..() /obj/item/thermal_drill/diamond_drill diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm index 7bb0deabe2e..3c2017b25ff 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -54,7 +54,6 @@ FLOOR SAFES /obj/structure/safe/Destroy() if(drill) - drill.soundloop.stop() drill.forceMove(loc) drill = null return ..() @@ -119,7 +118,6 @@ FLOOR SAFES if(broken) return last_drill_time = world.time - drill.soundloop.start() START_PROCESSING(SSprocessing, src) update_icon() if("Turn Off") @@ -128,7 +126,6 @@ FLOOR SAFES if(do_after(user, 2 SECONDS)) if(!drill || !isprocessing) return - drill.soundloop.stop() STOP_PROCESSING(SSprocessing, src) update_icon() if("Remove Drill") @@ -251,12 +248,10 @@ FLOOR SAFES /obj/structure/safe/proc/drill_open() broken = TRUE - if(drill) - drill.soundloop.stop() STOP_PROCESSING(SSprocessing, src) update_icon() -obj/structure/safe/ex_act(severity) +/obj/structure/safe/ex_act(severity) return //FLOOR SAFES diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index 6d1d7d64a4f..e7decd0e285 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -142,15 +142,12 @@ var/mobpresent = 0 //true if there is a mob on the shower's loc, this is to ease process() var/is_washing = 0 var/list/temperature_settings = list("normal" = 310, "boiling" = T0C+100, "freezing" = T0C) - var/datum/looping_sound/showering/soundloop /obj/machinery/shower/Initialize() . = ..() create_reagents(2) - soundloop = new(list(src), FALSE) /obj/machinery/shower/Destroy() - QDEL_NULL(soundloop) return ..() //add heat controls? when emagged, you can freeze to death in it? @@ -191,7 +188,6 @@ qdel(mymist) if(on) - soundloop.start(src) add_overlay(image('icons/obj/watercloset.dmi', src, "water", MOB_LAYER + 1, dir)) if(temperature_settings[watertemp] < T20C) return //no mist for cold water @@ -204,7 +200,6 @@ ismist = 1 mymist = new /obj/effect/mist(loc) else - soundloop.stop(src) if(ismist) ismist = 1 mymist = new /obj/effect/mist(loc) diff --git a/code/modules/admin/verbs/bluespacetech.dm b/code/modules/admin/verbs/bluespacetech.dm index 662c8540873..7c9e4cccdfe 100644 --- a/code/modules/admin/verbs/bluespacetech.dm +++ b/code/modules/admin/verbs/bluespacetech.dm @@ -161,7 +161,7 @@ src.custom_emote(VISIBLE_MESSAGE,"presses a button on their suit, followed by a polite bow.") spark(src, 5, alldirs) - addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, src), 10, TIMER_CLIENT_TIME) + QDEL_IN(src, 10) animate(src, alpha = 0, time = 9, easing = QUAD_EASING) if(key) if(client.holder && client.holder.original_mob) diff --git a/code/modules/cooking/machinery/cooking_machines/fryer.dm b/code/modules/cooking/machinery/cooking_machines/fryer.dm index 4de8faa54cd..9cd78aea694 100644 --- a/code/modules/cooking/machinery/cooking_machines/fryer.dm +++ b/code/modules/cooking/machinery/cooking_machines/fryer.dm @@ -24,7 +24,6 @@ var/datum/reagents/oil var/optimal_oil = 9000//90 litres of cooking oil - var/datum/looping_sound/deep_fryer/fry_loop /obj/machinery/appliance/cooker/fryer/examine(var/mob/user) . = ..() @@ -41,7 +40,6 @@ //Sometimes the fryer will start with much less than full oil, significantly impacting efficiency until filled variance = rand()*0.5 oil.add_reagent(/decl/reagent/nutriment/triglyceride/oil/corn, optimal_oil*(1 - variance)) - fry_loop = new(list(src), FALSE) /obj/machinery/appliance/cooker/fryer/heat_up() if (..()) @@ -74,10 +72,8 @@ /obj/machinery/appliance/cooker/fryer/update_icon() if (cooking) icon_state = on_icon - fry_loop.start() else icon_state = off_icon - fry_loop.stop(src) ..() //Fryer gradually infuses any cooked food with oil. Moar calories diff --git a/code/modules/mob/abstract/new_player/menu.dm b/code/modules/mob/abstract/new_player/menu.dm index 714ad526550..5564feb3a4a 100644 --- a/code/modules/mob/abstract/new_player/menu.dm +++ b/code/modules/mob/abstract/new_player/menu.dm @@ -19,6 +19,7 @@ using = new /obj/screen/new_player/selection/join_game(src) using.name = "Join Game" + using.hud = src adding += using using = new /obj/screen/new_player/selection/settings(src) diff --git a/code/modules/mob/abstract/new_player/new_player.dm b/code/modules/mob/abstract/new_player/new_player.dm index ee6e54a5bee..81dcd71336e 100644 --- a/code/modules/mob/abstract/new_player/new_player.dm +++ b/code/modules/mob/abstract/new_player/new_player.dm @@ -380,7 +380,7 @@ INITIALIZE_IMMEDIATE(/mob/abstract/new_player) SSrecords.open_manifest_vueui(src) /mob/abstract/new_player/Move() - return 0 + return TRUE /mob/abstract/new_player/proc/close_spawn_windows() src << browse(null, "window=playersetup") //closes the player setup window diff --git a/code/modules/mob/living/silicon/robot/presets.dm b/code/modules/mob/living/silicon/robot/presets.dm index d8293a217f3..a60a89e7a9d 100644 --- a/code/modules/mob/living/silicon/robot/presets.dm +++ b/code/modules/mob/living/silicon/robot/presets.dm @@ -62,7 +62,7 @@ src.custom_emote(VISIBLE_MESSAGE, "politely beeps as its lights start to flash.") spark(src, 5, alldirs) - addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, src), 10, TIMER_CLIENT_TIME) + QDEL_IN(src, 10) animate(src, alpha = 0, time = 9, easing = QUAD_EASING) if(key) diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index 059c0a12403..6ebf30b0263 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -13,11 +13,9 @@ var/open = FALSE var/power_output = 1 has_special_power_checks = TRUE - var/datum/looping_sound/generator/soundloop /obj/machinery/power/port_gen/Initialize() . = ..() - soundloop = new(list(src), active) /obj/machinery/power/port_gen/proc/IsBroken() return (stat & (BROKEN|EMPED)) @@ -51,11 +49,9 @@ /obj/machinery/power/port_gen/attack_hand(mob/user) if(..()) update_icon() - soundloop.stop() return if(!anchored) update_icon() - soundloop.start() return /obj/machinery/power/port_gen/update_icon() diff --git a/code/modules/supermatter/supermatter.dm b/code/modules/supermatter/supermatter.dm index 03b4f5c3ffb..ca2add4245c 100644 --- a/code/modules/supermatter/supermatter.dm +++ b/code/modules/supermatter/supermatter.dm @@ -108,16 +108,12 @@ var/debug = 0 var/last_message_time = -100 //for message - var/datum/looping_sound/supermatter/soundloop - /obj/machinery/power/supermatter/Initialize() . = ..() radio = new /obj/item/device/radio{channels=list("Engineering")}(src) - soundloop = new(list(src), TRUE) /obj/machinery/power/supermatter/Destroy() QDEL_NULL(radio) - QDEL_NULL(soundloop) . = ..() /obj/machinery/power/supermatter/proc/explode() @@ -209,11 +205,6 @@ if(!istype(L)) //We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now. return //Yeah just stop. - if(power) - soundloop.volume = min(100, (round(power/7)+1)) - else - soundloop.volume = 0 - if(damage > explosion_point) if(!exploded) if(!istype(L, /turf/space)) diff --git a/code/world.dm b/code/world.dm index 0cef1469500..7a3154909d6 100644 --- a/code/world.dm +++ b/code/world.dm @@ -56,6 +56,7 @@ var/global/datum/global_init/init = new () cache_lifespan = 0 //stops player uploaded stuff from being kept in the rsc past the current session maxx = WORLD_MIN_SIZE // So that we don't get map-window-popin at boot. DMMS will expand this. maxy = WORLD_MIN_SIZE + fps = 20 #define RECOMMENDED_VERSION 510 /world/New() diff --git a/html/changelogs/mattatlas-cripplinglag.yml b/html/changelogs/mattatlas-cripplinglag.yml new file mode 100644 index 00000000000..c613d6a012e --- /dev/null +++ b/html/changelogs/mattatlas-cripplinglag.yml @@ -0,0 +1,41 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: MattAtlas + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - bugfix: "Fixed a lobby spawn issue."