From 9d6a141e5b672650743c347d9eda782daed18acf Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:41:25 -0800 Subject: [PATCH] mc update: init stages, better subsystem init tracking, etc (#6857) modifies the hollow metrics API; that'll probably be implemented soon adds init stages changes SSatoms to not abuse its `initialized` variable closes #6858 --- citadel.dme | 20 +- code/__DEFINES/MC.dm | 118 ---- .../__DEFINES/controllers/_master-runlevel.dm | 32 + code/__DEFINES/controllers/_master.dm | 59 ++ .../{_repositories.dm => _repository.dm} | 0 code/__DEFINES/controllers/_subsystem-init.dm | 92 +++ .../controllers/_subsystem-priority.dm | 68 +++ code/__DEFINES/controllers/_subsystem.dm | 164 ++++++ code/__DEFINES/controllers/_subsystems.dm | 237 -------- code/__DEFINES/controllers/atoms.dm | 37 ++ code/__DEFINES/controllers/timer.dm | 10 + code/__DEFINES/metrics.dm | 6 + code/__HELPERS/sorts/comparators.dm | 19 - code/___compile_options.dm | 8 - code/controllers/failsafe.dm | 4 + code/controllers/master.dm | 546 +++++++++++------- code/controllers/subsystem.dm | 256 +++++--- .../subsystem/__test_bad_subsystem_sleeps.dm | 44 ++ code/controllers/subsystem/ai_holders.dm | 3 +- code/controllers/subsystem/ai_movement.dm | 11 +- code/controllers/subsystem/ai_scheduling.dm | 8 +- code/controllers/subsystem/air.dm | 17 +- code/controllers/subsystem/alarm.dm | 2 +- .../controllers/subsystem/ambient_lighting.dm | 2 +- code/controllers/subsystem/ao.dm | 2 +- code/controllers/subsystem/assets.dm | 6 +- code/controllers/subsystem/atoms.dm | 47 +- code/controllers/subsystem/automata.dm | 2 +- .../subsystem/characters/_characters.dm | 2 +- code/controllers/subsystem/chat.dm | 3 +- code/controllers/subsystem/chemistry.dm | 1 + code/controllers/subsystem/dbcore/_dbcore.dm | 3 +- code/controllers/subsystem/early_init.dm | 3 +- .../subsystem/emergency_shuttle.dm | 1 + code/controllers/subsystem/events.dm | 2 +- code/controllers/subsystem/fail2topic.dm | 3 +- code/controllers/subsystem/game_master.dm | 2 +- code/controllers/subsystem/holomaps.dm | 2 +- code/controllers/subsystem/icon_smooth.dm | 4 +- code/controllers/subsystem/inactivity.dm | 4 +- code/controllers/subsystem/input.dm | 9 +- code/controllers/subsystem/ipintel.dm | 3 +- code/controllers/subsystem/job/_job.dm | 3 +- code/controllers/subsystem/legacy_atc.dm | 2 +- code/controllers/subsystem/legacy_lore.dm | 2 +- code/controllers/subsystem/lighting.dm | 2 +- code/controllers/subsystem/machines.dm | 15 +- .../controllers/subsystem/mapping/_mapping.dm | 3 +- code/controllers/subsystem/materials.dm | 2 +- code/controllers/subsystem/media_tracks.dm | 2 +- code/controllers/subsystem/minimaps.dm | 2 +- code/controllers/subsystem/nanoui.dm | 15 +- code/controllers/subsystem/nightshift.dm | 5 +- code/controllers/subsystem/overlays.dm | 3 +- code/controllers/subsystem/overmap.dm | 2 +- code/controllers/subsystem/parallax.dm | 2 +- .../subsystem/persistence/persistence.dm | 5 +- code/controllers/subsystem/planets.dm | 2 +- code/controllers/subsystem/plants.dm | 2 +- code/controllers/subsystem/playtime.dm | 2 +- code/controllers/subsystem/preferences.dm | 3 +- .../subsystem/processing/circuits.dm | 2 +- .../subsystem/processing/instruments.dm | 3 +- .../subsystem/processing/processing.dm | 4 +- code/controllers/subsystem/repository.dm | 3 +- code/controllers/subsystem/shuttles.dm | 3 +- code/controllers/subsystem/sound/_sound.dm | 3 +- code/controllers/subsystem/spatial_grids.dm | 3 +- code/controllers/subsystem/statpanel.dm | 20 +- code/controllers/subsystem/status_effects.dm | 2 +- code/controllers/subsystem/sun.dm | 1 + code/controllers/subsystem/supply.dm | 2 +- code/controllers/subsystem/ticker.dm | 3 +- code/controllers/subsystem/timer.dm | 1 + .../subsystem/{lobby.dm => titlescreen.dm} | 21 +- code/controllers/subsystem/transcore_vr.dm | 4 +- code/controllers/subsystem/transfer.dm | 2 +- code/controllers/subsystem/turbolift.dm | 2 +- code/controllers/subsystem/vis_overlays.dm | 2 +- code/controllers/subsystem/vote.dm | 4 +- code/controllers/subsystem/world.dm | 2 +- code/controllers/subsystem/xenoarch.dm | 2 +- code/controllers/subsystem/zcopy.dm | 2 +- .../atoms/atoms_initializing_EXPENSIVE.dm | 6 +- .../objects/structures/tables/materials.dm | 2 +- .../structures/tables/update_triggers.dm | 2 +- code/modules/ai/holders/ai_holder-movement.dm | 7 + .../ai/holders/ai_holder-scheduling.dm | 1 + code/modules/ai/holders/ai_holder-ticking.dm | 3 + .../ai/holders/polaris/ai_holder_targeting.dm | 2 +- .../middleware/keybindings.dm | 2 +- code/modules/keybindings/bindings_client.dm | 4 +- code/modules/metrics/api.dm | 78 --- code/modules/metrics/api/nested_numerical.dm | 23 + code/modules/metrics/api/numerical.dm | 23 + code/modules/metrics/api/spatial_series.dm | 31 + code/modules/metrics/api/string_set.dm | 17 + code/modules/metrics/api/time_series.dm | 30 + code/modules/metrics/metrics/controllers.dm | 4 + code/modules/mob/mob_helpers.dm | 2 +- code/modules/mob/new_player/login.dm | 2 +- code/modules/power/apc.dm | 2 - 102 files changed, 1343 insertions(+), 925 deletions(-) delete mode 100644 code/__DEFINES/MC.dm create mode 100644 code/__DEFINES/controllers/_master-runlevel.dm create mode 100644 code/__DEFINES/controllers/_master.dm rename code/__DEFINES/controllers/{_repositories.dm => _repository.dm} (100%) create mode 100644 code/__DEFINES/controllers/_subsystem-init.dm create mode 100644 code/__DEFINES/controllers/_subsystem-priority.dm create mode 100644 code/__DEFINES/controllers/_subsystem.dm delete mode 100644 code/__DEFINES/controllers/_subsystems.dm create mode 100644 code/__DEFINES/controllers/atoms.dm create mode 100644 code/__DEFINES/metrics.dm create mode 100644 code/controllers/subsystem/__test_bad_subsystem_sleeps.dm rename code/controllers/subsystem/{lobby.dm => titlescreen.dm} (64%) delete mode 100644 code/modules/metrics/api.dm create mode 100644 code/modules/metrics/api/nested_numerical.dm create mode 100644 code/modules/metrics/api/numerical.dm create mode 100644 code/modules/metrics/api/spatial_series.dm create mode 100644 code/modules/metrics/api/string_set.dm create mode 100644 code/modules/metrics/api/time_series.dm create mode 100644 code/modules/metrics/metrics/controllers.dm diff --git a/citadel.dme b/citadel.dme index a6da8994250c..77dc70d24882 100644 --- a/citadel.dme +++ b/citadel.dme @@ -64,7 +64,7 @@ #include "code\__DEFINES\maps.dm" #include "code\__DEFINES\math.dm" #include "code\__DEFINES\matrices.dm" -#include "code\__DEFINES\MC.dm" +#include "code\__DEFINES\metrics.dm" #include "code\__DEFINES\misc.dm" #include "code\__DEFINES\move_force.dm" #include "code\__DEFINES\movement.dm" @@ -156,9 +156,14 @@ #include "code\__DEFINES\combat\damage.dm" #include "code\__DEFINES\combat\explosions.dm" #include "code\__DEFINES\combat\shieldcall.dm" -#include "code\__DEFINES\controllers\_repositories.dm" -#include "code\__DEFINES\controllers\_subsystems.dm" +#include "code\__DEFINES\controllers\_master-runlevel.dm" +#include "code\__DEFINES\controllers\_master.dm" +#include "code\__DEFINES\controllers\_repository.dm" +#include "code\__DEFINES\controllers\_subsystem-init.dm" +#include "code\__DEFINES\controllers\_subsystem-priority.dm" +#include "code\__DEFINES\controllers\_subsystem.dm" #include "code\__DEFINES\controllers\assets.dm" +#include "code\__DEFINES\controllers\atoms.dm" #include "code\__DEFINES\controllers\dbcore.dm" #include "code\__DEFINES\controllers\grids.dm" #include "code\__DEFINES\controllers\persistence.dm" @@ -570,7 +575,6 @@ #include "code\controllers\subsystem\legacy_atc.dm" #include "code\controllers\subsystem\legacy_lore.dm" #include "code\controllers\subsystem\lighting.dm" -#include "code\controllers\subsystem\lobby.dm" #include "code\controllers\subsystem\machines.dm" #include "code\controllers\subsystem\materials.dm" #include "code\controllers\subsystem\media_tracks.dm" @@ -606,6 +610,7 @@ #include "code\controllers\subsystem\ticker.dm" #include "code\controllers\subsystem\time_track.dm" #include "code\controllers\subsystem\timer.dm" +#include "code\controllers\subsystem\titlescreen.dm" #include "code\controllers\subsystem\transcore_vr.dm" #include "code\controllers\subsystem\transfer.dm" #include "code\controllers\subsystem\turbolift.dm" @@ -3494,9 +3499,14 @@ #include "code\modules\media\media_tracks.dm" #include "code\modules\media\mediamanager.dm" #include "code\modules\media\walkpod.dm" -#include "code\modules\metrics\api.dm" #include "code\modules\metrics\metric.dm" #include "code\modules\metrics\metric_base.dm" +#include "code\modules\metrics\api\nested_numerical.dm" +#include "code\modules\metrics\api\numerical.dm" +#include "code\modules\metrics\api\spatial_series.dm" +#include "code\modules\metrics\api\string_set.dm" +#include "code\modules\metrics\api\time_series.dm" +#include "code\modules\metrics\metrics\controllers.dm" #include "code\modules\mining\mine_turfs.dm" #include "code\modules\mining\drilling\drill.dm" #include "code\modules\mining\drilling\scanner.dm" diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm deleted file mode 100644 index 0cfbf865dd76..000000000000 --- a/code/__DEFINES/MC.dm +++ /dev/null @@ -1,118 +0,0 @@ -#define MC_TICK_CHECK ( ( TICK_USAGE > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 ) -#define MC_TICK_CHECK_USAGE ( ( TICK_USAGE > Master.current_ticklimit ) ? 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)){PreInit(TRUE);Preload(TRUE);Recover();qdel(varname);}varname = src;} - -#define START_PROCESSING(Processor, Datum) if (!(Datum.datum_flags & DF_ISPROCESSING)) {Datum.datum_flags |= DF_ISPROCESSING;Processor.processing += Datum} -#define STOP_PROCESSING(Processor, Datum) Datum.datum_flags &= ~DF_ISPROCESSING;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<<0) - -/** 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 (1<<1) - -/** 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 (1<<2) - -/// 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 (1<<3) - -/** 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) -/// -/// * Ticker is its own priority bucket. The highest one. Be careful. -/// * Ticker disables tick overrun punishment. -#define SS_TICKER (1<<4) - -/** 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. -/// -/// * This will only keep timing past the last 10 seconds, it will not attempt to catch the subsystem up without bounds. -/// * This disables tick overrun punishment. -#define SS_KEEP_TIMING (1<<5) - -/** 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 (1<<6) - -/// If this subsystem doesn't initialize, it should not report as a hard error in CI. -/// This should be used for subsystems that are flaky for complicated reasons, such as -/// the Lua subsystem, which relies on auxtools, which is unstable. //! We don't have the Lua system, but this is a good example. -/// It should not be used simply to silence CI. -#define SS_OK_TO_FAIL_INIT (1<<7) - -DEFINE_BITFIELD(subsystem_flags, list( - BITFIELD(SS_NO_INIT), - BITFIELD(SS_BACKGROUND), - BITFIELD(SS_NO_TICK_CHECK), - BITFIELD(SS_TICKER), - BITFIELD(SS_KEEP_TIMING), - BITFIELD(SS_POST_FIRE_TIMING), - BITFIELD(SS_OK_TO_FAIL_INIT), -)) - - -//! SUBSYSTEM STATES -/// aint doing shit. -#define SS_IDLE 0 -/// queued to run -#define SS_QUEUED 1 -/// actively running -#define SS_RUNNING 2 -/// paused by mc_tick_check -#define SS_PAUSED 3 -/// fire() slept. -#define SS_SLEEPING 4 -/// in the middle of pausing -#define SS_PAUSING 5 -#define SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/##X);\ -/datum/controller/subsystem/##X/New(){\ - NEW_SS_GLOBAL(SS##X);\ -}\ -/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);\ -}\ -/datum/controller/subsystem/processing/##X - -// Boilerplate code for multi-step processors. See machines.dm for example use. -#define INTERNAL_PROCESS_STEP(this_step, initial_step, proc_to_call, cost_var, next_step)\ -if(current_step == this_step || (initial_step && !resumed)) /* So we start at step 1 if not resumed.*/ {\ - timer = TICK_USAGE;\ - proc_to_call(resumed);\ - cost_var = MC_AVERAGE(cost_var, TICK_DELTA_TO_MS(TICK_USAGE - timer));\ - if(state != SS_RUNNING){\ - return;\ - }\ - resumed = 0;\ - current_step = next_step;\ -} diff --git a/code/__DEFINES/controllers/_master-runlevel.dm b/code/__DEFINES/controllers/_master-runlevel.dm new file mode 100644 index 000000000000..6f505ef2e8c7 --- /dev/null +++ b/code/__DEFINES/controllers/_master-runlevel.dm @@ -0,0 +1,32 @@ +//* Runlevels *// +//* Must be powers of 2. Runlevels should be in order of progression. *// +//* Only subsystem with a runlevel matching the MC's will be ticked. *// +//* The first runlevel (value '1') will be the default runlevel when the MC is first initialized. *// + +/// "Initialize Only" - Used for subsystems that should never be fired (Should also have SS_NO_FIRE set). +#define RUNLEVEL_INIT 0 +/// Initial runlevel before setup. Returns to here if setup fails. +#define RUNLEVEL_LOBBY 1 +/// While the gamemode setup is running. I.E gameticker.setup() +#define RUNLEVEL_SETUP 2 +/// After successful game ticker setup, while the round is running. +#define RUNLEVEL_GAME 4 +/// When round completes but before reboot. +#define RUNLEVEL_POSTGAME 8 + +/// default runlevels for most subsystems +#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) +/// all valid runlevels - subsystems with this will run all the time after their MC init stage. +#define RUNLEVELS_ALL (RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) + +var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_GAME, RUNLEVEL_POSTGAME) +/// Convert from the runlevel bitfield constants to index in runlevel_flags list. +#define RUNLEVEL_FLAG_TO_INDEX(flag) (log(2, flag) + 1) + +DEFINE_BITFIELD(runlevels, list( + BITFIELD(RUNLEVEL_INIT), + BITFIELD(RUNLEVEL_LOBBY), + BITFIELD(RUNLEVEL_SETUP), + BITFIELD(RUNLEVEL_GAME), + BITFIELD(RUNLEVEL_POSTGAME), +)) diff --git a/code/__DEFINES/controllers/_master.dm b/code/__DEFINES/controllers/_master.dm new file mode 100644 index 000000000000..26502007cf94 --- /dev/null +++ b/code/__DEFINES/controllers/_master.dm @@ -0,0 +1,59 @@ +/** + * This file (and its -dash files) is called MC, but actually holds quite a lot of logic including init orders + * and subsystems as the MC and subsystems make up the global orchestration system of the codebase. + */ + +#define MC_TICK_CHECK ( ( TICK_USAGE > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 ) +#define MC_TICK_CHECK_USAGE ( ( TICK_USAGE > Master.current_ticklimit ) ? 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 START_PROCESSING(Processor, Datum) if (!(Datum.datum_flags & DF_ISPROCESSING)) {Datum.datum_flags |= DF_ISPROCESSING;Processor.processing += Datum} +#define STOP_PROCESSING(Processor, Datum) Datum.datum_flags &= ~DF_ISPROCESSING;Processor.processing -= Datum + +/// Returns true if the MC is initialized and running. +/// Optional argument init_stage controls what stage the mc must have initializted to count as initialized. Defaults to INITSTAGE_MAX if not specified. +#define MC_RUNNING(INIT_STAGE...) (Master && Master.processing > 0 && Master.current_runlevel && Master.init_stage_completed == (max(min(INIT_STAGE_MAX, ##INIT_STAGE), 1))) +/// Returns true if the MC is at atleast a given init stage. Defaults to fully initialized. +/// +/// * This does not check anything else about the MC, including if it's actually running. +#define MC_INITIALIZED(INIT_STAGE...) (Master?.init_stage_completed >= max(INIT_STAGE_MAX, ##INIT_STAGE)) + +//* Recreate_MC() return values *// + +#define MC_RESTART_RTN_FAILED -1 +#define MC_RESTART_RTN_COOLDOWN 0 +#define MC_RESTART_RTN_SUCCESS 1 + +//* Master Controller Loop() return values *// + +/// Unknown or error +#define MC_LOOP_RTN_UNKNOWN 0 +/// New initialize stage happened +#define MC_LOOP_RTN_NEWSTAGES 1 +/// We want the MC to exit. +#define MC_LOOP_RTN_GRACEFUL_EXIT 2 + +//* Master Controller RunQueue() return values *// + +/// Unknown or error +#define MC_RUN_RTN_UNKNOWN 0 +/// Success; full completion +#define MC_RUN_RTN_FULL_COMPLETION 1 +/// Atleast one subsystem was sleeping or pausing +#define MC_RUN_RTN_PARTIAL_COMPLETION 2 diff --git a/code/__DEFINES/controllers/_repositories.dm b/code/__DEFINES/controllers/_repository.dm similarity index 100% rename from code/__DEFINES/controllers/_repositories.dm rename to code/__DEFINES/controllers/_repository.dm diff --git a/code/__DEFINES/controllers/_subsystem-init.dm b/code/__DEFINES/controllers/_subsystem-init.dm new file mode 100644 index 000000000000..12aaff4db93b --- /dev/null +++ b/code/__DEFINES/controllers/_subsystem-init.dm @@ -0,0 +1,92 @@ +//* Initialization Stages *// +//* After each stage, the MC starts ticking that stage while later stages are still waiting to init. *// +//* MC init stages must be a positive number, and init stages must all be consequetive! *// + +/// Early initializations required for server function; database, timers, tgui, etc +#define INIT_STAGE_BACKEND 1 +/// Pre-mapload initializations +#define INIT_STAGE_EARLY 2 +/// Mapload +#define INIT_STAGE_WORLD 3 +/// Late +#define INIT_STAGE_LATE 4 + +/// Last init stage we need to do. +/// +/// * This must be set to the maximum INIT_STAGE. +#define INIT_STAGE_MAX 4 + +//* Initialization Orders *// + +/** + *! Subsystem init_order, from highest priority to lowest priority. + *? Subsystems shutdown in the reverse of the order they initialize in. + *? Subsystems should always have init_order defined, even if they don't initialize, if they use init. + *? The numbers just define the ordering, they are meaningless otherwise. + */ + +//* Backend *// + +#define INIT_ORDER_FAIL2TOPIC 100 +#define INIT_ORDER_DBCORE 50 +#define INIT_ORDER_REPOSITORY 25 +#define INIT_ORDER_TIMER 10 +#define INIT_ORDER_STATPANELS 0 + +//* Early *// + +#define INIT_ORDER_EARLY_INIT 200 +#define INIT_ORDER_INPUT 170 +#define INIT_ORDER_PREFERENCES 150 +#define INIT_ORDER_JOBS 125 +#define INIT_ORDER_ASSETS 100 +#define INIT_ORDER_INSTRUMENTS 50 +#define INIT_ORDER_AI_SCHEDULING 25 +#define INIT_ORDER_AI_MOVEMENT 25 +#define INIT_ORDER_AI_HOLDERS 25 + +//* World *// + +#define INIT_ORDER_CHARACTERS 140 +#define INIT_ORDER_SOUNDS 130 +#define INIT_ORDER_GARBAGE 120 +#define INIT_ORDER_VIS 90 +#define INIT_ORDER_SERVER_MAINT 75 +#define INIT_ORDER_MEDIA_TRACKS 65 +#define INIT_ORDER_CHEMISTRY 60 +#define INIT_ORDER_MATERIALS 55 +#define INIT_ORDER_PHOTOGRAPHY 50 +#define INIT_ORDER_MAPPING 45 +#define INIT_ORDER_SPATIAL_GRIDS 43 //! must be after SSmapping so we know world.maxx and world.maxy +#define INIT_ORDER_GAME_WORLD 40 +#define INIT_ORDER_LEGACY_ATC 37 +#define INIT_ORDER_LEGACY_LORE 35 +#define INIT_ORDER_PLANTS 25 +#define INIT_ORDER_ALARMS 20 +#define INIT_ORDER_RESEARCH 17 +#define INIT_ORDER_ATOMS 15 +#define INIT_ORDER_MACHINES 10 +#define INIT_ORDER_SHUTTLES 3 +#define INIT_ORDER_DEFAULT 0 +#define INIT_ORDER_AIR -1 +#define INIT_ORDER_PLANETS -2 +#define INIT_ORDER_PERSISTENCE -3 +#define INIT_ORDER_AMBIENT_OCCLUSION -5 +#define INIT_ORDER_HOLOMAPS -5 +#define INIT_ORDER_ICON_SMOOTHING -6 +#define INIT_ORDER_EVENTS -10 +#define INIT_ORDER_OVERMAPS -20 +#define INIT_ORDER_TICKER -30 +#define INIT_ORDER_LIGHTING -40 +#define INIT_ORDER_ZMIMIC -45 +#define INIT_ORDER_AMBIENT_LIGHT -46 +#define INIT_ORDER_XENOARCH -50 +#define INIT_ORDER_CIRCUIT -60 +#define INIT_ORDER_AI -70 + +//* Late *// + +#define INIT_ORDER_OVERLAY 200 +#define INIT_ORDER_TITLESCREEN 150 +#define INIT_ORDER_NIGHTSHIFT 75 +#define INIT_ORDER_CHAT -100 //! Should be last to ensure chat remains smooth during init. diff --git a/code/__DEFINES/controllers/_subsystem-priority.dm b/code/__DEFINES/controllers/_subsystem-priority.dm new file mode 100644 index 000000000000..cd006fa47cdc --- /dev/null +++ b/code/__DEFINES/controllers/_subsystem-priority.dm @@ -0,0 +1,68 @@ + +/** + *! 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) + */ + +//? Background Subsystems - Below normal +// Any ../subsystem/.. is here unless it doesn't have SS_BACKGROUND in subsystem_flags! +// This means by default, ../subsystem/processing/.. is here! + +#define FIRE_PRIORITY_RADIATION 10 //! laggy as hell, bottom barrel until optimizations are done. +#define FIRE_PRIORITY_GARBAGE 15 +#define FIRE_PRIORITY_CHARACTERS 20 +#define FIRE_PRIORITY_PARALLAX 20 +#define FIRE_PRIORITY_AIR 25 +#define FIRE_PRIORITY_ASSET_LOADING 25 +#define FIRE_PRIORITY_PLANETS 25 +#define FIRE_PRIORITY_PROCESS 50 +// DEFAULT PRIORITY IS HERE (50) + +//? Normal Subsystems - Above background, below ticker +// Any ../subsystem/.. without SS_TICKER or SS_BACKGROUND in subsystem_flags is here! + +#define FIRE_PRIORITY_PING 5 +#define FIRE_PRIORITY_SHUTTLES 5 +#define FIRE_PRIORITY_PLANTS 5 +#define FIRE_PRIORITY_NIGHTSHIFT 6 +#define FIRE_PRIORITY_VOTE 9 +#define FIRE_PRIORITY_VIS 10 +#define FIRE_PRIORITY_SERVER_MAINT 10 +#define FIRE_PRIORITY_ZMIMIC 10 +#define FIRE_PRIORITY_ALARMS 20 +#define FIRE_PRIORITY_AIRFLOW 20 +#define FIRE_PRIORITY_SPACEDRIFT 25 +#define FIRE_PRIORITY_OBJ 40 +// DEFAULT PRIORITY IS HERE (50) +#define FIRE_PRIORITY_LIGHTING 50 +#define FIRE_PRIORITY_INSTRUMENTS 50 +#define FIRE_PRIORITY_MACHINES 50 +#define FIRE_PRIORITY_AI 65 +#define FIRE_PRIORITY_AI_HOLDERS 65 +#define FIRE_PRIORITY_AI_MOVEMENT 75 +#define FIRE_PRIORITY_AI_SCHEDULING 75 +#define FIRE_PRIORITY_NANO 80 +#define FIRE_PRIORITY_TGUI 80 +#define FIRE_PRIORITY_OVERMAP_PHYSICS 90 +#define FIRE_PRIORITY_PROJECTILES 90 +#define FIRE_PRIORITY_THROWING 90 +#define FIRE_PRIORITY_STATPANELS 100 +#define FIRE_PRIORITY_OVERLAYS 100 +#define FIRE_PRIORITY_SMOOTHING 100 +#define FIRE_PRIORITY_CHAT 100 +#define FIRE_PRIORITY_INPUT 100 + +//? Ticker Subsystems - Highest priority +// Any subsystem flagged with SS_TICKER is here! +// Do not unnecessarily set your subsystem as TICKER. +// Is your feature as important as movement, chat, or timers? +// Probably not! Go to normal bracket instead! + +// DEFAULT PRIORITY IS HERE (50) +#define FIRE_PRIORITY_DPC 100 +#define FIRE_PRIORITY_TIMER 100 + +//? Special + +/// This is used as the default regardless of bucket. Check above. +#define FIRE_PRIORITY_DEFAULT 50 diff --git a/code/__DEFINES/controllers/_subsystem.dm b/code/__DEFINES/controllers/_subsystem.dm new file mode 100644 index 000000000000..f9fce73f1a02 --- /dev/null +++ b/code/__DEFINES/controllers/_subsystem.dm @@ -0,0 +1,164 @@ +/** + *! Defines for subsystems + * + *? Lots of important stuff in here, make sure you have your brain switched on when editing this file! + */ + +//* Subsystem Definition Macros *// + +/** + * Macro to fire off all logic when a subsystem is created - this is done immediately on New(). + */ +#define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){PreInit(TRUE);Preload(TRUE);Recover();qdel(varname);}varname = src;} + +/** + * Defines a normal subsystem. + */ +#define SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/##X);\ +/datum/controller/subsystem/##X/New(){\ + NEW_SS_GLOBAL(SS##X);\ +}\ +/datum/controller/subsystem/##X + +/** + * Defines a processing subsystem. + */ +#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);\ +}\ +/datum/controller/subsystem/processing/##X + +//* Subsystem flags *// +//* Please design any new flags so that the default is off, to make adding flags to subsystems easier. *// + +/** + * Subsystem does not need Initialize() called. + * + * * The subsystem will still fire when its init stage is completed, unless it is + * marked with [SS_NO_FIRE] or its `can_fire` is set to FALSE. + * * The subsystem will still have its `initialized` variable set to TRUE. + */ +#define SS_NO_INIT (1<<0) + +/** + * Subsystem does not need fire() called / does not require scheduling and ticking. + * + * * This is exactly like setting `can_fire` to FALSE, but is permanent without a MC restart. + * * Use for subsystems that will never require processing, rather than one that needs it turned off and on now and then. + */ +#define SS_NO_FIRE (1<<1) + +/** + * Subsystem will try its best to only run on spare CPU, after all non-background subsystems have ran. + * + * * This pushes a subsystem into the background fire_priority bracket. + */ +#define SS_BACKGROUND (1<<2) + +/// subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background)) +// todo: this should be deprecated; please do not use this on new subsystems without good reason. +#define SS_NO_TICK_CHECK (1<<3) + +/** 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) +/// +/// * Ticker is its own priority bucket. The highest one. Be careful. +/// * Ticker disables tick overrun punishment. +#define SS_TICKER (1<<4) + +/** 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. +/// +/// * This will only keep timing past the last 10 seconds, it will not attempt to catch the subsystem up without bounds. +/// * This disables tick overrun punishment. +#define SS_KEEP_TIMING (1<<5) + +/** 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 (1<<6) + +/// If this subsystem doesn't initialize, it should not report as a hard error in CI. +/// This should be used for subsystems that are flaky for complicated reasons, such as +/// the Lua subsystem, which relies on auxtools, which is unstable. //! We don't have the Lua system, but this is a good example. +/// It should not be used simply to silence CI. +#define SS_OK_TO_FAIL_INIT (1<<7) + +DEFINE_BITFIELD(subsystem_flags, list( + BITFIELD(SS_NO_INIT), + BITFIELD(SS_BACKGROUND), + BITFIELD(SS_NO_TICK_CHECK), + BITFIELD(SS_TICKER), + BITFIELD(SS_KEEP_TIMING), + BITFIELD(SS_POST_FIRE_TIMING), + BITFIELD(SS_OK_TO_FAIL_INIT), +)) + +//* Subsystem `Initialize()` returns *// +/** + * Negative values incidate a failure or warning of some kind, positive are good. + * 0 and 1 are unused so that TRUE and FALSE are guarenteed to be invalid values. + */ + +/// Subsystem failed to initialize entirely. Print a warning, log, and disable firing. +#define SS_INIT_FAILURE -2 +/// The default return value which must be overriden. Will succeed with a warning. +#define SS_INIT_NONE -1 +/// Subsystem initialized sucessfully. +#define SS_INIT_SUCCESS 2 +/// If your system doesn't need to be initialized (by being disabled or something) +#define SS_INIT_NO_NEED 3 +/// Succesfully initialized, BUT do not announce it to players (generally to hide game mechanics it would otherwise spoil) +#define SS_INIT_NO_MESSAGE 4 + +//* Subsystem 'state' variable *// + +/** + * Not doing anything right now. + */ +#define SS_IDLE 0 +/** + * In the MC's run-queue + */ +#define SS_QUEUED 1 +/** + * Set before the MC ignites a subsystem. This is the state while it's currently running. + */ +#define SS_RUNNING 2 +/** + * We are requesting a pause. + * + * * Set by the pause() proc if we did not sleep yet during our fire(). + */ +#define SS_PAUSED 3 +/** + * fire() is currently sleeping. + */ +#define SS_SLEEPING 4 +/** + * We slept, and now we are requesting a pause. + * + * * Set by the pause() proc if we have slept since fire() was invoked. + * * Converted to SS_PAUSED by ignite() once we finally return from fire(), as we cannot immediately pause if + * we are sleeping. + */ +#define SS_PAUSING 5 + +//* Misc *// + +/// Boilerplate code for multi-step processors. See machines.dm for example use. +#define INTERNAL_SUBSYSTEM_PROCESS_STEP(this_step, initial_step, proc_to_call, cost_var, next_step)\ +if(current_step == this_step || (initial_step && !resumed)) /* So we start at step 1 if not resumed.*/ {\ + timer = TICK_USAGE;\ + proc_to_call(resumed);\ + cost_var = MC_AVERAGE(cost_var, TICK_DELTA_TO_MS(TICK_USAGE - timer));\ + if(state != SS_RUNNING){\ + return;\ + }\ + resumed = 0;\ + current_step = next_step;\ +} diff --git a/code/__DEFINES/controllers/_subsystems.dm b/code/__DEFINES/controllers/_subsystems.dm deleted file mode 100644 index 3a5f1b5360b2..000000000000 --- a/code/__DEFINES/controllers/_subsystems.dm +++ /dev/null @@ -1,237 +0,0 @@ -/** - *! Defines for subsystems and overlays - * - *? Lots of important stuff in here, make sure you have your brain switched on when editing this file! - */ - -//* Subsystem `initialized` variable *// - -// todo: implement these, separate out SSatoms initialization state to its own variable -// #define SUBSYSTEM_INITIALIZED_NOT_STARTED 0 -// #define SUBSYSTEM_INITIALIZED_INITIALIZING 1 -// #define SUBSYSTEM_INITIALIZED_DONE 2 - -//! ## Initialization subsystem - -/// New should not call Initialize. -#define INITIALIZATION_INSSATOMS 0 -/// New should call Initialize(FALSE). -#define INITIALIZATION_INNEW_REGULAR 1 -/// New should call Initialize(TRUE). -#define INITIALIZATION_INNEW_MAPLOAD 2 - -//! ### Initialization hints - -/// Nothing happens -#define INITIALIZE_HINT_NORMAL 0 - -/** - * call LateInitialize at the end of all atom Initalization. - * - * The item will be added to the late_loaders list, this is iterated over after - * initalization of subsystems is complete and calls LateInitalize on the atom - * see [this file for the LateIntialize proc](atom.html#proc/LateInitialize) - */ -#define INITIALIZE_HINT_LATELOAD 1 - -/// Call qdel on the atom after intialization. -#define INITIALIZE_HINT_QDEL 2 - -/// type and all subtypes should always immediately call Initialize in New(). -#define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\ - ..();\ - if(!(atom_flags & ATOM_INITIALIZED)) {\ - var/previous_initialized_value = SSatoms.initialized;\ - SSatoms.initialized = INITIALIZATION_INNEW_MAPLOAD;\ - args[1] = TRUE;\ - SSatoms.InitAtom(src, FALSE, args);\ - SSatoms.initialized = previous_initialized_value;\ - }\ -} - -//! ### SS runlevels - -/// "Initialize Only" - Used for subsystems that should never be fired (Should also have SS_NO_FIRE set). -#define RUNLEVEL_INIT 0 -/// Initial runlevel before setup. Returns to here if setup fails. -#define RUNLEVEL_LOBBY 1 -/// While the gamemode setup is running. I.E gameticker.setup() -#define RUNLEVEL_SETUP 2 -/// After successful game ticker setup, while the round is running. -#define RUNLEVEL_GAME 4 -/// When round completes but before reboot. -#define RUNLEVEL_POSTGAME 8 - -/// default runlevels for most subsystems -#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) -/// all valid runlevels - subsystems with this will run all the time after their MC init stage. -#define RUNLEVELS_ALL (RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) - -var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_GAME, RUNLEVEL_POSTGAME) -/// Convert from the runlevel bitfield constants to index in runlevel_flags list. -#define RUNLEVEL_FLAG_TO_INDEX(flag) (log(2, flag) + 1) - -DEFINE_BITFIELD(runlevels, list( - BITFIELD(RUNLEVEL_INIT), - BITFIELD(RUNLEVEL_LOBBY), - BITFIELD(RUNLEVEL_SETUP), - BITFIELD(RUNLEVEL_GAME), - BITFIELD(RUNLEVEL_POSTGAME), -)) - -/** - *! 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. - */ - -// todo: tg init brackets - -// core security system, used by client/New() -#define INIT_ORDER_FAIL2TOPIC 200 -// core security system, used by client/New() -#define INIT_ORDER_IPINTEL 197 - -// core timing system, used by almost everything -#define INIT_ORDER_TIMER 195 -// just about every feature on the server requires the database backend -// for storage and durability of permeance. -#define INIT_ORDER_DBCORE 190 -// repository is just struct storage. its things depend on database, -// but should depend on nothing else. -// -// for the rare occasion when a prototype requires asset registration, -// it should be able to recognize if SSassets is ready, -// and only queue an udpate if its asset is already loaded. -#define INIT_ORDER_REPOSITORY 187 -// early init initializes what is basically expensive global variables. it needs to go before assets. -#define INIT_ORDER_EARLY_INIT 185 -// assets is loaded early because things hook into this to register *their* assets -#define INIT_ORDER_ASSETS 180 - -#define INIT_ORDER_STATPANELS 170 -#define INIT_ORDER_PREFERENCES 165 -#define INIT_ORDER_INPUT 160 -#define INIT_ORDER_JOBS 150 -#define INIT_ORDER_CHARACTERS 140 -#define INIT_ORDER_SOUNDS 130 -#define INIT_ORDER_GARBAGE 120 -#define INIT_ORDER_VIS 90 -#define INIT_ORDER_SERVER_MAINT 75 -#define INIT_ORDER_INSTRUMENTS 70 -#define INIT_ORDER_MEDIA_TRACKS 65 -#define INIT_ORDER_CHEMISTRY 60 -#define INIT_ORDER_MATERIALS 55 -#define INIT_ORDER_PHOTOGRAPHY 50 -#define INIT_ORDER_AI_SCHEDULING 48 -#define INIT_ORDER_AI_MOVEMENT 48 -#define INIT_ORDER_AI_HOLDERS 48 -#define INIT_ORDER_MAPPING 45 -#define INIT_ORDER_SPATIAL_GRIDS 43 // must be after SSmapping so we know world.maxx and world.maxy -#define INIT_ORDER_GAME_WORLD 40 -#define INIT_ORDER_LEGACY_ATC 37 -#define INIT_ORDER_LEGACY_LORE 35 -#define INIT_ORDER_LOBBY 30 -#define INIT_ORDER_PLANTS 25 -#define INIT_ORDER_ALARMS 20 -#define INIT_ORDER_RESEARCH 17 -#define INIT_ORDER_ATOMS 15 -#define INIT_ORDER_MACHINES 10 -#define INIT_ORDER_SHUTTLES 3 -#define INIT_ORDER_DEFAULT 0 -#define INIT_ORDER_AIR -1 -#define INIT_ORDER_PLANETS -2 -#define INIT_ORDER_PERSISTENCE -3 -#define INIT_ORDER_AMBIENT_OCCLUSION -5 -#define INIT_ORDER_HOLOMAPS -5 -#define INIT_ORDER_NIGHTSHIFT -5 -#define INIT_ORDER_ICON_SMOOTHING -6 -#define INIT_ORDER_OVERLAY -7 -#define INIT_ORDER_EVENTS -10 -#define INIT_ORDER_OVERMAPS -20 -#define INIT_ORDER_TICKER -30 -#define INIT_ORDER_LIGHTING -40 -#define INIT_ORDER_ZMIMIC -45 -#define INIT_ORDER_AMBIENT_LIGHT -46 -#define INIT_ORDER_XENOARCH -50 -#define INIT_ORDER_CIRCUIT -60 -#define INIT_ORDER_AI -70 -#define INIT_ORDER_CHAT -100 //! Should be last to ensure chat remains smooth during init. - - -/** - *! 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) - */ - -//? Background Subsystems - Below normal -// Any ../subsystem/.. is here unless it doesn't have SS_BACKGROUND in subsystem_flags! -// This means by default, ../subsystem/processing/.. is here! - -#define FIRE_PRIORITY_RADIATION 10 //! laggy as hell, bottom barrel until optimizations are done. -#define FIRE_PRIORITY_GARBAGE 15 -#define FIRE_PRIORITY_CHARACTERS 20 -#define FIRE_PRIORITY_PARALLAX 20 -#define FIRE_PRIORITY_AIR 25 -#define FIRE_PRIORITY_ASSET_LOADING 25 -#define FIRE_PRIORITY_PLANETS 25 -#define FIRE_PRIORITY_PROCESS 50 -// DEFAULT PRIORITY IS HERE (50) - -//? Normal Subsystems - Above background, below ticker -// Any ../subsystem/.. without SS_TICKER or SS_BACKGROUND in subsystem_flags is here! - -#define FIRE_PRIORITY_PING 5 -#define FIRE_PRIORITY_SHUTTLES 5 -#define FIRE_PRIORITY_PLANTS 5 -#define FIRE_PRIORITY_NIGHTSHIFT 6 -#define FIRE_PRIORITY_VOTE 9 -#define FIRE_PRIORITY_VIS 10 -#define FIRE_PRIORITY_SERVER_MAINT 10 -#define FIRE_PRIORITY_ZMIMIC 10 -#define FIRE_PRIORITY_ALARMS 20 -#define FIRE_PRIORITY_AIRFLOW 20 -#define FIRE_PRIORITY_SPACEDRIFT 25 -#define FIRE_PRIORITY_OBJ 40 -// DEFAULT PRIORITY IS HERE (50) -#define FIRE_PRIORITY_LIGHTING 50 -#define FIRE_PRIORITY_INSTRUMENTS 50 -#define FIRE_PRIORITY_MACHINES 50 -#define FIRE_PRIORITY_AI 65 -#define FIRE_PRIORITY_AI_HOLDERS 65 -#define FIRE_PRIORITY_AI_MOVEMENT 75 -#define FIRE_PRIORITY_AI_SCHEDULING 75 -#define FIRE_PRIORITY_NANO 80 -#define FIRE_PRIORITY_TGUI 80 -#define FIRE_PRIORITY_OVERMAP_PHYSICS 90 -#define FIRE_PRIORITY_PROJECTILES 90 -#define FIRE_PRIORITY_THROWING 90 -#define FIRE_PRIORITY_STATPANELS 100 -#define FIRE_PRIORITY_OVERLAYS 100 -#define FIRE_PRIORITY_SMOOTHING 100 -#define FIRE_PRIORITY_CHAT 100 -#define FIRE_PRIORITY_INPUT 100 - -//? Ticker Subsystems - Highest priority -// Any subsystem flagged with SS_TICKER is here! -// Do not unnecessarily set your subsystem as TICKER. -// Is your feature as important as movement, chat, or timers? -// Probably not! Go to normal bracket instead! - -// DEFAULT PRIORITY IS HERE (50) -#define FIRE_PRIORITY_DPC 100 -#define FIRE_PRIORITY_TIMER 100 - -//? Special - -/// This is used as the default regardless of bucket. Check above. -#define FIRE_PRIORITY_DEFAULT 50 - -/** - * Create a new timer and add it to the queue. - * Arguments: - * * callback the callback to call on timer finish - * * wait deciseconds to run the timer for - * * atom_flags atom_flags for this timer, see: code\__DEFINES\subsystems.dm - */ -#define addtimer(args...) _addtimer(args, file = __FILE__, line = __LINE__) diff --git a/code/__DEFINES/controllers/atoms.dm b/code/__DEFINES/controllers/atoms.dm new file mode 100644 index 000000000000..851ab297914c --- /dev/null +++ b/code/__DEFINES/controllers/atoms.dm @@ -0,0 +1,37 @@ +//* Values for the `atom_init_status` variable on SSatoms. *// + +/// New should not call Initialize. +#define ATOM_INIT_IN_SUBSYSTEM 0 +/// New should call Initialize(FALSE) - not mapload +#define ATOM_INIT_IN_NEW_REGULAR 1 +/// New should call Initialize(TRUE) - is in a mapload +#define ATOM_INIT_IN_NEW_MAPLOAD 2 + +//! ### Initialization hints + +/// Nothing happens +#define INITIALIZE_HINT_NORMAL 0 + +/** + * call LateInitialize at the end of all atom Initalization. + * + * The item will be added to the late_loaders list, this is iterated over after + * initalization of subsystems is complete and calls LateInitalize on the atom + * see [this file for the LateIntialize proc](atom.html#proc/LateInitialize) + */ +#define INITIALIZE_HINT_LATELOAD 1 + +/// Call qdel on the atom after intialization. +#define INITIALIZE_HINT_QDEL 2 + +/// type and all subtypes should always immediately call Initialize in New(). +#define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\ + ..();\ + if(!(atom_flags & ATOM_INITIALIZED)) {\ + var/previous_initialized_value = SSatoms.initialized;\ + SSatoms.initialized = ATOM_INIT_IN_NEW_MAPLOAD;\ + args[1] = TRUE;\ + SSatoms.InitAtom(src, FALSE, args);\ + SSatoms.initialized = previous_initialized_value;\ + }\ +} diff --git a/code/__DEFINES/controllers/timer.dm b/code/__DEFINES/controllers/timer.dm index 500019080619..2d6de7928b1b 100644 --- a/code/__DEFINES/controllers/timer.dm +++ b/code/__DEFINES/controllers/timer.dm @@ -1,4 +1,14 @@ //! ## Timing subsystem + +/** + * Create a new timer and add it to the queue. + * Arguments: + * * callback the callback to call on timer finish + * * wait deciseconds to run the timer for + * * atom_flags atom_flags for this timer, see: code\__DEFINES\subsystems.dm + */ +#define addtimer(args...) _addtimer(args, file = __FILE__, line = __LINE__) + /** * Don't run if there is an identical unique timer active * diff --git a/code/__DEFINES/metrics.dm b/code/__DEFINES/metrics.dm new file mode 100644 index 000000000000..4286dc004792 --- /dev/null +++ b/code/__DEFINES/metrics.dm @@ -0,0 +1,6 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Metric Categories *// + +#define METRIC_CATEGORY_SERVER "server" diff --git a/code/__HELPERS/sorts/comparators.dm b/code/__HELPERS/sorts/comparators.dm index ace8dd066988..5049291233e3 100644 --- a/code/__HELPERS/sorts/comparators.dm +++ b/code/__HELPERS/sorts/comparators.dm @@ -92,25 +92,6 @@ GLOBAL_VAR_INIT(cmp_field, "name") /proc/cmp_ckey_dsc(client/a, client/b) return sorttext(a.ckey, b.ckey) -/** - * Sorts subsystems alphabetically. - */ -/proc/cmp_subsystem_display(datum/controller/subsystem/a, datum/controller/subsystem/b) - return sorttext(b.name, a.name) - -/** - * Sorts subsystems by init_order. - */ -/proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b) - // Uses initial() so it can be used on types. - return initial(b.init_order) - initial(a.init_order) - -/** - * Sorts subsystems by priority. - */ -/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b) - return a.priority - b.priority - /proc/cmp_filter_data_priority(list/A, list/B) return A["priority"] - B["priority"] diff --git a/code/___compile_options.dm b/code/___compile_options.dm index ed656eb5a738..456b5cabb66a 100644 --- a/code/___compile_options.dm +++ b/code/___compile_options.dm @@ -79,19 +79,11 @@ */ // #define USE_BYOND_TRACY -/** - * If this is uncommented, Autowiki will generate edits and shut down the server. - * Prefer the autowiki build target instead. - */ -// #define AUTOWIKI - - /** * If this is uncommented, will profile mapload atom initializations. */ // #define PROFILE_MAPLOAD_INIT_ATOM - /** * If this is uncommented, force our verb processing into just the 2% of a tick. * We normally reserve for it. diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index 3384590cfb3c..657bf083ffb5 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -36,6 +36,10 @@ var/datum/controller/failsafe/Failsafe /datum/controller/failsafe/New() + // Do not contaminate `usr`; if this is set, the MC main loop will have the usr of whoever called it, + // which results in all procs called by the MC inheriting that usr. + usr = null + // Highlander-style: there can only be one! Kill off the old and replace it with the new. if(Failsafe != src) if(istype(Failsafe)) diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 71dc99baf347..7d0c002dad77 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -27,9 +27,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /// How many times have we ran? var/iteration = 0 - /// Are we initialized? - var/initialized = FALSE - /// world.time of last fire, for tracking lag outside of the mc. var/last_run @@ -37,8 +34,15 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/list/subsystems //# Vars for keeping track of tick drift. - var/init_timeofday - var/init_time + + /** + * The world.timeofday that the current Loop() started at. + */ + var/loop_start_timeofday + /** + * The world.time that the current Loop() started at. + */ + var/loop_start_time var/tickdrift = 0 /// How long is the MC sleeping between runs, read only (set by Loop() based off of anti-tick-contention heuristics). @@ -47,15 +51,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /// Makes the mc main loop runtime. var/make_runtime = FALSE - 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 /// For scheduling different subsystems for different stages of the round. var/current_runlevel - var/sleep_offline_after_initializations = TRUE var/static/restart_clear = 0 var/static/restart_timeout = 0 @@ -63,7 +64,29 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/static/random_seed - //* Processing Variables *// + //* Iniitialization *// + + /// The subsystem currently being initialized. + var/datum/controller/subsystem/current_initializing_subsystem + /// Are we initialized? This means all subsystems have been initialized. + var/initialized = FALSE + /// Set if it is specified to pause the world while no one is logged in after initializations, and we did pause. + var/initializations_finished_with_no_players_logged_in + /// Set to determine if we should sleep offline after initializations if no one is connected. + /// + /// * This is turned off by unit tests automatically. + var/sleep_offline_after_initializations = TRUE + + //* Global State *// + //* These are tracked through MC restarts. *// + + /// The current initialization stage we're at. + var/static/init_stage_completed = 0 + /// The init stage currently being ran by the main ticker loop + var/init_stage_ticking + + //* Processing Variables *// + //* These are set during a Loop(). *// /// total fire_priority of all non-background subsystems in the queue var/queue_priority_count = 0 @@ -75,6 +98,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /// End of queue linked list (used for appending to the list). var/datum/controller/subsystem/queue_tail + //* Control Variables *// + //* These are accessed globally and are used to allow the MC to control the server's tick when outside of a MC proc. *// + /** * 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. @@ -82,6 +108,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/static/current_ticklimit = TICK_LIMIT_RUNNING /datum/controller/master/New() + // Do not contaminate `usr`; if this is set, the MC main loop will have the usr of whoever called it, + // which results in all procs called by the MC inheriting that usr. + usr = null + //# 1. load configs if(!config_legacy) load_configuration() @@ -129,13 +159,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new for(var/datum/controller/subsystem/S in _subsystems) S.Preload(FALSE) - /datum/controller/master/Destroy() ..() // Tell qdel() to Del() this object. return QDEL_HINT_HARDDEL_NOW - /datum/controller/master/Shutdown() processing = FALSE tim_sort(subsystems, GLOBAL_PROC_REF(cmp_subsystem_init)) @@ -146,18 +174,18 @@ GLOBAL_REAL(Master, /datum/controller/master) = new log_world("Shutdown complete") - /** + * MC reboot proc. + * * 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() - usr = null // yeah let's not contaminate the MC call stack with our usr. - . = -1 // So if we runtime, things know we failed. + . = MC_RESTART_RTN_FAILED // So if we runtime, things know we failed. if (world.time < Master.restart_timeout) - return 0 + return MC_RESTART_RTN_COOLDOWN if (world.time < Master.restart_clear) Master.restart_count *= 0.5 @@ -168,27 +196,28 @@ GLOBAL_REAL(Master, /datum/controller/master) = new try new/datum/controller/master() catch - return -1 - - return 1 + return MC_RESTART_RTN_FAILED + return MC_RESTART_RTN_SUCCESS /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" - + var/list/master_attributes = Master.vars + var/list/filtered_variables = list( + NAMEOF(src, name), + NAMEOF(src, parent_type), + NAMEOF(src, statclick), + NAMEOF(src, tag), + NAMEOF(src, type), + NAMEOF(src, vars), + ) + for (var/varname in master_attributes - filtered_variables) + var/varval = master_attributes[varname] + if (isdatum(varval)) // 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_world(msg) var/datum/controller/subsystem/BadBoy = Master.last_type_processed @@ -200,96 +229,169 @@ GLOBAL_REAL(Master, /datum/controller/master) = new 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." + msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be put offline." BadBoy.subsystem_flags |= SS_NO_FIRE - if(msg) to_chat(GLOB.admins, SPAN_BOLDANNOUNCE("[msg]")) log_world(msg) if (istype(Master.subsystems)) if(FireHim) - Master.subsystems += new BadBoy.type // NEW_SS_GLOBAL will remove the old one. - + Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one subsystems = Master.subsystems current_runlevel = Master.current_runlevel - initialized = TRUE StartProcessing(10) - else to_chat(world, SPAN_BOLDANNOUNCE("The Master Controller is having some issues, we will need to re-initialize EVERYTHING")) - Initialize(20, TRUE) - + Initialize(20, TRUE, FALSE) /** * 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() + * + * @params + * * delay - wait this many deciseconds before initializing + * * init_sss - initialize all subsystems + * * tgs_prime - notify TGS that initializations are done after */ /datum/controller/master/Initialize(delay, init_sss, tgs_prime) set waitfor = FALSE if(delay) sleep(delay) - - if(tgs_prime) - world.TgsInitializationComplete() - if(init_sss) init_subtypes(/datum/controller/subsystem, subsystems) + // Announce start to_chat(world, SPAN_BOLDANNOUNCE("Initializing subsystems...")) + var/mc_started = FALSE + + // We want to initialize subsystems by stage, in the init_order provided for subsystems within the same stage. + init_stage_completed = 0 + var/list/stage_sorted_subsystems = new(INIT_STAGE_MAX) + for(var/i in 1 to INIT_STAGE_MAX) + stage_sorted_subsystems[i] = list() // Sort subsystems by init_order, so they initialize in the correct order. tim_sort(subsystems, GLOBAL_PROC_REF(cmp_subsystem_init)) - var/start_timeofday = REALTIMEOFDAY - // Initialize subsystems. - current_ticklimit = config_legacy.tick_limit_mc_init - for (var/datum/controller/subsystem/SS in subsystems) - if (SS.subsystem_flags & SS_NO_INIT) - continue - - SS.Initialize(REALTIMEOFDAY) - CHECK_TICK + // Collect subsystems by init_stage. This has precedence over init_order. + for(var/datum/controller/subsystem/subsystem as anything in subsystems) + var/subsystem_init_stage = subsystem.init_stage + if (!isnum(subsystem_init_stage) || subsystem_init_stage < 1 || subsystem_init_stage > INIT_STAGE_MAX || round(subsystem_init_stage) != subsystem_init_stage) + stack_trace("ERROR: MC: subsystem `[subsystem.type]` has invalid init_stage: `[subsystem_init_stage]`. Setting to `[INIT_STAGE_MAX]`") + subsystem_init_stage = subsystem.init_stage = INIT_STAGE_MAX + stage_sorted_subsystems[subsystem_init_stage] += subsystem - current_ticklimit = TICK_LIMIT_RUNNING - var/time = (REALTIMEOFDAY - start_timeofday) / 10 + // Sort subsystems by display setting for easy access. + tim_sort(subsystems, GLOBAL_PROC_REF(cmp_subsystem_display)) - var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!" + // Initialize subsystems. The ticker loop will be started immediately upon the first stage being done. + var/rtod_start = REALTIMEOFDAY + + for(var/current_init_stage in 1 to INIT_STAGE_MAX) + for(var/datum/controller/subsystem/subsystem in stage_sorted_subsystems[current_init_stage]) + current_initializing_subsystem = subsystem + initialize_subsystem(subsystem) + current_initializing_subsystem = null + CHECK_TICK + init_stage_completed = current_init_stage + if(!mc_started) + mc_started = TRUE + if(!current_runlevel) + // intentionally not using the defines here as the MC does not care about runlevel semantics; + // the first runlevel is always used. + SetRunLevel(1) + Master.StartProcessing(0) + + var/rtod_end = REALTIMEOFDAY + var/took_seconds = round((rtod_end - rtod_start) / 10, 0.01) + + // Announce, log, and record end + var/msg = "Initializations complete within [took_seconds] second[took_seconds == 1 ? "" : "s"]!" to_chat(world, SPAN_BOLDANNOUNCE("[msg]")) log_world(msg) - if (!current_runlevel) - SetRunLevel(RUNLEVEL_LOBBY) - - // Sort subsystems by display setting for easy access. - tim_sort(subsystems, GLOBAL_PROC_REF(cmp_subsystem_display)) + // Set world options. + world.set_fps(config_legacy.fps) + // Fire initialization toast if(world.system_type == MS_WINDOWS && CONFIG_GET(flag/toast_notification_on_init) && !length(GLOB.clients)) world.shelleo("start /min powershell -ExecutionPolicy Bypass -File tools/initToast/initToast.ps1 -name \"[world.name]\" -icon %CD%\\icons\\CS13_16.png -port [world.port]") - // Set world options. - - world.set_fps(config_legacy.fps) + // Tell TGS we're initialized + if(tgs_prime) + world.TgsInitializationComplete() - var/initialized_tod = REALTIMEOFDAY + // Handle sleeping offline after initializations. + var/rtod_sleep_offline_check = REALTIMEOFDAY if(sleep_offline_after_initializations) world.sleep_offline = TRUE - - sleep(1) - + sleep(1 TICK) if(sleep_offline_after_initializations) // && CONFIG_GET(flag/resume_after_initializations)) world.sleep_offline = FALSE + initializations_finished_with_no_players_logged_in = rtod_sleep_offline_check < REALTIMEOFDAY - 10 - initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10 +/** + * Initialize a given subsystem and handle the results. + * + * Arguments: + * * subsystem - the subsystem to initialize. + */ +/datum/controller/master/proc/initialize_subsystem(datum/controller/subsystem/subsystem) + // Do not re-init already initialized subsystems if it's somehow called again. + if(subsystem.subsystem_flags & SS_NO_INIT) + subsystem.initialized = TRUE + return + if(subsystem.initialized) + return - initialized = TRUE + // todo: dylib high-precision timers + var/rtod_start = REALTIMEOFDAY + var/initialize_result = subsystem.Initialize() + var/rtod_end = REALTIMEOFDAY + var/took_seconds = round((rtod_end - rtod_start) / 10, 0.01) + + metric_set_nested_numerical(/datum/metric/nested_numerical/subsystem_init_time, "[subsystem.type]", took_seconds) + + // "[message_prefix] [seconds] seconds." + var/message_prefix + // should to_chat the message to world rather than just log + var/tell_everyone + // use a warning spans + var/chat_warning + + switch(initialize_result) + if(SS_INIT_FAILURE) + message_prefix = "Failed to initialize [subsystem.name] subsystem after" + tell_everyone = TRUE + chat_warning = TRUE + // Since this is an explicit failure, shut its ticking off. We also will not set its initialized variable. + subsystem.subsystem_flags |= SS_NO_FIRE + if(SS_INIT_NONE) + message_prefix = "Initialized [subsystem.name] subsystem with errors within" + tell_everyone = TRUE + chat_warning = TRUE + subsystem.initialized = TRUE + warning("[subsystem.name] subsystem does not implement Initialize() or it returns ..(). If the former is true, the SS_NO_INIT flag should be set for this subsystem.") + if(SS_INIT_SUCCESS) + message_prefix = "Initialized [subsystem.name] subsystem within" + tell_everyone = TRUE + subsystem.initialized = TRUE + if(SS_INIT_NO_MESSAGE) + message_prefix = "Initialized [subsystem.name] subsystem within" + subsystem.initialized = TRUE + if(SS_INIT_NO_NEED) + else + warning("[subsystem.name] subsystem initialized, returning invalid result [initialize_result]. This is a bug.") - // Loop. - Master.StartProcessing(0) + var/message = "[message_prefix] [took_seconds] second[took_seconds == 1 ? "" : "s"]." + var/chat_message = chat_warning ? SPAN_BOLDWARNING(message) : SPAN_BOLDANNOUNCE(message) + if(tell_everyone && message_prefix) + to_chat(world, chat_message) + log_world(message) /datum/controller/master/proc/SetRunLevel(new_runlevel) var/old_runlevel = current_runlevel @@ -311,11 +413,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new sleep(delay) testing("Master starting processing") - var/rtn = Loop() - if (rtn > 0 || processing < 0) - return // This was suppose to happen. + var/started_stage + var/rtn = MC_LOOP_RTN_UNKNOWN + do + started_stage = init_stage_completed + rtn = Loop(started_stage) + while (rtn == MC_LOOP_RTN_NEWSTAGES && processing > 0 && started_stage < init_stage_completed) + + if (rtn >= MC_LOOP_RTN_GRACEFUL_EXIT || processing < 0) + return //this was suppose to happen. - // Loop ended, restart the mc. + //loop ended, restart the mc log_game("MC crashed or runtimed, restarting") message_admins("MC crashed or runtimed, restarting") var/rtn2 = Recreate_MC() @@ -324,23 +432,26 @@ GLOBAL_REAL(Master, /datum/controller/master) = new message_admins("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") Failsafe.defcon = 2 - /** * Main loop! * This is where the magic happens. */ -/datum/controller/master/proc/Loop() +/datum/controller/master/proc/Loop(init_stage) . = -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/SStickersubsystems = list() + var/list/ticker_subsystems = list() var/list/runlevel_sorted_subsystems = list(list()) // Ensure we always have at least one runlevel. var/timer = world.time for (var/thing in subsystems) var/datum/controller/subsystem/SS = thing - if (SS.subsystem_flags & SS_NO_FIRE) + // Skip non-firing + if(SS.subsystem_flags & SS_NO_FIRE) + continue + // Skip those that are after our init stage, or are not initialized. + if(SS.init_stage > init_stage) continue SS.queued_time = 0 @@ -352,7 +463,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new SS.recompute_wait_dt() if (SS.subsystem_flags & SS_TICKER) - SStickersubsystems += SS + ticker_subsystems += SS // Timer subsystems aren't allowed to bunch up, so we offset them a bit. timer += world.tick_lag * rand(0, 1) SS.next_fire = timer @@ -378,52 +489,74 @@ GLOBAL_REAL(Master, /datum/controller/master) = new * 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) */ - tim_sort(SStickersubsystems, GLOBAL_PROC_REF(cmp_subsystem_priority)) + tim_sort(ticker_subsystems, GLOBAL_PROC_REF(cmp_subsystem_priority)) for(var/I in runlevel_sorted_subsystems) tim_sort(I, GLOBAL_PROC_REF(cmp_subsystem_priority)) - I += SStickersubsystems + I += ticker_subsystems var/cached_runlevel = current_runlevel var/list/current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel] - init_timeofday = REALTIMEOFDAY - init_time = world.time + loop_start_timeofday = REALTIMEOFDAY + loop_start_time = world.time + init_stage_ticking = init_stage iteration = 1 + + var/init_stage_change_pending = FALSE var/error_level = 0 var/sleep_delta = 1 //# The actual loop. while (1) - tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) + var/new_tickdrift = (((REALTIMEOFDAY - loop_start_timeofday) - (world.time - loop_start_time)) / world.tick_lag) + tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, new_tickdrift)) var/starting_tick_usage = TICK_USAGE + + // check if we need to queue an init stage change + if(init_stage != init_stage_completed) + // set stage change pending; this'll stop new (but not paused / sleeping) subsystems from being queued to run. + init_stage_change_pending = TRUE + // ensure that 1. queue is empty and 2. no sleeping subsystems (as those don't stay in queue) exist + if(!queue_head && !laggy_sleeping_subsystem_check()) + return MC_LOOP_RTN_NEWSTAGES + + // If we're paused for some reason, well, pause. 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 + // If we're fully initialized, run normal tick heuristics. Otherwise, always run every tick. + if (init_stage == INIT_STAGE_MAX) + /** + * 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 increasingly longer times. + sleep_delta *= 2 + // Instruct CHECK_TICK to use a lot less tick than it usually wouldb e allowed to. + 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 + /** + * 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. + // Decay sleep_delta + sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) - if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) // We ran 3/4 of the way into the tick. - sleep_delta += 1 + // We ran 3/4 of the way into the tick + if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) + sleep_delta += 1 + else + sleep_delta = 1 //# Debug. if (make_runtime) @@ -435,96 +568,103 @@ GLOBAL_REAL(Master, /datum/controller/master) = new //# Now do the actual stuff. - //* **Experimental**: Check every tick. + // Check if runlevel changed if(cached_runlevel != current_runlevel) - // Resechedule subsystems. - var/list/old_subsystems = current_runlevel_subsystems + // Resechedule subsystems that are not already part of the runlevel, and are running behind. + var/list/old_runlevel_subsystems = current_runlevel_subsystems cached_runlevel = current_runlevel current_runlevel_subsystems = runlevel_sorted_subsystems[cached_runlevel] - - // Now we'll go through all the subsystems we want to offset and give them a next_fire. - for(var/datum/controller/subsystem/SS as anything in current_runlevel_subsystems) - // We only want to offset it if it's new and also behind. - if(SS.next_fire > world.time || (SS in old_subsystems)) + for(var/datum/controller/subsystem/adding_to_runlevel as anything in (current_runlevel_subsystems - old_runlevel_subsystems)) + if(adding_to_runlevel.next_fire > world.time) continue + adding_to_runlevel.next_fire = world.time + world.tick_lag * rand(0, DS2TICKS(min(adding_to_runlevel.wait, 2 SECONDS))) - SS.next_fire = world.time + world.tick_lag * rand(0, DS2TICKS(min(SS.wait, 2 SECONDS))) - - //* **Experimental**: Check every queue, every tick. - if (CheckQueue(current_runlevel_subsystems) <= 0 || CheckQueue(SStickersubsystems) <= 0) - if (!SoftReset(SStickersubsystems, runlevel_sorted_subsystems)) - log_world("MC: SoftReset() failed, crashing") - return + // If no init stage change is pending, re-queue any subsystems that are idle and are ready to fire. + if(!init_stage_change_pending) + if (CheckQueue(current_runlevel_subsystems) <= 0 || CheckQueue(ticker_subsystems) <= 0) + stack_trace("MC: CheckQueue failed. Current error_level is [round(error_level, 0.25)]") + if (!SoftReset(ticker_subsystems, runlevel_sorted_subsystems)) + error_level++ + CRASH("MC: SoftReset() failed, exiting loop()") - if (!error_level) - iteration++ - - error_level++ - current_ticklimit = TICK_LIMIT_RUNNING - sleep(10) - continue - - if (queue_head) - if (RunQueue() <= 0) - if (!SoftReset(SStickersubsystems, runlevel_sorted_subsystems)) - log_world("MC: SoftReset() failed, crashing") - return - - if (!error_level) + if (error_level < 2) //except for the first strike, stop incrmenting our iteration so failsafe enters defcon iteration++ - - error_level++ + else + cached_runlevel = null //3 strikes, Lets reset the runlevel lists current_ticklimit = TICK_LIMIT_RUNNING - sleep(10) + sleep((1 SECONDS) * error_level) + error_level++ 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) + var/run_result = RunQueue() + switch(run_result) + if(MC_RUN_RTN_UNKNOWN) + // Error running queue + stack_trace("MC: RunQueue failed. Current error_level is [round(error_level, 0.25)]") + if (error_level > 1) //skip the first error, + if (!SoftReset(ticker_subsystems, runlevel_sorted_subsystems)) + CRASH("MC: SoftReset() failed, exiting loop()") + + if (error_level <= 2) //after 3 strikes stop incrmenting our iteration so failsafe enters defcon + iteration++ + else + cached_runlevel = null //3 strikes, Lets also reset the runlevel lists + current_ticklimit = TICK_LIMIT_RUNNING + sleep((1 SECONDS) * error_level) + error_level++ + continue + error_level++ + + if (error_level > 0) + error_level = max(MC_AVERAGE_SLOW(error_level-1, error_level), 0) + 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)) + // We're about to go to sleep. Set the tick budget for other sleeping procs. + if (init_stage != INIT_STAGE_MAX) + // Still initializing, allow up to 100% dilation (50% of normal FPS). + current_ticklimit = TICK_LIMIT_RUNNING * 2 + else + // Already initialized; use normal heuristics. + 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. + * Checks a list of subsystems and enqueues anything that is idle and is ready to run. * - * Arguments: + * @params * * subsystemstocheck - List of systems to check. */ -/datum/controller/master/proc/CheckQueue(list/subsystemstocheck) +/datum/controller/master/proc/CheckQueue(list/datum/controller/subsystem/subsystemstocheck) . = FALSE // 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 + for (var/datum/controller/subsystem/SS as anything in subsystemstocheck) + if(!SS) + subsystemstocheck -= SS - SS = thing if (SS.state != SS_IDLE) continue - if (SS.can_fire <= 0) continue - if (SS.next_fire > world.time) continue - SS_flags = SS.subsystem_flags + var/SS_flags = SS.subsystem_flags + + // if it's flagged as NO_FIRE for some reason (probably because something faulted and shut it off), completely boot it if (SS_flags & SS_NO_FIRE) subsystemstocheck -= SS continue - + // keep timing: do not run faster than 1.33x of base speed, to prevent catch-up from going too fast if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time) continue @@ -532,10 +672,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new . = TRUE - /// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage. /datum/controller/master/proc/RunQueue() - . = FALSE + . = MC_RUN_RTN_UNKNOWN var/datum/controller/subsystem/queue_node var/queue_node_flags var/queue_node_priority @@ -545,11 +684,13 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/tick_precentage var/tick_remaining var/ran = TRUE // This is right. - var/ran_non_SSticker = FALSE + var/ran_non_ticker = FALSE var/bg_calc // Have we swtiched current_tick_budget to background mode yet? // the % of tick used by the current running subsystem var/queue_node_tick_usage + // is a subsystem stopping mid-cycle? this means either pausing or sleeping + var/something_is_mid_cycle /** * Keep running while we have stuff to run and we haven't gone over a tick @@ -573,7 +714,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new * (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_SSticker) + 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 @@ -599,47 +740,58 @@ GLOBAL_REAL(Master, /datum/controller/master) = new current_ticklimit = round(TICK_USAGE + tick_precentage) if (!(queue_node_flags & SS_TICKER)) - ran_non_SSticker = TRUE + ran_non_ticker = TRUE ran = TRUE - queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING) + queue_node_paused = (queue_node.state == SS_PAUSED) last_type_processed = queue_node queue_node.state = SS_RUNNING - // ignite / fire the head node + // ignite() will return immediately even if fire() sleeps. queue_node_tick_usage = TICK_USAGE var/state = queue_node.ignite(queue_node_paused) queue_node_tick_usage = TICK_USAGE - queue_node_tick_usage - if (state == SS_RUNNING) - state = SS_IDLE + switch(state) + if(SS_RUNNING) + // fire() ran to completion + state = SS_IDLE + if(SS_PAUSED) + // fire() ran and then pause()'d + something_is_mid_cycle = TRUE + if(SS_SLEEPING) + // fire() slept; the subsystem may or may not pause later + something_is_mid_cycle = TRUE + else + stack_trace("subsystem had unexpected state: [state]") + state = SS_IDLE current_tick_budget -= queue_node_priority - if (queue_node_tick_usage < 0) queue_node_tick_usage = 0 queue_node.tick_overrun = max(0, MC_AVG_FAST_UP_SLOW_DOWN(queue_node.tick_overrun, queue_node_tick_usage-tick_precentage)) queue_node.state = state - // if it paused mid-run, track that + // if it paused mid-run, track that ; do not eject it from the queue if (state == SS_PAUSED) queue_node.paused_ticks++ queue_node.paused_tick_usage += queue_node_tick_usage queue_node = queue_node.queue_next continue - // it did not pause; this is a complete run - + // it did not pause. either this is a complete run, or the subsystem is sleeping. + // in either case, we will track what we can and eject it; if it's sleeping, we can no longer manage the fire() call. queue_node.ticks = MC_AVERAGE(queue_node.ticks, queue_node.paused_ticks) - queue_node_tick_usage += queue_node.paused_tick_usage + queue_node_tick_usage += queue_node.paused_tick_usage queue_node.tick_usage = MC_AVERAGE_FAST(queue_node.tick_usage, queue_node_tick_usage) queue_node.cost = MC_AVERAGE_FAST(queue_node.cost, TICK_DELTA_TO_MS(queue_node_tick_usage)) + queue_node.paused_ticks = 0 queue_node.paused_tick_usage = 0 @@ -651,29 +803,14 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node.last_fire = world.time queue_node.times_fired++ - // schedule next run - if (queue_node_flags & SS_TICKER) - // ticker: run this many ticks after always - queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait) - else if (queue_node_flags & SS_POST_FIRE_TIMING) - // post fire timing: fire this much wait after current time, with tick overrun punishment - 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) - // keep timing: fire this much wait after *the last time we should have fired*, without tick overrun punishment - // **experimental**: do not keep timing past last 10 seconds, if something is running behind that much don't permanently accelerate it. - queue_node.next_fire = max(world.time - 10 SECONDS, queue_node.next_fire + queue_node.wait) - else - // normal: fire this much wait after when we were queued, with tick overrun punishment - 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. + // update the next time it should be available to queue + queue_node.update_next_fire() + // remove from queue queue_node.dequeue() // move to next queue_node = queue_node.queue_next - . = TRUE + . = something_is_mid_cycle ? MC_RUN_RTN_PARTIAL_COMPLETION : MC_RUN_RTN_FULL_COMPLETION /** * Resets the queue, and all subsystems, while filtering out the subsystem lists called if any mc's queue procs runtime or exit improperly. @@ -729,11 +866,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new log_world("MC: SoftReset: Finished.") . = TRUE - /datum/controller/master/stat_entry() return "(TickRate:[Master.processing]) (Iteration:[Master.iteration])" - /datum/controller/master/StartLoadingMap() // todo: this is kind of awful because this procs every subsystem unnecessarily // you might say this is microoptimizations but this is called a seriously high number of times during a load. @@ -748,19 +883,16 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/datum/controller/subsystem/SS = S SS.StopLoadingMap() -/* -/datum/controller/master/proc/UpdateTickRate() - if (!processing) - return - var/client_count = length(GLOB.clients) - if (client_count < CONFIG_GET(number/mc_tick_rate/disable_high_pop_mc_mode_amount)) - processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate) - else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount)) - processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate) -*/ - - /datum/controller/master/proc/OnConfigLoad() for (var/thing in subsystems) var/datum/controller/subsystem/SS = thing SS.OnConfigLoad() + +/** + * CitRP snowflake special: Check if any subsystems are sleeping. + */ +/datum/controller/master/proc/laggy_sleeping_subsystem_check() + for(var/datum/controller/subsystem/ss in subsystems) + if(ss.state == SS_SLEEPING) + return TRUE + return FALSE diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index b12deb3adb41..bf1333d28b4e 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -1,3 +1,26 @@ +/** + * Sorts subsystems for display (alphabetically). + */ +/proc/cmp_subsystem_display(datum/controller/subsystem/a, datum/controller/subsystem/b) + return sorttext(b.name, a.name) + +/** + * Sorts subsystems by init_order and init_stage. + */ +/proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b) + // Uses initial() so it can be used on types. + if(a.init_stage != b.init_stage) + return initial(a.init_stage) - initial(b.init_stage) + return initial(b.init_order) - initial(a.init_order) + +/** + * Sorts subsystems by priority, from lowest to highest. + * + * * This does not take into account SS_BACKGROUND and SS_TICKER flags! + */ +/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b) + return a.priority - b.priority + /** * # Subsystem base class * @@ -5,6 +28,15 @@ * * Simply define a child of this subsystem, using the [SUBSYSTEM_DEF] macro, and the MC will handle registration. * Changing the name is required. + * + * ## Sleeping + * + * If a subsystem sleeps during a tick, it is very, very bad. + * + * * Sleeping orphans the subsystem's call stack from the MC's. The MC is no longer able to control the subsystem's tick usage. + * * Sleeping is handled, but not perfect. This means the MC won't crash / do anything nasty, but normal timing will nonetheless + * be affected. + * * Sleeping causes things like paused tick tracking to be inaccurate. */ /datum/controller/subsystem //# Metadata; you should define these. @@ -15,12 +47,30 @@ */ name = "fire coderbus" + //* Initialization & Shutdown *// + /** * Order of initialization. * Higher numbers are initialized first, lower numbers later. * Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file. + * + * * This is secondary to [init_stage]. */ var/init_order = INIT_ORDER_DEFAULT + /** + * Which stage does this subsystem init at. Earlier stages can fire while later stages init. + * + * * This is higher in precedence than [init_order]. + * * This determines when the subsystem starts firing; besure to set this if you need ticking even if you are using SS_NO_INIT! + */ + var/init_stage = INIT_STAGE_WORLD + /** + * This variable is set to TRUE after the subsystem has been initialized. + * + * * If this subsystem is marked as SS_NO_FIRE, this still will be set to TRUE. We just won't call Initialize(). + * * This will remain FALSE if initialization is an explicit failure. + */ + var/initialized = FALSE /** * Time to wait (in deciseconds) between each call to fire(). @@ -44,16 +94,6 @@ */ var/subsystem_flags = NONE - /** - * Which stage does this subsystem init at. - * Earlier stages can fire while later stages init. - */ - //var/init_stage = INITSTAGE_MAIN - - /// This var is set to TRUE after the subsystem has been initialized. - // todo: see __DEFINES/controllers/_subsystems.dm; this shouldn't just be TRUE / FALSE - var/initialized = FALSE - /** * Set to FALSE 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 list that is checked every tick. @@ -68,16 +108,20 @@ //# The following variables are managed by the MC and should not be modified directly. - /// Last world.time we did a full ignite()/fire() without pausing + /// Last time ignite() was called and fire() ran to completion. /// /// * this is set by the MC's processing loop - /// * this is a heuristic; subsystems that have weird pausing behaviors won't work right with this. - /// * this is why it's crucial subsystems call pause() if they didn't finish a run! + /// * sleeping will count as a fire(), despite potentially not finishing a cycle. var/last_fire = 0 /// Scheduled world.time for next ignite(). /// /// * this is set by the MC's processing loop var/next_fire = 0 + /// Tracks the number of times fire() was ran to completion after an ignite(). + /// + /// * this is set by the MC's processing loop + /// * sleeping will count as a time fired, despite potentially not finishing a cycle. + var/times_fired = 0 /// Running average of the amount of milliseconds it takes the subsystem to complete a run (including all resumes but not the time spent paused). var/cost = 0 @@ -100,10 +144,10 @@ /// Tracks how many fires the subsystem takes to complete a run on average. var/ticks = 1 - /// Tracks the amount of completed runs for the subsystem. - var/times_fired = 0 - /// Time the subsystem entered the queue, (for timing and priority reasons). + /// + /// * This doesn't take into account pauses and sleeps. queued_time is the time it was initially put into queue + /// for a full firing cycle. var/queued_time = 0 /** @@ -140,21 +184,20 @@ /// /// * this is pretty much time dilation for this subsystem /// * this is based on wait time; e.g. 100% means we're running twice as slow, etc - var/tick_dilation_avg = 0 - /// How much of a tick (in percents of a tick) were we allocated last fire. - var/tick_allocation_last = 0 - /// How much of a tick (in percents of a tick) do we get allocated by the mc on avg. - var/tick_allocation_avg = 0 + /// * this is also reset by update_next_fire() if 'reset timing' arg is specified + var/tracked_average_dilation = 0 + /// Last world.time we did a full ignite()/fire() without pausing + /// + /// * this is set when fire() finishes, whether normally or by sleeping, without pausing. + /// * this is set by ignite() + var/tracked_last_completion = 0 + /** * # Do not blindly add vars here to the bottom, put it where it goes above. * # If your var only has two values, put it in as a flag. */ -// Do not override -// /datum/controller/subsystem/New() -// return - /** * Called before global vars are initialized * Called before Recover() @@ -182,34 +225,60 @@ return /** - * This is used so the mc knows when the subsystem sleeps. - * DO NOT OVERRIDE THIS. + * Used to initialize the subsystem AFTER the map has loaded. + * This is expected to be overriden by subtypes. + */ +/datum/controller/subsystem/Initialize() + return SS_INIT_NONE + +/** + * 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() + return TRUE + +/** + * Handles logic used to track fire() and sleeps. + * + * * If fire() sleeps, the return value will be SS_SLEEPING. + * * If fire() does not sleep, the return value will be SS_PAUSED or SS_RUNNING. + * + * @return the state we're now in. This return value is only used if fire() does not sleep. */ /datum/controller/subsystem/proc/ignite(resumed = FALSE) SHOULD_NOT_OVERRIDE(TRUE) + // This makes us return the last return value when we (or anything we call; e.g. fire()) sleeps. set waitfor = FALSE + // Paranoid set. . = SS_IDLE - - tick_allocation_last = Master.current_ticklimit-(TICK_USAGE) - tick_allocation_avg = MC_AVERAGE(tick_allocation_avg, tick_allocation_last) - + // Set to SLEEPING so the MC knows if anything below this sleeps. . = SS_SLEEPING + // Fire. This can potentially sleep. If it does, the rest of the proc will be disregarded by the MC. fire(resumed) + // If fire() does not sleep, this will set our return value to RUNNING or PAUSED, depending on if we hit pause(). + // If fire() does sleep, 'state' will have already been overwritten by the MC to be SLEEPING, + // and if pause() is hit after the sleep, it will be changed to PAUSING. . = state - if (state == SS_SLEEPING) - state = SS_IDLE - if (state == SS_PAUSING) - var/QT = queued_time - enqueue() - state = SS_PAUSED - queued_time = QT - else - // track time between runs - var/full_run_took = world.time - last_fire - var/new_tick_dilation = (full_run_took / nominal_dt_ds) * 100 - 100 - tick_dilation_avg = max(0, MC_AVERAGE_SLOW(tick_dilation_avg, new_tick_dilation)) - last_fire = world.time + switch(state) + if(SS_PAUSING) + // sleeping & did pause; MC already moved on, and we've been ejected from queue. Re-insert into queue. + var/was_queued_at = queued_time + enqueue() + state = SS_PAUSED + queued_time = was_queued_at + if(SS_RUNNING, SS_SLEEPING) + // full run finished ; track tick dilation average, last fire, and prepare to re-insert into queue. + var/full_run_took = world.time - tracked_last_completion + var/new_tick_dilation = (full_run_took / nominal_dt_ds) * 100 - 100 + tracked_average_dilation = max(0, MC_AVERAGE_SLOW(tracked_average_dilation, new_tick_dilation)) + tracked_last_completion = world.time + state = SS_IDLE + if(SS_PAUSED) + // we paused; nothing special, move on. the MC will handle it. + else + CRASH("unexpected state in [src] ([type]): [state]") /** * previously, this would have been named 'process()' but that name is used everywhere for different things! @@ -222,13 +291,40 @@ /datum/controller/subsystem/Destroy() dequeue() - can_fire = 0 + can_fire = FALSE subsystem_flags |= SS_NO_FIRE if (Master) Master.subsystems -= src - return ..() +/** + * Updates `next_fire` for the next run. + * + * @params + * * reset_time - Entirely reset the subsystem's stateful time tracking including tick-overrun, post fire timing, etc. + */ +/datum/controller/subsystem/proc/update_next_fire(reset_time) + if(reset_time) + next_fire = (subsystem_flags & SS_TICKER) ? (world.time + (world.tick_lag * wait)) : (world.time + wait) + tracked_last_completion = world.time + return + + var/queue_node_flags = subsystem_flags + + if (queue_node_flags & SS_TICKER) + // ticker: run this many ticks after always + next_fire = world.time + (world.tick_lag * wait) + else if (queue_node_flags & SS_POST_FIRE_TIMING) + // post fire timing: fire this much wait after current time, with tick overrun punishment + next_fire = world.time + wait + (world.tick_lag * (tick_overrun / 100)) + else if (queue_node_flags & SS_KEEP_TIMING) + // keep timing: fire this much wait after *the last time we should have fired*, without tick overrun punishment + // **experimental**: do not keep timing past last 10 seconds, if something is running behind that much don't permanently accelerate it. + next_fire = max(world.time - 10 SECONDS, next_fire + wait) + else + // normal: fire this much wait after when we were queued, with tick overrun punishment + next_fire = queued_time + wait + (world.tick_lag * (tick_overrun / 100)) + /** * Queue it to run. * (we loop thru a linked list until we get to the end or find the right point) @@ -321,7 +417,6 @@ switch(state) if(SS_RUNNING) state = SS_PAUSED - if(SS_SLEEPING) state = SS_PAUSING @@ -329,43 +424,50 @@ /datum/controller/subsystem/proc/OnConfigLoad() return -/** - * Used to initialize the subsystem AFTER the map has loaded. - * This is expected to be overriden by subtypes. - */ -/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, SPAN_BOLDANNOUNCE("[msg]")) - log_world(msg) - log_subsystem("INIT", 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() if(can_fire && !(SS_NO_FIRE & subsystem_flags)) - . = "[round(cost,1)]ms | D:[round(tick_dilation_avg,1)]% | U:[round(tick_usage,1)]% | O:[round(tick_overrun,1)]% | T:[round(ticks,0.1)] " + . = "[round(cost,1)]ms | D:[round(tracked_average_dilation,1)]% | U:[round(tick_usage,1)]% | O:[round(tick_overrun,1)]% | T:[round(ticks,0.1)] " else . = "OFFLINE " /datum/controller/subsystem/stat_key() - return can_fire? "\[[state_letter()]\][name]" : name + return "\[[state_letter()]\] [name]" +/** + * Returns our status symbol. + */ /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) - . = " " + // R: running + // Q: queued + // P: pausing / paused + // S: sleeping + // I: initializing + // D: done initializing, waiting for init stage to finish + // blank: idle + if(Master.init_stage_completed >= init_stage) + switch (state) + if (SS_RUNNING) + . = "ï¼²" + if (SS_QUEUED) + . = "ï¼±" + if (SS_PAUSED, SS_PAUSING) + . = "ï¼°" + if (SS_SLEEPING) + . = "ï¼³" + if (SS_IDLE) + . = "   " + else + if(subsystem_flags & SS_NO_INIT) + . = "D" + if(src == Master.current_initializing_subsystem) + . = "I" + else if(initialized) + . = "D" + else + . = "ï¼·" /** * Could be used to postpone a costly subsystem for (default one) var/cycles, cycles. @@ -375,14 +477,6 @@ 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() - return TRUE - - /datum/controller/subsystem/vv_edit_var(var_name, var_value) switch (var_name) if (NAMEOF(src, can_fire)) diff --git a/code/controllers/subsystem/__test_bad_subsystem_sleeps.dm b/code/controllers/subsystem/__test_bad_subsystem_sleeps.dm new file mode 100644 index 000000000000..0b9f7ddd8833 --- /dev/null +++ b/code/controllers/subsystem/__test_bad_subsystem_sleeps.dm @@ -0,0 +1,44 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Used to intentionally fuck up the MC fire() by sleeping ridiculous + * amounts of time. + * + * Used to test sleep handling and stage change behavior. + * + * **This file should not be ticked outside of special development testing.** + */ +#warn Bad subsystem sleep tester is ticked. + +SUBSYSTEM_DEF(__test_bad_subsystem_sleeps) + name = "-- TEST BAD SUBSYSTEM SLEEPS --" + init_stage = INIT_STAGE_BACKEND + init_order = 100 // this doesn't matter tbh; what matters is it runs as soon as possible so the test goes faster + subsystem_flags = SS_NO_INIT + runlevels = RUNLEVELS_ALL + + var/is_currently_sleeping = FALSE + var/should_resume = FALSE + var/first_fire = TRUE + var/mc_init_stage_at_start_of_sleep + +/datum/controller/subsystem/__test_bad_subsystem_sleeps/fire(resumed) + if(first_fire) + is_currently_sleeping = TRUE + mc_init_stage_at_start_of_sleep = Master.init_stage_ticking + sleep(10 SECONDS) + is_currently_sleeping = FALSE + if(mc_init_stage_at_start_of_sleep != Master.init_stage_ticking) + CRASH("master controller moved on when we were trying to block init") + should_resume = TRUE + pause() + return + if(is_currently_sleeping) + CRASH("re-fired while sleeping") + if(!resumed && should_resume) + CRASH("wasn't resumed when we should resume") + if(resumed) + should_resume = FALSE + if(mc_init_stage_at_start_of_sleep != Master.init_stage_ticking) + CRASH("master controller moved on when we were trying to block init") diff --git a/code/controllers/subsystem/ai_holders.dm b/code/controllers/subsystem/ai_holders.dm index 2ecfc9b96c02..a4b304c37575 100644 --- a/code/controllers/subsystem/ai_holders.dm +++ b/code/controllers/subsystem/ai_holders.dm @@ -14,6 +14,7 @@ SUBSYSTEM_DEF(ai_holders) subsystem_flags = NONE priority = FIRE_PRIORITY_AI_HOLDERS init_order = INIT_ORDER_AI_HOLDERS + init_stage = INIT_STAGE_EARLY wait = 0 /// all ticking ai holders @@ -37,7 +38,7 @@ SUBSYSTEM_DEF(ai_holders) /datum/controller/subsystem/ai_holders/Initialize() active_holders = list() rebuild() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/ai_holders/on_ticklag_changed(old_ticklag, new_ticklag) rebuild() diff --git a/code/controllers/subsystem/ai_movement.dm b/code/controllers/subsystem/ai_movement.dm index 619744ad82fb..0c228e285c4e 100644 --- a/code/controllers/subsystem/ai_movement.dm +++ b/code/controllers/subsystem/ai_movement.dm @@ -18,6 +18,7 @@ SUBSYSTEM_DEF(ai_movement) subsystem_flags = NONE priority = FIRE_PRIORITY_AI_MOVEMENT init_order = INIT_ORDER_AI_MOVEMENT + init_stage = INIT_STAGE_EARLY wait = 0 /// ais that are moving using a movement handler right now @@ -45,7 +46,7 @@ SUBSYSTEM_DEF(ai_movement) moving_ais = list() rebuild() init_ai_pathfinders() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/ai_movement/on_ticklag_changed(old_ticklag, new_ticklag) rebuild() @@ -86,8 +87,10 @@ SUBSYSTEM_DEF(ai_movement) if(reschedule_delay) // eject; we don't change being_processed.ticking_(next|previous) if(being_processed.movement_bucket_next == being_processed) + // this was the only holder in the bucket buckets[bucket_offset] = null else + // this was not the only holder in the bucket, stitch it back together after the ejection. buckets[bucket_offset] = being_processed.movement_bucket_next being_processed.movement_bucket_next.movement_bucket_prev = being_processed.movement_bucket_prev being_processed.movement_bucket_prev.movement_bucket_next = being_processed.movement_bucket_next @@ -105,7 +108,7 @@ SUBSYSTEM_DEF(ai_movement) being_processed.movement_bucket_next = being_processed.movement_bucket_prev = being_processed being_processed.movement_bucket_position = inject_offset else - // get out + // get out if not rescheduling unregister_moving(being_processed) if(MC_TICK_CHECK) break @@ -136,7 +139,7 @@ SUBSYSTEM_DEF(ai_movement) moving_ais -= holder stack_trace("bad holder found") continue - // doubly linked list inject + // circular double-linked list inject if(!isnull(buckets[position])) var/datum/ai_holder/existing = buckets[position] holder.movement_bucket_next = existing.movement_bucket_next @@ -156,7 +159,7 @@ SUBSYSTEM_DEF(ai_movement) buckets.len = BUCKET_AMOUNT for(var/i in 1 to length(moving_ais)) var/datum/ai_holder/holder = buckets[i] - holder.movement_bucket_next = holder.movement_bucket_prev = null + holder.movement_bucket_next = holder.movement_bucket_prev = holder holder.movement_bucket_position = i /** diff --git a/code/controllers/subsystem/ai_scheduling.dm b/code/controllers/subsystem/ai_scheduling.dm index 0919a8e4c08a..13d7042b2d9b 100644 --- a/code/controllers/subsystem/ai_scheduling.dm +++ b/code/controllers/subsystem/ai_scheduling.dm @@ -14,6 +14,7 @@ SUBSYSTEM_DEF(ai_scheduling) subsystem_flags = NONE priority = FIRE_PRIORITY_AI_SCHEDULING init_order = INIT_ORDER_AI_SCHEDULING + init_stage = INIT_STAGE_EARLY wait = 0 /// rolling bucket list; these hold the head node of linked ai_holders. @@ -34,7 +35,7 @@ SUBSYSTEM_DEF(ai_scheduling) /datum/controller/subsystem/ai_scheduling/Initialize() rebuild() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/ai_scheduling/on_ticklag_changed(old_ticklag, new_ticklag) rebuild() @@ -116,6 +117,10 @@ SUBSYSTEM_DEF(ai_scheduling) * List of things allowed to use this: * * /datum/ai_holder * * /datum/ai_network + * + * Quirks: + * * This will never sleep on invocation. If the called proc sleeps, we blow right past. + * * Try to not have the called proc be ridiculously expensive as we are on a very fast-firing subsystem. */ /datum/ai_callback var/proc_ref @@ -143,4 +148,5 @@ SUBSYSTEM_DEF(ai_scheduling) /datum/ai_callback/proc/invoke() SHOULD_NOT_SLEEP(TRUE) + set waitfor = FALSE call(parent, proc_ref)(arglist(arguments)) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index a7b59ec711b0..4d5637252898 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -89,7 +89,7 @@ SUBSYSTEM_DEF(air) continue generated_atmospheres[id] = SSair.generated_atmospheres[id] -/datum/controller/subsystem/air/Initialize(timeofday) +/datum/controller/subsystem/air/Initialize() #ifndef FASTBOOT_DISABLE_ZONES report_progress("Initializing [name] subsystem...") @@ -114,9 +114,7 @@ SUBSYSTEM_DEF(air) startup_active_edge_log = edge_log.Copy() //! Fancy blockquote of data. - var/time = (REALTIMEOFDAY - timeofday) / 10 var/list/blockquote_data = list( - SPAN_BOLDANNOUNCE("Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!
"), SPAN_DEBUGINFO("Total Zones: [zones.len]"), SPAN_DEBUGINFO("\nTotal Edges: [edges.len]"), SPAN_DEBUGINFO("\nTotal Active Edges: [active_edges.len ? SPAN_DANGER("[active_edges.len]") : "None"]"), @@ -131,7 +129,8 @@ SUBSYSTEM_DEF(air) ) #endif - return ..() + + return SS_INIT_SUCCESS /datum/controller/subsystem/air/fire(resumed = FALSE) var/timer @@ -144,11 +143,11 @@ SUBSYSTEM_DEF(air) current_step = SSAIR_TURFS current_cycle++ - INTERNAL_PROCESS_STEP(SSAIR_TURFS, TRUE, process_tiles_to_update, cost_turfs, SSAIR_EDGES) - INTERNAL_PROCESS_STEP(SSAIR_EDGES, FALSE, process_active_edges, cost_edges, SSAIR_FIREZONES) - INTERNAL_PROCESS_STEP(SSAIR_FIREZONES, FALSE, process_active_fire_zones, cost_firezones, SSAIR_HOTSPOTS) - INTERNAL_PROCESS_STEP(SSAIR_HOTSPOTS, FALSE, process_active_hotspots, cost_hotspots, SSAIR_ZONES) - INTERNAL_PROCESS_STEP(SSAIR_ZONES, FALSE, process_zones_to_update, cost_zones, SSAIR_DONE) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSAIR_TURFS, TRUE, process_tiles_to_update, cost_turfs, SSAIR_EDGES) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSAIR_EDGES, FALSE, process_active_edges, cost_edges, SSAIR_FIREZONES) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSAIR_FIREZONES, FALSE, process_active_fire_zones, cost_firezones, SSAIR_HOTSPOTS) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSAIR_HOTSPOTS, FALSE, process_active_hotspots, cost_hotspots, SSAIR_ZONES) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSAIR_ZONES, FALSE, process_zones_to_update, cost_zones, SSAIR_DONE) // Okay, we're done! Woo! Got thru a whole SSair cycle! if(LAZYLEN(currentrun) != 0) diff --git a/code/controllers/subsystem/alarm.dm b/code/controllers/subsystem/alarm.dm index 749ce940ffcb..9a20e427768e 100644 --- a/code/controllers/subsystem/alarm.dm +++ b/code/controllers/subsystem/alarm.dm @@ -17,7 +17,7 @@ SUBSYSTEM_DEF(alarms) /datum/controller/subsystem/alarms/Initialize() SSalarms.all_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, motion_alarm, power_alarm) - . = ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/alarms/fire(resumed = FALSE) if(!resumed) diff --git a/code/controllers/subsystem/ambient_lighting.dm b/code/controllers/subsystem/ambient_lighting.dm index 222be9449a7f..56b5e46c45c5 100644 --- a/code/controllers/subsystem/ambient_lighting.dm +++ b/code/controllers/subsystem/ambient_lighting.dm @@ -13,7 +13,7 @@ SUBSYSTEM_DEF(ambient_lighting) /datum/controller/subsystem/ambient_lighting/Initialize(start_timeofday) fire(FALSE, TRUE) - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/ambient_lighting/fire(resumed = FALSE, no_mc_tick = FALSE) var/list/curr = queued diff --git a/code/controllers/subsystem/ao.dm b/code/controllers/subsystem/ao.dm index f038c6ff78d8..2baa405c8281 100644 --- a/code/controllers/subsystem/ao.dm +++ b/code/controllers/subsystem/ao.dm @@ -13,7 +13,7 @@ SUBSYSTEM_DEF(ao) /datum/controller/subsystem/ao/Initialize(start_timeofday) fire(FALSE, TRUE) - ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/ao/fire(resumed = 0, no_mc_tick = FALSE) if (!resumed) diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm index 210359902069..eff83427a200 100644 --- a/code/controllers/subsystem/assets.dm +++ b/code/controllers/subsystem/assets.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(assets) name = "Assets" init_order = INIT_ORDER_ASSETS + init_stage = INIT_STAGE_EARLY subsystem_flags = SS_NO_FIRE /// asset packs by type; this is for hardcoded assets @@ -31,13 +32,11 @@ SUBSYSTEM_DEF(assets) /datum/controller/subsystem/assets/Initialize(timeofday) // detect_cache_worthiness() - for(var/datum/asset_pack/path as anything in typesof(/datum/asset_pack)) if(path == initial(path.abstract_type)) continue var/datum/asset_pack/instance = new path register_asset_pack(instance, TRUE) - #ifndef DO_NOT_DEFER_ASSETS if(initial(instance.load_deferred)) continue @@ -48,8 +47,7 @@ SUBSYSTEM_DEF(assets) #else instance.load() #endif - - return ..() + return SS_INIT_SUCCESS /** * register an asset pack to make it able to be resolved or loaded diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index 74ebf6cd6021..33e4e1156c9b 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(atoms) name = "Atoms" init_order = INIT_ORDER_ATOMS + init_stage = INIT_STAGE_WORLD subsystem_flags = SS_NO_FIRE /// A stack of list(source, desired initialized state) @@ -24,28 +25,29 @@ SUBSYSTEM_DEF(atoms) var/list/mapload_init_times = list() #endif - initialized = INITIALIZATION_INSSATOMS + /// Status to use for atom New() / Initialize(). + var/atom_init_status = ATOM_INIT_IN_SUBSYSTEM /datum/controller/subsystem/atoms/Initialize(timeofday) init_start_time = world.time - initialized = INITIALIZATION_INNEW_MAPLOAD + atom_init_status = ATOM_INIT_IN_NEW_MAPLOAD InitializeAtoms() - initialized = INITIALIZATION_INNEW_REGULAR + atom_init_status = ATOM_INIT_IN_NEW_REGULAR - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return) - if(initialized == INITIALIZATION_INSSATOMS) + if(atom_init_status == ATOM_INIT_IN_SUBSYSTEM) return // Generate a unique mapload source for this run of InitializeAtoms var/static/uid = 0 uid = (uid + 1) % (SHORT_REAL_LIMIT - 1) var/source = "subsystem init [uid]" - set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, source) + set_tracked_initalized(ATOM_INIT_IN_NEW_MAPLOAD, source) - // This may look a bit odd, but if the actual atom creation runtimes for some reason, we absolutely need to set initialized BACK + // This may look a bit odd, but if the actual atom creation runtimes for some reason, we absolutely need to set atom_init_status BACK CreateAtoms(atoms, atoms_to_return, source) clear_tracked_initalize(source) @@ -56,6 +58,7 @@ SUBSYSTEM_DEF(atoms) if(QDELETED(A)) continue A.LateInitialize() + CHECK_TICK testing("Late initialized [late_loaders.len] atoms") late_loaders.Cut() @@ -97,7 +100,7 @@ SUBSYSTEM_DEF(atoms) clear_tracked_initalize(mapload_source) stoplag() if(mapload_source) - set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, mapload_source) + set_tracked_initalized(ATOM_INIT_IN_NEW_MAPLOAD, mapload_source) PROFILE_INIT_ATOM_BEGIN() InitAtom(A, TRUE, mapload_arg) PROFILE_INIT_ATOM_END(A) @@ -118,7 +121,7 @@ SUBSYSTEM_DEF(atoms) clear_tracked_initalize(mapload_source) stoplag() if(mapload_source) - set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, mapload_source) + set_tracked_initalized(ATOM_INIT_IN_NEW_MAPLOAD, mapload_source) testing("Initialized [count] atoms") @@ -132,14 +135,14 @@ SUBSYSTEM_DEF(atoms) * * ... - rest of args are passed to new() / Initialize(). */ /datum/controller/subsystem/atoms/proc/instance_atom_immediate(path, mapload, atom/where, ...) - var/old_initialized = initialized - initialized = mapload? INITIALIZATION_INNEW_MAPLOAD : INITIALIZATION_INNEW_REGULAR + var/old_init_status = atom_init_status + atom_init_status = mapload? ATOM_INIT_IN_NEW_MAPLOAD : ATOM_INIT_IN_NEW_REGULAR var/atom/created = new path(arglist(args.Copy())) - initialized = old_initialized + atom_init_status = old_init_status return created /datum/controller/subsystem/atoms/proc/map_loader_begin(source) - set_tracked_initalized(INITIALIZATION_INSSATOMS, source) + set_tracked_initalized(ATOM_INIT_IN_SUBSYSTEM, source) /datum/controller/subsystem/atoms/proc/map_loader_stop(source) clear_tracked_initalize(source) @@ -155,9 +158,9 @@ SUBSYSTEM_DEF(atoms) /// Accepts a state and a source, the most recent state is used, sources exist to prevent overriding old values accidentally /datum/controller/subsystem/atoms/proc/set_tracked_initalized(state, source) if(!length(initialized_state)) - base_initialized = initialized + base_initialized = atom_init_status initialized_state += list(list(source, state)) - initialized = state + atom_init_status = state /datum/controller/subsystem/atoms/proc/clear_tracked_initalize(source) if(!length(initialized_state)) @@ -168,17 +171,17 @@ SUBSYSTEM_DEF(atoms) break if(!length(initialized_state)) - initialized = base_initialized - base_initialized = INITIALIZATION_INNEW_REGULAR + atom_init_status = base_initialized + base_initialized = ATOM_INIT_IN_NEW_REGULAR return - initialized = initialized_state[length(initialized_state)][2] + atom_init_status = initialized_state[length(initialized_state)][2] /// Returns TRUE if anything is currently being initialized /datum/controller/subsystem/atoms/proc/initializing_something() return length(initialized_state) > 1 /datum/controller/subsystem/atoms/proc/init_map_bounds(list/bounds) - if (initialized == INITIALIZATION_INSSATOMS) + if (atom_init_status == ATOM_INIT_IN_SUBSYSTEM) return // Let proper initialisation handle it later var/prev_shuttle_queue_state = SSshuttle.block_init_queue @@ -228,8 +231,8 @@ SUBSYSTEM_DEF(atoms) /datum/controller/subsystem/atoms/Recover() - initialized = SSatoms.initialized - if(initialized == INITIALIZATION_INNEW_MAPLOAD) + atom_init_status = SSatoms.atom_init_status + if(atom_init_status == ATOM_INIT_IN_NEW_MAPLOAD) InitializeAtoms() initialized_state = SSatoms.initialized_state BadInitializeCalls = SSatoms.BadInitializeCalls @@ -250,7 +253,7 @@ SUBSYSTEM_DEF(atoms) /// Prepares an atom to be deleted once the atoms SS is initialized. /datum/controller/subsystem/atoms/proc/prepare_deletion(atom/target) - if (initialized == INITIALIZATION_INNEW_REGULAR) + if (atom_init_status == ATOM_INIT_IN_NEW_REGULAR) // Atoms SS has already completed, just kill it now. qdel(target) else diff --git a/code/controllers/subsystem/automata.dm b/code/controllers/subsystem/automata.dm index 6e7777980137..e1940555a400 100644 --- a/code/controllers/subsystem/automata.dm +++ b/code/controllers/subsystem/automata.dm @@ -4,7 +4,7 @@ SUBSYSTEM_DEF(automata) name = "Automata" wait = 1 - subsystem_flags = SS_TICKER + subsystem_flags = SS_TICKER | SS_NO_INIT /// all automata in world var/static/list/datum/automata/automatons = list() diff --git a/code/controllers/subsystem/characters/_characters.dm b/code/controllers/subsystem/characters/_characters.dm index 139ea5f58572..3fce249db414 100644 --- a/code/controllers/subsystem/characters/_characters.dm +++ b/code/controllers/subsystem/characters/_characters.dm @@ -29,7 +29,7 @@ SUBSYSTEM_DEF(characters) stack_trace("what?") continue P.Initialize() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/characters/Recover() . = ..() diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm index 3e20925ad97c..67b7f81a6ca8 100644 --- a/code/controllers/subsystem/chat.dm +++ b/code/controllers/subsystem/chat.dm @@ -5,11 +5,12 @@ SUBSYSTEM_DEF(chat) name = "Chat" - subsystem_flags = NONE + subsystem_flags = SS_NO_INIT wait = 0.25 // scale up to 40 fps runlevels = RUNLEVELS_ALL priority = FIRE_PRIORITY_CHAT init_order = INIT_ORDER_CHAT + init_stage = INIT_STAGE_LATE var/list/payload_by_client = list() diff --git a/code/controllers/subsystem/chemistry.dm b/code/controllers/subsystem/chemistry.dm index ec025f09b03e..602a248c0e92 100644 --- a/code/controllers/subsystem/chemistry.dm +++ b/code/controllers/subsystem/chemistry.dm @@ -2,6 +2,7 @@ SUBSYSTEM_DEF(chemistry) name = "Chemistry" wait = 10 init_order = INIT_ORDER_CHEMISTRY + subsystem_flags = SS_NO_INIT /// id to instance dict of reagents var/list/reagent_lookup = list() diff --git a/code/controllers/subsystem/dbcore/_dbcore.dm b/code/controllers/subsystem/dbcore/_dbcore.dm index 728980a93fc8..106a7fe09119 100644 --- a/code/controllers/subsystem/dbcore/_dbcore.dm +++ b/code/controllers/subsystem/dbcore/_dbcore.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(dbcore) subsystem_flags = SS_BACKGROUND wait = 1 MINUTES init_order = INIT_ORDER_DBCORE + init_stage = INIT_STAGE_BACKEND var/failed_connection_timeout = 0 var/schema_mismatch = 0 @@ -26,7 +27,7 @@ SUBSYSTEM_DEF(dbcore) if(2) message_admins("Could not get schema version from database") - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/dbcore/fire() for(var/I in active_queries) diff --git a/code/controllers/subsystem/early_init.dm b/code/controllers/subsystem/early_init.dm index 603af16e47fc..0460bc5da4dd 100644 --- a/code/controllers/subsystem/early_init.dm +++ b/code/controllers/subsystem/early_init.dm @@ -1,9 +1,10 @@ SUBSYSTEM_DEF(early_init) name = "Early Init" init_order = INIT_ORDER_EARLY_INIT + init_stage = INIT_STAGE_EARLY subsystem_flags = SS_NO_FIRE /datum/controller/subsystem/early_init/Initialize() init_inventory_slot_meta() init_crayon_decal_meta() - return ..() + return SS_INIT_SUCCESS diff --git a/code/controllers/subsystem/emergency_shuttle.dm b/code/controllers/subsystem/emergency_shuttle.dm index b1c07d9cc338..767db15201d7 100644 --- a/code/controllers/subsystem/emergency_shuttle.dm +++ b/code/controllers/subsystem/emergency_shuttle.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(emergencyshuttle) name = "Emergency Shuttle" wait = 20 + subsystem_flags = SS_NO_INIT /datum/controller/subsystem/emergencyshuttle var/datum/shuttle/autodock/ferry/emergency/shuttle // Set in shuttle_emergency.dm TODO - is it really? diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm index f846f197e125..b04bfb4b2e39 100644 --- a/code/controllers/subsystem/events.dm +++ b/code/controllers/subsystem/events.dm @@ -33,7 +33,7 @@ SUBSYSTEM_DEF(events) // unfortunately, character setup server startup hooks fire before /Initialize so :/ // SScharactersetup but not shit when :) InitializeHolidays() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/events/fire(resumed) if (!resumed) diff --git a/code/controllers/subsystem/fail2topic.dm b/code/controllers/subsystem/fail2topic.dm index cabfd73553a1..74e5c24040cf 100644 --- a/code/controllers/subsystem/fail2topic.dm +++ b/code/controllers/subsystem/fail2topic.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(fail2topic) name = "Fail2Topic" init_order = INIT_ORDER_FAIL2TOPIC + init_stage = INIT_STAGE_BACKEND subsystem_flags = SS_BACKGROUND runlevels = ALL @@ -28,7 +29,7 @@ SUBSYSTEM_DEF(fail2topic) subsystem_flags |= SS_NO_FIRE can_fire = FALSE - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/fail2topic/fire() if(length(rate_limiting)) diff --git a/code/controllers/subsystem/game_master.dm b/code/controllers/subsystem/game_master.dm index b6ec462c586c..6167c617639e 100644 --- a/code/controllers/subsystem/game_master.dm +++ b/code/controllers/subsystem/game_master.dm @@ -30,7 +30,7 @@ SUBSYSTEM_DEF(gamemaster) suspended = FALSE else sleep(30 SECONDS) - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/gamemaster/fire(resumed) if(SSticker && SSticker.current_state == GAME_STATE_PLAYING && !suspended) diff --git a/code/controllers/subsystem/holomaps.dm b/code/controllers/subsystem/holomaps.dm index b0ed4e6325a8..f26e2d178eb2 100644 --- a/code/controllers/subsystem/holomaps.dm +++ b/code/controllers/subsystem/holomaps.dm @@ -16,4 +16,4 @@ SUBSYSTEM_DEF(holomaps) /datum/controller/subsystem/holomaps/Initialize(timeofday) generateHoloMinimaps() - . = ..() + return SS_INIT_SUCCESS diff --git a/code/controllers/subsystem/icon_smooth.dm b/code/controllers/subsystem/icon_smooth.dm index 737fcee26167..a2008657cea3 100644 --- a/code/controllers/subsystem/icon_smooth.dm +++ b/code/controllers/subsystem/icon_smooth.dm @@ -44,9 +44,7 @@ SUBSYSTEM_DEF(icon_smooth) smoothing_atom.smooth_icon() CHECK_TICK #endif - - return ..() - + return SS_INIT_SUCCESS /datum/controller/subsystem/icon_smooth/proc/add_to_queue(atom/thing) if(thing.smoothing_flags & SMOOTH_QUEUED) diff --git a/code/controllers/subsystem/inactivity.dm b/code/controllers/subsystem/inactivity.dm index 53e15c3bc179..70d04751155b 100644 --- a/code/controllers/subsystem/inactivity.dm +++ b/code/controllers/subsystem/inactivity.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(inactivity) - name = "AFK Kick" + name = "Inactivity" wait = 600 - subsystem_flags = SS_BACKGROUND | SS_NO_TICK_CHECK + subsystem_flags = SS_BACKGROUND | SS_NO_TICK_CHECK | SS_NO_INIT /datum/controller/subsystem/inactivity/fire() if(config_legacy.kick_inactive) diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm index 31d921504741..b1c113aa2d62 100644 --- a/code/controllers/subsystem/input.dm +++ b/code/controllers/subsystem/input.dm @@ -2,7 +2,7 @@ SUBSYSTEM_DEF(input) name = "Input" wait = 0.25 // scale to 40 fps init_order = INIT_ORDER_INPUT - subsystem_flags = NONE + init_stage = INIT_STAGE_EARLY priority = FIRE_PRIORITY_INPUT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY @@ -23,16 +23,13 @@ SUBSYSTEM_DEF(input) /datum/controller/subsystem/input/Initialize() setup_macrosets() - // set init early so refresh macrosets works - initialized = TRUE refresh_client_macro_sets() - - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/input/Recover() - initialized = SSinput.initialized setup_macrosets() refresh_client_macro_sets() + initialized = SSinput.initialized /// Sets up the key list for classic mode for when badmins screw up vv's. /datum/controller/subsystem/input/proc/setup_macrosets() diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index e35e0c9e3941..170e520e4cfe 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -3,8 +3,7 @@ */ SUBSYSTEM_DEF(ipintel) name = "IPIntel" - init_order = INIT_ORDER_IPINTEL - subsystem_flags = SS_NO_FIRE + subsystem_flags = SS_NO_FIRE | SS_NO_INIT /// is ipintel enabled? var/enabled = FALSE diff --git a/code/controllers/subsystem/job/_job.dm b/code/controllers/subsystem/job/_job.dm index 8d3e88e0349b..dc63fb444b0f 100644 --- a/code/controllers/subsystem/job/_job.dm +++ b/code/controllers/subsystem/job/_job.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(job) name = "Job" init_order = INIT_ORDER_JOBS + init_stage = INIT_STAGE_EARLY subsystem_flags = SS_NO_FIRE /// List of all jobs @@ -27,7 +28,7 @@ SUBSYSTEM_DEF(job) if(!length(occupations)) setup_occupations() reconstruct_job_ui_caches() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/job/Recover() init_access() diff --git a/code/controllers/subsystem/legacy_atc.dm b/code/controllers/subsystem/legacy_atc.dm index 68ed238958f5..407bc57ad910 100644 --- a/code/controllers/subsystem/legacy_atc.dm +++ b/code/controllers/subsystem/legacy_atc.dm @@ -41,7 +41,7 @@ SUBSYSTEM_DEF(legacy_atc) next_message = world.time + initial_delay START_PROCESSING(SSobj, src) - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/legacy_atc/process(delta_time) if(world.time >= next_message) diff --git a/code/controllers/subsystem/legacy_lore.dm b/code/controllers/subsystem/legacy_lore.dm index 163dedfd9e91..56001882b5b8 100644 --- a/code/controllers/subsystem/legacy_lore.dm +++ b/code/controllers/subsystem/legacy_lore.dm @@ -13,4 +13,4 @@ SUBSYSTEM_DEF(legacy_lore) if(initial(instance.name)) instance = new path() organizations[path] = instance - return ..() + return SS_INIT_SUCCESS diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index 5bff0ce41f92..2413696bd967 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -114,7 +114,7 @@ SUBSYSTEM_DEF(lighting) ) log_subsystem("lighting", "NOv:[overlaycount] L:[processed_lights] C:[processed_corners] O:[processed_overlays]") - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/lighting/proc/InitializeZlev(zlev) for (var/thing in Z_ALL_TURFS(zlev)) diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm index d0fa25883a7f..d377218545d0 100644 --- a/code/controllers/subsystem/machines.dm +++ b/code/controllers/subsystem/machines.dm @@ -44,16 +44,17 @@ SUBSYSTEM_DEF(machines) makepowernets() report_progress("Initializing atmos machinery...") setup_atmos_machinery(GLOB.machines) + update_all_apcs() fire() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/machines/fire(resumed = 0) var/timer = TICK_USAGE - INTERNAL_PROCESS_STEP(SSMACHINES_POWER_OBJECTS,FALSE,process_power_objects,cost_power_objects,SSMACHINES_PIPENETS) // Higher priority, damnit - INTERNAL_PROCESS_STEP(SSMACHINES_PIPENETS,TRUE,process_pipenets,cost_pipenets,SSMACHINES_MACHINERY) - INTERNAL_PROCESS_STEP(SSMACHINES_MACHINERY,FALSE,process_machinery,cost_machinery,SSMACHINES_POWERNETS) - INTERNAL_PROCESS_STEP(SSMACHINES_POWERNETS,FALSE,process_powernets,cost_powernets,SSMACHINES_POWER_OBJECTS) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSMACHINES_POWER_OBJECTS,FALSE,process_power_objects,cost_power_objects,SSMACHINES_PIPENETS) // Higher priority, damnit + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSMACHINES_PIPENETS,TRUE,process_pipenets,cost_pipenets,SSMACHINES_MACHINERY) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSMACHINES_MACHINERY,FALSE,process_machinery,cost_machinery,SSMACHINES_POWERNETS) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSMACHINES_POWERNETS,FALSE,process_powernets,cost_powernets,SSMACHINES_POWER_OBJECTS) // rebuild all power networks from scratch - only called at world creation or by the admin verb // The above is a lie. Turbolifts also call this proc. @@ -89,6 +90,10 @@ SUBSYSTEM_DEF(machines) T.broadcast_status() CHECK_TICK +/datum/controller/subsystem/machines/proc/update_all_apcs() + for(var/obj/machinery/power/apc/apc in GLOB.apcs) + apc.update() + /datum/controller/subsystem/machines/proc/process_pipenets(resumed = 0) if (!resumed) src.current_run = global.pipe_networks.Copy() diff --git a/code/controllers/subsystem/mapping/_mapping.dm b/code/controllers/subsystem/mapping/_mapping.dm index eabccb9b489d..b51368f09eb9 100644 --- a/code/controllers/subsystem/mapping/_mapping.dm +++ b/code/controllers/subsystem/mapping/_mapping.dm @@ -43,8 +43,7 @@ SUBSYSTEM_DEF(mapping) // finalize // todo: refactor repopulate_sorted_areas() - - return ..() + return SS_INIT_SUCCESS // // Mapping subsystem handles initialization of random map elements at server start diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm index 4b53be2b5be1..7311cbbc0976 100644 --- a/code/controllers/subsystem/materials.dm +++ b/code/controllers/subsystem/materials.dm @@ -25,7 +25,7 @@ SUBSYSTEM_DEF(materials) /datum/controller/subsystem/materials/Initialize() initialize_material_recipes() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/materials/Recover() initialize_material_recipes() diff --git a/code/controllers/subsystem/media_tracks.dm b/code/controllers/subsystem/media_tracks.dm index 960e1be5b261..968dea1f9a5d 100644 --- a/code/controllers/subsystem/media_tracks.dm +++ b/code/controllers/subsystem/media_tracks.dm @@ -13,7 +13,7 @@ SUBSYSTEM_DEF(media_tracks) /datum/controller/subsystem/media_tracks/Initialize(timeofday) load_tracks() sort_tracks() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/media_tracks/proc/load_tracks() var/jukebox_track_file = "config_static/jukebox.json" diff --git a/code/controllers/subsystem/minimaps.dm b/code/controllers/subsystem/minimaps.dm index 893554388f5d..63a4d3bb9a13 100644 --- a/code/controllers/subsystem/minimaps.dm +++ b/code/controllers/subsystem/minimaps.dm @@ -9,7 +9,7 @@ SUBSYSTEM_DEF(minimaps) to_chat(world, "Minimaps disabled! Skipping init.") return ..() build_minimaps() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/minimaps/proc/build_minimaps() station_minimaps = list() diff --git a/code/controllers/subsystem/nanoui.dm b/code/controllers/subsystem/nanoui.dm index e5bc5d6314f7..301ca96e7ced 100644 --- a/code/controllers/subsystem/nanoui.dm +++ b/code/controllers/subsystem/nanoui.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(nanoui) name = "NanoUI" priority = FIRE_PRIORITY_NANO + subsystem_flags = SS_NO_INIT wait = 7 /// A list of current open /nanoui UIs, grouped by src_object and ui_key. @@ -9,25 +10,20 @@ SUBSYSTEM_DEF(nanoui) /// A list of current open /nanoui UIs, not grouped, for use in processing. var/list/processing_uis = list() - - /datum/controller/subsystem/nanoui/fire(resumed) for(var/thing in processing_uis) var/datum/nanoui/UI = thing UI.process() - /datum/controller/subsystem/nanoui/Recover() if(SSnanoui.open_uis) open_uis |= SSnanoui.open_uis if(SSnanoui.processing_uis) processing_uis |= SSnanoui.processing_uis - /datum/controller/subsystem/nanoui/stat_entry() return ..() + " [processing_uis.len] UIs" - /** * Get an open /nanoui ui for the current user, src_object and ui_key and try to update it with data * @@ -56,7 +52,6 @@ SUBSYSTEM_DEF(nanoui) return ui - /** * Get an open /nanoui ui for the current user, src_object and ui_key * @@ -75,7 +70,6 @@ SUBSYSTEM_DEF(nanoui) if (ui.user == user) return ui - /** * Update all /nanoui uis attached to src_object * @@ -98,7 +92,6 @@ SUBSYSTEM_DEF(nanoui) else ui.close() - /** * Close all /nanoui uis attached to src_object * @@ -117,7 +110,6 @@ SUBSYSTEM_DEF(nanoui) ui.close() // If it's missing src_object or user, we want to close it even more. .++ - /** * Update /nanoui uis belonging to user * @@ -137,7 +129,6 @@ SUBSYSTEM_DEF(nanoui) ui.try_update(1) .++ - /** * Close /nanoui uis belonging to user * @@ -157,7 +148,6 @@ SUBSYSTEM_DEF(nanoui) ui.close() .++ - /** * Add a /nanoui ui to the list of open uis * This is called by the /nanoui open() proc @@ -173,7 +163,6 @@ SUBSYSTEM_DEF(nanoui) LAZYDISTINCTADD(ui.user.open_uis, ui) processing_uis += ui - /** * Remove a /nanoui ui from the list of open uis * This is called by the /nanoui close() proc @@ -199,7 +188,6 @@ SUBSYSTEM_DEF(nanoui) return TRUE - /** * This is called on user logout * Closes/clears all uis attached to the user's /mob @@ -211,7 +199,6 @@ SUBSYSTEM_DEF(nanoui) /datum/controller/subsystem/nanoui/proc/user_logout(mob/user) return close_user_uis(user) - /** * This is called when a player transfers from one mob to another * Transfers all open UIs to the new mob diff --git a/code/controllers/subsystem/nightshift.dm b/code/controllers/subsystem/nightshift.dm index 7321979537bb..f0fa4f7388b1 100644 --- a/code/controllers/subsystem/nightshift.dm +++ b/code/controllers/subsystem/nightshift.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(nightshift) name = "Night Shift" init_order = INIT_ORDER_NIGHTSHIFT + init_stage = INIT_STAGE_LATE priority = FIRE_PRIORITY_NIGHTSHIFT wait = 60 SECONDS subsystem_flags = SS_NO_TICK_CHECK @@ -22,9 +23,7 @@ SUBSYSTEM_DEF(nightshift) /datum/controller/subsystem/nightshift/Initialize() if (!CONFIG_GET(flag/nightshifts_enabled)) can_fire = FALSE - //if(CONFIG_GET(flag/randomized_start_time_enabled)) - //GLOB.gametime_offset = rand(0, 23) HOURS - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/nightshift/fire(resumed = FALSE) if(world.time - round_duration_in_ds < nightshift_first_check) diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm index 1545a91280b0..5ed931866d9b 100644 --- a/code/controllers/subsystem/overlays.dm +++ b/code/controllers/subsystem/overlays.dm @@ -3,6 +3,7 @@ SUBSYSTEM_DEF(overlays) wait = 1 priority = FIRE_PRIORITY_OVERLAYS init_order = INIT_ORDER_OVERLAY + init_stage = INIT_STAGE_LATE runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY var/list/processing = list() @@ -23,7 +24,7 @@ SUBSYSTEM_DEF(overlays) /datum/controller/subsystem/overlays/Initialize() overlays_initialized = TRUE Flush() - ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/overlays/Shutdown() text2file(render_stats(stats), "[GLOB.log_directory]/overlay.log") diff --git a/code/controllers/subsystem/overmap.dm b/code/controllers/subsystem/overmap.dm index bc994f2c4e2b..224e460db3c8 100644 --- a/code/controllers/subsystem/overmap.dm +++ b/code/controllers/subsystem/overmap.dm @@ -19,7 +19,7 @@ SUBSYSTEM_DEF(overmaps) /datum/controller/subsystem/overmaps/Initialize() make_default_overmap() rebuild_helm_computers() - return ..() + return SS_INIT_SUCCESS //! legacy code below diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm index 5c1b72de730e..0d5874546188 100644 --- a/code/controllers/subsystem/parallax.dm +++ b/code/controllers/subsystem/parallax.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(parallax) name = "Parallax" wait = 2 - subsystem_flags = SS_POST_FIRE_TIMING | SS_BACKGROUND + subsystem_flags = SS_POST_FIRE_TIMING | SS_BACKGROUND | SS_NO_INIT priority = FIRE_PRIORITY_PARALLAX runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT var/list/currentrun diff --git a/code/controllers/subsystem/persistence/persistence.dm b/code/controllers/subsystem/persistence/persistence.dm index 806071a997be..e5965a985a0c 100644 --- a/code/controllers/subsystem/persistence/persistence.dm +++ b/code/controllers/subsystem/persistence/persistence.dm @@ -24,14 +24,11 @@ SUBSYSTEM_DEF(persistence) /datum/controller/subsystem/persistence/Initialize() /// build prototype lookup list build_prototype_id_lookup() - LoadPersistence() - // todo: should this be here? save_the_world is in ticker. if(CONFIG_GET(flag/persistence)) load_the_world() - - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/persistence/Shutdown() SavePersistence() diff --git a/code/controllers/subsystem/planets.dm b/code/controllers/subsystem/planets.dm index b01d171eff85..74e2b069c440 100644 --- a/code/controllers/subsystem/planets.dm +++ b/code/controllers/subsystem/planets.dm @@ -21,7 +21,7 @@ SUBSYSTEM_DEF(planets) report_progress("Initializing planetary weather.") allocateTurfs(TRUE) fire() // Fire once to preemptively set up weather and planetary ambient lighting. - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/planets/on_max_z_changed(old_z_count, new_z_count) . = ..() diff --git a/code/controllers/subsystem/plants.dm b/code/controllers/subsystem/plants.dm index 0ed5ca68eb1f..d32669e0be5f 100644 --- a/code/controllers/subsystem/plants.dm +++ b/code/controllers/subsystem/plants.dm @@ -34,7 +34,7 @@ SUBSYSTEM_DEF(plants) /datum/controller/subsystem/plants/Initialize(timeofday) setup() - return ..() + return SS_INIT_SUCCESS /** * Predefined/roundstart varieties use a string key to make it diff --git a/code/controllers/subsystem/playtime.dm b/code/controllers/subsystem/playtime.dm index 4dbfd544fc68..617cdbf03350 100644 --- a/code/controllers/subsystem/playtime.dm +++ b/code/controllers/subsystem/playtime.dm @@ -9,7 +9,7 @@ SUBSYSTEM_DEF(playtime) name = "Playtime" wait = 10 MINUTES - subsystem_flags = SS_NO_TICK_CHECK + subsystem_flags = SS_NO_TICK_CHECK | SS_NO_INIT /datum/controller/subsystem/playtime/Shutdown() flush_playtimes() diff --git a/code/controllers/subsystem/preferences.dm b/code/controllers/subsystem/preferences.dm index cf35c223cf47..f9090109db7d 100644 --- a/code/controllers/subsystem/preferences.dm +++ b/code/controllers/subsystem/preferences.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(preferences) name = "Preferences" init_order = INIT_ORDER_PREFERENCES + init_stage = INIT_STAGE_EARLY subsystem_flags = SS_NO_FIRE var/list/datum/game_preference_entry/entries_by_key @@ -13,7 +14,7 @@ SUBSYSTEM_DEF(preferences) for(var/key in preferences_by_key) var/datum/game_preferences/prefs = preferences_by_key[key] prefs.initialize() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/preferences/proc/resolve_preference_entry(datum/game_preference_entry/entrylike) if(ispath(entrylike)) diff --git a/code/controllers/subsystem/processing/circuits.dm b/code/controllers/subsystem/processing/circuits.dm index cbf0adcf1b8b..52ded6c5325c 100644 --- a/code/controllers/subsystem/processing/circuits.dm +++ b/code/controllers/subsystem/processing/circuits.dm @@ -23,7 +23,7 @@ PROCESSING_SUBSYSTEM_DEF(circuit) /datum/controller/subsystem/processing/circuit/Initialize(timeofday) SScircuit.cipherkey = uppertext(random_string(2000+rand(0,10), GLOB.alphabet)) circuits_init() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/processing/circuit/proc/circuits_init() //Cached lists for free performance diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm index 730f89d4b8ea..129506f2c5fe 100644 --- a/code/controllers/subsystem/processing/instruments.dm +++ b/code/controllers/subsystem/processing/instruments.dm @@ -2,6 +2,7 @@ PROCESSING_SUBSYSTEM_DEF(instruments) name = "Instruments" wait = 0.5 init_order = INIT_ORDER_INSTRUMENTS + init_stage = INIT_STAGE_EARLY subsystem_flags = SS_KEEP_TIMING priority = FIRE_PRIORITY_INSTRUMENTS var/static/list/datum/instrument/instrument_data = list() //id = datum @@ -14,7 +15,7 @@ PROCESSING_SUBSYSTEM_DEF(instruments) /datum/controller/subsystem/processing/instruments/Initialize() initialize_instrument_data() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S) songs += S diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm index 3e4829857a2a..1b98510c602f 100644 --- a/code/controllers/subsystem/processing/processing.dm +++ b/code/controllers/subsystem/processing/processing.dm @@ -1,12 +1,12 @@ //Used to process objects. SUBSYSTEM_DEF(processing) - name = "Processing" + name = "Processing - 1 FPS" priority = FIRE_PRIORITY_PROCESS subsystem_flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT wait = 1 SECONDS - var/stat_tag = "P" //Used for logging + var/stat_tag = "P1" //Used for logging var/list/processing = list() var/list/currentrun = list() diff --git a/code/controllers/subsystem/repository.dm b/code/controllers/subsystem/repository.dm index 17a553eaa18d..04db037a4973 100644 --- a/code/controllers/subsystem/repository.dm +++ b/code/controllers/subsystem/repository.dm @@ -4,9 +4,10 @@ SUBSYSTEM_DEF(repository) name = "Repository System" init_order = INIT_ORDER_REPOSITORY + init_stage = INIT_STAGE_BACKEND subsystem_flags = SS_NO_FIRE /datum/controller/subsystem/repository/Initialize() __create_repositories() __init_repositories() - return ..() + return SS_INIT_SUCCESS diff --git a/code/controllers/subsystem/shuttles.dm b/code/controllers/subsystem/shuttles.dm index 2f1019cfa039..4bb02c2c0eca 100644 --- a/code/controllers/subsystem/shuttles.dm +++ b/code/controllers/subsystem/shuttles.dm @@ -49,7 +49,6 @@ SUBSYSTEM_DEF(shuttle) /// Shuttles remaining to process this fire() tick var/tmp/list/current_run - /** *! I made these shitty vars so we don't search for these in GOD DAMN WORLD *! If I find these are still here in 2023 I'll be very upset. @@ -63,7 +62,7 @@ SUBSYSTEM_DEF(shuttle) last_landmark_registration_time = world.time block_init_queue = FALSE process_init_queues() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/shuttle/fire(resumed = 0) if (!resumed) diff --git a/code/controllers/subsystem/sound/_sound.dm b/code/controllers/subsystem/sound/_sound.dm index 477ff2965771..3a2121a2fe78 100644 --- a/code/controllers/subsystem/sound/_sound.dm +++ b/code/controllers/subsystem/sound/_sound.dm @@ -3,8 +3,9 @@ SUBSYSTEM_DEF(sounds) name = "Sounds" subsystem_flags = SS_NO_FIRE init_order = INIT_ORDER_SOUNDS + init_stage = INIT_STAGE_BACKEND /datum/controller/subsystem/sounds/Initialize() setup_available_channels() setup_soundbytes() - return ..() + return SS_INIT_SUCCESS diff --git a/code/controllers/subsystem/spatial_grids.dm b/code/controllers/subsystem/spatial_grids.dm index 5f782bbc4ddd..b19dfcced2a1 100644 --- a/code/controllers/subsystem/spatial_grids.dm +++ b/code/controllers/subsystem/spatial_grids.dm @@ -8,6 +8,7 @@ SUBSYSTEM_DEF(spatial_grids) name = "Spatial Grids" init_order = INIT_ORDER_SPATIAL_GRIDS + init_stage = INIT_STAGE_WORLD subsystem_flags = SS_NO_FIRE /// /living mobs. they don't have to be alive, just a subtype of /living. @@ -19,7 +20,7 @@ SUBSYSTEM_DEF(spatial_grids) /datum/controller/subsystem/spatial_grids/Initialize() make_grids() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/spatial_grids/proc/make_grids() living = new /datum/spatial_grid(/mob/living) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 5c5ea3e4dfaf..a7fbbe9abaa5 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -2,7 +2,9 @@ SUBSYSTEM_DEF(statpanels) name = "Stat Panels" wait = 4 init_order = INIT_ORDER_STATPANELS + init_stage = INIT_STAGE_BACKEND priority = FIRE_PRIORITY_STATPANELS + subsystem_flags = SS_NO_INIT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY //! ticking @@ -19,11 +21,6 @@ SUBSYSTEM_DEF(statpanels) /// cached sdql2 data var/cache_sdql_data -/datum/controller/subsystem/statpanels/Initialize() - spawn() - manual_ticking() - return ..() - /datum/controller/subsystem/statpanels/fire(resumed = FALSE, no_tick_check) if(!resumed) // dispose / rebuild caches @@ -144,16 +141,3 @@ SUBSYSTEM_DEF(statpanels) . += Q.generate_stat() . = url_encode(json_encode(.)) cache_sdql_data = . - -/** - * is this shitcode? - * yes it is - * if you wanna do better, do better; i'm not at the point of janking up our MC with my own - * fuckery. - * - * tl;dr this ensures we push data while MC is initializing. - */ -/datum/controller/subsystem/statpanels/proc/manual_ticking() - while(!Master.initialized) - fire(null, TRUE) - sleep(10) diff --git a/code/controllers/subsystem/status_effects.dm b/code/controllers/subsystem/status_effects.dm index cceedbafb1b4..03ec92e68bfa 100644 --- a/code/controllers/subsystem/status_effects.dm +++ b/code/controllers/subsystem/status_effects.dm @@ -1,6 +1,6 @@ SUBSYSTEM_DEF(status_effects) wait = 0.5 - subsystem_flags = NONE + subsystem_flags = SS_NO_INIT name = "Status Effects" /// ticking effects diff --git a/code/controllers/subsystem/sun.dm b/code/controllers/subsystem/sun.dm index 59ef967b6069..c93852ef30fa 100644 --- a/code/controllers/subsystem/sun.dm +++ b/code/controllers/subsystem/sun.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(sun) name = "Sun" wait = 1 MINUTE + subsystem_flags = SS_NO_INIT var/static/datum/sun/sun = new /datum/controller/subsystem/sun/fire() diff --git a/code/controllers/subsystem/supply.dm b/code/controllers/subsystem/supply.dm index c830a7159281..00b3f3856b9b 100644 --- a/code/controllers/subsystem/supply.dm +++ b/code/controllers/subsystem/supply.dm @@ -59,7 +59,7 @@ SUBSYSTEM_DEF(supply) for(var/key in legacy_supply_categories) flattened += key legacy_supply_categories = flattened - return ..() + return SS_INIT_SUCCESS // Supply shuttle SSticker - handles supply point regeneration // This is called by the process scheduler every thirty seconds diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 85c11386e172..a997765b4bd5 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -75,8 +75,7 @@ SUBSYSTEM_DEF(ticker) syndicate_code_response = generate_code_phrase() start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10) - - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/ticker/fire() switch(current_state) diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 865e1d7c30b4..f812db13b9f3 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -18,6 +18,7 @@ SUBSYSTEM_DEF(timer) name = "Timer" wait = 1 // SS_TICKER subsystem, so wait is in ticks init_order = INIT_ORDER_TIMER + init_stage = INIT_STAGE_BACKEND runlevels = RUNLEVELS_ALL priority = FIRE_PRIORITY_TIMER subsystem_flags = SS_TICKER|SS_NO_INIT diff --git a/code/controllers/subsystem/lobby.dm b/code/controllers/subsystem/titlescreen.dm similarity index 64% rename from code/controllers/subsystem/lobby.dm rename to code/controllers/subsystem/titlescreen.dm index e0c8f1b8d8d5..b2f834a97b57 100644 --- a/code/controllers/subsystem/lobby.dm +++ b/code/controllers/subsystem/titlescreen.dm @@ -1,29 +1,30 @@ /** - * manager for everything involving the lobby, including the title screen + * Manages the lobby's titlescreen. */ -SUBSYSTEM_DEF(lobby) - name = "Lobby Manager" +SUBSYSTEM_DEF(titlescreen) + name = "Titlescreens" subsystem_flags = SS_NO_FIRE - init_order = INIT_ORDER_LOBBY + init_order = INIT_ORDER_TITLESCREEN + init_stage = INIT_STAGE_LATE /// our titlescreen var/datum/cutscene/titlescreen -/datum/controller/subsystem/lobby/Initialize() +/datum/controller/subsystem/titlescreen/Initialize() initialize_title_scene() - return ..() + return SS_INIT_NO_MESSAGE -/datum/controller/subsystem/lobby/proc/initialize_title_scene() +/datum/controller/subsystem/titlescreen/proc/initialize_title_scene() refresh_title_scene() for(var/client/C as anything in GLOB.clients) if(!isnewplayer(C.mob)) continue C.start_cutscene(titlescreen) -/datum/controller/subsystem/lobby/proc/refresh_title_scene() +/datum/controller/subsystem/titlescreen/proc/refresh_title_scene() set_title_scene(make_title_scene()) -/datum/controller/subsystem/lobby/proc/make_title_scene() +/datum/controller/subsystem/titlescreen/proc/make_title_scene() var/picked = pickweight((LEGACY_MAP_DATUM).titlescreens.Copy()) if(isnull(picked)) return @@ -41,7 +42,7 @@ SUBSYSTEM_DEF(lobby) built.init() return built -/datum/controller/subsystem/lobby/proc/set_title_scene(datum/cutscene/scene) +/datum/controller/subsystem/titlescreen/proc/set_title_scene(datum/cutscene/scene) var/list/client/old_viewing if(!isnull(titlescreen)) old_viewing = titlescreen.viewing?.Copy() diff --git a/code/controllers/subsystem/transcore_vr.dm b/code/controllers/subsystem/transcore_vr.dm index 70b1e3fa8fed..fe550f4f95ca 100644 --- a/code/controllers/subsystem/transcore_vr.dm +++ b/code/controllers/subsystem/transcore_vr.dm @@ -32,8 +32,8 @@ SUBSYSTEM_DEF(transcore) /datum/controller/subsystem/transcore/fire(resumed = 0) var/timer = TICK_USAGE - INTERNAL_PROCESS_STEP(SSTRANSCORE_IMPLANTS,TRUE,process_implants,cost_implants,SSTRANSCORE_BACKUPS) -// INTERNAL_PROCESS_STEP(SSTRANSCORE_BACKUPS,FALSE,process_backups,cost_backups,SSTRANSCORE_IMPLANTS) + INTERNAL_SUBSYSTEM_PROCESS_STEP(SSTRANSCORE_IMPLANTS,TRUE,process_implants,cost_implants,SSTRANSCORE_BACKUPS) +// INTERNAL_SUBSYSTEM_PROCESS_STEP(SSTRANSCORE_BACKUPS,FALSE,process_backups,cost_backups,SSTRANSCORE_IMPLANTS) /datum/controller/subsystem/transcore/proc/process_implants(resumed = 0) if (!resumed) diff --git a/code/controllers/subsystem/transfer.dm b/code/controllers/subsystem/transfer.dm index 0040f5b42ce9..9820da271ec1 100644 --- a/code/controllers/subsystem/transfer.dm +++ b/code/controllers/subsystem/transfer.dm @@ -13,7 +13,7 @@ SUBSYSTEM_DEF(transfer) timerbuffer = config_legacy.vote_autotransfer_initial shift_hard_end = config_legacy.vote_autotransfer_initial + (config_legacy.vote_autotransfer_interval * NUMBER_OF_VOTE_EXTENSIONS) //Change this "1" to how many extend votes you want there to be. shift_last_vote = shift_hard_end - config_legacy.vote_autotransfer_interval - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/transfer/fire(resumed) currenttick = currenttick + 1 diff --git a/code/controllers/subsystem/turbolift.dm b/code/controllers/subsystem/turbolift.dm index fd506f39f669..0afd8de21b43 100644 --- a/code/controllers/subsystem/turbolift.dm +++ b/code/controllers/subsystem/turbolift.dm @@ -1,6 +1,6 @@ SUBSYSTEM_DEF(turbolifts) name = "Turbolifts" - subsystem_flags = SS_NO_TICK_CHECK + subsystem_flags = SS_NO_TICK_CHECK | SS_NO_INIT wait = 10 var/static/list/moving_lifts = list() diff --git a/code/controllers/subsystem/vis_overlays.dm b/code/controllers/subsystem/vis_overlays.dm index b0e5d6c6896d..e8f0c71e7ba9 100644 --- a/code/controllers/subsystem/vis_overlays.dm +++ b/code/controllers/subsystem/vis_overlays.dm @@ -11,7 +11,7 @@ SUBSYSTEM_DEF(vis_overlays) /datum/controller/subsystem/vis_overlays/Initialize() vis_overlay_cache = list() unique_vis_overlays = list() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/vis_overlays/fire(resumed = FALSE) if(!resumed) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 32a95513032c..3b57bd293c9a 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -35,9 +35,9 @@ SUBSYSTEM_DEF(vote) /datum/config_entry/string/default_on_transfer_tie default = "Extend the Shift" -/datum/controller/subsystem/vote/Initialize(start_timeofday) - . = ..() +/datum/controller/subsystem/vote/Initialize() ghost_weight_percent = CONFIG_GET(number/ghost_weight) + return SS_INIT_SUCCESS /datum/controller/subsystem/vote/fire(resumed) if(mode) diff --git a/code/controllers/subsystem/world.dm b/code/controllers/subsystem/world.dm index 36f95e492e2a..ff43eb9d9be5 100644 --- a/code/controllers/subsystem/world.dm +++ b/code/controllers/subsystem/world.dm @@ -41,7 +41,7 @@ SUBSYSTEM_DEF(game_world) initialize_locations() initialize_factions() initialize_map() - return ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/game_world/proc/initialize_locations() location_lookup = list() diff --git a/code/controllers/subsystem/xenoarch.dm b/code/controllers/subsystem/xenoarch.dm index 0c0574a95f2b..e833522e1910 100644 --- a/code/controllers/subsystem/xenoarch.dm +++ b/code/controllers/subsystem/xenoarch.dm @@ -16,7 +16,7 @@ SUBSYSTEM_DEF(xenoarch) /datum/controller/subsystem/xenoarch/Initialize(timeofday) SetupXenoarch() - ..() + return SS_INIT_SUCCESS /datum/controller/subsystem/xenoarch/Recover() if (istype(SSxenoarch.artifact_spawning_turfs)) diff --git a/code/controllers/subsystem/zcopy.dm b/code/controllers/subsystem/zcopy.dm index c745fdca6731..9adf8a3e0cf4 100644 --- a/code/controllers/subsystem/zcopy.dm +++ b/code/controllers/subsystem/zcopy.dm @@ -180,7 +180,7 @@ SUBSYSTEM_DEF(zcopy) calculate_zstack_limits() // Flush the queue. fire(FALSE, TRUE) - return ..() + return SS_INIT_SUCCESS // If you add a new Zlevel or change Z-connections, call this. /datum/controller/subsystem/zcopy/proc/calculate_zstack_limits() diff --git a/code/game/atoms/atoms_initializing_EXPENSIVE.dm b/code/game/atoms/atoms_initializing_EXPENSIVE.dm index 3c1dbacb0149..1cb28418a64e 100644 --- a/code/game/atoms/atoms_initializing_EXPENSIVE.dm +++ b/code/game/atoms/atoms_initializing_EXPENSIVE.dm @@ -70,9 +70,9 @@ if(datum_flags & DF_USE_TAG) generate_tag() - var/do_initialize = SSatoms.initialized - if(do_initialize != INITIALIZATION_INSSATOMS) - args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD + var/do_initialize = SSatoms.atom_init_status + if(do_initialize != ATOM_INIT_IN_SUBSYSTEM) + args[1] = do_initialize == ATOM_INIT_IN_NEW_MAPLOAD if(SSatoms.InitAtom(src, FALSE, args)) //we were deleted return diff --git a/code/game/objects/structures/tables/materials.dm b/code/game/objects/structures/tables/materials.dm index 6fdf575888dd..63bbcffae7b9 100644 --- a/code/game/objects/structures/tables/materials.dm +++ b/code/game/objects/structures/tables/materials.dm @@ -13,7 +13,7 @@ (reinforcing) = MATERIAL_SIGNIFICANCE_TABLE_REINFORCEMENT, ))) // sigh - if(SSatoms.initialized == INITIALIZATION_INNEW_REGULAR) + if(SSatoms.atom_init_status == ATOM_INIT_IN_NEW_REGULAR) update_connections(TRUE) update_appearance() diff --git a/code/game/objects/structures/tables/update_triggers.dm b/code/game/objects/structures/tables/update_triggers.dm index 237842124f91..cb7e785e52d4 100644 --- a/code/game/objects/structures/tables/update_triggers.dm +++ b/code/game/objects/structures/tables/update_triggers.dm @@ -1,6 +1,6 @@ /obj/structure/window/Initialize(mapload) . = ..() - if(SSatoms.initialized == INITIALIZATION_INNEW_REGULAR) + if(SSatoms.atom_init_status == ATOM_INIT_IN_NEW_REGULAR) for(var/obj/structure/table/T in view(src, 1)) T.update_connections() T.update_icon() diff --git a/code/modules/ai/holders/ai_holder-movement.dm b/code/modules/ai/holders/ai_holder-movement.dm index ce2b6e76d3de..c0a2f1d62a2b 100644 --- a/code/modules/ai/holders/ai_holder-movement.dm +++ b/code/modules/ai/holders/ai_holder-movement.dm @@ -11,8 +11,12 @@ /// bucket position var/movement_bucket_position /// last datum in bucket + /// + /// * We are a circularly double-linked list. If we are the only one in the bucket, this is ourselves. var/datum/ai_holder/movement_bucket_prev /// next datum in bucket + /// + /// * We are a circularly double-linked list. If we are the only one in the bucket, this is ourselves. var/datum/ai_holder/movement_bucket_next /// movement cycle var/movement_cycle @@ -23,6 +27,7 @@ * @return amount of time to next move; 0 to stop moving */ /datum/ai_holder/proc/move(cycles) + SHOULD_NOT_SLEEP(TRUE) . = 0 CRASH("unimplemented move proc called; what happened here?") @@ -30,10 +35,12 @@ * register on movement subsystem to move */ /datum/ai_holder/proc/start_moving(initial_delay) + SHOULD_NOT_SLEEP(TRUE) return SSai_movement.register_moving(src) /** * stop moving */ /datum/ai_holder/proc/stop_moving() + SHOULD_NOT_SLEEP(TRUE) return SSai_movement.unregister_moving(src) diff --git a/code/modules/ai/holders/ai_holder-scheduling.dm b/code/modules/ai/holders/ai_holder-scheduling.dm index 80d29a46a6ce..59454eee8754 100644 --- a/code/modules/ai/holders/ai_holder-scheduling.dm +++ b/code/modules/ai/holders/ai_holder-scheduling.dm @@ -19,5 +19,6 @@ * * arguments - a list of arguments to use with the call */ /datum/ai_holder/proc/schedule(time, proc_ref, list/arguments) + SHOULD_NOT_SLEEP(TRUE) var/datum/ai_callback/ai_callback = new(proc_ref, arguments, src) SSai_scheduling.schedule_callback(ai_callback, time) diff --git a/code/modules/ai/holders/ai_holder-ticking.dm b/code/modules/ai/holders/ai_holder-ticking.dm index 872bf0e306d7..08db4bc01b37 100644 --- a/code/modules/ai/holders/ai_holder-ticking.dm +++ b/code/modules/ai/holders/ai_holder-ticking.dm @@ -23,6 +23,7 @@ * correct bucket for our target cycle. */ /datum/ai_holder/proc/set_ticking(delay) + SHOULD_NOT_SLEEP(TRUE) ASSERT(delay > 0) ASSERT(delay <= AI_SCHEDULING_LIMIT) if(ticking > 0) @@ -39,6 +40,7 @@ * Unregister us from the doubly linked list we're in and removes us from the ai_holders subsystem. */ /datum/ai_holder/proc/stop_ticking() + SHOULD_NOT_SLEEP(TRUE) if(!ticking) return SSai_holders.bucket_evict(src) @@ -50,6 +52,7 @@ * Called by subsystem to tick this holder. */ /datum/ai_holder/proc/tick(cycles) + SHOULD_NOT_SLEEP(TRUE) return /** diff --git a/code/modules/ai/holders/polaris/ai_holder_targeting.dm b/code/modules/ai/holders/polaris/ai_holder_targeting.dm index 9f2f90656264..b6028156a45e 100644 --- a/code/modules/ai/holders/polaris/ai_holder_targeting.dm +++ b/code/modules/ai/holders/polaris/ai_holder_targeting.dm @@ -235,7 +235,7 @@ if(!hostile && !retaliate) // Not allowed to defend ourselves. ai_log("react_to_attack_polaris() : Was attacked by [attacker], but we are not allowed to attack back.", AI_LOG_TRACE) return FALSE - if(holder.IIsAlly(attacker)) // I'll overlook it THIS time... + if(ismob(attacker) && holder.IIsAlly(attacker)) // I'll overlook it THIS time... ai_log("react_to_attack_polaris() : Was attacked by [attacker], but they were an ally.", AI_LOG_TRACE) return FALSE if(target) // Already fighting someone. Switching every time we get hit would impact our combat performance. diff --git a/code/modules/client/game_preferences/middleware/keybindings.dm b/code/modules/client/game_preferences/middleware/keybindings.dm index 006a156a7a5d..607b9e040db2 100644 --- a/code/modules/client/game_preferences/middleware/keybindings.dm +++ b/code/modules/client/game_preferences/middleware/keybindings.dm @@ -6,8 +6,8 @@ key = "keybindings" /datum/game_preference_middleware/keybindings/on_initial_load(datum/game_preferences/prefs) - prefs.active?.update_movement_keys(src) prefs.active?.set_macros() + prefs.active?.update_movement_keys(src) return ..() /datum/game_preference_middleware/keybindings/handle_reset(datum/game_preferences/prefs) diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm index 71775b65f6a2..517cbb2fcbdd 100644 --- a/code/modules/keybindings/bindings_client.dm +++ b/code/modules/keybindings/bindings_client.dm @@ -75,7 +75,7 @@ else full_key = "[AltMod][CtrlMod][ShiftMod][_key]" var/keycount = 0 - for(var/kb_name in preferences.keybindings[full_key]) + for(var/kb_name in preferences?.keybindings[full_key]) keycount++ var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name] if(kb.can_use(src) && kb.down(src) && keycount >= MAX_COMMANDS_PER_KEY) @@ -137,7 +137,7 @@ // We don't do full key for release, because for mod keys you // can hold different keys and releasing any should be handled by the key binding specifically - for (var/kb_name in preferences.keybindings[_key]) + for (var/kb_name in preferences?.keybindings[_key]) var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name] if(kb.can_use(src) && kb.up(src)) break diff --git a/code/modules/metrics/api.dm b/code/modules/metrics/api.dm deleted file mode 100644 index f8dd53c35b56..000000000000 --- a/code/modules/metrics/api.dm +++ /dev/null @@ -1,78 +0,0 @@ -//* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 Citadel Station Developers *// - -/** - * counter metric - * - * * numerical, can only go up through a round - */ -/datum/metric/counter - -/** - * increments a counter metric by an amount, defaulting to 1 - * - * * The time at which this is called does matter. The recorded metric will be at the current - * time of the recording. - * - * This is what you can use for things like: - * - * * How many times an admin verb was pressed in a round - * * How many times a thing happened in a round - */ -/proc/metric_record_counter(datum/metric/counter/typepath, amount = 1) - return - -/** - * records a series of values at given times - * - * * Supports a number, string, or both. - * - * This is what you can use for things like: - * - * * Time dilation tracking - */ -/datum/metric/series - /// has numerical data to graph - /// - /// * doesn't limit the data, only determines if we try to pull a graph - var/graph_exists = FALSE - /// representation of numerical data in graph - /// - /// * valid values are ["average", "tally"] - var/graph_collate = "tally" - -/** - * records a number or a string in a series metric - * - * * The time at which this is called does matter. The recorded metric will be at the current - * time of the recording. - */ -/proc/metric_record_series(datum/metric/series/typepath, tally, comment) - return - -/** - * Records an event at a specific tile of a map - * - * * Supports a single tile event with annotation of number and/or string - * * Supports a rectangular event with annotation of number and/or string - * - * This is usually used for game-map purposes, but is actually usable as an arbitrary - * spatial metric if you need it for whatever reason. - * - * This is what you can use for things like: - * - * * Tracking where people died - * * Tracking what explodes - * * Tracking what goes wrong where - */ -/datum/metric/spatial - /// Whether the spatial metric corrosponds to the actual in-game map - var/is_game_world = FALSE - /// Don't render a tally of '1' - var/elide_singular_tally = TRUE - -/proc/metric_record_spatial_single(datum/metric/spatial/typepath, x, y, level_id, tally, comment) - return - -/proc/metric_record_spatial_box(datum/metric/spatial/typepath, x1, y1, x2, y2, level_id, tally, comment) - return diff --git a/code/modules/metrics/api/nested_numerical.dm b/code/modules/metrics/api/nested_numerical.dm new file mode 100644 index 000000000000..6bcddacf95da --- /dev/null +++ b/code/modules/metrics/api/nested_numerical.dm @@ -0,0 +1,23 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * nested numerical metric + * + * This is what you can use for things like: + * + * * How many times an admin verb was pressed in a round + */ +/datum/metric/nested_numerical + +/** + * overwrites a nested numerical metric + */ +/proc/metric_set_nested_numerical(datum/metric/nested_numerical/typepath, key = "--unset--", amount) + return + +/** + * increments a nested numerical metric + */ +/proc/metric_increment_nested_numerical(datum/metric/nested_numerical/typepath, key = "--unset--", amount = 1) + return diff --git a/code/modules/metrics/api/numerical.dm b/code/modules/metrics/api/numerical.dm new file mode 100644 index 000000000000..517efd200f01 --- /dev/null +++ b/code/modules/metrics/api/numerical.dm @@ -0,0 +1,23 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * numerical metric + * + * This is what you can use for things like: + * + * * How many times a thing happened in a round + */ +/datum/metric/numerical + +/** + * overwrites a numerical metric + */ +/proc/metric_set_numerical(datum/metric/numerical/typepath, amount) + return + +/** + * increments a numerical metric + */ +/proc/metric_increment_numerical(datum/metric/numerical/typepath, amount = 1) + return diff --git a/code/modules/metrics/api/spatial_series.dm b/code/modules/metrics/api/spatial_series.dm new file mode 100644 index 000000000000..36fa66f3bce3 --- /dev/null +++ b/code/modules/metrics/api/spatial_series.dm @@ -0,0 +1,31 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Records an event at a specific tile of a map + * + * * Supports a single tile event with annotation of number and/or string + * * Supports a rectangular event with annotation of number and/or string + * + * This is usually used for game-map purposes, but is actually usable as an arbitrary + * spatial metric if you need it for whatever reason. + * + * This is what you can use for things like: + * + * * Tracking where people died + * * Tracking what explodes + * * Tracking what goes wrong where + */ +/datum/metric/spatial_series + /// Whether the spatial metric corrosponds to the actual in-game map + var/is_game_world = FALSE + /// Don't render a tally of '1' + var/elide_singular_tally = TRUE + /// We care about time + var/is_time_series = FALSE + +/proc/metric_record_spatial_series_single(datum/metric/spatial_series/typepath, x, y, level_id, tally, comment) + return + +/proc/metric_record_spatial_series_box(datum/metric/spatial_series/typepath, x1, y1, x2, y2, level_id, tally, comment) + return diff --git a/code/modules/metrics/api/string_set.dm b/code/modules/metrics/api/string_set.dm new file mode 100644 index 000000000000..c3b75fc7ba12 --- /dev/null +++ b/code/modules/metrics/api/string_set.dm @@ -0,0 +1,17 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Records an unique set of strings. + * + * This is what you can use for things like: + * + * * What subsystems failed init + */ +/datum/metric/string_set + +/** + * records a string into a string set + */ +/proc/metric_record_string_set(datum/metric/string_set/typepath, string) + return diff --git a/code/modules/metrics/api/time_series.dm b/code/modules/metrics/api/time_series.dm new file mode 100644 index 000000000000..94cea76620bc --- /dev/null +++ b/code/modules/metrics/api/time_series.dm @@ -0,0 +1,30 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * records a series of values at given times + * + * * Supports a number, string, or both. + * + * This is what you can use for things like: + * + * * Time dilation tracking + */ +/datum/metric/time_series + /// has numerical data to graph + /// + /// * doesn't limit the data, only determines if we try to pull a graph + var/graph_exists = FALSE + /// representation of numerical data in graph + /// + /// * valid values are ["average", "tally"] + var/graph_collate = "tally" + +/** + * records a number or a string in a series metric + * + * * The time at which this is called does matter. The recorded metric will be at the current + * time of the recording. + */ +/proc/metric_record_time_series(datum/metric/time_series/typepath, value, comment) + return diff --git a/code/modules/metrics/metrics/controllers.dm b/code/modules/metrics/metrics/controllers.dm new file mode 100644 index 000000000000..9fa1d5837fa3 --- /dev/null +++ b/code/modules/metrics/metrics/controllers.dm @@ -0,0 +1,4 @@ +/datum/metric/nested_numerical/subsystem_init_time + id = "subsystem-init-time" + name = "Init Time - Subsystem" + category = METRIC_CATEGORY_SERVER diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index b81b51c2bd56..8107d87fc978 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -389,7 +389,7 @@ var/list/intents = list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM) */ /proc/notify_ghosts(message, ghost_sound, enter_link, atom/source, mutable_appearance/alert_overlay, action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, ignore_dnr_observers = FALSE, header) //Easy notification of ghosts. // Don't notify for objects created during a mapload. - if(ignore_mapload && SSatoms.initialized != INITIALIZATION_INNEW_REGULAR) + if(ignore_mapload && SSatoms.atom_init_status != ATOM_INIT_IN_NEW_REGULAR) return for(var/mob/observer/dead/O in GLOB.player_list) if(!O.client) diff --git a/code/modules/mob/new_player/login.dm b/code/modules/mob/new_player/login.dm index 0aa83c75b444..4644f71fa8e9 100644 --- a/code/modules/mob/new_player/login.dm +++ b/code/modules/mob/new_player/login.dm @@ -22,4 +22,4 @@ return ..() /mob/new_player/login_cutscene() - client.start_cutscene(SSlobby.titlescreen) + client.start_cutscene(SStitlescreen.titlescreen) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index e42290cc3c60..69230f569620 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -321,8 +321,6 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/power/apc, 22) make_terminal() - addtimer(CALLBACK(src, PROC_REF(update)), 5) - /obj/machinery/power/apc/examine(mob/user, dist) . = ..() if(Adjacent(user))