From 47cd4cb12752ef5e07e35664c6c39007f2b766da Mon Sep 17 00:00:00 2001 From: tigercat2000 Date: Fri, 2 Mar 2018 21:12:49 -0800 Subject: [PATCH 1/7] StonedMC This commit ports the StonedMC from /tg/station, intended to replace the Process Scheduler from goon. Currently, they exist simultaneously, as it's very difficult to port our 22 processes to the SMC all at once. Instead, we can make them work together until everything is converted over at a later point, and then take the old PS out back and put a couple of rounds into it's deformed skull. Primary benefits of this new process controller include: Other people that can actually maintain it, unlike the PS, pre-world-init initialization for subsystems, ease of ports from /tg/station13, and potential performance improvement (to be seen). --- code/__DEFINES/MC.dm | 78 +++ code/__DEFINES/_globals.dm | 38 + code/__DEFINES/flags.dm | 2 + code/__DEFINES/math.dm | 18 +- code/__DEFINES/subsystems.dm | 119 ++++ code/__DEFINES/tick.dm | 9 +- code/__HELPERS/cmp.dm | 38 + code/__HELPERS/lists.dm | 82 ++- code/__HELPERS/sorts/InsertSort.dm | 19 + code/__HELPERS/sorts/MergeSort.dm | 19 + code/__HELPERS/sorts/TimSort.dm | 20 + code/__HELPERS/sorts/__main.dm | 647 ++++++++++++++++++ code/__HELPERS/time.dm | 9 +- code/__HELPERS/unsorted.dm | 7 + .../ProcessScheduler/core/process.dm | 4 +- .../ProcessScheduler/core/processScheduler.dm | 2 +- code/controllers/configuration.dm | 10 + code/controllers/controller.dm | 19 + code/controllers/failsafe.dm | 112 ++- code/controllers/globals.dm | 69 ++ code/controllers/master.dm | 611 +++++++++++++++++ code/controllers/master_controller.dm | 18 +- code/controllers/subsystem.dm | 215 ++++++ code/controllers/verbs.dm | 16 +- code/datums/statclick.dm | 35 +- code/game/gamemodes/gameticker.dm | 6 + code/modules/mob/mob.dm | 19 + code/world.dm | 6 +- paradise.dme | 12 + 29 files changed, 2181 insertions(+), 78 deletions(-) create mode 100644 code/__DEFINES/MC.dm create mode 100644 code/__DEFINES/_globals.dm create mode 100644 code/__DEFINES/subsystems.dm create mode 100644 code/__HELPERS/cmp.dm create mode 100644 code/__HELPERS/sorts/InsertSort.dm create mode 100644 code/__HELPERS/sorts/MergeSort.dm create mode 100644 code/__HELPERS/sorts/TimSort.dm create mode 100644 code/__HELPERS/sorts/__main.dm create mode 100644 code/controllers/controller.dm create mode 100644 code/controllers/globals.dm create mode 100644 code/controllers/master.dm create mode 100644 code/controllers/subsystem.dm diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm new file mode 100644 index 00000000000..cbcf2c1dd90 --- /dev/null +++ b/code/__DEFINES/MC.dm @@ -0,0 +1,78 @@ +#define MC_TICK_CHECK ( ( TICK_USAGE > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 ) + +#define MC_SPLIT_TICK_INIT(phase_count) var/original_tick_limit = Master.current_ticklimit; var/split_tick_phases = ##phase_count +#define MC_SPLIT_TICK \ + if(split_tick_phases > 1){\ + Master.current_ticklimit = ((original_tick_limit - TICK_USAGE) / split_tick_phases) + TICK_USAGE;\ + --split_tick_phases;\ + } else {\ + Master.current_ticklimit = original_tick_limit;\ + } + +// Used to smooth out costs to try and avoid oscillation. +#define MC_AVERAGE_FAST(average, current) (0.7 * (average) + 0.3 * (current)) +#define MC_AVERAGE(average, current) (0.8 * (average) + 0.2 * (current)) +#define MC_AVERAGE_SLOW(average, current) (0.9 * (average) + 0.1 * (current)) + +#define MC_AVG_FAST_UP_SLOW_DOWN(average, current) (average > current ? MC_AVERAGE_SLOW(average, current) : MC_AVERAGE_FAST(average, current)) +#define MC_AVG_SLOW_UP_FAST_DOWN(average, current) (average < current ? MC_AVERAGE_SLOW(average, current) : MC_AVERAGE_FAST(average, current)) + +#define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;} + +#define START_PROCESSING(Processor, Datum) if (!Datum.isprocessing) {Datum.isprocessing = TRUE;Processor.processing += Datum} +#define STOP_PROCESSING(Processor, Datum) Datum.isprocessing = FALSE;Processor.processing -= Datum + +//SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier) + +//subsystem does not initialize. +#define SS_NO_INIT 1 + +//subsystem does not fire. +// (like can_fire = 0, but keeps it from getting added to the processing subsystems list) +// (Requires a MC restart to change) +#define SS_NO_FIRE 2 + +//subsystem only runs on spare cpu (after all non-background subsystems have ran that tick) +// SS_BACKGROUND has its own priority bracket +#define SS_BACKGROUND 4 + +//subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background)) +#define SS_NO_TICK_CHECK 8 + +//Treat wait as a tick count, not DS, run every wait ticks. +// (also forces it to run first in the tick, above even SS_NO_TICK_CHECK subsystems) +// (implies all runlevels because of how it works) +// (overrides SS_BACKGROUND) +// This is designed for basically anything that works as a mini-mc (like SStimer) +#define SS_TICKER 16 + +//keep the subsystem's timing on point by firing early if it fired late last fire because of lag +// ie: if a 20ds subsystem fires say 5 ds late due to lag or what not, its next fire would be in 15ds, not 20ds. +#define SS_KEEP_TIMING 32 + +//Calculate its next fire after its fired. +// (IE: if a 5ds wait SS takes 2ds to run, its next fire should be 5ds away, not 3ds like it normally would be) +// This flag overrides SS_KEEP_TIMING +#define SS_POST_FIRE_TIMING 64 + +//SUBSYSTEM STATES +#define SS_IDLE 0 //aint doing shit. +#define SS_QUEUED 1 //queued to run +#define SS_RUNNING 2 //actively running +#define SS_PAUSED 3 //paused by mc_tick_check +#define SS_SLEEPING 4 //fire() slept. +#define SS_PAUSING 5 //in the middle of pausing + +#define SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/##X);\ +/datum/controller/subsystem/##X/New(){\ + NEW_SS_GLOBAL(SS##X);\ + PreInit();\ +}\ +/datum/controller/subsystem/##X + +#define PROCESSING_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/processing/##X);\ +/datum/controller/subsystem/processing/##X/New(){\ + NEW_SS_GLOBAL(SS##X);\ + PreInit();\ +}\ +/datum/controller/subsystem/processing/##X diff --git a/code/__DEFINES/_globals.dm b/code/__DEFINES/_globals.dm new file mode 100644 index 00000000000..7e7aa3158f3 --- /dev/null +++ b/code/__DEFINES/_globals.dm @@ -0,0 +1,38 @@ +//See controllers/globals.dm +#define GLOBAL_MANAGED(X, InitValue)\ +/datum/controller/global_vars/proc/InitGlobal##X(){\ + ##X = ##InitValue;\ + gvars_datum_init_order += #X;\ +} +#define GLOBAL_UNMANAGED(X) /datum/controller/global_vars/proc/InitGlobal##X() { return; } + +#ifndef TESTING +#define GLOBAL_PROTECT(X)\ +/datum/controller/global_vars/InitGlobal##X(){\ + ..();\ + gvars_datum_protected_varlist[#X] = TRUE;\ +} +#else +#define GLOBAL_PROTECT(X) +#endif + +#define GLOBAL_REAL_VAR(X) var/global/##X +#define GLOBAL_REAL(X, Typepath) var/global##Typepath/##X + +#define GLOBAL_RAW(X) /datum/controller/global_vars/var/global##X + +#define GLOBAL_VAR_INIT(X, InitValue) GLOBAL_RAW(/##X); GLOBAL_MANAGED(X, InitValue) + +#define GLOBAL_VAR_CONST(X, InitValue) GLOBAL_RAW(/const/##X) = InitValue; GLOBAL_UNMANAGED(X) + +#define GLOBAL_LIST_INIT(X, InitValue) GLOBAL_RAW(/list/##X); GLOBAL_MANAGED(X, InitValue) + +#define GLOBAL_LIST_EMPTY(X) GLOBAL_LIST_INIT(X, list()) + +#define GLOBAL_DATUM_INIT(X, Typepath, InitValue) GLOBAL_RAW(Typepath/##X); GLOBAL_MANAGED(X, InitValue) + +#define GLOBAL_VAR(X) GLOBAL_RAW(/##X); GLOBAL_UNMANAGED(X) + +#define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_UNMANAGED(X) + +#define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_UNMANAGED(X) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index a03ba7c9d4c..b140ca4d627 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -89,3 +89,5 @@ #define AFFECT_ROBOTIC_ORGAN 1 #define AFFECT_ORGANIC_ORGAN 2 #define AFFECT_ALL_ORGANS 3 + +GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)) \ No newline at end of file diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm index d71047fe3d3..c6ffc61495e 100644 --- a/code/__DEFINES/math.dm +++ b/code/__DEFINES/math.dm @@ -30,4 +30,20 @@ #define Lcm(a, b) (abs(a) / Gcd((a), (b)) * abs(b)) #define Root(n, x) ((x) ** (1 / (n))) #define ToDegrees(radians) ((radians) * 57.2957795) // 180 / Pi -#define ToRadians(degrees) ((degrees) * 0.0174532925) // Pi / 180 \ No newline at end of file +#define ToRadians(degrees) ((degrees) * 0.0174532925) // Pi / 180 + +//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks +//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio) +//collapsed to percent_of_tick_used * tick_lag +#define TICK_DELTA_TO_MS(percent_of_tick_used) ((percent_of_tick_used) * world.tick_lag) +#define TICK_USAGE_TO_MS(starting_tickusage) (TICK_DELTA_TO_MS(TICK_USAGE_REAL - starting_tickusage)) + +//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 ( GLOB.rollovercheck_last_timeofday != world.timeofday ? update_midnight_rollover() : GLOB.midnight_rollovers ) + +#define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) + +// round() acts like floor(x, 1) by default but can't handle other values +#define FLOOR(x, y) ( round((x) / (y)) * (y) ) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm new file mode 100644 index 00000000000..a0ea6b60777 --- /dev/null +++ b/code/__DEFINES/subsystems.dm @@ -0,0 +1,119 @@ +//Update this whenever the db schema changes +//make sure you add an update to the schema_version stable in the db changelog +#define DB_MAJOR_VERSION 4 +#define DB_MINOR_VERSION 1 + +//Timing subsystem +//Don't run if there is an identical unique timer active +//if the arguments to addtimer are the same as an existing timer, it doesn't create a new timer, and returns the id of the existing timer +#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 + +#define TIMER_NO_INVOKE_WARNING 600 //number of byond ticks that are allowed to pass before the timer subsystem thinks it hung on something + +#define TIMER_ID_NULL -1 + +//For servers that can't do with any additional lag, set this to none in flightpacks.dm in subsystem/processing. +#define FLIGHTSUIT_PROCESSING_NONE 0 +#define FLIGHTSUIT_PROCESSING_FULL 1 + +#define INITIALIZATION_INSSATOMS 0 //New should not call Initialize +#define INITIALIZATION_INNEW_MAPLOAD 2 //New should call Initialize(TRUE) +#define INITIALIZATION_INNEW_REGULAR 1 //New should call Initialize(FALSE) + +#define INITIALIZE_HINT_NORMAL 0 //Nothing happens +#define INITIALIZE_HINT_LATELOAD 1 //Call LateInitialize +#define INITIALIZE_HINT_QDEL 2 //Call qdel on the atom + +//type and all subtypes should always call Initialize in New() +#define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\ + ..();\ + if(!initialized) {\ + args[1] = TRUE;\ + SSatoms.InitAtom(src, args);\ + }\ +} + +// Subsystem init_order, from highest priority to lowest priority +// Subsystems shutdown in the reverse of the order they initialize in +// The numbers just define the ordering, they are meaningless otherwise. + +#define INIT_ORDER_GARBAGE 19 +#define INIT_ORDER_DBCORE 18 +#define INIT_ORDER_BLACKBOX 17 +#define INIT_ORDER_SERVER_MAINT 16 +#define INIT_ORDER_INPUT 15 +#define INIT_ORDER_RESEARCH 14 +#define INIT_ORDER_EVENTS 13 +#define INIT_ORDER_JOBS 12 +#define INIT_ORDER_TRAITS 11 +#define INIT_ORDER_TICKER 10 +#define INIT_ORDER_MAPPING 9 +#define INIT_ORDER_ATOMS 8 +#define INIT_ORDER_NETWORKS 7 +#define INIT_ORDER_LANGUAGE 6 +#define INIT_ORDER_MACHINES 5 +#define INIT_ORDER_CIRCUIT 4 +#define INIT_ORDER_TIMER 1 +#define INIT_ORDER_DEFAULT 0 +#define INIT_ORDER_AIR -1 +#define INIT_ORDER_MINIMAP -3 +#define INIT_ORDER_ASSETS -4 +#define INIT_ORDER_ICON_SMOOTHING -5 +#define INIT_ORDER_OVERLAY -6 +#define INIT_ORDER_XKEYSCORE -10 +#define INIT_ORDER_STICKY_BAN -10 +#define INIT_ORDER_LIGHTING -20 +#define INIT_ORDER_SHUTTLE -21 +#define INIT_ORDER_SQUEAK -40 +#define INIT_ORDER_PATH -50 +#define INIT_ORDER_PERSISTENCE -100 + +// Subsystem fire priority, from lowest to highest priority +// If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) + +#define FIRE_PRIORITY_IDLE_NPC 10 +#define FIRE_PRIORITY_SERVER_MAINT 10 +#define FIRE_PRIORITY_GARBAGE 15 +#define FIRE_PRIORITY_RESEARCH 15 +#define FIRE_PRIORITY_AIR 20 +#define FIRE_PRIORITY_NPC 20 +#define FIRE_PRIORITY_PROCESS 25 +#define FIRE_PRIORITY_THROWING 25 +#define FIRE_PRIORITY_FLIGHTPACKS 30 +#define FIRE_PRIORITY_SPACEDRIFT 30 +#define FIRE_PRIOTITY_SMOOTHING 35 +#define FIRE_PRIORITY_ORBIT 35 +#define FIRE_PRIORITY_OBJ 40 +#define FIRE_PRIORUTY_FIELDS 40 +#define FIRE_PRIORITY_ACID 40 +#define FIRE_PRIOTITY_BURNING 40 +#define FIRE_PRIORITY_INBOUNDS 40 +#define FIRE_PRIORITY_DEFAULT 50 +#define FIRE_PRIORITY_PARALLAX 65 +#define FIRE_PRIORITY_NETWORKS 80 +#define FIRE_PRIORITY_MOBS 100 +#define FIRE_PRIORITY_TGUI 110 +#define FIRE_PRIORITY_TICKER 200 +#define FIRE_PRIORITY_OVERLAYS 500 +#define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. + +// SS runlevels + +#define RUNLEVEL_INIT 0 +#define RUNLEVEL_LOBBY 1 +#define RUNLEVEL_SETUP 2 +#define RUNLEVEL_GAME 4 +#define RUNLEVEL_POSTGAME 8 + +#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) diff --git a/code/__DEFINES/tick.dm b/code/__DEFINES/tick.dm index 71a63af6289..80550ab5e7a 100644 --- a/code/__DEFINES/tick.dm +++ b/code/__DEFINES/tick.dm @@ -1,5 +1,10 @@ -#define TICK_LIMIT_RUNNING 90 -#define TICK_LIMIT_TO_RUN 85 +#define TICK_LIMIT_RUNNING 80 +#define TICK_LIMIT_TO_RUN 70 +#define TICK_LIMIT_MC 70 +#define TICK_LIMIT_MC_INIT_DEFAULT 98 + +#define TICK_USAGE world.tick_usage //for general usage +#define TICK_USAGE_REAL world.tick_usage //to be used where the result isn't checked #define TICK_CHECK ( world.tick_usage > TICK_LIMIT_RUNNING ? stoplag() : 0 ) #define CHECK_TICK if(world.tick_usage > TICK_LIMIT_RUNNING) stoplag() \ No newline at end of file diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm new file mode 100644 index 00000000000..2b02c212659 --- /dev/null +++ b/code/__HELPERS/cmp.dm @@ -0,0 +1,38 @@ +/proc/cmp_numeric_dsc(a,b) + return b - a + +/proc/cmp_numeric_asc(a,b) + return a - b + +/proc/cmp_text_asc(a,b) + return sorttext(b,a) + +/proc/cmp_text_dsc(a,b) + return sorttext(a,b) + +/proc/cmp_name_asc(atom/a, atom/b) + return sorttext(b.name, a.name) + +/proc/cmp_name_dsc(atom/a, atom/b) + return sorttext(a.name, b.name) + +/proc/cmp_ckey_asc(client/a, client/b) + return sorttext(b.ckey, a.ckey) + +/proc/cmp_ckey_dsc(client/a, client/b) + return sorttext(a.ckey, b.ckey) + +/proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b) + return initial(b.init_order) - initial(a.init_order) //uses initial() so it can be used on types + +/proc/cmp_subsystem_display(datum/controller/subsystem/a, datum/controller/subsystem/b) + return sorttext(b.name, a.name) + +/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b) + return a.priority - b.priority + +/proc/cmp_atom_layer_asc(atom/A,atom/B) + if(A.plane != B.plane) + return A.plane - B.plane + else + return A.layer - B.layer \ No newline at end of file diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 6ee5097a76e..5627a355557 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -678,4 +678,84 @@ proc/dd_sortedObjectList(list/incoming) if(isnum(key)) L |= key else - L[key] = temp[key] \ No newline at end of file + L[key] = temp[key] + +//Move a single element from position fromIndex within a list, to position toIndex +//All elements in the range [1,toIndex) before the move will be before the pivot afterwards +//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards +//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. +//fromIndex and toIndex must be in the range [1,L.len+1] +//This will preserve associations ~Carnie +/proc/moveElement(list/L, fromIndex, toIndex) + if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move + return + if(fromIndex > toIndex) + ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one + + L.Insert(toIndex, null) + L.Swap(fromIndex, toIndex) + L.Cut(fromIndex, fromIndex+1) + + +//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) +//Same as moveElement but for ranges of elements +//This will preserve associations ~Carnie +/proc/moveRange(list/L, fromIndex, toIndex, len=1) + var/distance = abs(toIndex - fromIndex) + if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements + if(fromIndex <= toIndex) + return //no need to move + fromIndex += len //we want to shift left instead of right + + for(var/i=0, i toIndex) + fromIndex += len + + for(var/i=0, i distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements + if(fromIndex < toIndex) + toIndex += len + else + fromIndex += len + + for(var/i=0, i fromIndex) + var/a = toIndex + toIndex = fromIndex + fromIndex = a + + for(var/i=0, i= 2) + fromIndex = fromIndex % L.len + toIndex = toIndex % (L.len+1) + if(fromIndex <= 0) + fromIndex += L.len + if(toIndex <= 0) + toIndex += L.len + 1 + + var/datum/sortInstance/SI = GLOB.sortInstance + if(!SI) + SI = new + SI.L = L + SI.cmp = cmp + SI.associative = associative + + SI.binarySort(fromIndex, toIndex, fromIndex) + return L \ No newline at end of file diff --git a/code/__HELPERS/sorts/MergeSort.dm b/code/__HELPERS/sorts/MergeSort.dm new file mode 100644 index 00000000000..39d37997255 --- /dev/null +++ b/code/__HELPERS/sorts/MergeSort.dm @@ -0,0 +1,19 @@ +//merge-sort - gernerally faster than insert sort, for runs of 7 or larger +/proc/sortMerge(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex) + if(L && L.len >= 2) + fromIndex = fromIndex % L.len + toIndex = toIndex % (L.len+1) + if(fromIndex <= 0) + fromIndex += L.len + if(toIndex <= 0) + toIndex += L.len + 1 + + var/datum/sortInstance/SI = GLOB.sortInstance + if(!SI) + SI = new + SI.L = L + SI.cmp = cmp + SI.associative = associative + + SI.mergeSort(fromIndex, toIndex) + return L \ No newline at end of file diff --git a/code/__HELPERS/sorts/TimSort.dm b/code/__HELPERS/sorts/TimSort.dm new file mode 100644 index 00000000000..d709044dc05 --- /dev/null +++ b/code/__HELPERS/sorts/TimSort.dm @@ -0,0 +1,20 @@ +//TimSort interface +/proc/sortTim(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex=0) + if(L && L.len >= 2) + fromIndex = fromIndex % L.len + toIndex = toIndex % (L.len+1) + if(fromIndex <= 0) + fromIndex += L.len + if(toIndex <= 0) + toIndex += L.len + 1 + + var/datum/sortInstance/SI = GLOB.sortInstance + if(!SI) + SI = new + + SI.L = L + SI.cmp = cmp + SI.associative = associative + + SI.timSort(fromIndex, toIndex) + return L \ No newline at end of file diff --git a/code/__HELPERS/sorts/__main.dm b/code/__HELPERS/sorts/__main.dm new file mode 100644 index 00000000000..768622818ff --- /dev/null +++ b/code/__HELPERS/sorts/__main.dm @@ -0,0 +1,647 @@ + //These are macros used to reduce on proc calls +#define fetchElement(L, i) (associative) ? L[L[i]] : L[i] + + //Minimum sized sequence that will be merged. Anything smaller than this will use binary-insertion sort. + //Should be a power of 2 +#define MIN_MERGE 32 + + //When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times. +#define MIN_GALLOP 7 + + //This is a global instance to allow much of this code to be reused. The interfaces are kept separately +GLOBAL_DATUM_INIT(sortInstance, /datum/sortInstance, new()) +/datum/sortInstance + //The array being sorted. + var/list/L + + //The comparator proc-reference + var/cmp = /proc/cmp_numeric_asc + + //whether we are sorting list keys (0: L[i]) or associated values (1: L[L[i]]) + var/associative = 0 + + //This controls when we get *into* galloping mode. It is initialized to MIN_GALLOP. + //The mergeLo and mergeHi methods nudge it higher for random data, and lower for highly structured data. + var/minGallop = MIN_GALLOP + + //Stores information regarding runs yet to be merged. + //Run i starts at runBase[i] and extends for runLen[i] elements. + //runBase[i] + runLen[i] == runBase[i+1] + var/list/runBases = list() + var/list/runLens = list() + + + proc/timSort(start, end) + runBases.Cut() + runLens.Cut() + + var/remaining = end - start + + //If array is small, do a 'mini-TimSort' with no merges + if(remaining < MIN_MERGE) + var/initRunLen = countRunAndMakeAscending(start, end) + binarySort(start, end, start+initRunLen) + return + + //March over the array finding natural runs + //Extend any short natural runs to runs of length minRun + var/minRun = minRunLength(remaining) + + do + //identify next run + var/runLen = countRunAndMakeAscending(start, end) + + //if run is short, extend to min(minRun, remaining) + if(runLen < minRun) + var/force = (remaining <= minRun) ? remaining : minRun + + binarySort(start, start+force, start+runLen) + runLen = force + + //add data about run to queue + runBases.Add(start) + runLens.Add(runLen) + + //maybe merge + mergeCollapse() + + //Advance to find next run + start += runLen + remaining -= runLen + + while(remaining > 0) + + + //Merge all remaining runs to complete sort + //ASSERT(start == end) + mergeForceCollapse(); + //ASSERT(runBases.len == 1) + + //reset minGallop, for successive calls + minGallop = MIN_GALLOP + + return L + + /* + Sorts the specified portion of the specified array using a binary + insertion sort. This is the best method for sorting small numbers + of elements. It requires O(n log n) compares, but O(n^2) data + movement (worst case). + + If the initial part of the specified range is already sorted, + this method can take advantage of it: the method assumes that the + elements in range [lo,start) are already sorted + + lo the index of the first element in the range to be sorted + hi the index after the last element in the range to be sorted + start the index of the first element in the range that is not already known to be sorted + */ + proc/binarySort(lo, hi, start) + //ASSERT(lo <= start && start <= hi) + if(start <= lo) + start = lo + 1 + + for(,start < hi, ++start) + var/pivot = fetchElement(L,start) + + //set left and right to the index where pivot belongs + var/left = lo + var/right = start + //ASSERT(left <= right) + + //[lo, left) elements <= pivot < [right, start) elements + //in other words, find where the pivot element should go using bisection search + while(left < right) + var/mid = (left + right) >> 1 //round((left+right)/2) + if(call(cmp)(fetchElement(L,mid), pivot) > 0) + right = mid + else + left = mid+1 + + //ASSERT(left == right) + moveElement(L, start, left) //move pivot element to correct location in the sorted range + + /* + Returns the length of the run beginning at the specified position and reverses the run if it is back-to-front + + A run is the longest ascending sequence with: + a[lo] <= a[lo + 1] <= a[lo + 2] <= ... + or the longest descending sequence with: + a[lo] > a[lo + 1] > a[lo + 2] > ... + + For its intended use in a stable mergesort, the strictness of the + definition of "descending" is needed so that the call can safely + reverse a descending sequence without violating stability. + */ + proc/countRunAndMakeAscending(lo, hi) + //ASSERT(lo < hi) + + var/runHi = lo + 1 + if(runHi >= hi) + return 1 + + var/last = fetchElement(L,lo) + var/current = fetchElement(L,runHi++) + + if(call(cmp)(current, last) < 0) + while(runHi < hi) + last = current + current = fetchElement(L,runHi) + if(call(cmp)(current, last) >= 0) + break + ++runHi + reverseRange(L, lo, runHi) + else + while(runHi < hi) + last = current + current = fetchElement(L,runHi) + if(call(cmp)(current, last) < 0) + break + ++runHi + + return runHi - lo + + //Returns the minimum acceptable run length for an array of the specified length. + //Natural runs shorter than this will be extended with binarySort + proc/minRunLength(n) + //ASSERT(n >= 0) + var/r = 0 //becomes 1 if any bits are shifted off + while(n >= MIN_MERGE) + r |= (n & 1) + n >>= 1 + return n + r + + //Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished: + // runLen[i-3] > runLen[i-2] + runLen[i-1] + // runLen[i-2] > runLen[i-1] + //This method is called each time a new run is pushed onto the stack. + //So the invariants are guaranteed to hold for i= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1]) + if(runLens[n-1] < runLens[n+1]) + --n + mergeAt(n) + else if(runLens[n] <= runLens[n+1]) + mergeAt(n) + else + break //Invariant is established + + + //Merges all runs on the stack until only one remains. + //Called only once, to finalise the sort + proc/mergeForceCollapse() + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] < runLens[n+1]) + --n + mergeAt(n) + + + //Merges the two consecutive runs at stack indices i and i+1 + //Run i must be the penultimate or antepenultimate run on the stack + //In other words, i must be equal to stackSize-2 or stackSize-3 + proc/mergeAt(i) + //ASSERT(runBases.len >= 2) + //ASSERT(i >= 1) + //ASSERT(i == runBases.len - 1 || i == runBases.len - 2) + + var/base1 = runBases[i] + var/base2 = runBases[i+1] + var/len1 = runLens[i] + var/len2 = runLens[i+1] + + //ASSERT(len1 > 0 && len2 > 0) + //ASSERT(base1 + len1 == base2) + + //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run + //(which isn't involved in this merge). The current run (i+1) goes away in any case. + runLens[i] += runLens[i+1] + runLens.Cut(i+1, i+2) + runBases.Cut(i+1, i+2) + + + //Find where the first element of run2 goes in run1. + //Prior elements in run1 can be ignored (because they're already in place) + var/k = gallopRight(fetchElement(L,base2), base1, len1, 0) + //ASSERT(k >= 0) + base1 += k + len1 -= k + if(len1 == 0) + return + + //Find where the last element of run1 goes in run2. + //Subsequent elements in run2 can be ignored (because they're already in place) + len2 = gallopLeft(fetchElement(L,base1 + len1 - 1), base2, len2, len2-1) + //ASSERT(len2 >= 0) + if(len2 == 0) + return + + //Merge remaining runs, using tmp array with min(len1, len2) elements + if(len1 <= len2) + mergeLo(base1, len1, base2, len2) + else + mergeHi(base1, len1, base2, len2) + + + /* + Locates the position to insert key within the specified sorted range + If the range contains elements equal to key, this will return the index of the LEFTMOST of those elements + + key the element to be inserted into the sorted range + base the index of the first element of the sorted range + len the length of the sorted range, must be greater than 0 + hint the offset from base at which to begin the search, such that 0 <= hint < len; i.e. base <= hint < base+hint + + Returns the index at which to insert element 'key' + */ + proc/gallopLeft(key, base, len, hint) + //ASSERT(len > 0 && hint >= 0 && hint < len) + + var/lastOffset = 0 + var/offset = 1 + if(call(cmp)(key, fetchElement(L,base+hint)) > 0) + var/maxOffset = len - hint + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) > 0) + lastOffset = offset + offset = (offset << 1) + 1 + + if(offset > maxOffset) + offset = maxOffset + + lastOffset += hint + offset += hint + + else + var/maxOffset = hint + 1 + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) <= 0) + lastOffset = offset + offset = (offset << 1) + 1 + + if(offset > maxOffset) + offset = maxOffset + + var/temp = lastOffset + lastOffset = hint - offset + offset = hint - temp + + //ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len) + + //Now L[base+lastOffset] < key <= L[base+offset], so key belongs somewhere to the right of lastOffset but no farther than + //offset. Do a binary search with invariant L[base+lastOffset-1] < key <= L[base+offset] + ++lastOffset + while(lastOffset < offset) + var/m = lastOffset + ((offset - lastOffset) >> 1) + + if(call(cmp)(key, fetchElement(L,base+m)) > 0) + lastOffset = m + 1 + else + offset = m + + //ASSERT(lastOffset == offset) + return offset + + /** + * Like gallopLeft, except that if the range contains an element equal to + * key, gallopRight returns the index after the rightmost equal element. + * + * @param key the key whose insertion point to search for + * @param a the array in which to search + * @param base the index of the first element in the range + * @param len the length of the range; must be > 0 + * @param hint the index at which to begin the search, 0 <= hint < n. + * The closer hint is to the result, the faster this method will run. + * @param c the comparator used to order the range, and to search + * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] + */ + proc/gallopRight(key, base, len, hint) + //ASSERT(len > 0 && hint >= 0 && hint < len) + + var/offset = 1 + var/lastOffset = 0 + if(call(cmp)(key, fetchElement(L,base+hint)) < 0) //key <= L[base+hint] + var/maxOffset = hint + 1 //therefore we want to insert somewhere in the range [base,base+hint] = [base+,base+(hint+1)) + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) < 0) //we are iterating backwards + lastOffset = offset + offset = (offset << 1) + 1 //1 3 7 15 + + if(offset > maxOffset) + offset = maxOffset + + var/temp = lastOffset + lastOffset = hint - offset + offset = hint - temp + + else //key > L[base+hint] + var/maxOffset = len - hint //therefore we want to insert somewhere in the range (base+hint,base+len) = [base+hint+1, base+hint+(len-hint)) + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) >= 0) + lastOffset = offset + offset = (offset << 1) + 1 + + if(offset > maxOffset) + offset = maxOffset + + lastOffset += hint + offset += hint + + //ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len) + + ++lastOffset + while(lastOffset < offset) + var/m = lastOffset + ((offset - lastOffset) >> 1) + + if(call(cmp)(key, fetchElement(L,base+m)) < 0) //key <= L[base+m] + offset = m + else //key > L[base+m] + lastOffset = m + 1 + + //ASSERT(lastOffset == offset) + + return offset + + + //Merges two adjacent runs in-place in a stable fashion. + //For performance this method should only be called when len1 <= len2! + proc/mergeLo(base1, len1, base2, len2) + //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) + + var/cursor1 = base1 + var/cursor2 = base2 + + //degenerate cases + if(len2 == 1) + moveElement(L, cursor2, cursor1) + return + + if(len1 == 1) + moveElement(L, cursor1, cursor2+len2) + return + + + //Move first element of second run + moveElement(L, cursor2++, cursor1++) + --len2 + + outer: + while(1) + var/count1 = 0 //# of times in a row that first run won + var/count2 = 0 // " " " " " " second run won + + //do the straightfoward thin until one run starts winning consistently + + do + //ASSERT(len1 > 1 && len2 > 0) + if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0) + moveElement(L, cursor2++, cursor1++) + --len2 + + ++count2 + count1 = 0 + + if(len2 == 0) + break outer + else + ++cursor1 + + ++count1 + count2 = 0 + + if(--len1 == 1) + break outer + + while((count1 | count2) < minGallop) + + + //one run is winning consistently so galloping may provide huge benifits + //so try galloping, until such time as the run is no longer consistently winning + do + //ASSERT(len1 > 1 && len2 > 0) + + count1 = gallopRight(fetchElement(L,cursor2), cursor1, len1, 0) + if(count1) + cursor1 += count1 + len1 -= count1 + + if(len1 <= 1) + break outer + + moveElement(L, cursor2, cursor1) + ++cursor2 + ++cursor1 + if(--len2 == 0) + break outer + + count2 = gallopLeft(fetchElement(L,cursor1), cursor2, len2, 0) + if(count2) + moveRange(L, cursor2, cursor1, count2) + + cursor2 += count2 + cursor1 += count2 + len2 -= count2 + + if(len2 == 0) + break outer + + ++cursor1 + if(--len1 == 1) + break outer + + --minGallop + + while((count1|count2) > MIN_GALLOP) + + if(minGallop < 0) + minGallop = 0 + minGallop += 2; // Penalize for leaving gallop mode + + + if(len1 == 1) + //ASSERT(len2 > 0) + moveElement(L, cursor1, cursor2+len2) + + //else + //ASSERT(len2 == 0) + //ASSERT(len1 > 1) + + + proc/mergeHi(base1, len1, base2, len2) + //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) + + var/cursor1 = base1 + len1 - 1 //start at end of sublists + var/cursor2 = base2 + len2 - 1 + + //degenerate cases + if(len2 == 1) + moveElement(L, base2, base1) + return + + if(len1 == 1) + moveElement(L, base1, cursor2+1) + return + + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + outer: + while(1) + var/count1 = 0 //# of times in a row that first run won + var/count2 = 0 // " " " " " " second run won + + //do the straightfoward thing until one run starts winning consistently + do + //ASSERT(len1 > 0 && len2 > 1) + if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0) + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + ++count1 + count2 = 0 + + if(len1 == 0) + break outer + else + --cursor2 + --len2 + + ++count2 + count1 = 0 + + if(len2 == 1) + break outer + while((count1 | count2) < minGallop) + + //one run is winning consistently so galloping may provide huge benifits + //so try galloping, until such time as the run is no longer consistently winning + do + //ASSERT(len1 > 0 && len2 > 1) + + count1 = len1 - gallopRight(fetchElement(L,cursor2), base1, len1, len1-1) //should cursor1 be base1? + if(count1) + cursor1 -= count1 + + moveRange(L, cursor1+1, cursor2+1, count1) //cursor1+1 == cursor2 by definition + + cursor2 -= count1 + len1 -= count1 + + if(len1 == 0) + break outer + + --cursor2 + + if(--len2 == 1) + break outer + + count2 = len2 - gallopLeft(fetchElement(L,cursor1), cursor1+1, len2, len2-1) + if(count2) + cursor2 -= count2 + len2 -= count2 + + if(len2 <= 1) + break outer + + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + if(len1 == 0) + break outer + + --minGallop + while((count1|count2) > MIN_GALLOP) + + if(minGallop < 0) + minGallop = 0 + minGallop += 2 // Penalize for leaving gallop mode + + if(len2 == 1) + //ASSERT(len1 > 0) + + cursor1 -= len1 + moveRange(L, cursor1+1, cursor2+1, len1) + + //else + //ASSERT(len1 == 0) + //ASSERT(len2 > 0) + + + proc/mergeSort(start, end) + var/remaining = end - start + + //If array is small, do an insertion sort + if(remaining < MIN_MERGE) + binarySort(start, end, start/*+initRunLen*/) + return + + var/minRun = minRunLength(remaining) + + do + var/runLen = (remaining <= minRun) ? remaining : minRun + + binarySort(start, start+runLen, start) + + //add data about run to queue + runBases.Add(start) + runLens.Add(runLen) + + //Advance to find next run + start += runLen + remaining -= runLen + + while(remaining > 0) + + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1]) + if(runLens[n-1] < runLens[n+1]) + --n + mergeAt2(n) + else if(runLens[n] <= runLens[n+1]) + mergeAt2(n) + else + break //Invariant is established + + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] < runLens[n+1]) + --n + mergeAt2(n) + + return L + + proc/mergeAt2(i) + var/cursor1 = runBases[i] + var/cursor2 = runBases[i+1] + + var/end1 = cursor1+runLens[i] + var/end2 = cursor2+runLens[i+1] + + var/val1 = fetchElement(L,cursor1) + var/val2 = fetchElement(L,cursor2) + + while(1) + if(call(cmp)(val1,val2) <= 0) + if(++cursor1 >= end1) + break + val1 = fetchElement(L,cursor1) + else + moveElement(L,cursor2,cursor1) + + if(++cursor2 >= end2) + break + ++end1 + ++cursor1 + + val2 = fetchElement(L,cursor2) + + + //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run + //(which isn't involved in this merge). The current run (i+1) goes away in any case. + runLens[i] += runLens[i+1] + runLens.Cut(i+1, i+2) + runBases.Cut(i+1, i+2) + +#undef MIN_GALLOP +#undef MIN_MERGE + +#undef fetchElement diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index e4d834a35a0..0fceedac3b0 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -165,4 +165,11 @@ proc/isDay(var/month, var/day) else day = "1 day" - return "[day][hour][minute][second]" \ No newline at end of file + return "[day][hour][minute][second]" + +GLOBAL_VAR_INIT(midnight_rollovers, 0) +GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) +/proc/update_midnight_rollover() + if (world.timeofday < GLOB.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! + return GLOB.midnight_rollovers++ + return GLOB.midnight_rollovers \ No newline at end of file diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index f106d579e94..f1ee4e5415f 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1914,3 +1914,10 @@ var/mob/dview/dview_mob = new for(var/atom/thing in here) if(istype(thing, type) && (check_shift && thing.pixel_x == shift_x && thing.pixel_y == shift_y)) . += thing + +//gives us the stack trace from CRASH() without ending the current proc. +/proc/stack_trace(msg) + CRASH(msg) + +/datum/proc/stack_trace(msg) + CRASH(msg) \ No newline at end of file diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm index 5fe1663d7e8..4fc377e9e37 100644 --- a/code/controllers/ProcessScheduler/core/process.dm +++ b/code/controllers/ProcessScheduler/core/process.dm @@ -34,8 +34,6 @@ /** * Config vars */ - // Process name - var/name // Process schedule interval // This controls how often the process would run under ideal conditions. @@ -364,7 +362,7 @@ var/highestRunTime = round(highest_run_time, 0.001) var/deferTime = round(cpu_defer_count / 10 * world.tick_lag, 0.01) if(!statclick) - statclick = new (src) + statclick = new /obj/effect/statclick/debug(src) stat("[name]", statclick.update("T#[getTicks()] | AR [averageRunTime] | LR [lastRunTime] | HR [highestRunTime] | D [deferTime]")) /datum/controller/process/proc/catchException(var/exception/e, var/thrower) diff --git a/code/controllers/ProcessScheduler/core/processScheduler.dm b/code/controllers/ProcessScheduler/core/processScheduler.dm index e8f723586d8..9fcf6d13759 100644 --- a/code/controllers/ProcessScheduler/core/processScheduler.dm +++ b/code/controllers/ProcessScheduler/core/processScheduler.dm @@ -232,7 +232,7 @@ var/global/datum/controller/processScheduler/processScheduler stat("Processes", "Scheduler not running") return if(!statclick) - statclick = new (src) + statclick = new /obj/effect/statclick/debug(src) stat("Processes", statclick.update("[processes.len] (R [running.len] / Q [queued.len] / I [idle.len])")) for(var/datum/controller/process/p in processes) p.statProcess() diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 1e95c7b8977..cb363880fff 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -189,6 +189,16 @@ var/disable_karma = 0 // Disable all karma functions and unlock karma jobs by default + // StonedMC + var/tick_limit_mc_init = TICK_LIMIT_MC_INIT_DEFAULT //SSinitialization throttling + + // Highpop tickrates + var/base_mc_tick_rate = 1 + var/high_pop_mc_tick_rate = 1.1 + + var/high_pop_mc_mode_amount = 65 + var/disable_high_pop_mc_mode_amount = 60 + /datum/configuration/New() var/list/L = subtypesof(/datum/game_mode) for(var/T in L) diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm new file mode 100644 index 00000000000..06547d120d5 --- /dev/null +++ b/code/controllers/controller.dm @@ -0,0 +1,19 @@ +/datum/controller + var/name + // The object used for the clickable stat() button. + var/obj/effect/statclick/statclick + +/datum/controller/proc/Initialize() + +//cleanup actions +/datum/controller/proc/Shutdown() + +//when we enter dmm_suite.load_map +/datum/controller/proc/StartLoadingMap() + +//when we exit dmm_suite.load_map +/datum/controller/proc/StopLoadingMap() + +/datum/controller/proc/Recover() + +/datum/controller/proc/stat_entry() \ No newline at end of file diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index afb0e68d83f..232a6bd054b 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -1,39 +1,97 @@ -var/global/datum/controller/failsafe/failsafe +var/global/datum/controller/failsafe/Failsafe -/datum/controller/failsafe // This thing pretty much just keeps poking the controllers. - processing_interval = 100 // Poke the controllers every 10 seconds. - /* - * Controller alert level. - * For every poke that fails this is raised by 1. - * When it reaches 5 the MC is replaced with a new one - * (effectively killing any controller process() and starting a new one). - */ +/datum/controller/failsafe // This thing pretty much just keeps poking the master controller + name = "Failsafe" - // master - var/masterControllerIteration = 0 - var/masterControllerAlertLevel = 0 + // The length of time to check on the MC (in deciseconds). + // Set to 0 to disable. + var/processing_interval = 20 + // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. + var/defcon = 5 + //the world.time of the last check, so the mc can restart US if we hang. + // (Real friends look out for *eachother*) + var/lasttick = 0 + + // Track the MC iteration to make sure its still on track. + var/master_iteration = 0 + var/running = TRUE /datum/controller/failsafe/New() - . = ..() + // Highlander-style: there can only be one! Kill off the old and replace it with the new. + if(Failsafe != src) + if(istype(Failsafe)) + qdel(Failsafe) + Failsafe = src + Initialize() - // There can be only one failsafe. Out with the old in with the new (that way we can restart the Failsafe by spawning a new one). - if(failsafe != src) - if(istype(failsafe)) - recover() - qdel(failsafe) +/datum/controller/failsafe/Initialize() + set waitfor = 0 + Failsafe.Loop() + if(!qdeleted(src)) + qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us - failsafe = src +/datum/controller/failsafe/Destroy() + running = FALSE + ..() + return QDEL_HINT_HARDDEL_NOW - //failsafe.process() +/datum/controller/failsafe/proc/Loop() + while(running) + lasttick = world.time + if(!Master) + // Replace the missing Master! This should never, ever happen. + new /datum/controller/master() + // Only poke it if overrides are not in effect. + if(processing_interval > 0) + if(Master.processing && Master.iteration) + // Check if processing is done yet. + if(Master.iteration == master_iteration) + switch(defcon) + if(4,5) + --defcon + if(3) + message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") + --defcon + if(2) + to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") + --defcon + if(1) -/datum/controller/failsafe/proc/process() - processing = 1 + to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") + --defcon + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(admins, "MC restarted successfully") + else if(rtn < 0) + log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") + to_chat(admins, "ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.") + //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again + //no need to handle that specially when defcon 0 can handle it + if(0) //DEFCON 0! (mc failed to restart) + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(admins, "MC restarted successfully") + else + defcon = min(defcon + 1,5) + master_iteration = Master.iteration + if (defcon <= 1) + sleep(processing_interval*2) + else + sleep(processing_interval) + else + defcon = 5 + sleep(initial(processing_interval)) - spawn(0) - set background = BACKGROUND_ENABLED +/datum/controller/failsafe/proc/defcon_pretty() + return defcon - while(1) // More efficient than recursivly calling ourself over and over. background = 1 ensures we do not trigger an infinite loop. - iteration++ +/datum/controller/failsafe/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(src, "Initializing...") - sleep(processing_interval) + stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")) diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm new file mode 100644 index 00000000000..ab92ca49be7 --- /dev/null +++ b/code/controllers/globals.dm @@ -0,0 +1,69 @@ +GLOBAL_REAL(GLOB, /datum/controller/global_vars) + +/datum/controller/global_vars + name = "Global Variables" + + var/list/gvars_datum_protected_varlist + var/list/gvars_datum_in_built_vars + var/list/gvars_datum_init_order + +/datum/controller/global_vars/New() + if(GLOB) + CRASH("Multiple instances of global variable controller created") + GLOB = src + + var/datum/controller/exclude_these = new + gvars_datum_in_built_vars = exclude_these.vars + list("gvars_datum_protected_varlist", "gvars_datum_in_built_vars", "gvars_datum_init_order") + qdel(exclude_these) + + log_to_dd("[vars.len - gvars_datum_in_built_vars.len] global variables") + + Initialize() + +/datum/controller/global_vars/Destroy(force) + stack_trace("Some fucker qdel'd the global holder!") + if(!force) + return QDEL_HINT_LETMELIVE + + QDEL_NULL(statclick) + gvars_datum_protected_varlist.Cut() + gvars_datum_in_built_vars.Cut() + + GLOB = null + + return ..() + +/datum/controller/global_vars/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(src, "Initializing...") + + stat("Globals:", statclick.update("Edit")) + +/datum/controller/global_vars/can_vv_get(var_name) + if(gvars_datum_protected_varlist[var_name]) + return FALSE + return ..() + +/datum/controller/global_vars/vv_edit_var(var_name, var_value) + if(gvars_datum_protected_varlist[var_name]) + return FALSE + return ..() + +/datum/controller/global_vars/Initialize() + gvars_datum_init_order = list() + gvars_datum_protected_varlist = list("gvars_datum_protected_varlist" = TRUE) + var/list/global_procs = typesof(/datum/controller/global_vars/proc) + var/expected_len = vars.len - gvars_datum_in_built_vars.len + if(global_procs.len != expected_len) + warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!") + if(global_procs.len) + var/list/expected_global_procs = vars - gvars_datum_in_built_vars + for(var/I in global_procs) + expected_global_procs -= replacetext("[I]", "InitGlobal", "") + log_to_dd("Missing procs: [expected_global_procs.Join(", ")]") + for(var/I in global_procs) + var/start_tick = world.time + call(src, I)() + var/end_tick = world.time + if(end_tick - start_tick) + warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!") \ No newline at end of file diff --git a/code/controllers/master.dm b/code/controllers/master.dm new file mode 100644 index 00000000000..6c094208926 --- /dev/null +++ b/code/controllers/master.dm @@ -0,0 +1,611 @@ + /** + * StonedMC + * + * Designed to properly split up a given tick among subsystems + * Note: if you read parts of this code and think "why is it doing it that way" + * Odds are, there is a reason + * + **/ + +//This is the ABSOLUTE ONLY THING that should init globally like this +GLOBAL_REAL(Master, /datum/controller/master) = new + +//THIS IS THE INIT ORDER +//Master -> SSPreInit -> GLOB -> world -> config -> SSInit -> Failsafe +//GOT IT MEMORIZED? + +/datum/controller/master + name = "Master" + + // Are we processing (higher values increase the processing delay by n ticks) + var/processing = TRUE + // How many times have we ran + var/iteration = 0 + + // world.time of last fire, for tracking lag outside of the mc + var/last_run + + // List of subsystems to process(). + var/list/subsystems + + // Vars for keeping track of tick drift. + var/init_timeofday + var/init_time + var/tickdrift = 0 + + var/sleep_delta = 1 + + var/make_runtime = 0 + + var/initializations_finished_with_no_players_logged_in //I wonder what this could be? + + // The type of the last subsystem to be process()'d. + var/last_type_processed + + var/datum/controller/subsystem/queue_head //Start of queue linked list + var/datum/controller/subsystem/queue_tail //End of queue linked list (used for appending to the list) + var/queue_priority_count = 0 //Running total so that we don't have to loop thru the queue each run to split up the tick + var/queue_priority_count_bg = 0 //Same, but for background subsystems + var/map_loading = FALSE //Are we loading in a new map? + + var/current_runlevel //for scheduling different subsystems for different stages of the round + var/sleep_offline_after_initializations = TRUE + + var/static/restart_clear = 0 + var/static/restart_timeout = 0 + var/static/restart_count = 0 + + var/static/random_seed + + //current tick limit, assigned before running a subsystem. + //used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits + var/static/current_ticklimit = TICK_LIMIT_RUNNING + +/datum/controller/master/New() + makeDatumRefLists() + load_configuration() + // Highlander-style: there can only be one! Kill off the old and replace it with the new. + + if(!random_seed) + random_seed = rand(1, 1e9) + rand_seed(random_seed) + + var/list/_subsystems = list() + subsystems = _subsystems + if (Master != src) + if (istype(Master)) + Recover() + qdel(Master) + else + var/list/subsytem_types = subtypesof(/datum/controller/subsystem) + sortTim(subsytem_types, /proc/cmp_subsystem_init) + for(var/I in subsytem_types) + _subsystems += new I + Master = src + + if(!GLOB) + new /datum/controller/global_vars + +/datum/controller/master/Destroy() + ..() + // Tell qdel() to Del() this object. + return QDEL_HINT_HARDDEL_NOW + +/datum/controller/master/Shutdown() + processing = FALSE + sortTim(subsystems, /proc/cmp_subsystem_init) + reverseRange(subsystems) + for(var/datum/controller/subsystem/ss in subsystems) + log_to_dd("Shutting down [ss.name] subsystem...") + ss.Shutdown() + log_to_dd("Shutdown complete") + +// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart, +// -1 if we encountered a runtime trying to recreate it +/proc/Recreate_MC() + . = -1 //so if we runtime, things know we failed + if (world.time < Master.restart_timeout) + return 0 + if (world.time < Master.restart_clear) + Master.restart_count *= 0.5 + + var/delay = 50 * ++Master.restart_count + Master.restart_timeout = world.time + delay + Master.restart_clear = world.time + (delay * 2) + Master.processing = FALSE //stop ticking this one + try + new/datum/controller/master() + catch + return -1 + return 1 + + +/datum/controller/master/Recover() + var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n" + for (var/varname in Master.vars) + switch (varname) + if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk. + continue + else + var/varval = Master.vars[varname] + if (istype(varval, /datum)) // Check if it has a type var. + var/datum/D = varval + msg += "\t [varname] = [D]([D.type])\n" + else + msg += "\t [varname] = [varval]\n" + log_to_dd(msg) + + var/datum/controller/subsystem/BadBoy = Master.last_type_processed + var/FireHim = FALSE + if(istype(BadBoy)) + msg = null + LAZYINITLIST(BadBoy.failure_strikes) + switch(++BadBoy.failure_strikes[BadBoy.type]) + if(2) + msg = "The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again." + FireHim = TRUE + if(3) + msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined." + BadBoy.flags |= SS_NO_FIRE + if(msg) + to_chat(admins, "[msg]") + log_to_dd(msg) + + if (istype(Master.subsystems)) + if(FireHim) + Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one + subsystems = Master.subsystems + current_runlevel = Master.current_runlevel + StartProcessing(10) + else + to_chat(world, "The Master Controller is having some issues, we will need to re-initialize EVERYTHING") + Initialize(20, TRUE) + + +// Please don't stuff random bullshit here, +// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize() +/datum/controller/master/Initialize(delay, init_sss) + set waitfor = 0 + + if(delay) + sleep(delay) + + if(init_sss) + init_subtypes(/datum/controller/subsystem, subsystems) + + to_chat(world, "Initializing subsystems...") + + // Sort subsystems by init_order, so they initialize in the correct order. + sortTim(subsystems, /proc/cmp_subsystem_init) + + var/start_timeofday = REALTIMEOFDAY + // Initialize subsystems. + current_ticklimit = config.tick_limit_mc_init + for (var/datum/controller/subsystem/SS in subsystems) + if (SS.flags & SS_NO_INIT) + continue + SS.Initialize(REALTIMEOFDAY) + CHECK_TICK + current_ticklimit = TICK_LIMIT_RUNNING + var/time = (REALTIMEOFDAY - start_timeofday) / 10 + + var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!" + to_chat(world, "[msg]") + log_to_dd(msg) + + if (!current_runlevel) + SetRunLevel(1) + + // Sort subsystems by display setting for easy access. + sortTim(subsystems, /proc/cmp_subsystem_display) + // Set world options. + if(sleep_offline_after_initializations) + world.sleep_offline = TRUE + // world.fps = CONFIG_GET(number/fps) // TIGER TODO + world.tick_lag = config.Ticklag + var/initialized_tod = REALTIMEOFDAY + sleep(1) + initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10 + // Loop. + Master.StartProcessing(0) + +/datum/controller/master/proc/SetRunLevel(new_runlevel) + var/old_runlevel = current_runlevel + if(isnull(old_runlevel)) + old_runlevel = "NULL" + + testing("MC: Runlevel changed from [old_runlevel] to [new_runlevel]") + current_runlevel = log(2, new_runlevel) + 1 + if(current_runlevel < 1) + CRASH("Attempted to set invalid runlevel: [new_runlevel]") + +// Starts the mc, and sticks around to restart it if the loop ever ends. +/datum/controller/master/proc/StartProcessing(delay) + set waitfor = 0 + if(delay) + sleep(delay) + testing("Master starting processing") + var/rtn = Loop() + if (rtn > 0 || processing < 0) + return //this was suppose to happen. + //loop ended, restart the mc + log_game("MC crashed or runtimed, restarting") + message_admins("MC crashed or runtimed, restarting") + var/rtn2 = Recreate_MC() + if (rtn2 <= 0) + log_game("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") + message_admins("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") + Failsafe.defcon = 2 + +// Main loop. +/datum/controller/master/proc/Loop() + . = -1 + //Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because + // local vars rock + + //all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed) + var/list/tickersubsystems = list() + var/list/runlevel_sorted_subsystems = list(list(), list(), list(), list(), list(), list(), list(), list()) //ensure we always have as many runlevels as we need to operate with no subsystems (8 currently) + var/timer = world.time + for (var/thing in subsystems) + var/datum/controller/subsystem/SS = thing + if (SS.flags & SS_NO_FIRE) + continue + SS.queued_time = 0 + SS.queue_next = null + SS.queue_prev = null + SS.state = SS_IDLE + if (SS.flags & SS_TICKER) + tickersubsystems += SS + timer += world.tick_lag * rand(1, 5) + SS.next_fire = timer + continue + + var/ss_runlevels = SS.runlevels + var/added_to_any = FALSE + for(var/I in 1 to GLOB.bitflags.len) + if(ss_runlevels & GLOB.bitflags[I]) + while(runlevel_sorted_subsystems.len < I) + runlevel_sorted_subsystems += list(list()) + runlevel_sorted_subsystems[I] += SS + added_to_any = TRUE + if(!added_to_any) + WARNING("[SS.name] subsystem is not SS_NO_FIRE but also does not have any runlevels set!") + + queue_head = null + queue_tail = null + //these sort by lower priorities first to reduce the number of loops needed to add subsequent SS's to the queue + //(higher subsystems will be sooner in the queue, adding them later in the loop means we don't have to loop thru them next queue add) + sortTim(tickersubsystems, /proc/cmp_subsystem_priority) + for(var/I in runlevel_sorted_subsystems) + sortTim(runlevel_sorted_subsystems, /proc/cmp_subsystem_priority) + I += tickersubsystems + + var/cached_runlevel = current_runlevel + var/list/current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel] + + init_timeofday = REALTIMEOFDAY + init_time = world.time + + iteration = 1 + var/error_level = 0 + var/sleep_delta = 1 + var/list/subsystems_to_check + //the actual loop. + + while (1) + tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) + var/starting_tick_usage = TICK_USAGE + if (processing <= 0) + current_ticklimit = TICK_LIMIT_RUNNING + sleep(10) + continue + + //Anti-tick-contention heuristics: + //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later. + // (because sleeps are processed in the order received, longer sleeps are more likely to run first) + if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit. + sleep_delta *= 2 + current_ticklimit = TICK_LIMIT_RUNNING * 0.5 + sleep(world.tick_lag * (processing * sleep_delta)) + continue + + //Byond resumed us late. assume it might have to do the same next tick + if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time) + sleep_delta += 1 + + sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta + + if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick + sleep_delta += 1 + + //debug + if (make_runtime) + var/datum/controller/subsystem/SS + SS.can_fire = 0 + + if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time)) + new/datum/controller/failsafe() // (re)Start the failsafe. + + //now do the actual stuff + if (!queue_head || !(iteration % 3)) + var/checking_runlevel = current_runlevel + if(cached_runlevel != checking_runlevel) + //resechedule subsystems + cached_runlevel = checking_runlevel + current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel] + var/stagger = world.time + for(var/I in current_runlevel_subsystems) + var/datum/controller/subsystem/SS = I + if(SS.next_fire <= world.time) + stagger += world.tick_lag * rand(1, 5) + SS.next_fire = stagger + + subsystems_to_check = current_runlevel_subsystems + else + subsystems_to_check = tickersubsystems + + if (CheckQueue(subsystems_to_check) <= 0) + if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) + log_to_dd("MC: SoftReset() failed, crashing") + return + if (!error_level) + iteration++ + error_level++ + current_ticklimit = TICK_LIMIT_RUNNING + sleep(10) + continue + + if (queue_head) + if (RunQueue() <= 0) + if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) + log_to_dd("MC: SoftReset() failed, crashing") + return + if (!error_level) + iteration++ + error_level++ + current_ticklimit = TICK_LIMIT_RUNNING + sleep(10) + continue + error_level-- + if (!queue_head) //reset the counts if the queue is empty, in the off chance they get out of sync + queue_priority_count = 0 + queue_priority_count_bg = 0 + + iteration++ + last_run = world.time + src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta) + current_ticklimit = TICK_LIMIT_RUNNING + if (processing * sleep_delta <= world.tick_lag) + current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick + sleep(world.tick_lag * (processing * sleep_delta)) + + + + +// This is what decides if something should run. +/datum/controller/master/proc/CheckQueue(list/subsystemstocheck) + . = 0 //so the mc knows if we runtimed + + //we create our variables outside of the loops to save on overhead + var/datum/controller/subsystem/SS + var/SS_flags + + for (var/thing in subsystemstocheck) + if (!thing) + subsystemstocheck -= thing + SS = thing + if (SS.state != SS_IDLE) + continue + if (SS.can_fire <= 0) + continue + if (SS.next_fire > world.time) + continue + SS_flags = SS.flags + if (SS_flags & SS_NO_FIRE) + subsystemstocheck -= SS + continue + if (!(SS_flags & SS_TICKER) && (SS_flags & SS_KEEP_TIMING) && SS.last_fire + (SS.wait * 0.75) > world.time) + continue + SS.enqueue() + . = 1 + + +// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage +/datum/controller/master/proc/RunQueue() + . = 0 + var/datum/controller/subsystem/queue_node + var/queue_node_flags + var/queue_node_priority + var/queue_node_paused + + var/current_tick_budget + var/tick_precentage + var/tick_remaining + var/ran = TRUE //this is right + var/ran_non_ticker = FALSE + var/bg_calc //have we swtiched current_tick_budget to background mode yet? + var/tick_usage + + //keep running while we have stuff to run and we haven't gone over a tick + // this is so subsystems paused eariler can use tick time that later subsystems never used + while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC) + ran = FALSE + bg_calc = FALSE + current_tick_budget = queue_priority_count + queue_node = queue_head + while (queue_node) + if (ran && TICK_USAGE > TICK_LIMIT_RUNNING) + break + + queue_node_flags = queue_node.flags + queue_node_priority = queue_node.queued_priority + + //super special case, subsystems where we can't make them pause mid way through + //if we can't run them this tick (without going over a tick) + //we bump up their priority and attempt to run them next tick + //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run + // in those cases, so we just let them run) + if (queue_node_flags & SS_NO_TICK_CHECK) + if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker) + queue_node.queued_priority += queue_priority_count * 0.1 + queue_priority_count -= queue_node_priority + queue_priority_count += queue_node.queued_priority + current_tick_budget -= queue_node_priority + queue_node = queue_node.queue_next + continue + + if ((queue_node_flags & SS_BACKGROUND) && !bg_calc) + current_tick_budget = queue_priority_count_bg + bg_calc = TRUE + + tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE + + if (current_tick_budget > 0 && queue_node_priority > 0) + tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority) + else + tick_precentage = tick_remaining + + tick_precentage = max(tick_precentage*0.5, tick_precentage-queue_node.tick_overrun) + + current_ticklimit = round(TICK_USAGE + tick_precentage) + + if (!(queue_node_flags & SS_TICKER)) + ran_non_ticker = TRUE + ran = TRUE + + queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING) + last_type_processed = queue_node + + queue_node.state = SS_RUNNING + + tick_usage = TICK_USAGE + var/state = queue_node.ignite(queue_node_paused) + tick_usage = TICK_USAGE - tick_usage + + if (state == SS_RUNNING) + state = SS_IDLE + current_tick_budget -= queue_node_priority + + + if (tick_usage < 0) + tick_usage = 0 + queue_node.tick_overrun = max(0, MC_AVG_FAST_UP_SLOW_DOWN(queue_node.tick_overrun, tick_usage-tick_precentage)) + queue_node.state = state + + if (state == SS_PAUSED) + queue_node.paused_ticks++ + queue_node.paused_tick_usage += tick_usage + queue_node = queue_node.queue_next + continue + + queue_node.ticks = MC_AVERAGE(queue_node.ticks, queue_node.paused_ticks) + tick_usage += queue_node.paused_tick_usage + + queue_node.tick_usage = MC_AVERAGE_FAST(queue_node.tick_usage, tick_usage) + + queue_node.cost = MC_AVERAGE_FAST(queue_node.cost, TICK_DELTA_TO_MS(tick_usage)) + queue_node.paused_ticks = 0 + queue_node.paused_tick_usage = 0 + + if (queue_node_flags & SS_BACKGROUND) //update our running total + queue_priority_count_bg -= queue_node_priority + else + queue_priority_count -= queue_node_priority + + queue_node.last_fire = world.time + queue_node.times_fired++ + + if (queue_node_flags & SS_TICKER) + 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) + queue_node.next_fire += queue_node.wait + else + queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) + + queue_node.queued_time = 0 + + //remove from queue + queue_node.dequeue() + + queue_node = queue_node.queue_next + + . = 1 + +//resets the queue, and all subsystems, while filtering out the subsystem lists +// called if any mc's queue procs runtime or exit improperly. +/datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS) + . = 0 + log_to_dd("MC: SoftReset called, resetting MC queue state.") + if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS)) + log_to_dd("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]'") + return + var/subsystemstocheck = subsystems + ticker_SS + for(var/I in runlevel_SS) + subsystemstocheck |= I + + for (var/thing in subsystemstocheck) + var/datum/controller/subsystem/SS = thing + if (!SS || !istype(SS)) + //list(SS) is so if a list makes it in the subsystem list, we remove the list, not the contents + subsystems -= list(SS) + ticker_SS -= list(SS) + for(var/I in runlevel_SS) + I -= list(SS) + log_to_dd("MC: SoftReset: Found bad entry in subsystem list, '[SS]'") + continue + if (SS.queue_next && !istype(SS.queue_next)) + log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_next = '[SS.queue_next]'") + SS.queue_next = null + if (SS.queue_prev && !istype(SS.queue_prev)) + log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_prev = '[SS.queue_prev]'") + SS.queue_prev = null + SS.queued_priority = 0 + SS.queued_time = 0 + SS.state = SS_IDLE + if (queue_head && !istype(queue_head)) + log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_head = '[queue_head]'") + queue_head = null + if (queue_tail && !istype(queue_tail)) + log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_tail = '[queue_tail]'") + queue_tail = null + queue_priority_count = 0 + queue_priority_count_bg = 0 + log_to_dd("MC: SoftReset: Finished.") + . = 1 + + + +/datum/controller/master/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(src, "Initializing...") + + stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))") + stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")) + +/datum/controller/master/StartLoadingMap() + //disallow more than one map to load at once, multithreading it will just cause race conditions + while(map_loading) + stoplag() + for(var/S in subsystems) + var/datum/controller/subsystem/SS = S + SS.StartLoadingMap() + map_loading = TRUE + +/datum/controller/master/StopLoadingMap(bounds = null) + map_loading = FALSE + for(var/S in subsystems) + var/datum/controller/subsystem/SS = S + SS.StopLoadingMap() + + +/datum/controller/master/proc/UpdateTickRate() + if (!processing) + return + var/client_count = length(clients) + if (client_count < config.disable_high_pop_mc_mode_amount) + processing = config.base_mc_tick_rate + else if (client_count > config.high_pop_mc_mode_amount) + processing = config.high_pop_mc_tick_rate diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index 2dc0e38e66c..cfaa3f825e7 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -1,6 +1,6 @@ -//simplified MC that is designed to fail when procs 'break'. When it fails it's just replaced with a new one. -//It ensures master_controller.process() is never doubled up by killing the MC (hence terminating any of its sleeping procs) -//WIP, needs lots of work still +// old deprecated rusted piece of shit MC +// All this does now is misc. world init stuff because too lazy to put it somewhere else +// It used to run all of the repeating processes controlling the game but now the SMC and Process Scheduler do that var/global/datum/controller/game_controller/master_controller //Set in world.New() @@ -10,16 +10,6 @@ var/global/last_tick_duration = 0 var/global/air_processing_killed = 0 var/global/pipe_processing_killed = 0 -/datum/controller - var/processing = 0 - var/iteration = 0 - var/processing_interval = 0 - - // Dummy object to let us click it to debug while in the stat panel - var/obj/effect/statclick/debug/statclick - -/datum/controller/proc/recover() // If we are replacing an existing controller (due to a crash) we attempt to preserve as much as we can. - /datum/controller/game_controller var/list/shuttle_list // For debugging and VV @@ -46,8 +36,6 @@ var/global/pipe_processing_killed = 0 return QDEL_HINT_HARDDEL_NOW /datum/controller/game_controller/proc/setup() - world.tick_lag = config.Ticklag - preloadTemplates() if(!config.disable_away_missions) createRandomZlevel() diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm new file mode 100644 index 00000000000..0ffd9e09d35 --- /dev/null +++ b/code/controllers/subsystem.dm @@ -0,0 +1,215 @@ + +/datum/controller/subsystem + // Metadata; you should define these. + name = "fire coderbus" //name of the subsystem + var/init_order = INIT_ORDER_DEFAULT //order of initialization. Higher numbers are initialized first, lower numbers later. Use defines in __DEFINES/subsystems.dm for easy understanding of order. + var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer. + var/priority = FIRE_PRIORITY_DEFAULT //When mutiple subsystems need to run in the same tick, higher priority subsystems will run first and be given a higher share of the tick before MC_TICK_CHECK triggers a sleep + + var/flags = 0 //see MC.dm in __DEFINES Most flags must be set on world start to take full effect. (You can also restart the mc to force them to process again) + + var/initialized = FALSE //set to TRUE after it has been initialized, will obviously never be set if the subsystem doesn't initialize + + //set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later + // use the SS_NO_FIRE flag instead for systems that never fire to keep it from even being added to the list + var/can_fire = TRUE + + // Bookkeeping variables; probably shouldn't mess with these. + var/last_fire = 0 //last world.time we called fire() + var/next_fire = 0 //scheduled world.time for next fire() + var/cost = 0 //average time to execute + var/tick_usage = 0 //average tick usage + var/tick_overrun = 0 //average tick overrun + var/state = SS_IDLE //tracks the current state of the ss, running, paused, etc. + var/paused_ticks = 0 //ticks this ss is taking to run right now. + var/paused_tick_usage //total tick_usage of all of our runs while pausing this run + var/ticks = 1 //how many ticks does this ss take to run on avg. + var/times_fired = 0 //number of times we have called fire() + var/queued_time = 0 //time we entered the queue, (for timing and priority reasons) + var/queued_priority //we keep a running total to make the math easier, if priority changes mid-fire that would break our running total, so we store it here + //linked list stuff for the queue + var/datum/controller/subsystem/queue_next + var/datum/controller/subsystem/queue_prev + + var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire + + var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out! + +//Do not override +///datum/controller/subsystem/New() + +// Used to initialize the subsystem BEFORE the map has loaded +// Called AFTER Recover if that is called +// Prefer to use Initialize if possible +/datum/controller/subsystem/proc/PreInit() + return + +//This is used so the mc knows when the subsystem sleeps. do not override. +/datum/controller/subsystem/proc/ignite(resumed = 0) + set waitfor = 0 + . = SS_SLEEPING + fire(resumed) + . = state + if (state == SS_SLEEPING) + state = SS_IDLE + if (state == SS_PAUSING) + var/QT = queued_time + enqueue() + state = SS_PAUSED + queued_time = QT + +//previously, this would have been named 'process()' but that name is used everywhere for different things! +//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. +//Sleeping in here prevents future fires until returned. +/datum/controller/subsystem/proc/fire(resumed = 0) + flags |= SS_NO_FIRE + throw EXCEPTION("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.") + +/datum/controller/subsystem/Destroy() + dequeue() + can_fire = 0 + flags |= SS_NO_FIRE + Master.subsystems -= src + return ..() + +//Queue it to run. +// (we loop thru a linked list until we get to the end or find the right point) +// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) +/datum/controller/subsystem/proc/enqueue() + var/SS_priority = priority + var/SS_flags = flags + var/datum/controller/subsystem/queue_node + var/queue_node_priority + var/queue_node_flags + + for (queue_node = Master.queue_head; queue_node; queue_node = queue_node.queue_next) + queue_node_priority = queue_node.queued_priority + queue_node_flags = queue_node.flags + + if (queue_node_flags & SS_TICKER) + if (!(SS_flags & SS_TICKER)) + continue + if (queue_node_priority < SS_priority) + break + + else if (queue_node_flags & SS_BACKGROUND) + if (!(SS_flags & SS_BACKGROUND)) + break + if (queue_node_priority < SS_priority) + break + + else + if (SS_flags & SS_BACKGROUND) + continue + if (SS_flags & SS_TICKER) + break + if (queue_node_priority < SS_priority) + break + + queued_time = world.time + queued_priority = SS_priority + state = SS_QUEUED + if (SS_flags & SS_BACKGROUND) //update our running total + Master.queue_priority_count_bg += SS_priority + else + Master.queue_priority_count += SS_priority + + queue_next = queue_node + if (!queue_node)//we stopped at the end, add to tail + queue_prev = Master.queue_tail + if (Master.queue_tail) + Master.queue_tail.queue_next = src + else //empty queue, we also need to set the head + Master.queue_head = src + Master.queue_tail = src + + else if (queue_node == Master.queue_head)//insert at start of list + Master.queue_head.queue_prev = src + Master.queue_head = src + queue_prev = null + else + queue_node.queue_prev.queue_next = src + queue_prev = queue_node.queue_prev + queue_node.queue_prev = src + + +/datum/controller/subsystem/proc/dequeue() + if (queue_next) + queue_next.queue_prev = queue_prev + if (queue_prev) + queue_prev.queue_next = queue_next + if (src == Master.queue_tail) + Master.queue_tail = queue_prev + if (src == Master.queue_head) + Master.queue_head = queue_next + queued_time = 0 + if (state == SS_QUEUED) + state = SS_IDLE + + +/datum/controller/subsystem/proc/pause() + . = 1 + switch(state) + if(SS_RUNNING) + state = SS_PAUSED + if(SS_SLEEPING) + state = SS_PAUSING + + +//used to initialize the subsystem AFTER the map has loaded +/datum/controller/subsystem/Initialize(start_timeofday) + initialized = TRUE + var/time = (REALTIMEOFDAY - start_timeofday) / 10 + var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!" + to_chat(world, "[msg]") + log_to_dd(msg) + return time + +//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. +/datum/controller/subsystem/stat_entry(msg) + if(!statclick) + statclick = new/obj/effect/statclick/debug(src, "Initializing...") + + if(can_fire && !(SS_NO_FIRE & flags)) + msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]" + else + msg = "OFFLINE\t[msg]" + + var/title = name + if (can_fire) + title = "\[[state_letter()]][title]" + + stat(title, statclick.update(msg)) + +/datum/controller/subsystem/proc/state_letter() + switch (state) + if (SS_RUNNING) + . = "R" + if (SS_QUEUED) + . = "Q" + if (SS_PAUSED, SS_PAUSING) + . = "P" + if (SS_SLEEPING) + . = "S" + if (SS_IDLE) + . = " " + +//could be used to postpone a costly subsystem for (default one) var/cycles, cycles +//for instance, during cpu intensive operations like explosions +/datum/controller/subsystem/proc/postpone(cycles = 1) + if(next_fire - world.time < wait) + next_fire += (wait*cycles) + +//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash) +//should attempt to salvage what it can from the old instance of subsystem +/datum/controller/subsystem/Recover() + +/datum/controller/subsystem/vv_edit_var(var_name, var_value) + switch (var_name) + if ("can_fire") + //this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag + if (var_value) + next_fire = world.time + wait + if ("queued_priority") //editing this breaks things. + return 0 + . = ..() diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index 1cc9787420d..4ea43a02e08 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -17,7 +17,10 @@ message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.") return -/client/proc/debug_controller(controller in list("Master","failsafe","Ticker","Air","Jobs","Sun","Radio","Configuration","pAI", "Cameras","Garbage", "Transfer Controller","Event","Alarm","Scheduler","Nano","Vote","Diseases","Fires","Mob","NPC AI","Shuttle","Timer","Weather","Space","Mob Hunt Server")) +/client/proc/debug_controller(controller in list("Master", + "failsafe","Scheduler","StonedMaster","Ticker","Air","Jobs","Sun","Radio","Configuration","pAI", + "Cameras","Garbage", "Transfer Controller","Event","Alarm","Nano","Vote","Fires", + "Mob","NPC AI","Shuttle","Timer","Weather","Space","Mob Hunt Server")) set category = "Debug" set name = "Debug Controller" set desc = "Debug the various periodic loop controllers for the game (be careful!)" @@ -28,8 +31,14 @@ debug_variables(master_controller) feedback_add_details("admin_verb","DMC") if("failsafe") - debug_variables(failsafe) + debug_variables(Failsafe) feedback_add_details("admin_verb", "dfailsafe") + if("Scheduler") + debug_variables(processScheduler) + feedback_add_details("admin_verb","DprocessScheduler") + if("StonedMaster") + debug_variables(Master) + feedback_add_details("admin_verb","Dsmc") if("Ticker") debug_variables(ticker) feedback_add_details("admin_verb","DTicker") @@ -63,9 +72,6 @@ if("Garbage") debug_variables(garbageCollector) feedback_add_details("admin_verb","DGarbage") - if("Scheduler") - debug_variables(processScheduler) - feedback_add_details("admin_verb","DprocessScheduler") if("Nano") debug_variables(nanomanager) feedback_add_details("admin_verb","DNano") diff --git a/code/datums/statclick.dm b/code/datums/statclick.dm index e10a63b8356..c7273e4744b 100644 --- a/code/datums/statclick.dm +++ b/code/datums/statclick.dm @@ -5,8 +5,8 @@ var/target /obj/effect/statclick/New(ntarget, text) - name = text target = ntarget + name = text /obj/effect/statclick/proc/update(text) name = text @@ -15,25 +15,22 @@ /obj/effect/statclick/debug var/class -/obj/effect/statclick/debug/New(ntarget) - name = "Initializing..." - target = ntarget - if(istype(target, /datum/controller/process)) - class = "process" - else if(istype(target, /datum/controller/processScheduler)) - class = "scheduler" - else if(istype(target, /datum/controller)) - class = "controller" - else if(istype(target, /datum)) - class = "datum" - else - class = "unknown" - -// This bit is called when clicked in the stat panel /obj/effect/statclick/debug/Click() - if(!is_admin(usr)) + if(!is_admin(usr) || !target) return + if(!class) + if(istype(target, /datum/controller/process)) + class = "process" + else if(istype(target, /datum/controller/processScheduler)) + class = "scheduler" + if(istype(target, /datum/controller/subsystem)) + class = "subsystem" + else if(istype(target, /datum/controller)) + class = "controller" + else if(istype(target, /datum)) + class = "datum" + else + class = "unknown" usr.client.debug_variables(target) - - message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].") + message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].") \ No newline at end of file diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm index fb4ba668bef..6612f1e7192 100644 --- a/code/game/gamemodes/gameticker.dm +++ b/code/game/gamemodes/gameticker.dm @@ -55,6 +55,7 @@ var/round_start_time = 0 if(pregame_timeleft <= 0) current_state = GAME_STATE_SETTING_UP + Master.SetRunLevel(RUNLEVEL_SETUP) while(!setup()) /datum/controller/gameticker/proc/votetimer() @@ -77,6 +78,7 @@ var/round_start_time = 0 runnable_modes = config.get_runnable_modes() if(runnable_modes.len==0) current_state = GAME_STATE_PREGAME + Master.SetRunLevel(RUNLEVEL_LOBBY) to_chat(world, "Unable to choose playable game mode. Reverting to pre-game lobby.") return 0 if(secret_force_mode != "secret") @@ -96,6 +98,7 @@ var/round_start_time = 0 mode = null current_state = GAME_STATE_PREGAME job_master.ResetOccupations() + Master.SetRunLevel(RUNLEVEL_LOBBY) return 0 //Configure mode and assign player to special mode stuff @@ -108,6 +111,7 @@ var/round_start_time = 0 current_state = GAME_STATE_PREGAME to_chat(world, "Error setting up [master_mode]. Reverting to pre-game lobby.") job_master.ResetOccupations() + Master.SetRunLevel(RUNLEVEL_LOBBY) return 0 if(hide_mode) @@ -125,6 +129,7 @@ var/round_start_time = 0 equip_characters() data_core.manifest() current_state = GAME_STATE_PLAYING + Master.SetRunLevel(RUNLEVEL_GAME) callHook("roundstart") @@ -385,6 +390,7 @@ var/round_start_time = 0 if((!mode.explosion_in_progress && game_finished) || force_ending) current_state = GAME_STATE_FINISHED + Master.SetRunLevel(RUNLEVEL_POSTGAME) auto_toggle_ooc(1) // Turn it on spawn declare_completion() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index a98540b0a2d..06bd75f39e8 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -946,6 +946,25 @@ var/list/slot_equipment_priority = list( \ if(processScheduler) processScheduler.statProcesses() + if(statpanel("MC")) //looking at that panel + var/turf/T = get_turf(client.eye) + stat("Location:", COORD(T)) + stat("CPU:", "[world.cpu]") + stat("Instances:", "[num2text(world.contents.len, 10)]") + GLOB.stat_entry() + stat(null) + if(Master) + Master.stat_entry() + else + stat("Master Controller:", "ERROR") + if(Failsafe) + Failsafe.stat_entry() + else + stat("Failsafe Controller:", "ERROR") + if(Master) + stat(null) + for(var/datum/controller/subsystem/SS in Master.subsystems) + SS.stat_entry() statpanel("Status") // Switch to the Status panel again, for the sake of the lazy Stat procs diff --git a/code/world.dm b/code/world.dm index b9063683763..9614373c901 100644 --- a/code/world.dm +++ b/code/world.dm @@ -4,9 +4,6 @@ var/global/datum/global_init/init = new () Pre-map initialization stuff should go here. */ /datum/global_init/New() - - makeDatumRefLists() - load_configuration() setLog() del(src) @@ -37,6 +34,7 @@ var/global/list/map_transition_config = MAP_TRANSITION_CONFIG timezoneOffset = text2num(time2text(0,"hh")) * 36000 + callHook("startup") src.update_status() @@ -48,6 +46,8 @@ var/global/list/map_transition_config = MAP_TRANSITION_CONFIG space_manager.initialize() //Before the MC starts up + Master.Initialize(10, FALSE) + processScheduler = new master_controller = new /datum/controller/game_controller() spawn(1) diff --git a/paradise.dme b/paradise.dme index fe4daedb1ee..b46d43f0b7e 100644 --- a/paradise.dme +++ b/paradise.dme @@ -15,6 +15,7 @@ #include "code\_compile_options.dm" #include "code\hub.dm" #include "code\world.dm" +#include "code\__DEFINES\_globals.dm" #include "code\__DEFINES\_readme.dm" #include "code\__DEFINES\admin.dm" #include "code\__DEFINES\atmospherics.dm" @@ -38,6 +39,7 @@ #include "code\__DEFINES\lighting.dm" #include "code\__DEFINES\machines.dm" #include "code\__DEFINES\math.dm" +#include "code\__DEFINES\MC.dm" #include "code\__DEFINES\misc.dm" #include "code\__DEFINES\mob.dm" #include "code\__DEFINES\pda.dm" @@ -52,12 +54,14 @@ #include "code\__DEFINES\sound.dm" #include "code\__DEFINES\stat.dm" #include "code\__DEFINES\status_effects.dm" +#include "code\__DEFINES\subsystems.dm" #include "code\__DEFINES\tick.dm" #include "code\__DEFINES\typeids.dm" #include "code\__DEFINES\vv.dm" #include "code\__DEFINES\zlevel.dm" #include "code\__HELPERS\_string_lists.dm" #include "code\__HELPERS\AnimationLibrary.dm" +#include "code\__HELPERS\cmp.dm" #include "code\__HELPERS\constants.dm" #include "code\__HELPERS\experimental.dm" #include "code\__HELPERS\files.dm" @@ -78,6 +82,10 @@ #include "code\__HELPERS\type2type.dm" #include "code\__HELPERS\unique_ids.dm" #include "code\__HELPERS\unsorted.dm" +#include "code\__HELPERS\sorts\__main.dm" +#include "code\__HELPERS\sorts\InsertSort.dm" +#include "code\__HELPERS\sorts\MergeSort.dm" +#include "code\__HELPERS\sorts\TimSort.dm" #include "code\_DATASTRUCTURES\heap.dm" #include "code\_DATASTRUCTURES\linked_lists.dm" #include "code\_DATASTRUCTURES\priority_queue.dm" @@ -169,10 +177,14 @@ #include "code\ATMOSPHERICS\pipes\simple\pipe_simple_visible.dm" #include "code\controllers\communications.dm" #include "code\controllers\configuration.dm" +#include "code\controllers\controller.dm" #include "code\controllers\failsafe.dm" +#include "code\controllers\globals.dm" #include "code\controllers\hooks-defs.dm" #include "code\controllers\hooks.dm" +#include "code\controllers\master.dm" #include "code\controllers\master_controller.dm" +#include "code\controllers\subsystem.dm" #include "code\controllers\verbs.dm" #include "code\controllers\voting.dm" #include "code\controllers\Processes\air.dm" From dca16e60cfe55424b15009059c72b52bbd18de03 Mon Sep 17 00:00:00 2001 From: tigercat2000 Date: Fri, 2 Mar 2018 23:58:57 -0800 Subject: [PATCH 2/7] PS -> SMC Ports: LINDA, Spacedrift, Throwing This commit ports LINDA, spacedrifting, and throwing to the SMC. --- code/ATMOSPHERICS/atmospherics.dm | 4 +- .../unary_devices/heat_exchanger.dm | 6 +- code/ATMOSPHERICS/datum_pipeline.dm | 5 +- code/LINDA/LINDA_fire.dm | 8 +- code/LINDA/LINDA_system.dm | 6 +- code/LINDA/LINDA_turf_tile.dm | 65 ++- code/__HELPERS/unsorted.dm | 10 +- code/_globalvars/lists/objects.dm | 1 - code/controllers/master_controller.dm | 22 - code/controllers/subsystem/air.dm | 398 ++++++++++++++++++ code/controllers/subsystem/spacedrift.dm | 59 +++ code/controllers/subsystem/throwing.dm | 149 +++++++ code/controllers/verbs.dm | 2 +- code/game/atoms_movable.dm | 7 +- code/game/machinery/atmo_control.dm | 4 +- code/game/machinery/atmoalter/meter.dm | 4 +- .../atmoalter/portable_atmospherics.dm | 4 +- code/game/machinery/atmoalter/zvent.dm | 4 +- .../objects/items/weapons/flamethrower.dm | 2 +- code/game/turfs/turf.dm | 20 +- code/modules/awaymissions/zlevel.dm | 4 +- .../mapGeneratorModules/helpers.dm | 4 +- code/modules/shuttle/shuttle.dm | 8 +- code/world.dm | 1 - paradise.dme | 6 +- 25 files changed, 703 insertions(+), 100 deletions(-) create mode 100644 code/controllers/subsystem/air.dm create mode 100644 code/controllers/subsystem/spacedrift.dm create mode 100644 code/controllers/subsystem/throwing.dm diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm index b2f58691818..23eead0c4bf 100644 --- a/code/ATMOSPHERICS/atmospherics.dm +++ b/code/ATMOSPHERICS/atmospherics.dm @@ -33,7 +33,7 @@ Pipelines + Other Objects -> Pipe network /obj/machinery/atmospherics/New() ..() - atmos_machinery += src + SSair.atmos_machinery += src if(!icon_manager) icon_manager = new() @@ -54,7 +54,7 @@ Pipelines + Other Objects -> Pipe network /obj/machinery/atmospherics/Destroy() QDEL_NULL(stored) - atmos_machinery -= src + SSair.atmos_machinery -= src for(var/mob/living/L in src) //ventcrawling is serious business L.remove_ventcrawl() L.forceMove(get_turf(src)) diff --git a/code/ATMOSPHERICS/components/unary_devices/heat_exchanger.dm b/code/ATMOSPHERICS/components/unary_devices/heat_exchanger.dm index 1b726a6c066..496413c881c 100644 --- a/code/ATMOSPHERICS/components/unary_devices/heat_exchanger.dm +++ b/code/ATMOSPHERICS/components/unary_devices/heat_exchanger.dm @@ -37,11 +37,11 @@ if(!partner) return 0 - if(!air_master || air_master.current_cycle <= update_cycle) + if(!SSair || SSair.times_fired <= update_cycle) return 0 - update_cycle = air_master.current_cycle - partner.update_cycle = air_master.current_cycle + update_cycle = SSair.times_fired + partner.update_cycle = SSair.times_fired var/air_heat_capacity = air_contents.heat_capacity() var/other_air_heat_capacity = partner.air_contents.heat_capacity() diff --git a/code/ATMOSPHERICS/datum_pipeline.dm b/code/ATMOSPHERICS/datum_pipeline.dm index 156d6ffa851..cd3cdfffb53 100644 --- a/code/ATMOSPHERICS/datum_pipeline.dm +++ b/code/ATMOSPHERICS/datum_pipeline.dm @@ -1,4 +1,3 @@ -var/global/list/pipe_networks = list() var/global/list/deferred_pipenet_rebuilds = list() /datum/pipeline @@ -13,10 +12,10 @@ var/global/list/deferred_pipenet_rebuilds = list() var/alert_pressure = 0 /datum/pipeline/New() - pipe_networks += src + SSair.networks += src /datum/pipeline/Destroy() - pipe_networks -= src + SSair.networks -= src if(air && air.volume) temporarily_store_air() for(var/obj/machinery/atmospherics/pipe/P in members) diff --git a/code/LINDA/LINDA_fire.dm b/code/LINDA/LINDA_fire.dm index d5ab43d4ab3..11ecc8f10e3 100644 --- a/code/LINDA/LINDA_fire.dm +++ b/code/LINDA/LINDA_fire.dm @@ -34,9 +34,9 @@ active_hotspot.temperature = exposed_temperature active_hotspot.volume = exposed_volume - active_hotspot.just_spawned = (current_cycle < air_master.current_cycle) + active_hotspot.just_spawned = (current_cycle < SSair.times_fired) //remove just_spawned protection if no longer processing this cell - air_master.add_to_active(src, 0) + SSair.add_to_active(src, 0) return igniting //This is the icon for fire on turfs, also helps for nurturing small fires until they are full tile @@ -58,7 +58,7 @@ /obj/effect/hotspot/New() ..() - air_master.hotspots += src + SSair.hotspots += src perform_exposure() dir = pick(cardinal) air_update_turf() @@ -154,7 +154,7 @@ /obj/effect/hotspot/Destroy() set_light(0) - air_master.hotspots -= src + SSair.hotspots -= src DestroyTurf() if(istype(loc, /turf/simulated)) var/turf/simulated/T = loc diff --git a/code/LINDA/LINDA_system.dm b/code/LINDA/LINDA_system.dm index cbb3c546990..c9d168a0159 100644 --- a/code/LINDA/LINDA_system.dm +++ b/code/LINDA/LINDA_system.dm @@ -126,8 +126,8 @@ turf/CanPass(atom/movable/mover, turf/target, height=1.5) /turf/proc/air_update_turf(var/command = 0) if(command) CalculateAdjacentTurfs() - if(air_master) - air_master.add_to_active(src,command) + if(SSair) + SSair.add_to_active(src,command) /atom/movable/proc/move_update_air(var/turf/T) if(istype(T,/turf)) @@ -184,4 +184,4 @@ var/const/SPAWN_AIR = 256 G.nitrogen += MOLES_N2STANDARD * amount air.merge(G) - air_master.add_to_active(src, 0) + SSair.add_to_active(src, 0) diff --git a/code/LINDA/LINDA_turf_tile.dm b/code/LINDA/LINDA_turf_tile.dm index a2414d9cac3..e10e53859d7 100644 --- a/code/LINDA/LINDA_turf_tile.dm +++ b/code/LINDA/LINDA_turf_tile.dm @@ -141,9 +141,9 @@ /turf/simulated/proc/process_cell() - if(archived_cycle < air_master.current_cycle) //archive self if not already done + if(archived_cycle < SSair.times_fired) //archive self if not already done archive() - current_cycle = air_master.current_cycle + current_cycle = SSair.times_fired var/remove = 1 //set by non simulated turfs who are sharing with this turf @@ -184,7 +184,7 @@ share_air(enemy_simulated) //share else if(!air.compare(enemy_simulated.air)) //compare if - air_master.add_to_active(enemy_simulated) //excite enemy + SSair.add_to_active(enemy_simulated) //excite enemy if(excited_group) excited_group.add_turf(enemy_simulated) //add enemy to group else @@ -227,14 +227,14 @@ update_visuals() if(!excited_group && remove == 1) - air_master.remove_from_active(src) + SSair.remove_from_active(src) /turf/simulated/proc/archive() if(air) //For open space like floors air.archive() temperature_archived = temperature - archived_cycle = air_master.current_cycle + archived_cycle = SSair.times_fired /turf/simulated/proc/update_visuals() if(icy && !icyoverlay) @@ -288,7 +288,7 @@ last_share_check() /turf/proc/consider_pressure_difference(var/turf/simulated/T, var/difference) - air_master.high_pressure_delta |= src + SSair.high_pressure_delta |= src if(difference > pressure_difference) pressure_direction = get_dir(src, T) pressure_difference = difference @@ -309,24 +309,24 @@ /atom/movable/var/last_forced_movement = 0 /atom/movable/proc/experience_pressure_difference(pressure_difference, direction) - if(last_forced_movement >= air_master.current_cycle) + if(last_forced_movement >= SSair.times_fired) return 0 else if(!anchored && !pulledby) if(pressure_difference >= throw_pressure_limit) var/general_direction = get_edge_target_turf(src, direction) - if(last_forced_movement + 10 < air_master.current_cycle && is_valid_tochat_target(src)) //the first check prevents spamming throw to_chat + if(last_forced_movement + 10 < SSair.times_fired && is_valid_tochat_target(src)) //the first check prevents spamming throw to_chat to_chat(src, "The pressure sends you flying!") if(ishuman(src)) var/mob/living/carbon/human/H = src H.Weaken(min(pressure_difference / 50, 2)) spawn() throw_at(general_direction, pressure_difference / 10, pressure_difference / 200, null, 0, 0, null) - last_forced_movement = air_master.current_cycle + last_forced_movement = SSair.times_fired return 1 else if(pressure_difference > pressure_resistance) spawn() step(src, direction) - last_forced_movement = air_master.current_cycle + last_forced_movement = SSair.times_fired return 1 return 0 @@ -337,8 +337,8 @@ var/breakdown_cooldown = 0 /datum/excited_group/New() - if(air_master) - air_master.excited_groups += src + if(SSair) + SSair.excited_groups += src /datum/excited_group/proc/add_turf(var/turf/simulated/T) turf_list += T @@ -348,13 +348,13 @@ /datum/excited_group/proc/merge_groups(var/datum/excited_group/E) if(turf_list.len > E.turf_list.len) - air_master.excited_groups -= E + SSair.excited_groups -= E for(var/turf/simulated/T in E.turf_list) T.excited_group = src turf_list += T reset_cooldowns() else - air_master.excited_groups -= src + SSair.excited_groups -= src for(var/turf/simulated/T in turf_list) T.excited_group = E E.turf_list += T @@ -400,14 +400,14 @@ T.excited = 0 T.recently_active = 0 T.excited_group = null - air_master.active_turfs -= T + SSair.active_turfs -= T garbage_collect() /datum/excited_group/proc/garbage_collect() for(var/turf/simulated/T in turf_list) T.excited_group = null turf_list.Cut() - air_master.excited_groups -= src + SSair.excited_groups -= src @@ -424,7 +424,7 @@ turf/simulated/proc/super_conduct() //Does not participate in air exchange, so will conduct heat across all four borders at this time conductivity_directions = NORTH|SOUTH|EAST|WEST - if(archived_cycle < air_master.current_cycle) + if(archived_cycle < SSair.times_fired) archive() else //Does particate in air exchange so only consider directions not considered during process_cell() @@ -444,7 +444,7 @@ turf/simulated/proc/super_conduct() if(istype(neighbor, /turf/simulated)) //anything under this subtype will share in the exchange var/turf/simulated/T = neighbor - if(T.archived_cycle < air_master.current_cycle) + if(T.archived_cycle < SSair.times_fired) T.archive() if(T.air) @@ -452,7 +452,7 @@ turf/simulated/proc/super_conduct() air.temperature_share(T.air, WINDOW_HEAT_TRANSFER_COEFFICIENT) else //Solid but neighbor is open T.air.temperature_turf_share(src, T.thermal_conductivity) - air_master.add_to_active(T, 0) + SSair.add_to_active(T, 0) else if(air) //Open but neighbor is solid air.temperature_turf_share(T, T.thermal_conductivity) @@ -476,12 +476,12 @@ turf/simulated/proc/super_conduct() //Make sure still hot enough to continue conducting heat if(air.temperature < MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION) - air_master.active_super_conductivity -= src + SSair.active_super_conductivity -= src return 0 else if(temperature < MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION) - air_master.active_super_conductivity -= src + SSair.active_super_conductivity -= src return 0 turf/simulated/proc/consider_superconductivity(starting) @@ -497,7 +497,7 @@ turf/simulated/proc/consider_superconductivity(starting) if(temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION)) return 0 - air_master.active_super_conductivity |= src + SSair.active_super_conductivity |= src return 1 turf/simulated/proc/radiate_to_spess() //Radiate excess tile heat to space @@ -508,3 +508,24 @@ turf/simulated/proc/radiate_to_spess() //Radiate excess tile heat to space var/heat = thermal_conductivity*delta_temperature* \ (heat_capacity*700000/(heat_capacity+700000)) //700000 is the heat_capacity from a space turf, hardcoded here temperature -= heat/heat_capacity + +/turf/proc/Initialize_Atmos(times_fired) + CalculateAdjacentTurfs() + +/turf/simulated/Initialize_Atmos(times_fired) + ..() + update_visuals() + for(var/direction in cardinal) + if(!(atmos_adjacent_turfs & direction)) + continue + var/turf/enemy_tile = get_step(src, direction) + if(istype(enemy_tile, /turf/simulated)) + var/turf/simulated/enemy_simulated = enemy_tile + if(!air.compare(enemy_simulated.air)) + excited = 1 + SSair.active_turfs |= src + break + else + if(!air.check_turf_total(enemy_tile)) + excited = 1 + SSair.active_turfs |= src \ No newline at end of file diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index f1ee4e5415f..852f656705c 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -777,15 +777,15 @@ proc/GaussRandRound(var/sigma,var/roundto) if(toupdate.len) for(var/turf/simulated/T1 in toupdate) - air_master.remove_from_active(T1) + SSair.remove_from_active(T1) T1.CalculateAdjacentTurfs() - air_master.add_to_active(T1,1) + SSair.add_to_active(T1,1) if(fromupdate.len) for(var/turf/simulated/T2 in fromupdate) - air_master.remove_from_active(T2) + SSair.remove_from_active(T2) T2.CalculateAdjacentTurfs() - air_master.add_to_active(T2,1) + SSair.add_to_active(T2,1) @@ -941,7 +941,7 @@ proc/GaussRandRound(var/sigma,var/roundto) if(toupdate.len) for(var/turf/simulated/T1 in toupdate) T1.CalculateAdjacentTurfs() - air_master.add_to_active(T1,1) + SSair.add_to_active(T1,1) return copiedobjs diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index cb1ce8f10a7..0cc712799c7 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -19,7 +19,6 @@ var/global/list/all_areas = list() var/global/list/machines = list() var/global/list/machine_processing = list() var/global/list/fast_processing = list() -var/global/list/atmos_machinery = list() var/global/list/processing_power_items = list() //items that ask to be called every cycle var/global/list/rcd_list = list() //list of Rapid Construction Devices. diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index cfaa3f825e7..11cd37343fe 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -82,26 +82,4 @@ var/global/pipe_processing_killed = 0 count++ log_startup_progress(" Initialized [count] objects in [stop_watch(watch)]s.") - watch = start_watch() - count = 0 - log_startup_progress("Initializing atmospherics machinery...") - for(var/obj/machinery/atmospherics/unary/U in machines) - if(istype(U, /obj/machinery/atmospherics/unary/vent_pump)) - var/obj/machinery/atmospherics/unary/vent_pump/T = U - T.broadcast_status() - count++ - else if(istype(U, /obj/machinery/atmospherics/unary/vent_scrubber)) - var/obj/machinery/atmospherics/unary/vent_scrubber/T = U - T.broadcast_status() - count++ - log_startup_progress(" Initialized [count] atmospherics machines in [stop_watch(watch)]s.") - - watch = start_watch() - count = 0 - log_startup_progress("Initializing pipe networks...") - for(var/obj/machinery/atmospherics/machine in machines) - machine.build_network() - count++ - log_startup_progress(" Initialized [count] pipes in [stop_watch(watch)]s.") - log_startup_progress("Finished object initializations in [stop_watch(overwatch)]s.") diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm new file mode 100644 index 00000000000..732863cfc13 --- /dev/null +++ b/code/controllers/subsystem/air.dm @@ -0,0 +1,398 @@ +#define SSAIR_DEFERREDPIPENETS 1 +#define SSAIR_PIPENETS 2 +#define SSAIR_ATMOSMACHINERY 3 +#define SSAIR_ACTIVETURFS 4 +#define SSAIR_EXCITEDGROUPS 5 +#define SSAIR_HIGHPRESSURE 6 +#define SSAIR_HOTSPOTS 7 +#define SSAIR_SUPERCONDUCTIVITY 8 + +SUBSYSTEM_DEF(air) + name = "Atmospherics" + init_order = INIT_ORDER_AIR + priority = FIRE_PRIORITY_AIR + wait = 5 + flags = SS_BACKGROUND + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/cost_turfs = 0 + var/cost_groups = 0 + var/cost_highpressure = 0 + var/cost_hotspots = 0 + var/cost_superconductivity = 0 + var/cost_pipenets = 0 + var/cost_deferred_pipenets = 0 + var/cost_atmos_machinery = 0 + + var/list/excited_groups = list() + var/list/active_turfs = list() + var/list/hotspots = list() + var/list/networks = list() + var/list/atmos_machinery = list() + var/list/pipe_init_dirs_cache = list() + + + + //Special functions lists + var/list/active_super_conductivity = list() + var/list/high_pressure_delta = list() + + + var/list/currentrun = list() + var/currentpart = SSAIR_PIPENETS + +/datum/controller/subsystem/air/stat_entry(msg) + msg += "C:{" + msg += "AT:[round(cost_turfs,1)]|" + msg += "EG:[round(cost_groups,1)]|" + msg += "HP:[round(cost_highpressure,1)]|" + msg += "HS:[round(cost_hotspots,1)]|" + msg += "SC:[round(cost_superconductivity,1)]|" + msg += "PN:[round(cost_pipenets,1)]|" + msg += "DPN:[round(cost_deferred_pipenets,1)]|" + msg += "AM:[round(cost_atmos_machinery,1)]" + msg += "} " + msg += "AT:[active_turfs.len]|" + msg += "EG:[excited_groups.len]|" + msg += "HS:[hotspots.len]|" + msg += "PN:[networks.len]|" + msg += "HP:[high_pressure_delta.len]|" + msg += "AS:[active_super_conductivity.len]|" + msg += "AT/MS:[round((cost ? active_turfs.len/cost : 0),0.1)]" + ..(msg) + + +/datum/controller/subsystem/air/Initialize(timeofday) + setup_overlays() // Assign icons and such for gas-turf-overlays + setup_allturfs() + setup_atmos_machinery() + setup_pipenets() + ..() + + +/datum/controller/subsystem/air/fire(resumed = 0) + var/timer = TICK_USAGE_REAL + + if(currentpart == SSAIR_DEFERREDPIPENETS || !resumed) + process_deferred_pipenets(resumed) + cost_deferred_pipenets = MC_AVERAGE(cost_deferred_pipenets, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_PIPENETS + + if(currentpart == SSAIR_PIPENETS || !resumed) + process_pipenets(resumed) + cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_ATMOSMACHINERY + + if(currentpart == SSAIR_ATMOSMACHINERY) + timer = TICK_USAGE_REAL + process_atmos_machinery(resumed) + cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_ACTIVETURFS + + if(currentpart == SSAIR_ACTIVETURFS) + timer = TICK_USAGE_REAL + process_active_turfs(resumed) + cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_EXCITEDGROUPS + + if(currentpart == SSAIR_EXCITEDGROUPS) + timer = TICK_USAGE_REAL + process_excited_groups(resumed) + cost_groups = MC_AVERAGE(cost_groups, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_HIGHPRESSURE + + if(currentpart == SSAIR_HIGHPRESSURE) + timer = TICK_USAGE_REAL + process_high_pressure_delta(resumed) + cost_highpressure = MC_AVERAGE(cost_highpressure, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_HOTSPOTS + + if(currentpart == SSAIR_HOTSPOTS) + timer = TICK_USAGE_REAL + process_hotspots(resumed) + cost_hotspots = MC_AVERAGE(cost_hotspots, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_SUPERCONDUCTIVITY + + if(currentpart == SSAIR_SUPERCONDUCTIVITY) + timer = TICK_USAGE_REAL + process_super_conductivity(resumed) + cost_superconductivity = MC_AVERAGE(cost_superconductivity, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(state != SS_RUNNING) + return + resumed = 0 + currentpart = SSAIR_PIPENETS + +/datum/controller/subsystem/air/proc/process_deferred_pipenets(resumed = 0) + if (!resumed) + src.currentrun = deferred_pipenet_rebuilds.Copy() + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + while(currentrun.len) + var/obj/machinery/atmospherics/A = currentrun[currentrun.len] + currentrun.len-- + if(A) + A.build_network() + else + deferred_pipenet_rebuilds.Remove(A) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/process_pipenets(resumed = 0) + if (!resumed) + src.currentrun = networks.Copy() + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + while(currentrun.len) + var/datum/pipeline/thing = currentrun[currentrun.len] + currentrun.len-- + if(thing) + thing.process() + else + networks.Remove(thing) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/process_atmos_machinery(resumed = 0) + var/seconds = wait * 0.1 + if (!resumed) + src.currentrun = atmos_machinery.Copy() + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + while(currentrun.len) + var/obj/machinery/M = currentrun[currentrun.len] + currentrun.len-- + if(!M || (M.process_atmos(seconds) == PROCESS_KILL)) + atmos_machinery.Remove(M) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/process_super_conductivity(resumed = 0) + if (!resumed) + src.currentrun = active_super_conductivity.Copy() + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + while(currentrun.len) + var/turf/simulated/T = currentrun[currentrun.len] + currentrun.len-- + T.super_conduct() + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/process_hotspots(resumed = 0) + if (!resumed) + src.currentrun = hotspots.Copy() + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + while(currentrun.len) + var/obj/effect/hotspot/H = currentrun[currentrun.len] + currentrun.len-- + if (H) + H.process() + else + hotspots -= H + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/process_high_pressure_delta(resumed = 0) + while (high_pressure_delta.len) + var/turf/simulated/T = high_pressure_delta[high_pressure_delta.len] + high_pressure_delta.len-- + T.high_pressure_movements() + T.pressure_difference = 0 + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/process_active_turfs(resumed = 0) + //cache for sanic speed + var/fire_count = times_fired + if (!resumed) + src.currentrun = active_turfs.Copy() + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + while(currentrun.len) + var/turf/simulated/T = currentrun[currentrun.len] + currentrun.len-- + if (T) + T.process_cell(fire_count) + if (MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/process_excited_groups(resumed = 0) + if (!resumed) + src.currentrun = excited_groups.Copy() + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + while(currentrun.len) + var/datum/excited_group/EG = currentrun[currentrun.len] + currentrun.len-- + EG.breakdown_cooldown++ + if(EG.breakdown_cooldown == 10) + EG.self_breakdown() + else if(EG.breakdown_cooldown >= 20) + EG.dismantle() + if (MC_TICK_CHECK) + return + +/datum/controller/subsystem/air/proc/remove_from_active(turf/simulated/T) + active_turfs -= T + if(currentpart == SSAIR_ACTIVETURFS) + currentrun -= T + if(istype(T)) + T.excited = 0 + if(T.excited_group) + T.excited_group.garbage_collect() + +/datum/controller/subsystem/air/proc/add_to_active(turf/simulated/T, blockchanges = 1) + if(istype(T) && T.air) + T.excited = 1 + active_turfs |= T + if(currentpart == SSAIR_ACTIVETURFS) + currentrun |= T + if(blockchanges && T.excited_group) + T.excited_group.garbage_collect() + else + for(var/direction in cardinal) + if(!(T.atmos_adjacent_turfs & direction)) + continue + var/turf/simulated/S = get_step(T, direction) + if(istype(S)) + add_to_active(S) + +/datum/controller/subsystem/air/proc/setup_allturfs(var/list/turfs_to_init = block(locate(1, 1, 1), locate(world.maxx, world.maxy, world.maxz))) + var/list/active_turfs = src.active_turfs + + for(var/thing in turfs_to_init) + var/turf/T = thing + active_turfs -= T + if(T.blocks_air) + continue + T.Initialize_Atmos(times_fired) + CHECK_TICK + + if(active_turfs.len) + var/starting_ats = active_turfs.len + sleep(world.tick_lag) + var/timer = world.timeofday + warning("There are [starting_ats] active turfs at roundstart, this is a mapping error caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required)") + + //now lets clear out these active turfs + var/list/turfs_to_check = active_turfs.Copy() + do + var/list/new_turfs_to_check = list() + for(var/turf/simulated/T in turfs_to_check) + new_turfs_to_check += T.resolve_active_graph() + CHECK_TICK + + active_turfs += new_turfs_to_check + turfs_to_check = new_turfs_to_check + + while (turfs_to_check.len) + var/ending_ats = active_turfs.len + for(var/thing in excited_groups) + var/datum/excited_group/EG = thing + EG.self_breakdown() + EG.dismantle() + CHECK_TICK + + var/msg = "HEY! LISTEN! [DisplayTimeText(world.timeofday - timer)] were wasted processing [starting_ats] turf(s) (connected to [ending_ats] other turfs) with atmos differences at round start." + to_chat(world, "[msg]") + warning(msg) + +/turf/simulated/proc/resolve_active_graph() + . = list() + var/datum/excited_group/EG = excited_group + if (blocks_air || !air) + return + if (!EG) + EG = new + EG.add_turf(src) + + for (var/turf/simulated/ET in atmos_adjacent_turfs) + if ( ET.blocks_air || !ET.air) + continue + + var/ET_EG = ET.excited_group + if (ET_EG) + if (ET_EG != EG) + EG.merge_groups(ET_EG) + EG = excited_group //merge_groups() may decide to replace our current EG + else + EG.add_turf(ET) + if (!ET.excited) + ET.excited = 1 + . += ET + +/datum/controller/subsystem/air/proc/setup_atmos_machinery() + var/watch = start_watch() + var/count = 0 + log_startup_progress("Initializing atmospherics machinery...") + for(var/obj/machinery/atmospherics/unary/U in machines) + if(istype(U, /obj/machinery/atmospherics/unary/vent_pump)) + var/obj/machinery/atmospherics/unary/vent_pump/T = U + T.broadcast_status() + count++ + else if(istype(U, /obj/machinery/atmospherics/unary/vent_scrubber)) + var/obj/machinery/atmospherics/unary/vent_scrubber/T = U + T.broadcast_status() + count++ + log_startup_progress(" Initialized [count] atmospherics machines in [stop_watch(watch)]s.") + +//this can't be done with setup_atmos_machinery() because +// all atmos machinery has to initalize before the first +// pipenet can be built. +/datum/controller/subsystem/air/proc/setup_pipenets() + var/watch = start_watch() + var/count = 0 + log_startup_progress("Initializing pipe networks...") + for(var/obj/machinery/atmospherics/machine in machines) + machine.build_network() + count++ + log_startup_progress(" Initialized [count] pipes in [stop_watch(watch)]s.") + +/datum/controller/subsystem/air/proc/setup_overlays() + plmaster = new /obj/effect/overlay() + plmaster.icon = 'icons/effects/tile_effects.dmi' + plmaster.icon_state = "plasma" + plmaster.layer = FLY_LAYER + plmaster.mouse_opacity = 0 + + slmaster = new /obj/effect/overlay() + slmaster.icon = 'icons/effects/tile_effects.dmi' + slmaster.icon_state = "sleeping_agent" + slmaster.layer = FLY_LAYER + slmaster.mouse_opacity = 0 + + icemaster = new /obj/effect/overlay() + icemaster.icon = 'icons/turf/overlays.dmi' + icemaster.icon_state = "snowfloor" + icemaster.layer = TURF_LAYER+0.1 + icemaster.mouse_opacity = 0 + +#undef SSAIR_PIPENETS +#undef SSAIR_ATMOSMACHINERY +#undef SSAIR_ACTIVETURFS +#undef SSAIR_EXCITEDGROUPS +#undef SSAIR_HIGHPRESSURE +#undef SSAIR_HOTSPOT +#undef SSAIR_SUPERCONDUCTIVITY diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm new file mode 100644 index 00000000000..c251492227a --- /dev/null +++ b/code/controllers/subsystem/spacedrift.dm @@ -0,0 +1,59 @@ +SUBSYSTEM_DEF(spacedrift) + name = "Space Drift" + priority = FIRE_PRIORITY_SPACEDRIFT + wait = 5 + flags = SS_NO_INIT|SS_KEEP_TIMING + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun = list() + var/list/processing = list() + +/datum/controller/subsystem/spacedrift/stat_entry() + ..("P:[processing.len]") + + +/datum/controller/subsystem/spacedrift/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while (currentrun.len) + var/atom/movable/AM = currentrun[currentrun.len] + currentrun.len-- + if (!AM) + processing -= AM + if (MC_TICK_CHECK) + return + continue + + if (AM.inertia_next_move > world.time) + if (MC_TICK_CHECK) + return + continue + + if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0)) + AM.inertia_dir = 0 + + if (!AM.inertia_dir) + AM.inertia_last_loc = null + processing -= AM + if (MC_TICK_CHECK) + return + continue + + var/old_dir = AM.dir + var/old_loc = AM.loc + AM.inertia_moving = TRUE + step(AM, AM.inertia_dir) + AM.inertia_moving = FALSE + AM.inertia_next_move = world.time + AM.inertia_move_delay + if (AM.loc == old_loc) + AM.inertia_dir = 0 + + AM.setDir(old_dir) + AM.inertia_last_loc = AM.loc + if (MC_TICK_CHECK) + return + diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm new file mode 100644 index 00000000000..c32b5a707ae --- /dev/null +++ b/code/controllers/subsystem/throwing.dm @@ -0,0 +1,149 @@ +#define MAX_THROWING_DIST 512 // 2 z-levels on default width +#define MAX_TICKS_TO_MAKE_UP 3 //how many missed ticks will we attempt to make up for this run. + +SUBSYSTEM_DEF(throwing) + name = "Throwing" + priority = FIRE_PRIORITY_THROWING + wait = 1 + flags = SS_NO_INIT|SS_KEEP_TIMING|SS_TICKER + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + + var/list/currentrun + var/list/processing = list() + +/datum/controller/subsystem/throwing/stat_entry() + ..("P:[processing.len]") + + +/datum/controller/subsystem/throwing/fire(resumed = 0) + if (!resumed) + src.currentrun = processing.Copy() + + //cache for sanic speed (lists are references anyways) + var/list/currentrun = src.currentrun + + while(length(currentrun)) + var/atom/movable/AM = currentrun[currentrun.len] + var/datum/thrownthing/TT = currentrun[AM] + currentrun.len-- + if (!AM || !TT) + processing -= AM + if (MC_TICK_CHECK) + return + continue + + TT.tick() + + if (MC_TICK_CHECK) + return + + currentrun = null + +/datum/thrownthing + var/atom/movable/thrownthing + var/atom/target + var/turf/target_turf + var/init_dir + var/maxrange + var/speed + var/mob/thrower + var/diagonals_first + var/dist_travelled = 0 + var/start_time + var/dist_x + var/dist_y + var/dx + var/dy + var/pure_diagonal + var/diagonal_error + var/datum/callback/callback + var/paused = FALSE + var/delayed_time = 0 + var/last_move = 0 + +/datum/thrownthing/proc/tick() + var/atom/movable/AM = thrownthing + if (!isturf(AM.loc) || !AM.throwing) + finalize() + return + + if(paused) + delayed_time += world.time - last_move + return + + if (dist_travelled && hitcheck()) //to catch sneaky things moving on our tile while we slept + finalize() + return + + var/atom/step + + last_move = world.time + + //calculate how many tiles to move, making up for any missed ticks. + var/tilestomove = CEILING(min(((((world.time+world.tick_lag) - start_time + delayed_time) * speed) - (dist_travelled ? dist_travelled : -1)), speed*MAX_TICKS_TO_MAKE_UP) * (world.tick_lag * SSthrowing.wait), 1) + while (tilestomove-- > 0) + if ((dist_travelled >= maxrange || AM.loc == target_turf) && has_gravity(AM, AM.loc)) + finalize() + return + + if (dist_travelled <= max(dist_x, dist_y)) //if we haven't reached the target yet we home in on it, otherwise we use the initial direction + step = get_step(AM, get_dir(AM, target_turf)) + else + step = get_step(AM, init_dir) + + if (!pure_diagonal && !diagonals_first) // not a purely diagonal trajectory and we don't want all diagonal moves to be done first + if (diagonal_error >= 0 && max(dist_x,dist_y) - dist_travelled != 1) //we do a step forward unless we're right before the target + step = get_step(AM, dx) + diagonal_error += (diagonal_error < 0) ? dist_x/2 : -dist_y + + if (!step) // going off the edge of the map makes get_step return null, don't let things go off the edge + finalize() + return + + AM.Move(step, get_dir(AM, step)) + + if (!AM.throwing) // we hit something during our move + finalize(hit = TRUE) + return + + dist_travelled++ + + if (dist_travelled > MAX_THROWING_DIST) + finalize() + return + +/datum/thrownthing/proc/finalize(hit = FALSE, target=null) + set waitfor = 0 + SSthrowing.processing -= thrownthing + //done throwing, either because it hit something or it finished moving + thrownthing.throwing = null + if (!hit) + for (var/thing in get_turf(thrownthing)) //looking for our target on the turf we land on. + var/atom/A = thing + if (A == target) + hit = 1 + thrownthing.throw_impact(A, src) + break + if (!hit) + thrownthing.throw_impact(get_turf(thrownthing), src) // we haven't hit something yet and we still must, let's hit the ground. + thrownthing.newtonian_move(init_dir) + else + thrownthing.newtonian_move(init_dir) + + if(target) + thrownthing.throw_impact(target, src) + + if (callback) + callback.Invoke() + +/datum/thrownthing/proc/hit_atom(atom/A) + finalize(hit=TRUE, target=A) + +/datum/thrownthing/proc/hitcheck() + for (var/thing in get_turf(thrownthing)) + var/atom/movable/AM = thing + if (AM == thrownthing) + continue + if(AM.density && !(AM.pass_flags & LETPASSTHROW) && !(AM.flags & ON_BORDER)) + finalize(hit=TRUE, target=AM) + return TRUE diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index 4ea43a02e08..efbe9502e7c 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -43,7 +43,7 @@ debug_variables(ticker) feedback_add_details("admin_verb","DTicker") if("Air") - debug_variables(air_master) + debug_variables(SSair) feedback_add_details("admin_verb","DAir") if("Jobs") debug_variables(job_master) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 0e982a27288..27bc98e2b42 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -217,14 +217,15 @@ return 1 inertia_last_loc = loc - drift_master.processing_list[src] = src + SSspacedrift.processing[src] = src return 1 //called when src is thrown into hit_atom /atom/movable/proc/throw_impact(atom/hit_atom, throwingdatum) set waitfor = 0 - return hit_atom.hitby(src) + if(!qdeleted(hit_atom)) + return hit_atom.hitby(src) /atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = 1, blocked) if(!anchored && hitpush) @@ -301,7 +302,7 @@ if(spin && !no_spin && !no_spin_thrown) SpinAnimation(5, 1) - throw_master.processing_list[src] = TT + SSthrowing.processing[src] = TT TT.tick() diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm index 62996473291..fcb9bd0d28e 100644 --- a/code/game/machinery/atmo_control.dm +++ b/code/game/machinery/atmo_control.dm @@ -129,11 +129,11 @@ obj/machinery/air_sensor initialize() ..() - atmos_machinery += src + SSair.atmos_machinery += src set_frequency(frequency) Destroy() - atmos_machinery -= src + SSair.atmos_machinery -= src if(radio_controller) radio_controller.remove_object(src,frequency) return ..() diff --git a/code/game/machinery/atmoalter/meter.dm b/code/game/machinery/atmoalter/meter.dm index d3f1f25a52d..7365ca39271 100644 --- a/code/game/machinery/atmoalter/meter.dm +++ b/code/game/machinery/atmoalter/meter.dm @@ -18,14 +18,14 @@ /obj/machinery/meter/New() ..() - atmos_machinery += src + SSair.atmos_machinery += src target = locate(/obj/machinery/atmospherics/pipe) in loc if(id && !id_tag)//i'm not dealing with further merge conflicts, fuck it id_tag = id return 1 /obj/machinery/meter/Destroy() - atmos_machinery -= src + SSair.atmos_machinery -= src target = null return ..() diff --git a/code/game/machinery/atmoalter/portable_atmospherics.dm b/code/game/machinery/atmoalter/portable_atmospherics.dm index 45dcb5f7880..4a9eb2fa05d 100644 --- a/code/game/machinery/atmoalter/portable_atmospherics.dm +++ b/code/game/machinery/atmoalter/portable_atmospherics.dm @@ -13,7 +13,7 @@ /obj/machinery/portable_atmospherics/New() ..() - atmos_machinery += src + SSair.atmos_machinery += src air_contents.volume = volume air_contents.temperature = T20C @@ -36,7 +36,7 @@ update_icon() /obj/machinery/portable_atmospherics/Destroy() - atmos_machinery -= src + SSair.atmos_machinery -= src disconnect() QDEL_NULL(air_contents) QDEL_NULL(holding) diff --git a/code/game/machinery/atmoalter/zvent.dm b/code/game/machinery/atmoalter/zvent.dm index a9d288fe37e..18af424c4d6 100644 --- a/code/game/machinery/atmoalter/zvent.dm +++ b/code/game/machinery/atmoalter/zvent.dm @@ -11,10 +11,10 @@ /obj/machinery/zvent/New() ..() - atmos_machinery += src + SSair.atmos_machinery += src /obj/machinery/zvent/Destroy() - atmos_machinery -= src + SSair.atmos_machinery -= src return ..() /obj/machinery/zvent/process_atmos() diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm index 9601b86beec..ee0a5510240 100644 --- a/code/game/objects/items/weapons/flamethrower.dm +++ b/code/game/objects/items/weapons/flamethrower.dm @@ -193,7 +193,7 @@ //Burn it based on transfered gas target.hotspot_expose((ptank.air_contents.temperature*2) + 380,500) // -- More of my "how do I shot fire?" dickery. -- TLE //location.hotspot_expose(1000,500,1) - air_master.add_to_active(target, 0) + SSair.add_to_active(target, 0) return diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 2f05b738385..514d145810d 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -56,12 +56,12 @@ /turf/Destroy() // Adds the adjacent turfs to the current atmos processing - if(air_master) + if(SSair) for(var/direction in cardinal) if(atmos_adjacent_turfs & direction) var/turf/simulated/T = get_step(src, direction) if(istype(T)) - air_master.add_to_active(T) + SSair.add_to_active(T) ..() return QDEL_HINT_HARDDEL_NOW @@ -167,8 +167,8 @@ var/old_corners = corners BeforeChange() - if(air_master) - air_master.remove_from_active(src) + if(SSair) + SSair.remove_from_active(src) var/turf/W = new path(src) if(!defer_change) W.AfterChange() @@ -204,8 +204,8 @@ levelupdate() CalculateAdjacentTurfs() - if(air_master && !ignore_air) - air_master.add_to_active(src) + if(SSair && !ignore_air) + SSair.add_to_active(src) if(!keep_cabling && !can_have_cabling()) for(var/obj/structure/cable/C in contents) @@ -246,8 +246,8 @@ air.carbon_dioxide = (aco/max(turf_count,1)) air.toxins = (atox/max(turf_count,1)) air.temperature = (atemp/max(turf_count,1))//Trace gases can get bant - if(air_master) - air_master.add_to_active(src) + if(SSair) + SSair.add_to_active(src) /turf/proc/ReplaceWithLattice() src.ChangeTurf(/turf/space) @@ -457,6 +457,6 @@ T0.ChangeTurf(turf_type) - air_master.remove_from_active(T0) + SSair.remove_from_active(T0) T0.CalculateAdjacentTurfs() - air_master.add_to_active(T0,1) + SSair.add_to_active(T0,1) diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm index 50b09d72788..bdb8bc01bc4 100644 --- a/code/modules/awaymissions/zlevel.dm +++ b/code/modules/awaymissions/zlevel.dm @@ -9,8 +9,8 @@ var/global/list/potentialRandomZlevels = generateMapList(filename = "config/away smoothTurfs = turfs log_debug("Setting up atmos") - if(air_master) - air_master.setup_allturfs(turfs) + if(SSair) + SSair.setup_allturfs(turfs) log_debug("\tTook [stop_watch(subtimer)]s") subtimer = start_watch() diff --git a/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm b/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm index a392105513a..11873b5acdf 100644 --- a/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm +++ b/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm @@ -11,7 +11,7 @@ return var/list/map = mother.map for(var/turf/simulated/T in map) - air_master.remove_from_active(T) + SSair.remove_from_active(T) for(var/turf/simulated/T in map) if(T.air) T.air.oxygen = T.oxygen @@ -19,7 +19,7 @@ T.air.carbon_dioxide = T.carbon_dioxide T.air.toxins = T.toxins T.air.temperature = T.temperature - air_master.add_to_active(T) + SSair.add_to_active(T) //Only places atoms/turfs on area borders /datum/mapGeneratorModule/border diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index d3406556e27..3cd9bb49432 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -490,15 +490,15 @@ T1.shuttleRotate(rotation) //lighting stuff - air_master.remove_from_active(T1) + SSair.remove_from_active(T1) T1.CalculateAdjacentTurfs() - air_master.add_to_active(T1,1) + SSair.add_to_active(T1,1) T0.ChangeTurf(turf_type) - air_master.remove_from_active(T0) + SSair.remove_from_active(T0) T0.CalculateAdjacentTurfs() - air_master.add_to_active(T0,1) + SSair.add_to_active(T0,1) for(var/A1 in L1) var/turf/T1 = A1 diff --git a/code/world.dm b/code/world.dm index 9614373c901..df7fc20437c 100644 --- a/code/world.dm +++ b/code/world.dm @@ -55,7 +55,6 @@ var/global/list/map_transition_config = MAP_TRANSITION_CONFIG processScheduler.setup() master_controller.setup() - sleep_offline = 1 if(using_map && using_map.name) map_name = "[using_map.name]" diff --git a/paradise.dme b/paradise.dme index b46d43f0b7e..923f7a2740c 100644 --- a/paradise.dme +++ b/paradise.dme @@ -187,7 +187,6 @@ #include "code\controllers\subsystem.dm" #include "code\controllers\verbs.dm" #include "code\controllers\voting.dm" -#include "code\controllers\Processes\air.dm" #include "code\controllers\Processes\alarm.dm" #include "code\controllers\Processes\event.dm" #include "code\controllers\Processes\fast_process.dm" @@ -203,14 +202,15 @@ #include "code\controllers\Processes\npcpool.dm" #include "code\controllers\Processes\obj.dm" #include "code\controllers\Processes\shuttles.dm" -#include "code\controllers\Processes\spacedrift.dm" #include "code\controllers\Processes\sun.dm" -#include "code\controllers\Processes\throwing.dm" #include "code\controllers\Processes\ticker.dm" #include "code\controllers\Processes\timer.dm" #include "code\controllers\Processes\weather.dm" #include "code\controllers\ProcessScheduler\core\process.dm" #include "code\controllers\ProcessScheduler\core\processScheduler.dm" +#include "code\controllers\subsystem\air.dm" +#include "code\controllers\subsystem\spacedrift.dm" +#include "code\controllers\subsystem\throwing.dm" #include "code\datums\action.dm" #include "code\datums\ai_law_sets.dm" #include "code\datums\ai_laws.dm" From c609f25a09ac35eba5b11ed328e523db22b35c09 Mon Sep 17 00:00:00 2001 From: tigercat2000 Date: Sat, 3 Mar 2018 00:10:49 -0800 Subject: [PATCH 3/7] Styling fixes --- code/controllers/configuration.dm | 11 +++ code/controllers/failsafe.dm | 10 +-- code/controllers/master.dm | 141 +++++++++++++++--------------- code/controllers/subsystem.dm | 66 +++++++------- config/example/config.txt | 24 +++++ 5 files changed, 144 insertions(+), 108 deletions(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index cb363880fff..c76e04fc9d8 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -607,6 +607,17 @@ if("disable_karma") disable_karma = 1 + if("tick_limit_mc_init") + tick_limit_mc_init = text2num(value) + if("base_mc_tick_rate") + base_mc_tick_rate = text2num(value) + if("high_pop_mc_tick_rate") + high_pop_mc_tick_rate = text2num(value) + if("high_pop_mc_mode_amount") + high_pop_mc_mode_amount = text2num(value) + if("disable_high_pop_mc_mode_amount") + disable_high_pop_mc_mode_amount = text2num(value) + else diary << "Unknown setting in configuration: '[name]'" diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index 232a6bd054b..b3a47e3ce22 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -51,14 +51,14 @@ var/global/datum/controller/failsafe/Failsafe if(4,5) --defcon if(3) - message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") + message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5 - defcon) * processing_interval] ticks.") --defcon if(2) - to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") + to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5 - defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") --defcon if(1) - to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") + to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5 - defcon) * processing_interval] ticks. Killing and restarting...") --defcon var/rtn = Recreate_MC() if(rtn > 0) @@ -79,8 +79,8 @@ var/global/datum/controller/failsafe/Failsafe else defcon = min(defcon + 1,5) master_iteration = Master.iteration - if (defcon <= 1) - sleep(processing_interval*2) + if(defcon <= 1) + sleep(processing_interval * 2) else sleep(processing_interval) else diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 6c094208926..8aa1f1b0873 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -72,8 +72,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/list/_subsystems = list() subsystems = _subsystems - if (Master != src) - if (istype(Master)) + if(Master != src) + if(istype(Master)) Recover() qdel(Master) else @@ -104,9 +104,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new // -1 if we encountered a runtime trying to recreate it /proc/Recreate_MC() . = -1 //so if we runtime, things know we failed - if (world.time < Master.restart_timeout) + if(world.time < Master.restart_timeout) return 0 - if (world.time < Master.restart_clear) + if(world.time < Master.restart_clear) Master.restart_count *= 0.5 var/delay = 50 * ++Master.restart_count @@ -122,13 +122,13 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /datum/controller/master/Recover() var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n" - for (var/varname in Master.vars) + for(var/varname in Master.vars) switch (varname) if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk. continue else var/varval = Master.vars[varname] - if (istype(varval, /datum)) // Check if it has a type var. + if(istype(varval, /datum)) // Check if it has a type var. var/datum/D = varval msg += "\t [varname] = [D]([D.type])\n" else @@ -151,7 +151,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new to_chat(admins, "[msg]") log_to_dd(msg) - if (istype(Master.subsystems)) + if(istype(Master.subsystems)) if(FireHim) Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one subsystems = Master.subsystems @@ -181,8 +181,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/start_timeofday = REALTIMEOFDAY // Initialize subsystems. current_ticklimit = config.tick_limit_mc_init - for (var/datum/controller/subsystem/SS in subsystems) - if (SS.flags & SS_NO_INIT) + for(var/datum/controller/subsystem/SS in subsystems) + if(SS.flags & SS_NO_INIT) continue SS.Initialize(REALTIMEOFDAY) CHECK_TICK @@ -193,7 +193,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new to_chat(world, "[msg]") log_to_dd(msg) - if (!current_runlevel) + if(!current_runlevel) SetRunLevel(1) // Sort subsystems by display setting for easy access. @@ -226,13 +226,13 @@ GLOBAL_REAL(Master, /datum/controller/master) = new sleep(delay) testing("Master starting processing") var/rtn = Loop() - if (rtn > 0 || processing < 0) + if(rtn > 0 || processing < 0) return //this was suppose to happen. //loop ended, restart the mc log_game("MC crashed or runtimed, restarting") message_admins("MC crashed or runtimed, restarting") var/rtn2 = Recreate_MC() - if (rtn2 <= 0) + if(rtn2 <= 0) log_game("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") message_admins("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") Failsafe.defcon = 2 @@ -247,15 +247,15 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/list/tickersubsystems = list() var/list/runlevel_sorted_subsystems = list(list(), list(), list(), list(), list(), list(), list(), list()) //ensure we always have as many runlevels as we need to operate with no subsystems (8 currently) var/timer = world.time - for (var/thing in subsystems) + for(var/thing in subsystems) var/datum/controller/subsystem/SS = thing - if (SS.flags & SS_NO_FIRE) + if(SS.flags & SS_NO_FIRE) continue SS.queued_time = 0 SS.queue_next = null SS.queue_prev = null SS.state = SS_IDLE - if (SS.flags & SS_TICKER) + if(SS.flags & SS_TICKER) tickersubsystems += SS timer += world.tick_lag * rand(1, 5) SS.next_fire = timer @@ -293,10 +293,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/list/subsystems_to_check //the actual loop. - while (1) + while(1) tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) var/starting_tick_usage = TICK_USAGE - if (processing <= 0) + if(processing <= 0) current_ticklimit = TICK_LIMIT_RUNNING sleep(10) continue @@ -304,31 +304,31 @@ GLOBAL_REAL(Master, /datum/controller/master) = new //Anti-tick-contention heuristics: //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later. // (because sleeps are processed in the order received, longer sleeps are more likely to run first) - if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit. + if(starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit. sleep_delta *= 2 current_ticklimit = TICK_LIMIT_RUNNING * 0.5 sleep(world.tick_lag * (processing * sleep_delta)) continue //Byond resumed us late. assume it might have to do the same next tick - if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time) + if(last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time) sleep_delta += 1 sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta - if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick + if(starting_tick_usage > (TICK_LIMIT_MC * 0.75)) //we ran 3/4 of the way into the tick sleep_delta += 1 //debug - if (make_runtime) + if(make_runtime) var/datum/controller/subsystem/SS SS.can_fire = 0 - if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time)) + if(!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick + (Failsafe.processing_interval * 5)) < world.time)) new/datum/controller/failsafe() // (re)Start the failsafe. //now do the actual stuff - if (!queue_head || !(iteration % 3)) + if(!queue_head || !(iteration % 3)) var/checking_runlevel = current_runlevel if(cached_runlevel != checking_runlevel) //resechedule subsystems @@ -345,30 +345,30 @@ GLOBAL_REAL(Master, /datum/controller/master) = new else subsystems_to_check = tickersubsystems - if (CheckQueue(subsystems_to_check) <= 0) - if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) + if(CheckQueue(subsystems_to_check) <= 0) + if(!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) log_to_dd("MC: SoftReset() failed, crashing") return - if (!error_level) + if(!error_level) iteration++ error_level++ current_ticklimit = TICK_LIMIT_RUNNING sleep(10) continue - if (queue_head) - if (RunQueue() <= 0) - if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) + if(queue_head) + if(RunQueue() <= 0) + if(!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) log_to_dd("MC: SoftReset() failed, crashing") return - if (!error_level) + if(!error_level) iteration++ error_level++ current_ticklimit = TICK_LIMIT_RUNNING sleep(10) continue error_level-- - if (!queue_head) //reset the counts if the queue is empty, in the off chance they get out of sync + if(!queue_head) //reset the counts if the queue is empty, in the off chance they get out of sync queue_priority_count = 0 queue_priority_count_bg = 0 @@ -376,7 +376,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new last_run = world.time src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta) current_ticklimit = TICK_LIMIT_RUNNING - if (processing * sleep_delta <= world.tick_lag) + if(processing * sleep_delta <= world.tick_lag) current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick sleep(world.tick_lag * (processing * sleep_delta)) @@ -391,21 +391,21 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/datum/controller/subsystem/SS var/SS_flags - for (var/thing in subsystemstocheck) - if (!thing) + for(var/thing in subsystemstocheck) + if(!thing) subsystemstocheck -= thing SS = thing - if (SS.state != SS_IDLE) + if(SS.state != SS_IDLE) continue - if (SS.can_fire <= 0) + if(SS.can_fire <= 0) continue - if (SS.next_fire > world.time) + if(SS.next_fire > world.time) continue SS_flags = SS.flags - if (SS_flags & SS_NO_FIRE) + if(SS_flags & SS_NO_FIRE) subsystemstocheck -= SS continue - if (!(SS_flags & SS_TICKER) && (SS_flags & SS_KEEP_TIMING) && SS.last_fire + (SS.wait * 0.75) > world.time) + if(!(SS_flags & SS_TICKER) && (SS_flags & SS_KEEP_TIMING) && SS.last_fire + (SS.wait * 0.75) > world.time) continue SS.enqueue() . = 1 @@ -429,13 +429,13 @@ GLOBAL_REAL(Master, /datum/controller/master) = new //keep running while we have stuff to run and we haven't gone over a tick // this is so subsystems paused eariler can use tick time that later subsystems never used - while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC) + while(ran && queue_head && TICK_USAGE < TICK_LIMIT_MC) ran = FALSE bg_calc = FALSE current_tick_budget = queue_priority_count queue_node = queue_head - while (queue_node) - if (ran && TICK_USAGE > TICK_LIMIT_RUNNING) + while(queue_node) + if(ran && TICK_USAGE > TICK_LIMIT_RUNNING) break queue_node_flags = queue_node.flags @@ -446,8 +446,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new //we bump up their priority and attempt to run them next tick //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run // in those cases, so we just let them run) - if (queue_node_flags & SS_NO_TICK_CHECK) - if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker) + if(queue_node_flags & SS_NO_TICK_CHECK) + if(queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker) queue_node.queued_priority += queue_priority_count * 0.1 queue_priority_count -= queue_node_priority queue_priority_count += queue_node.queued_priority @@ -455,22 +455,22 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node = queue_node.queue_next continue - if ((queue_node_flags & SS_BACKGROUND) && !bg_calc) + if((queue_node_flags & SS_BACKGROUND) && !bg_calc) current_tick_budget = queue_priority_count_bg bg_calc = TRUE tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE - if (current_tick_budget > 0 && queue_node_priority > 0) + if(current_tick_budget > 0 && queue_node_priority > 0) tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority) else tick_precentage = tick_remaining - tick_precentage = max(tick_precentage*0.5, tick_precentage-queue_node.tick_overrun) + tick_precentage = max(tick_precentage*0.5, tick_precentage - queue_node.tick_overrun) current_ticklimit = round(TICK_USAGE + tick_precentage) - if (!(queue_node_flags & SS_TICKER)) + if(!(queue_node_flags & SS_TICKER)) ran_non_ticker = TRUE ran = TRUE @@ -483,17 +483,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/state = queue_node.ignite(queue_node_paused) tick_usage = TICK_USAGE - tick_usage - if (state == SS_RUNNING) + if(state == SS_RUNNING) state = SS_IDLE current_tick_budget -= queue_node_priority - if (tick_usage < 0) + if(tick_usage < 0) tick_usage = 0 - queue_node.tick_overrun = max(0, MC_AVG_FAST_UP_SLOW_DOWN(queue_node.tick_overrun, tick_usage-tick_precentage)) + queue_node.tick_overrun = max(0, MC_AVG_FAST_UP_SLOW_DOWN(queue_node.tick_overrun, tick_usage - tick_precentage)) queue_node.state = state - if (state == SS_PAUSED) + if(state == SS_PAUSED) queue_node.paused_ticks++ queue_node.paused_tick_usage += tick_usage queue_node = queue_node.queue_next @@ -508,7 +508,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node.paused_ticks = 0 queue_node.paused_tick_usage = 0 - if (queue_node_flags & SS_BACKGROUND) //update our running total + if(queue_node_flags & SS_BACKGROUND) //update our running total queue_priority_count_bg -= queue_node_priority else queue_priority_count -= queue_node_priority @@ -516,14 +516,14 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node.last_fire = world.time queue_node.times_fired++ - if (queue_node_flags & SS_TICKER) + if(queue_node_flags & SS_TICKER) 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) + 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) queue_node.next_fire += queue_node.wait else - queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) + queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun / 100)) queue_node.queued_time = 0 @@ -539,16 +539,16 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS) . = 0 log_to_dd("MC: SoftReset called, resetting MC queue state.") - if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS)) + if(!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS)) log_to_dd("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]'") return var/subsystemstocheck = subsystems + ticker_SS for(var/I in runlevel_SS) subsystemstocheck |= I - for (var/thing in subsystemstocheck) + for(var/thing in subsystemstocheck) var/datum/controller/subsystem/SS = thing - if (!SS || !istype(SS)) + if(!SS || !istype(SS)) //list(SS) is so if a list makes it in the subsystem list, we remove the list, not the contents subsystems -= list(SS) ticker_SS -= list(SS) @@ -556,19 +556,19 @@ GLOBAL_REAL(Master, /datum/controller/master) = new I -= list(SS) log_to_dd("MC: SoftReset: Found bad entry in subsystem list, '[SS]'") continue - if (SS.queue_next && !istype(SS.queue_next)) + if(SS.queue_next && !istype(SS.queue_next)) log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_next = '[SS.queue_next]'") SS.queue_next = null - if (SS.queue_prev && !istype(SS.queue_prev)) + if(SS.queue_prev && !istype(SS.queue_prev)) log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_prev = '[SS.queue_prev]'") SS.queue_prev = null SS.queued_priority = 0 SS.queued_time = 0 SS.state = SS_IDLE - if (queue_head && !istype(queue_head)) + if(queue_head && !istype(queue_head)) log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_head = '[queue_head]'") queue_head = null - if (queue_tail && !istype(queue_tail)) + if(queue_tail && !istype(queue_tail)) log_to_dd("MC: SoftReset: Found bad data in subsystem queue, queue_tail = '[queue_tail]'") queue_tail = null queue_priority_count = 0 @@ -582,9 +582,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(!statclick) statclick = new/obj/effect/statclick/debug(src, "Initializing...") - stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))") - stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")) + stat("Byond", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))") + stat("Master Controller", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")) +// Currently unimplemented /datum/controller/master/StartLoadingMap() //disallow more than one map to load at once, multithreading it will just cause race conditions while(map_loading) @@ -602,10 +603,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /datum/controller/master/proc/UpdateTickRate() - if (!processing) + if(!processing) return var/client_count = length(clients) - if (client_count < config.disable_high_pop_mc_mode_amount) + if(client_count < config.disable_high_pop_mc_mode_amount) processing = config.base_mc_tick_rate - else if (client_count > config.high_pop_mc_mode_amount) + else if(client_count > config.high_pop_mc_mode_amount) processing = config.high_pop_mc_tick_rate diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 0ffd9e09d35..a78750bf8d0 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -50,9 +50,9 @@ . = SS_SLEEPING fire(resumed) . = state - if (state == SS_SLEEPING) + if(state == SS_SLEEPING) state = SS_IDLE - if (state == SS_PAUSING) + if(state == SS_PAUSING) var/QT = queued_time enqueue() state = SS_PAUSED @@ -82,48 +82,48 @@ var/queue_node_priority var/queue_node_flags - for (queue_node = Master.queue_head; queue_node; queue_node = queue_node.queue_next) + for(queue_node = Master.queue_head; queue_node; queue_node = queue_node.queue_next) queue_node_priority = queue_node.queued_priority queue_node_flags = queue_node.flags - if (queue_node_flags & SS_TICKER) - if (!(SS_flags & SS_TICKER)) + if(queue_node_flags & SS_TICKER) + if(!(SS_flags & SS_TICKER)) continue - if (queue_node_priority < SS_priority) + if(queue_node_priority < SS_priority) break - else if (queue_node_flags & SS_BACKGROUND) - if (!(SS_flags & SS_BACKGROUND)) + else if(queue_node_flags & SS_BACKGROUND) + if(!(SS_flags & SS_BACKGROUND)) break - if (queue_node_priority < SS_priority) + if(queue_node_priority < SS_priority) break else - if (SS_flags & SS_BACKGROUND) + if(SS_flags & SS_BACKGROUND) continue - if (SS_flags & SS_TICKER) + if(SS_flags & SS_TICKER) break - if (queue_node_priority < SS_priority) + if(queue_node_priority < SS_priority) break queued_time = world.time queued_priority = SS_priority state = SS_QUEUED - if (SS_flags & SS_BACKGROUND) //update our running total + if(SS_flags & SS_BACKGROUND) //update our running total Master.queue_priority_count_bg += SS_priority else Master.queue_priority_count += SS_priority queue_next = queue_node - if (!queue_node)//we stopped at the end, add to tail + if(!queue_node)//we stopped at the end, add to tail queue_prev = Master.queue_tail - if (Master.queue_tail) + if(Master.queue_tail) Master.queue_tail.queue_next = src else //empty queue, we also need to set the head Master.queue_head = src Master.queue_tail = src - else if (queue_node == Master.queue_head)//insert at start of list + else if(queue_node == Master.queue_head)//insert at start of list Master.queue_head.queue_prev = src Master.queue_head = src queue_prev = null @@ -134,16 +134,16 @@ /datum/controller/subsystem/proc/dequeue() - if (queue_next) + if(queue_next) queue_next.queue_prev = queue_prev - if (queue_prev) + if(queue_prev) queue_prev.queue_next = queue_next - if (src == Master.queue_tail) + if(src == Master.queue_tail) Master.queue_tail = queue_prev - if (src == Master.queue_head) + if(src == Master.queue_head) Master.queue_head = queue_next queued_time = 0 - if (state == SS_QUEUED) + if(state == SS_QUEUED) state = SS_IDLE @@ -171,27 +171,27 @@ statclick = new/obj/effect/statclick/debug(src, "Initializing...") if(can_fire && !(SS_NO_FIRE & flags)) - msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]" + msg = "[round(cost, 1)]ms|[round(tick_usage, 1)]%([round(tick_overrun, 1)]%)|[round(ticks, 0.1)]\t[msg]" else msg = "OFFLINE\t[msg]" var/title = name - if (can_fire) + if(can_fire) title = "\[[state_letter()]][title]" stat(title, statclick.update(msg)) /datum/controller/subsystem/proc/state_letter() - switch (state) - if (SS_RUNNING) + switch(state) + if(SS_RUNNING) . = "R" - if (SS_QUEUED) + if(SS_QUEUED) . = "Q" - if (SS_PAUSED, SS_PAUSING) + if(SS_PAUSED, SS_PAUSING) . = "P" - if (SS_SLEEPING) + if(SS_SLEEPING) . = "S" - if (SS_IDLE) + if(SS_IDLE) . = " " //could be used to postpone a costly subsystem for (default one) var/cycles, cycles @@ -205,11 +205,11 @@ /datum/controller/subsystem/Recover() /datum/controller/subsystem/vv_edit_var(var_name, var_value) - switch (var_name) - if ("can_fire") + switch(var_name) + if("can_fire") //this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag - if (var_value) + if(var_value) next_fire = world.time + wait - if ("queued_priority") //editing this breaks things. + if("queued_priority") //editing this breaks things. return 0 . = ..() diff --git a/config/example/config.txt b/config/example/config.txt index 2dcd5da6573..0f0b1eb57f2 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -347,3 +347,27 @@ DISABLE_SPACE_RUINS ## Uncomment this to disable karma and unlock all karma purchases for players by default #DISABLE_KARMA + +## Defines the ticklimit for subsystem initialization (In percents of a byond tick). Lower makes world start smoother. Higher makes it faster. +##This is currently a testing optimized setting. A good value for production would be 98. +TICK_LIMIT_MC_INIT 500 + +###Master Controller High Pop Mode### + +##The Master Controller(MC) is the primary system controlling timed tasks and events in SS13 (lobby timer, game checks, lighting updates, atmos, etc) +##Default base MC tick rate (1 = process every "byond tick" (see: tick_lag/fps config settings), 2 = process every 2 byond ticks, etc) +## Setting this to 0 will prevent the Master Controller from ticking +BASE_MC_TICK_RATE 1 + +##High population MC tick rate +## Byond rounds timer values UP, but the tick rate is modified with heuristics during lag spites so setting this to something like 2 +## will make it run every 2 byond ticks, but will also double the effect of anti-lag heuristics. You can instead set it to something like +## 1.1 to make it run every 2 byond ticks, but only increase the effect of anti-lag heuristics by 10%. or 1.5 for 50%. +## (As an aside, you could in theory also reduce the effect of anti-lag heuristics in the base tick rate by setting it to something like 0.5) +HIGH_POP_MC_TICK_RATE 1.1 + +##Engage high pop mode if player count raises above this (Player in this context means any connected user. Lobby, ghost or in-game all count) +HIGH_POP_MC_MODE_AMOUNT 65 + +##Disengage high pop mode if player count drops below this +DISABLE_HIGH_POP_MC_MODE_AMOUNT 60 \ No newline at end of file From d88a17a0f4661e244c722c34cd8b707270384f00 Mon Sep 17 00:00:00 2001 From: tigercat2000 Date: Tue, 20 Mar 2018 18:32:45 -0700 Subject: [PATCH 4/7] Fix runtimes, remove world init BS, UpdateTickRate() --- code/controllers/subsystem/air.dm | 32 ++--------------------------- code/game/atoms_movable.dm | 2 +- code/modules/client/client procs.dm | 3 +++ 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 732863cfc13..7ff5743294f 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -256,7 +256,8 @@ SUBSYSTEM_DEF(air) /datum/controller/subsystem/air/proc/remove_from_active(turf/simulated/T) active_turfs -= T - if(currentpart == SSAIR_ACTIVETURFS) + active_super_conductivity -= T // bug: if a turf is hit by ex_act 1 while processing, it can end up in super conductivity as /turf/space and cause runtimes + if(currentpart == SSAIR_ACTIVETURFS || currentpart == SSAIR_SUPERCONDUCTIVITY) currentrun -= T if(istype(T)) T.excited = 0 @@ -290,35 +291,6 @@ SUBSYSTEM_DEF(air) T.Initialize_Atmos(times_fired) CHECK_TICK - if(active_turfs.len) - var/starting_ats = active_turfs.len - sleep(world.tick_lag) - var/timer = world.timeofday - warning("There are [starting_ats] active turfs at roundstart, this is a mapping error caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required)") - - //now lets clear out these active turfs - var/list/turfs_to_check = active_turfs.Copy() - do - var/list/new_turfs_to_check = list() - for(var/turf/simulated/T in turfs_to_check) - new_turfs_to_check += T.resolve_active_graph() - CHECK_TICK - - active_turfs += new_turfs_to_check - turfs_to_check = new_turfs_to_check - - while (turfs_to_check.len) - var/ending_ats = active_turfs.len - for(var/thing in excited_groups) - var/datum/excited_group/EG = thing - EG.self_breakdown() - EG.dismantle() - CHECK_TICK - - var/msg = "HEY! LISTEN! [DisplayTimeText(world.timeofday - timer)] were wasted processing [starting_ats] turf(s) (connected to [ending_ats] other turfs) with atmos differences at round start." - to_chat(world, "[msg]") - warning(msg) - /turf/simulated/proc/resolve_active_graph() . = list() var/datum/excited_group/EG = excited_group diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 27bc98e2b42..6ca5047ac6c 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -224,7 +224,7 @@ //called when src is thrown into hit_atom /atom/movable/proc/throw_impact(atom/hit_atom, throwingdatum) set waitfor = 0 - if(!qdeleted(hit_atom)) + if(exists(hit_atom)) return hit_atom.hitby(src) /atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = 1, blocked) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 9e731953ce5..9b30cc9e599 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -478,6 +478,8 @@ if(!tooltips) tooltips = new /datum/tooltip(src) + Master.UpdateTickRate() + ////////////// //DISCONNECT// ////////////// @@ -487,6 +489,7 @@ admins -= src directory -= ckey clients -= src + Master.UpdateTickRate() return ..() From f5940155ca23e2fd480d0cd1542509a81364cfe8 Mon Sep 17 00:00:00 2001 From: tigercat2000 Date: Tue, 20 Mar 2018 19:12:55 -0700 Subject: [PATCH 5/7] Spacing fixes. --- code/__DEFINES/subsystems.dm | 15 +- code/__HELPERS/lists.dm | 28 +- code/__HELPERS/sorts/InsertSort.dm | 4 +- code/__HELPERS/sorts/MergeSort.dm | 4 +- code/__HELPERS/sorts/TimSort.dm | 4 +- code/__HELPERS/sorts/__main.dm | 1002 +++++++++++----------- code/__HELPERS/time.dm | 2 +- code/controllers/configuration.dm | 2 +- code/controllers/subsystem/air.dm | 40 +- code/controllers/subsystem/spacedrift.dm | 16 +- code/controllers/subsystem/throwing.dm | 53 +- 11 files changed, 582 insertions(+), 588 deletions(-) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index a0ea6b60777..35f837e97fd 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -1,23 +1,18 @@ -//Update this whenever the db schema changes -//make sure you add an update to the schema_version stable in the db changelog -#define DB_MAJOR_VERSION 4 -#define DB_MINOR_VERSION 1 - //Timing subsystem //Don't run if there is an identical unique timer active //if the arguments to addtimer are the same as an existing timer, it doesn't create a new timer, and returns the id of the existing timer -#define TIMER_UNIQUE 0x1 +#define TIMER_UNIQUE 1 //For unique timers: Replace the old timer rather then not start this one -#define TIMER_OVERRIDE 0x2 +#define TIMER_OVERRIDE 2 //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 +#define TIMER_CLIENT_TIME 4 //Timer can be stopped using deltimer() -#define TIMER_STOPPABLE 0x8 +#define TIMER_STOPPABLE 8 //To be used with TIMER_UNIQUE //prevents distinguishing identical timers with the wait variable -#define TIMER_NO_HASH_WAIT 0x10 +#define TIMER_NO_HASH_WAIT 16 #define TIMER_NO_INVOKE_WARNING 600 //number of byond ticks that are allowed to pass before the timer subsystem thinks it hung on something diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 5627a355557..799f94feebb 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -687,43 +687,43 @@ proc/dd_sortedObjectList(list/incoming) //fromIndex and toIndex must be in the range [1,L.len+1] //This will preserve associations ~Carnie /proc/moveElement(list/L, fromIndex, toIndex) - if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move + if(fromIndex == toIndex || fromIndex + 1 == toIndex) //no need to move return if(fromIndex > toIndex) ++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one L.Insert(toIndex, null) L.Swap(fromIndex, toIndex) - L.Cut(fromIndex, fromIndex+1) + L.Cut(fromIndex, fromIndex + 1) //Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) //Same as moveElement but for ranges of elements //This will preserve associations ~Carnie -/proc/moveRange(list/L, fromIndex, toIndex, len=1) +/proc/moveRange(list/L, fromIndex, toIndex, len = 1) var/distance = abs(toIndex - fromIndex) if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements if(fromIndex <= toIndex) return //no need to move fromIndex += len //we want to shift left instead of right - for(var/i=0, i toIndex) fromIndex += len - for(var/i=0, i distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements if(fromIndex < toIndex) @@ -731,24 +731,24 @@ proc/dd_sortedObjectList(list/incoming) else fromIndex += len - for(var/i=0, i fromIndex) var/a = toIndex toIndex = fromIndex fromIndex = a - for(var/i=0, i= 2) fromIndex = fromIndex % L.len - toIndex = toIndex % (L.len+1) + toIndex = toIndex % (L.len + 1) if(fromIndex <= 0) fromIndex += L.len if(toIndex <= 0) diff --git a/code/__HELPERS/sorts/MergeSort.dm b/code/__HELPERS/sorts/MergeSort.dm index 39d37997255..d8c3d1477d0 100644 --- a/code/__HELPERS/sorts/MergeSort.dm +++ b/code/__HELPERS/sorts/MergeSort.dm @@ -1,8 +1,8 @@ //merge-sort - gernerally faster than insert sort, for runs of 7 or larger -/proc/sortMerge(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex) +/proc/sortMerge(list/L, cmp = /proc/cmp_numeric_asc, associative, fromIndex = 1, toIndex) if(L && L.len >= 2) fromIndex = fromIndex % L.len - toIndex = toIndex % (L.len+1) + toIndex = toIndex % (L.len + 1) if(fromIndex <= 0) fromIndex += L.len if(toIndex <= 0) diff --git a/code/__HELPERS/sorts/TimSort.dm b/code/__HELPERS/sorts/TimSort.dm index d709044dc05..b3aca97ea99 100644 --- a/code/__HELPERS/sorts/TimSort.dm +++ b/code/__HELPERS/sorts/TimSort.dm @@ -1,8 +1,8 @@ //TimSort interface -/proc/sortTim(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex=0) +/proc/sortTim(list/L, cmp = /proc/cmp_numeric_asc, associative, fromIndex = 1, toIndex = 0) if(L && L.len >= 2) fromIndex = fromIndex % L.len - toIndex = toIndex % (L.len+1) + toIndex = toIndex % (L.len + 1) if(fromIndex <= 0) fromIndex += L.len if(toIndex <= 0) diff --git a/code/__HELPERS/sorts/__main.dm b/code/__HELPERS/sorts/__main.dm index 768622818ff..b5368330d59 100644 --- a/code/__HELPERS/sorts/__main.dm +++ b/code/__HELPERS/sorts/__main.dm @@ -8,7 +8,7 @@ //When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times. #define MIN_GALLOP 7 - //This is a global instance to allow much of this code to be reused. The interfaces are kept separately +//This is a global instance to allow much of this code to be reused. The interfaces are kept separately GLOBAL_DATUM_INIT(sortInstance, /datum/sortInstance, new()) /datum/sortInstance //The array being sorted. @@ -31,615 +31,615 @@ GLOBAL_DATUM_INIT(sortInstance, /datum/sortInstance, new()) var/list/runLens = list() - proc/timSort(start, end) - runBases.Cut() - runLens.Cut() +/datum/sortInstance/proc/timSort(start, end) + runBases.Cut() + runLens.Cut() - var/remaining = end - start + var/remaining = end - start - //If array is small, do a 'mini-TimSort' with no merges - if(remaining < MIN_MERGE) - var/initRunLen = countRunAndMakeAscending(start, end) - binarySort(start, end, start+initRunLen) - return + //If array is small, do a 'mini-TimSort' with no merges + if(remaining < MIN_MERGE) + var/initRunLen = countRunAndMakeAscending(start, end) + binarySort(start, end, start + initRunLen) + return - //March over the array finding natural runs - //Extend any short natural runs to runs of length minRun - var/minRun = minRunLength(remaining) + //March over the array finding natural runs + //Extend any short natural runs to runs of length minRun + var/minRun = minRunLength(remaining) - do - //identify next run - var/runLen = countRunAndMakeAscending(start, end) + do + //identify next run + var/runLen = countRunAndMakeAscending(start, end) - //if run is short, extend to min(minRun, remaining) - if(runLen < minRun) - var/force = (remaining <= minRun) ? remaining : minRun + //if run is short, extend to min(minRun, remaining) + if(runLen < minRun) + var/force = (remaining <= minRun) ? remaining : minRun - binarySort(start, start+force, start+runLen) - runLen = force + binarySort(start, start + force, start+runLen) + runLen = force - //add data about run to queue - runBases.Add(start) - runLens.Add(runLen) + //add data about run to queue + runBases.Add(start) + runLens.Add(runLen) - //maybe merge - mergeCollapse() + //maybe merge + mergeCollapse() - //Advance to find next run - start += runLen - remaining -= runLen + //Advance to find next run + start += runLen + remaining -= runLen - while(remaining > 0) + while(remaining > 0) - //Merge all remaining runs to complete sort - //ASSERT(start == end) - mergeForceCollapse(); - //ASSERT(runBases.len == 1) + //Merge all remaining runs to complete sort + //ASSERT(start == end) + mergeForceCollapse(); + //ASSERT(runBases.len == 1) - //reset minGallop, for successive calls - minGallop = MIN_GALLOP + //reset minGallop, for successive calls + minGallop = MIN_GALLOP - return L + return L - /* - Sorts the specified portion of the specified array using a binary - insertion sort. This is the best method for sorting small numbers - of elements. It requires O(n log n) compares, but O(n^2) data - movement (worst case). +/* +Sorts the specified portion of the specified array using a binary +insertion sort. This is the best method for sorting small numbers +of elements. It requires O(n log n) compares, but O(n^2) data +movement (worst case). - If the initial part of the specified range is already sorted, - this method can take advantage of it: the method assumes that the - elements in range [lo,start) are already sorted +If the initial part of the specified range is already sorted, +this method can take advantage of it: the method assumes that the +elements in range [lo,start) are already sorted - lo the index of the first element in the range to be sorted - hi the index after the last element in the range to be sorted - start the index of the first element in the range that is not already known to be sorted - */ - proc/binarySort(lo, hi, start) - //ASSERT(lo <= start && start <= hi) - if(start <= lo) - start = lo + 1 +lo the index of the first element in the range to be sorted +hi the index after the last element in the range to be sorted +start the index of the first element in the range that is not already known to be sorted +*/ +/datum/sortInstance/proc/binarySort(lo, hi, start) + //ASSERT(lo <= start && start <= hi) + if(start <= lo) + start = lo + 1 - for(,start < hi, ++start) - var/pivot = fetchElement(L,start) + for(,start < hi, ++start) + var/pivot = fetchElement(L,start) - //set left and right to the index where pivot belongs - var/left = lo - var/right = start - //ASSERT(left <= right) + //set left and right to the index where pivot belongs + var/left = lo + var/right = start + //ASSERT(left <= right) - //[lo, left) elements <= pivot < [right, start) elements - //in other words, find where the pivot element should go using bisection search - while(left < right) - var/mid = (left + right) >> 1 //round((left+right)/2) - if(call(cmp)(fetchElement(L,mid), pivot) > 0) - right = mid - else - left = mid+1 - - //ASSERT(left == right) - moveElement(L, start, left) //move pivot element to correct location in the sorted range - - /* - Returns the length of the run beginning at the specified position and reverses the run if it is back-to-front - - A run is the longest ascending sequence with: - a[lo] <= a[lo + 1] <= a[lo + 2] <= ... - or the longest descending sequence with: - a[lo] > a[lo + 1] > a[lo + 2] > ... - - For its intended use in a stable mergesort, the strictness of the - definition of "descending" is needed so that the call can safely - reverse a descending sequence without violating stability. - */ - proc/countRunAndMakeAscending(lo, hi) - //ASSERT(lo < hi) - - var/runHi = lo + 1 - if(runHi >= hi) - return 1 - - var/last = fetchElement(L,lo) - var/current = fetchElement(L,runHi++) - - if(call(cmp)(current, last) < 0) - while(runHi < hi) - last = current - current = fetchElement(L,runHi) - if(call(cmp)(current, last) >= 0) - break - ++runHi - reverseRange(L, lo, runHi) - else - while(runHi < hi) - last = current - current = fetchElement(L,runHi) - if(call(cmp)(current, last) < 0) - break - ++runHi - - return runHi - lo - - //Returns the minimum acceptable run length for an array of the specified length. - //Natural runs shorter than this will be extended with binarySort - proc/minRunLength(n) - //ASSERT(n >= 0) - var/r = 0 //becomes 1 if any bits are shifted off - while(n >= MIN_MERGE) - r |= (n & 1) - n >>= 1 - return n + r - - //Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished: - // runLen[i-3] > runLen[i-2] + runLen[i-1] - // runLen[i-2] > runLen[i-1] - //This method is called each time a new run is pushed onto the stack. - //So the invariants are guaranteed to hold for i= 2) - var/n = runBases.len - 1 - if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1]) - if(runLens[n-1] < runLens[n+1]) - --n - mergeAt(n) - else if(runLens[n] <= runLens[n+1]) - mergeAt(n) + //[lo, left) elements <= pivot < [right, start) elements + //in other words, find where the pivot element should go using bisection search + while(left < right) + var/mid = (left + right) >> 1 //round((left+right)/2) + if(call(cmp)(fetchElement(L, mid), pivot) > 0) + right = mid else - break //Invariant is established + left = mid + 1 + //ASSERT(left == right) + moveElement(L, start, left) //move pivot element to correct location in the sorted range - //Merges all runs on the stack until only one remains. - //Called only once, to finalise the sort - proc/mergeForceCollapse() - while(runBases.len >= 2) - var/n = runBases.len - 1 - if(n > 1 && runLens[n-1] < runLens[n+1]) +/* +Returns the length of the run beginning at the specified position and reverses the run if it is back-to-front + +A run is the longest ascending sequence with: + a[lo] <= a[lo + 1] <= a[lo + 2] <= ... +or the longest descending sequence with: + a[lo] > a[lo + 1] > a[lo + 2] > ... + +For its intended use in a stable mergesort, the strictness of the +definition of "descending" is needed so that the call can safely +reverse a descending sequence without violating stability. +*/ +/datum/sortInstance/proc/countRunAndMakeAscending(lo, hi) + //ASSERT(lo < hi) + + var/runHi = lo + 1 + if(runHi >= hi) + return 1 + + var/last = fetchElement(L, lo) + var/current = fetchElement(L, runHi++) + + if(call(cmp)(current, last) < 0) + while(runHi < hi) + last = current + current = fetchElement(L,runHi) + if(call(cmp)(current, last) >= 0) + break + ++runHi + reverseRange(L, lo, runHi) + else + while(runHi < hi) + last = current + current = fetchElement(L,runHi) + if(call(cmp)(current, last) < 0) + break + ++runHi + + return runHi - lo + +//Returns the minimum acceptable run length for an array of the specified length. +//Natural runs shorter than this will be extended with binarySort +/datum/sortInstance/proc/minRunLength(n) + //ASSERT(n >= 0) + var/r = 0 //becomes 1 if any bits are shifted off + while(n >= MIN_MERGE) + r |= (n & 1) + n >>= 1 + return n + r + +//Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished: +// runLen[i-3] > runLen[i-2] + runLen[i-1] +// runLen[i-2] > runLen[i-1] +//This method is called each time a new run is pushed onto the stack. +//So the invariants are guaranteed to hold for i= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n - 1] <= runLens[n] + runLens[n + 1]) + if(runLens[n - 1] < runLens[n + 1]) --n mergeAt(n) - - - //Merges the two consecutive runs at stack indices i and i+1 - //Run i must be the penultimate or antepenultimate run on the stack - //In other words, i must be equal to stackSize-2 or stackSize-3 - proc/mergeAt(i) - //ASSERT(runBases.len >= 2) - //ASSERT(i >= 1) - //ASSERT(i == runBases.len - 1 || i == runBases.len - 2) - - var/base1 = runBases[i] - var/base2 = runBases[i+1] - var/len1 = runLens[i] - var/len2 = runLens[i+1] - - //ASSERT(len1 > 0 && len2 > 0) - //ASSERT(base1 + len1 == base2) - - //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run - //(which isn't involved in this merge). The current run (i+1) goes away in any case. - runLens[i] += runLens[i+1] - runLens.Cut(i+1, i+2) - runBases.Cut(i+1, i+2) - - - //Find where the first element of run2 goes in run1. - //Prior elements in run1 can be ignored (because they're already in place) - var/k = gallopRight(fetchElement(L,base2), base1, len1, 0) - //ASSERT(k >= 0) - base1 += k - len1 -= k - if(len1 == 0) - return - - //Find where the last element of run1 goes in run2. - //Subsequent elements in run2 can be ignored (because they're already in place) - len2 = gallopLeft(fetchElement(L,base1 + len1 - 1), base2, len2, len2-1) - //ASSERT(len2 >= 0) - if(len2 == 0) - return - - //Merge remaining runs, using tmp array with min(len1, len2) elements - if(len1 <= len2) - mergeLo(base1, len1, base2, len2) + else if(runLens[n] <= runLens[n + 1]) + mergeAt(n) else - mergeHi(base1, len1, base2, len2) + break //Invariant is established - /* - Locates the position to insert key within the specified sorted range - If the range contains elements equal to key, this will return the index of the LEFTMOST of those elements +//Merges all runs on the stack until only one remains. +//Called only once, to finalise the sort +/datum/sortInstance/proc/mergeForceCollapse() + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n - 1] < runLens[n + 1]) + --n + mergeAt(n) - key the element to be inserted into the sorted range - base the index of the first element of the sorted range - len the length of the sorted range, must be greater than 0 - hint the offset from base at which to begin the search, such that 0 <= hint < len; i.e. base <= hint < base+hint - Returns the index at which to insert element 'key' - */ - proc/gallopLeft(key, base, len, hint) - //ASSERT(len > 0 && hint >= 0 && hint < len) +//Merges the two consecutive runs at stack indices i and i+1 +//Run i must be the penultimate or antepenultimate run on the stack +//In other words, i must be equal to stackSize-2 or stackSize-3 +/datum/sortInstance/proc/mergeAt(i) + //ASSERT(runBases.len >= 2) + //ASSERT(i >= 1) + //ASSERT(i == runBases.len - 1 || i == runBases.len - 2) - var/lastOffset = 0 - var/offset = 1 - if(call(cmp)(key, fetchElement(L,base+hint)) > 0) - var/maxOffset = len - hint - while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) > 0) - lastOffset = offset - offset = (offset << 1) + 1 + var/base1 = runBases[i] + var/base2 = runBases[i + 1] + var/len1 = runLens[i] + var/len2 = runLens[i + 1] - if(offset > maxOffset) - offset = maxOffset + //ASSERT(len1 > 0 && len2 > 0) + //ASSERT(base1 + len1 == base2) - lastOffset += hint - offset += hint + //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run + //(which isn't involved in this merge). The current run (i+1) goes away in any case. + runLens[i] += runLens[i + 1] + runLens.Cut(i + 1, i + 2) + runBases.Cut(i + 1, i + 2) - else - var/maxOffset = hint + 1 - while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) <= 0) - lastOffset = offset - offset = (offset << 1) + 1 - if(offset > maxOffset) - offset = maxOffset + //Find where the first element of run2 goes in run1. + //Prior elements in run1 can be ignored (because they're already in place) + var/k = gallopRight(fetchElement(L, base2), base1, len1, 0) + //ASSERT(k >= 0) + base1 += k + len1 -= k + if(len1 == 0) + return - var/temp = lastOffset - lastOffset = hint - offset - offset = hint - temp + //Find where the last element of run1 goes in run2. + //Subsequent elements in run2 can be ignored (because they're already in place) + len2 = gallopLeft(fetchElement(L, base1 + len1 - 1), base2, len2, len2 - 1) + //ASSERT(len2 >= 0) + if(len2 == 0) + return - //ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len) + //Merge remaining runs, using tmp array with min(len1, len2) elements + if(len1 <= len2) + mergeLo(base1, len1, base2, len2) + else + mergeHi(base1, len1, base2, len2) - //Now L[base+lastOffset] < key <= L[base+offset], so key belongs somewhere to the right of lastOffset but no farther than - //offset. Do a binary search with invariant L[base+lastOffset-1] < key <= L[base+offset] - ++lastOffset - while(lastOffset < offset) - var/m = lastOffset + ((offset - lastOffset) >> 1) - if(call(cmp)(key, fetchElement(L,base+m)) > 0) - lastOffset = m + 1 - else - offset = m +/* + Locates the position to insert key within the specified sorted range + If the range contains elements equal to key, this will return the index of the LEFTMOST of those elements - //ASSERT(lastOffset == offset) - return offset + key the element to be inserted into the sorted range + base the index of the first element of the sorted range + len the length of the sorted range, must be greater than 0 + hint the offset from base at which to begin the search, such that 0 <= hint < len; i.e. base <= hint < base+hint - /** - * Like gallopLeft, except that if the range contains an element equal to - * key, gallopRight returns the index after the rightmost equal element. - * - * @param key the key whose insertion point to search for - * @param a the array in which to search - * @param base the index of the first element in the range - * @param len the length of the range; must be > 0 - * @param hint the index at which to begin the search, 0 <= hint < n. - * The closer hint is to the result, the faster this method will run. - * @param c the comparator used to order the range, and to search - * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] - */ - proc/gallopRight(key, base, len, hint) - //ASSERT(len > 0 && hint >= 0 && hint < len) + Returns the index at which to insert element 'key' +*/ +/datum/sortInstance/proc/gallopLeft(key, base, len, hint) + //ASSERT(len > 0 && hint >= 0 && hint < len) - var/offset = 1 - var/lastOffset = 0 - if(call(cmp)(key, fetchElement(L,base+hint)) < 0) //key <= L[base+hint] - var/maxOffset = hint + 1 //therefore we want to insert somewhere in the range [base,base+hint] = [base+,base+(hint+1)) - while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) < 0) //we are iterating backwards - lastOffset = offset - offset = (offset << 1) + 1 //1 3 7 15 + var/lastOffset = 0 + var/offset = 1 + if(call(cmp)(key, fetchElement(L,base + hint)) > 0) + var/maxOffset = len - hint + while(offset < maxOffset && call(cmp)(key, fetchElement(L, base + hint + offset)) > 0) + lastOffset = offset + offset = (offset << 1) + 1 - if(offset > maxOffset) - offset = maxOffset + if(offset > maxOffset) + offset = maxOffset - var/temp = lastOffset - lastOffset = hint - offset - offset = hint - temp + lastOffset += hint + offset += hint - else //key > L[base+hint] - var/maxOffset = len - hint //therefore we want to insert somewhere in the range (base+hint,base+len) = [base+hint+1, base+hint+(len-hint)) - while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) >= 0) - lastOffset = offset - offset = (offset << 1) + 1 + else + var/maxOffset = hint + 1 + while(offset < maxOffset && call(cmp)(key, fetchElement(L, base + hint - offset)) <= 0) + lastOffset = offset + offset = (offset << 1) + 1 - if(offset > maxOffset) - offset = maxOffset + if(offset > maxOffset) + offset = maxOffset - lastOffset += hint - offset += hint + var/temp = lastOffset + lastOffset = hint - offset + offset = hint - temp //ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len) - ++lastOffset - while(lastOffset < offset) - var/m = lastOffset + ((offset - lastOffset) >> 1) + //Now L[base+lastOffset] < key <= L[base+offset], so key belongs somewhere to the right of lastOffset but no farther than + //offset. Do a binary search with invariant L[base+lastOffset-1] < key <= L[base+offset] + ++lastOffset + while(lastOffset < offset) + var/m = lastOffset + ((offset - lastOffset) >> 1) - if(call(cmp)(key, fetchElement(L,base+m)) < 0) //key <= L[base+m] - offset = m - else //key > L[base+m] - lastOffset = m + 1 + if(call(cmp)(key, fetchElement(L,base + m)) > 0) + lastOffset = m + 1 + else + offset = m - //ASSERT(lastOffset == offset) + //ASSERT(lastOffset == offset) + return offset - return offset +/** + * Like gallopLeft, except that if the range contains an element equal to + * key, gallopRight returns the index after the rightmost equal element. + * + * @param key the key whose insertion point to search for + * @param a the array in which to search + * @param base the index of the first element in the range + * @param len the length of the range; must be > 0 + * @param hint the index at which to begin the search, 0 <= hint < n. + * The closer hint is to the result, the faster this method will run. + * @param c the comparator used to order the range, and to search + * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] + */ +/datum/sortInstance/proc/gallopRight(key, base, len, hint) + //ASSERT(len > 0 && hint >= 0 && hint < len) + + var/offset = 1 + var/lastOffset = 0 + if(call(cmp)(key, fetchElement(L, base + hint)) < 0) //key <= L[base+hint] + var/maxOffset = hint + 1 //therefore we want to insert somewhere in the range [base,base+hint] = [base+,base+(hint+1)) + while(offset < maxOffset && call(cmp)(key, fetchElement(L, base + hint - offset)) < 0) //we are iterating backwards + lastOffset = offset + offset = (offset << 1) + 1 //1 3 7 15 + + if(offset > maxOffset) + offset = maxOffset + + var/temp = lastOffset + lastOffset = hint - offset + offset = hint - temp + + else //key > L[base+hint] + var/maxOffset = len - hint //therefore we want to insert somewhere in the range (base+hint,base+len) = [base+hint+1, base+hint+(len-hint)) + while(offset < maxOffset && call(cmp)(key, fetchElement(L, base + hint + offset)) >= 0) + lastOffset = offset + offset = (offset << 1) + 1 + + if(offset > maxOffset) + offset = maxOffset + + lastOffset += hint + offset += hint + + //ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len) + + ++lastOffset + while(lastOffset < offset) + var/m = lastOffset + ((offset - lastOffset) >> 1) + + if(call(cmp)(key, fetchElement(L, base + m)) < 0) //key <= L[base+m] + offset = m + else //key > L[base+m] + lastOffset = m + 1 + + //ASSERT(lastOffset == offset) + + return offset - //Merges two adjacent runs in-place in a stable fashion. - //For performance this method should only be called when len1 <= len2! - proc/mergeLo(base1, len1, base2, len2) - //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) +//Merges two adjacent runs in-place in a stable fashion. +//For performance this method should only be called when len1 <= len2! +/datum/sortInstance/proc/mergeLo(base1, len1, base2, len2) + //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) - var/cursor1 = base1 - var/cursor2 = base2 + var/cursor1 = base1 + var/cursor2 = base2 - //degenerate cases - if(len2 == 1) - moveElement(L, cursor2, cursor1) - return + //degenerate cases + if(len2 == 1) + moveElement(L, cursor2, cursor1) + return - if(len1 == 1) - moveElement(L, cursor1, cursor2+len2) - return + if(len1 == 1) + moveElement(L, cursor1, cursor2 + len2) + return - //Move first element of second run - moveElement(L, cursor2++, cursor1++) - --len2 + //Move first element of second run + moveElement(L, cursor2++, cursor1++) + --len2 - outer: - while(1) - var/count1 = 0 //# of times in a row that first run won - var/count2 = 0 // " " " " " " second run won + outer: + while(1) + var/count1 = 0 //# of times in a row that first run won + var/count2 = 0 // " " " " " " second run won - //do the straightfoward thin until one run starts winning consistently + //do the straightfoward thin until one run starts winning consistently - do - //ASSERT(len1 > 1 && len2 > 0) - if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0) - moveElement(L, cursor2++, cursor1++) - --len2 + do + //ASSERT(len1 > 1 && len2 > 0) + if(call(cmp)(fetchElement(L, cursor2), fetchElement(L, cursor1)) < 0) + moveElement(L, cursor2++, cursor1++) + --len2 - ++count2 - count1 = 0 + ++count2 + count1 = 0 - if(len2 == 0) - break outer - else - ++cursor1 - - ++count1 - count2 = 0 - - if(--len1 == 1) - break outer - - while((count1 | count2) < minGallop) - - - //one run is winning consistently so galloping may provide huge benifits - //so try galloping, until such time as the run is no longer consistently winning - do - //ASSERT(len1 > 1 && len2 > 0) - - count1 = gallopRight(fetchElement(L,cursor2), cursor1, len1, 0) - if(count1) - cursor1 += count1 - len1 -= count1 - - if(len1 <= 1) - break outer - - moveElement(L, cursor2, cursor1) - ++cursor2 - ++cursor1 - if(--len2 == 0) + if(len2 == 0) break outer - - count2 = gallopLeft(fetchElement(L,cursor1), cursor2, len2, 0) - if(count2) - moveRange(L, cursor2, cursor1, count2) - - cursor2 += count2 - cursor1 += count2 - len2 -= count2 - - if(len2 == 0) - break outer - + else ++cursor1 + + ++count1 + count2 = 0 + if(--len1 == 1) break outer - --minGallop - - while((count1|count2) > MIN_GALLOP) - - if(minGallop < 0) - minGallop = 0 - minGallop += 2; // Penalize for leaving gallop mode + while((count1 | count2) < minGallop) - if(len1 == 1) - //ASSERT(len2 > 0) - moveElement(L, cursor1, cursor2+len2) + //one run is winning consistently so galloping may provide huge benifits + //so try galloping, until such time as the run is no longer consistently winning + do + //ASSERT(len1 > 1 && len2 > 0) - //else - //ASSERT(len2 == 0) - //ASSERT(len1 > 1) + count1 = gallopRight(fetchElement(L, cursor2), cursor1, len1, 0) + if(count1) + cursor1 += count1 + len1 -= count1 - - proc/mergeHi(base1, len1, base2, len2) - //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) - - var/cursor1 = base1 + len1 - 1 //start at end of sublists - var/cursor2 = base2 + len2 - 1 - - //degenerate cases - if(len2 == 1) - moveElement(L, base2, base1) - return - - if(len1 == 1) - moveElement(L, base1, cursor2+1) - return - - moveElement(L, cursor1--, cursor2-- + 1) - --len1 - - outer: - while(1) - var/count1 = 0 //# of times in a row that first run won - var/count2 = 0 // " " " " " " second run won - - //do the straightfoward thing until one run starts winning consistently - do - //ASSERT(len1 > 0 && len2 > 1) - if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0) - moveElement(L, cursor1--, cursor2-- + 1) - --len1 - - ++count1 - count2 = 0 - - if(len1 == 0) - break outer - else - --cursor2 - --len2 - - ++count2 - count1 = 0 - - if(len2 == 1) - break outer - while((count1 | count2) < minGallop) - - //one run is winning consistently so galloping may provide huge benifits - //so try galloping, until such time as the run is no longer consistently winning - do - //ASSERT(len1 > 0 && len2 > 1) - - count1 = len1 - gallopRight(fetchElement(L,cursor2), base1, len1, len1-1) //should cursor1 be base1? - if(count1) - cursor1 -= count1 - - moveRange(L, cursor1+1, cursor2+1, count1) //cursor1+1 == cursor2 by definition - - cursor2 -= count1 - len1 -= count1 - - if(len1 == 0) - break outer - - --cursor2 - - if(--len2 == 1) + if(len1 <= 1) break outer - count2 = len2 - gallopLeft(fetchElement(L,cursor1), cursor1+1, len2, len2-1) - if(count2) - cursor2 -= count2 - len2 -= count2 + moveElement(L, cursor2, cursor1) + ++cursor2 + ++cursor1 + if(--len2 == 0) + break outer - if(len2 <= 1) - break outer + count2 = gallopLeft(fetchElement(L, cursor1), cursor2, len2, 0) + if(count2) + moveRange(L, cursor2, cursor1, count2) + cursor2 += count2 + cursor1 += count2 + len2 -= count2 + + if(len2 == 0) + break outer + + ++cursor1 + if(--len1 == 1) + break outer + + --minGallop + + while((count1|count2) > MIN_GALLOP) + + if(minGallop < 0) + minGallop = 0 + minGallop += 2; // Penalize for leaving gallop mode + + + if(len1 == 1) + //ASSERT(len2 > 0) + moveElement(L, cursor1, cursor2 + len2) + + //else + //ASSERT(len2 == 0) + //ASSERT(len1 > 1) + + +/datum/sortInstance/proc/mergeHi(base1, len1, base2, len2) + //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) + + var/cursor1 = base1 + len1 - 1 //start at end of sublists + var/cursor2 = base2 + len2 - 1 + + //degenerate cases + if(len2 == 1) + moveElement(L, base2, base1) + return + + if(len1 == 1) + moveElement(L, base1, cursor2 + 1) + return + + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + outer: + while(1) + var/count1 = 0 //# of times in a row that first run won + var/count2 = 0 // " " " " " " second run won + + //do the straightfoward thing until one run starts winning consistently + do + //ASSERT(len1 > 0 && len2 > 1) + if(call(cmp)(fetchElement(L, cursor2), fetchElement(L, cursor1)) < 0) moveElement(L, cursor1--, cursor2-- + 1) --len1 + ++count1 + count2 = 0 + + if(len1 == 0) + break outer + else + --cursor2 + --len2 + + ++count2 + count1 = 0 + + if(len2 == 1) + break outer + while((count1 | count2) < minGallop) + + //one run is winning consistently so galloping may provide huge benifits + //so try galloping, until such time as the run is no longer consistently winning + do + //ASSERT(len1 > 0 && len2 > 1) + + count1 = len1 - gallopRight(fetchElement(L, cursor2), base1, len1, len1 - 1) //should cursor1 be base1? + if(count1) + cursor1 -= count1 + + moveRange(L, cursor1 + 1, cursor2 + 1, count1) //cursor1+1 == cursor2 by definition + + cursor2 -= count1 + len1 -= count1 + if(len1 == 0) break outer - --minGallop - while((count1|count2) > MIN_GALLOP) + --cursor2 - if(minGallop < 0) - minGallop = 0 - minGallop += 2 // Penalize for leaving gallop mode + if(--len2 == 1) + break outer - if(len2 == 1) - //ASSERT(len1 > 0) + count2 = len2 - gallopLeft(fetchElement(L, cursor1), cursor1 + 1, len2, len2 - 1) + if(count2) + cursor2 -= count2 + len2 -= count2 - cursor1 -= len1 - moveRange(L, cursor1+1, cursor2+1, len1) + if(len2 <= 1) + break outer - //else - //ASSERT(len1 == 0) - //ASSERT(len2 > 0) + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + if(len1 == 0) + break outer + + --minGallop + while((count1|count2) > MIN_GALLOP) + + if(minGallop < 0) + minGallop = 0 + minGallop += 2 // Penalize for leaving gallop mode + + if(len2 == 1) + //ASSERT(len1 > 0) + + cursor1 -= len1 + moveRange(L, cursor1 + 1, cursor2 + 1, len1) + + //else + //ASSERT(len1 == 0) + //ASSERT(len2 > 0) - proc/mergeSort(start, end) - var/remaining = end - start +/datum/sortInstance/proc/mergeSort(start, end) + var/remaining = end - start - //If array is small, do an insertion sort - if(remaining < MIN_MERGE) - binarySort(start, end, start/*+initRunLen*/) - return + //If array is small, do an insertion sort + if(remaining < MIN_MERGE) + binarySort(start, end, start/*+initRunLen*/) + return - var/minRun = minRunLength(remaining) + var/minRun = minRunLength(remaining) - do - var/runLen = (remaining <= minRun) ? remaining : minRun + do + var/runLen = (remaining <= minRun) ? remaining : minRun - binarySort(start, start+runLen, start) + binarySort(start, start + runLen, start) - //add data about run to queue - runBases.Add(start) - runLens.Add(runLen) + //add data about run to queue + runBases.Add(start) + runLens.Add(runLen) - //Advance to find next run - start += runLen - remaining -= runLen + //Advance to find next run + start += runLen + remaining -= runLen - while(remaining > 0) + while(remaining > 0) - while(runBases.len >= 2) - var/n = runBases.len - 1 - if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1]) - if(runLens[n-1] < runLens[n+1]) - --n - mergeAt2(n) - else if(runLens[n] <= runLens[n+1]) - mergeAt2(n) - else - break //Invariant is established - - while(runBases.len >= 2) - var/n = runBases.len - 1 - if(n > 1 && runLens[n-1] < runLens[n+1]) + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n - 1] <= runLens[n] + runLens[n + 1]) + if(runLens[n - 1] < runLens[n + 1]) --n mergeAt2(n) + else if(runLens[n] <= runLens[n + 1]) + mergeAt2(n) + else + break //Invariant is established - return L + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n - 1] < runLens[n + 1]) + --n + mergeAt2(n) - proc/mergeAt2(i) - var/cursor1 = runBases[i] - var/cursor2 = runBases[i+1] + return L - var/end1 = cursor1+runLens[i] - var/end2 = cursor2+runLens[i+1] +/datum/sortInstance/proc/mergeAt2(i) + var/cursor1 = runBases[i] + var/cursor2 = runBases[i + 1] - var/val1 = fetchElement(L,cursor1) - var/val2 = fetchElement(L,cursor2) + var/end1 = cursor1+runLens[i] + var/end2 = cursor2+runLens[i + 1] - while(1) - if(call(cmp)(val1,val2) <= 0) - if(++cursor1 >= end1) - break - val1 = fetchElement(L,cursor1) - else - moveElement(L,cursor2,cursor1) + var/val1 = fetchElement(L, cursor1) + var/val2 = fetchElement(L, cursor2) - if(++cursor2 >= end2) - break - ++end1 - ++cursor1 + while(1) + if(call(cmp)(val1, val2) <= 0) + if(++cursor1 >= end1) + break + val1 = fetchElement(L, cursor1) + else + moveElement(L, cursor2, cursor1) - val2 = fetchElement(L,cursor2) + if(++cursor2 >= end2) + break + ++end1 + ++cursor1 + + val2 = fetchElement(L, cursor2) - //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run - //(which isn't involved in this merge). The current run (i+1) goes away in any case. - runLens[i] += runLens[i+1] - runLens.Cut(i+1, i+2) - runBases.Cut(i+1, i+2) + //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run + //(which isn't involved in this merge). The current run (i+1) goes away in any case. + runLens[i] += runLens[i + 1] + runLens.Cut(i + 1, i + 2) + runBases.Cut(i + 1, i + 2) #undef MIN_GALLOP #undef MIN_MERGE diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index 0fceedac3b0..6d28c2c0cd4 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -170,6 +170,6 @@ proc/isDay(var/month, var/day) GLOBAL_VAR_INIT(midnight_rollovers, 0) GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) /proc/update_midnight_rollover() - if (world.timeofday < GLOB.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! + if(world.timeofday < GLOB.rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! return GLOB.midnight_rollovers++ return GLOB.midnight_rollovers \ No newline at end of file diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index c76e04fc9d8..88fdb83da10 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -197,7 +197,7 @@ var/high_pop_mc_tick_rate = 1.1 var/high_pop_mc_mode_amount = 65 - var/disable_high_pop_mc_mode_amount = 60 + var/disable_high_pop_mc_mode_amount = 60 /datum/configuration/New() var/list/L = subtypesof(/datum/game_mode) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 7ff5743294f..4434053e4ec 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -144,7 +144,7 @@ SUBSYSTEM_DEF(air) currentpart = SSAIR_PIPENETS /datum/controller/subsystem/air/proc/process_deferred_pipenets(resumed = 0) - if (!resumed) + if(!resumed) src.currentrun = deferred_pipenet_rebuilds.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun @@ -159,7 +159,7 @@ SUBSYSTEM_DEF(air) return /datum/controller/subsystem/air/proc/process_pipenets(resumed = 0) - if (!resumed) + if(!resumed) src.currentrun = networks.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun @@ -175,7 +175,7 @@ SUBSYSTEM_DEF(air) /datum/controller/subsystem/air/proc/process_atmos_machinery(resumed = 0) var/seconds = wait * 0.1 - if (!resumed) + if(!resumed) src.currentrun = atmos_machinery.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun @@ -188,7 +188,7 @@ SUBSYSTEM_DEF(air) return /datum/controller/subsystem/air/proc/process_super_conductivity(resumed = 0) - if (!resumed) + if(!resumed) src.currentrun = active_super_conductivity.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun @@ -200,14 +200,14 @@ SUBSYSTEM_DEF(air) return /datum/controller/subsystem/air/proc/process_hotspots(resumed = 0) - if (!resumed) + if(!resumed) src.currentrun = hotspots.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun while(currentrun.len) var/obj/effect/hotspot/H = currentrun[currentrun.len] currentrun.len-- - if (H) + if(H) H.process() else hotspots -= H @@ -215,7 +215,7 @@ SUBSYSTEM_DEF(air) return /datum/controller/subsystem/air/proc/process_high_pressure_delta(resumed = 0) - while (high_pressure_delta.len) + while(high_pressure_delta.len) var/turf/simulated/T = high_pressure_delta[high_pressure_delta.len] high_pressure_delta.len-- T.high_pressure_movements() @@ -226,20 +226,20 @@ SUBSYSTEM_DEF(air) /datum/controller/subsystem/air/proc/process_active_turfs(resumed = 0) //cache for sanic speed var/fire_count = times_fired - if (!resumed) + if(!resumed) src.currentrun = active_turfs.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun while(currentrun.len) var/turf/simulated/T = currentrun[currentrun.len] currentrun.len-- - if (T) + if(T) T.process_cell(fire_count) - if (MC_TICK_CHECK) + if(MC_TICK_CHECK) return /datum/controller/subsystem/air/proc/process_excited_groups(resumed = 0) - if (!resumed) + if(!resumed) src.currentrun = excited_groups.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun @@ -251,7 +251,7 @@ SUBSYSTEM_DEF(air) EG.self_breakdown() else if(EG.breakdown_cooldown >= 20) EG.dismantle() - if (MC_TICK_CHECK) + if(MC_TICK_CHECK) return /datum/controller/subsystem/air/proc/remove_from_active(turf/simulated/T) @@ -294,24 +294,24 @@ SUBSYSTEM_DEF(air) /turf/simulated/proc/resolve_active_graph() . = list() var/datum/excited_group/EG = excited_group - if (blocks_air || !air) + if(blocks_air || !air) return - if (!EG) + if(!EG) EG = new EG.add_turf(src) - for (var/turf/simulated/ET in atmos_adjacent_turfs) - if ( ET.blocks_air || !ET.air) + for(var/turf/simulated/ET in atmos_adjacent_turfs) + if(ET.blocks_air || !ET.air) continue var/ET_EG = ET.excited_group - if (ET_EG) - if (ET_EG != EG) + if(ET_EG) + if(ET_EG != EG) EG.merge_groups(ET_EG) EG = excited_group //merge_groups() may decide to replace our current EG else EG.add_turf(ET) - if (!ET.excited) + if(!ET.excited) ET.excited = 1 . += ET @@ -358,7 +358,7 @@ SUBSYSTEM_DEF(air) icemaster = new /obj/effect/overlay() icemaster.icon = 'icons/turf/overlays.dmi' icemaster.icon_state = "snowfloor" - icemaster.layer = TURF_LAYER+0.1 + icemaster.layer = TURF_LAYER + 0.1 icemaster.mouse_opacity = 0 #undef SSAIR_PIPENETS diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm index c251492227a..fcc62a2fa50 100644 --- a/code/controllers/subsystem/spacedrift.dm +++ b/code/controllers/subsystem/spacedrift.dm @@ -13,30 +13,30 @@ SUBSYSTEM_DEF(spacedrift) /datum/controller/subsystem/spacedrift/fire(resumed = 0) - if (!resumed) + if(!resumed) src.currentrun = processing.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun - while (currentrun.len) + while(currentrun.len) var/atom/movable/AM = currentrun[currentrun.len] currentrun.len-- - if (!AM) + if(!AM) processing -= AM if (MC_TICK_CHECK) return continue - if (AM.inertia_next_move > world.time) + if(AM.inertia_next_move > world.time) if (MC_TICK_CHECK) return continue - if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0)) + if(!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0)) AM.inertia_dir = 0 - if (!AM.inertia_dir) + if(!AM.inertia_dir) AM.inertia_last_loc = null processing -= AM if (MC_TICK_CHECK) @@ -49,11 +49,11 @@ SUBSYSTEM_DEF(spacedrift) step(AM, AM.inertia_dir) AM.inertia_moving = FALSE AM.inertia_next_move = world.time + AM.inertia_move_delay - if (AM.loc == old_loc) + if(AM.loc == old_loc) AM.inertia_dir = 0 AM.setDir(old_dir) AM.inertia_last_loc = AM.loc - if (MC_TICK_CHECK) + if(MC_TICK_CHECK) return diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm index c32b5a707ae..b74e3a1a6c9 100644 --- a/code/controllers/subsystem/throwing.dm +++ b/code/controllers/subsystem/throwing.dm @@ -14,9 +14,8 @@ SUBSYSTEM_DEF(throwing) /datum/controller/subsystem/throwing/stat_entry() ..("P:[processing.len]") - /datum/controller/subsystem/throwing/fire(resumed = 0) - if (!resumed) + if(!resumed) src.currentrun = processing.Copy() //cache for sanic speed (lists are references anyways) @@ -26,15 +25,15 @@ SUBSYSTEM_DEF(throwing) var/atom/movable/AM = currentrun[currentrun.len] var/datum/thrownthing/TT = currentrun[AM] currentrun.len-- - if (!AM || !TT) + if(!AM || !TT) processing -= AM - if (MC_TICK_CHECK) + if(MC_TICK_CHECK) return continue TT.tick() - if (MC_TICK_CHECK) + if(MC_TICK_CHECK) return currentrun = null @@ -63,7 +62,7 @@ SUBSYSTEM_DEF(throwing) /datum/thrownthing/proc/tick() var/atom/movable/AM = thrownthing - if (!isturf(AM.loc) || !AM.throwing) + if(!isturf(AM.loc) || !AM.throwing) finalize() return @@ -71,7 +70,7 @@ SUBSYSTEM_DEF(throwing) delayed_time += world.time - last_move return - if (dist_travelled && hitcheck()) //to catch sneaky things moving on our tile while we slept + if(dist_travelled && hitcheck()) //to catch sneaky things moving on our tile while we slept finalize() return @@ -80,51 +79,51 @@ SUBSYSTEM_DEF(throwing) last_move = world.time //calculate how many tiles to move, making up for any missed ticks. - var/tilestomove = CEILING(min(((((world.time+world.tick_lag) - start_time + delayed_time) * speed) - (dist_travelled ? dist_travelled : -1)), speed*MAX_TICKS_TO_MAKE_UP) * (world.tick_lag * SSthrowing.wait), 1) - while (tilestomove-- > 0) - if ((dist_travelled >= maxrange || AM.loc == target_turf) && has_gravity(AM, AM.loc)) + var/tilestomove = CEILING(min(((((world.time + world.tick_lag) - start_time + delayed_time) * speed) - (dist_travelled ? dist_travelled : -1)), speed * MAX_TICKS_TO_MAKE_UP) * (world.tick_lag * SSthrowing.wait), 1) + while(tilestomove-- > 0) + if((dist_travelled >= maxrange || AM.loc == target_turf) && has_gravity(AM, AM.loc)) finalize() return - if (dist_travelled <= max(dist_x, dist_y)) //if we haven't reached the target yet we home in on it, otherwise we use the initial direction + if(dist_travelled <= max(dist_x, dist_y)) //if we haven't reached the target yet we home in on it, otherwise we use the initial direction step = get_step(AM, get_dir(AM, target_turf)) else step = get_step(AM, init_dir) - if (!pure_diagonal && !diagonals_first) // not a purely diagonal trajectory and we don't want all diagonal moves to be done first - if (diagonal_error >= 0 && max(dist_x,dist_y) - dist_travelled != 1) //we do a step forward unless we're right before the target + if(!pure_diagonal && !diagonals_first) // not a purely diagonal trajectory and we don't want all diagonal moves to be done first + if (diagonal_error >= 0 && max(dist_x, dist_y) - dist_travelled != 1) //we do a step forward unless we're right before the target step = get_step(AM, dx) - diagonal_error += (diagonal_error < 0) ? dist_x/2 : -dist_y + diagonal_error += (diagonal_error < 0) ? dist_x / 2 : -dist_y - if (!step) // going off the edge of the map makes get_step return null, don't let things go off the edge + if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge finalize() return AM.Move(step, get_dir(AM, step)) - if (!AM.throwing) // we hit something during our move + if(!AM.throwing) // we hit something during our move finalize(hit = TRUE) return dist_travelled++ - if (dist_travelled > MAX_THROWING_DIST) + if(dist_travelled > MAX_THROWING_DIST) finalize() return -/datum/thrownthing/proc/finalize(hit = FALSE, target=null) +/datum/thrownthing/proc/finalize(hit = FALSE, target = null) set waitfor = 0 SSthrowing.processing -= thrownthing //done throwing, either because it hit something or it finished moving thrownthing.throwing = null - if (!hit) - for (var/thing in get_turf(thrownthing)) //looking for our target on the turf we land on. + if(!hit) + for(var/thing in get_turf(thrownthing)) //looking for our target on the turf we land on. var/atom/A = thing - if (A == target) + if(A == target) hit = 1 thrownthing.throw_impact(A, src) break - if (!hit) + if(!hit) thrownthing.throw_impact(get_turf(thrownthing), src) // we haven't hit something yet and we still must, let's hit the ground. thrownthing.newtonian_move(init_dir) else @@ -133,17 +132,17 @@ SUBSYSTEM_DEF(throwing) if(target) thrownthing.throw_impact(target, src) - if (callback) + if(callback) callback.Invoke() /datum/thrownthing/proc/hit_atom(atom/A) - finalize(hit=TRUE, target=A) + finalize(hit = TRUE, target = A) /datum/thrownthing/proc/hitcheck() - for (var/thing in get_turf(thrownthing)) + for(var/thing in get_turf(thrownthing)) var/atom/movable/AM = thing - if (AM == thrownthing) + if(AM == thrownthing) continue if(AM.density && !(AM.pass_flags & LETPASSTHROW) && !(AM.flags & ON_BORDER)) - finalize(hit=TRUE, target=AM) + finalize(hit = TRUE, target = AM) return TRUE From 47f1e2c1e4764d9689cc57f8f8b127dfd48bb7c4 Mon Sep 17 00:00:00 2001 From: tigercat2000 Date: Thu, 22 Mar 2018 15:39:41 -0700 Subject: [PATCH 6/7] Address Fox's concerns --- code/__DEFINES/subsystems.dm | 14 ++++++++------ code/controllers/failsafe.dm | 2 +- code/controllers/master.dm | 2 +- code/modules/ext_scripts/python.dm | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 35f837e97fd..180007fa1b8 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -54,8 +54,8 @@ #define INIT_ORDER_TRAITS 11 #define INIT_ORDER_TICKER 10 #define INIT_ORDER_MAPPING 9 -#define INIT_ORDER_ATOMS 8 -#define INIT_ORDER_NETWORKS 7 +#define INIT_ORDER_NETWORKS 8 +#define INIT_ORDER_ATOMS 7 #define INIT_ORDER_LANGUAGE 6 #define INIT_ORDER_MACHINES 5 #define INIT_ORDER_CIRCUIT 4 @@ -79,30 +79,32 @@ #define FIRE_PRIORITY_IDLE_NPC 10 #define FIRE_PRIORITY_SERVER_MAINT 10 +#define FIRE_PRIORITY_RESEARCH 10 #define FIRE_PRIORITY_GARBAGE 15 -#define FIRE_PRIORITY_RESEARCH 15 +#define FIRE_PRIORITY_WET_FLOORS 20 #define FIRE_PRIORITY_AIR 20 #define FIRE_PRIORITY_NPC 20 #define FIRE_PRIORITY_PROCESS 25 #define FIRE_PRIORITY_THROWING 25 -#define FIRE_PRIORITY_FLIGHTPACKS 30 #define FIRE_PRIORITY_SPACEDRIFT 30 +#define FIRE_PRIORITY_FIELDS 30 #define FIRE_PRIOTITY_SMOOTHING 35 #define FIRE_PRIORITY_ORBIT 35 +#define FIRE_PRIORITY_NETWORKS 40 #define FIRE_PRIORITY_OBJ 40 -#define FIRE_PRIORUTY_FIELDS 40 #define FIRE_PRIORITY_ACID 40 #define FIRE_PRIOTITY_BURNING 40 #define FIRE_PRIORITY_INBOUNDS 40 #define FIRE_PRIORITY_DEFAULT 50 #define FIRE_PRIORITY_PARALLAX 65 -#define FIRE_PRIORITY_NETWORKS 80 +#define FIRE_PRIORITY_FLIGHTPACKS 80 #define FIRE_PRIORITY_MOBS 100 #define FIRE_PRIORITY_TGUI 110 #define FIRE_PRIORITY_TICKER 200 #define FIRE_PRIORITY_OVERLAYS 500 #define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. + // SS runlevels #define RUNLEVEL_INIT 0 diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index b3a47e3ce22..78303449f26 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -1,4 +1,4 @@ -var/global/datum/controller/failsafe/Failsafe +GLOBAL_REAL(Failsafe, /datum/controller/failsafe) /datum/controller/failsafe // This thing pretty much just keeps poking the master controller diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 8aa1f1b0873..28395717f08 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -405,7 +405,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(SS_flags & SS_NO_FIRE) subsystemstocheck -= SS continue - if(!(SS_flags & SS_TICKER) && (SS_flags & SS_KEEP_TIMING) && SS.last_fire + (SS.wait * 0.75) > world.time) + if((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time) continue SS.enqueue() . = 1 diff --git a/code/modules/ext_scripts/python.dm b/code/modules/ext_scripts/python.dm index 6781af866e3..1f761542a35 100644 --- a/code/modules/ext_scripts/python.dm +++ b/code/modules/ext_scripts/python.dm @@ -1,4 +1,5 @@ /proc/ext_python(var/script, var/args, var/scriptsprefix = 1) + to_chat(world, "ext python [script] [args]") if(scriptsprefix) script = "scripts/" + script if(world.system_type == MS_WINDOWS) From c4449fea397ebb9fde1726ab0e110ace750bf7c1 Mon Sep 17 00:00:00 2001 From: tigercat2000 Date: Thu, 22 Mar 2018 15:41:33 -0700 Subject: [PATCH 7/7] Whoops --- code/modules/ext_scripts/python.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/modules/ext_scripts/python.dm b/code/modules/ext_scripts/python.dm index 1f761542a35..6781af866e3 100644 --- a/code/modules/ext_scripts/python.dm +++ b/code/modules/ext_scripts/python.dm @@ -1,5 +1,4 @@ /proc/ext_python(var/script, var/args, var/scriptsprefix = 1) - to_chat(world, "ext python [script] [args]") if(scriptsprefix) script = "scripts/" + script if(world.system_type == MS_WINDOWS)