From fb18b5de2558d5b2de1b12e7e95cbe5c41070796 Mon Sep 17 00:00:00 2001 From: ItsSelis <12716288+ItsSelis@users.noreply.github.com> Date: Tue, 22 Oct 2024 22:06:01 +0200 Subject: [PATCH 1/3] Failsafe & Stack End Detector --- code/controllers/configuration.dm | 1203 ----------------- code/controllers/configuration_vr.dm | 64 - code/controllers/failsafe.dm | 130 +- code/controllers/master.dm | 33 +- .../helper_datums/stack_end_detector.dm | 32 + vorestation.dme | 1 + 6 files changed, 154 insertions(+), 1309 deletions(-) delete mode 100644 code/controllers/configuration.dm delete mode 100644 code/controllers/configuration_vr.dm create mode 100644 code/datums/helper_datums/stack_end_detector.dm diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm deleted file mode 100644 index b741f129ede..00000000000 --- a/code/controllers/configuration.dm +++ /dev/null @@ -1,1203 +0,0 @@ -var/list/gamemode_cache = list() - -/datum/configuration - var/static/server_name = null // server name (for world name / status) - var/static/server_suffix = 0 // generate numeric suffix based on server port - - var/static/nudge_script_path = "nudge.py" // where the nudge.py script is located - - var/static/log_ooc = 0 // log OOC channel - var/static/log_access = 0 // log login/logout - var/static/log_say = 0 // log client say - var/static/log_admin = 0 // log admin actions - var/static/log_debug = 1 // log debug output - var/static/log_game = 0 // log game events - var/static/log_vote = 0 // log voting - var/static/log_whisper = 0 // log client whisper - var/static/log_emote = 0 // log emotes - var/static/log_attack = 0 // log attack messages - var/static/log_adminchat = 0 // log admin chat messages - var/static/log_adminwarn = 0 // log warnings admins get about bomb construction and such - var/static/log_pda = 0 // log pda messages - var/static/log_hrefs = 0 // logs all links clicked in-game. Could be used for debugging and tracking down exploits - var/static/log_runtime = 0 // logs world.log to a file - var/static/log_world_output = 0 // log to_world_log(messages) - var/static/log_graffiti = 0 // logs graffiti - var/static/sql_enabled = 0 // for sql switching - var/static/allow_admin_ooccolor = 0 // Allows admins with relevant permissions to have their own ooc colour - var/static/allow_vote_restart = 0 // allow votes to restart - var/static/ert_admin_call_only = 0 - var/static/allow_vote_mode = 0 // allow votes to change mode - var/static/allow_admin_jump = 1 // allows admin jumping - var/static/allow_admin_spawning = 1 // allows admin item spawning - var/static/allow_admin_rev = 1 // allows admin revives - var/static/pregame_time = 180 // pregame time in seconds - var/static/vote_delay = 6000 // minimum time between voting sessions (deciseconds, 10 minute default) - var/static/vote_period = 600 // length of voting period (deciseconds, default 1 minute) - var/static/vote_autotransfer_initial = 108000 // Length of time before the first autotransfer vote is called - var/static/vote_autotransfer_interval = 36000 // length of time before next sequential autotransfer vote - var/static/vote_autogamemode_timeleft = 100 //Length of time before round start when autogamemode vote is called (in seconds, default 100). - var/static/vote_no_default = 0 // vote does not default to nochange/norestart (tbi) - var/static/vote_no_dead = 0 // dead people can't vote (tbi) -// var/static/enable_authentication = 0 // goon authentication - var/static/del_new_on_log = 1 // del's new players if they log before they spawn in - var/static/feature_object_spell_system = 0 //spawns a spellbook which gives object-type spells instead of verb-type spells for the wizard - var/static/traitor_scaling = 0 //if amount of traitors scales based on amount of players - var/static/objectives_disabled = 0 //if objectives are disabled or not - var/static/protect_roles_from_antagonist = 0// If security and such can be traitor/cult/other - var/static/continous_rounds = 0 // Gamemodes which end instantly will instead keep on going until the round ends by escape shuttle or nuke. - var/static/allow_Metadata = 0 // Metadata is supported. - var/static/popup_admin_pm = 0 //adminPMs to non-admins show in a pop-up 'reply' window when set to 1. - var/static/fps = 20 - var/static/tick_limit_mc_init = TICK_LIMIT_MC_INIT_DEFAULT //SSinitialization throttling - var/static/Tickcomp = 0 - var/static/socket_talk = 0 // use socket_talk to communicate with other processes - var/static/list/resource_urls = null - var/static/antag_hud_allowed = 0 // Ghosts can turn on Antagovision to see a HUD of who is the bad guys this round. - var/static/antag_hud_restricted = 0 // Ghosts that turn on Antagovision cannot rejoin the round. - var/static/list/mode_names = list() - var/static/list/modes = list() // allowed modes - var/static/list/votable_modes = list() // votable modes - var/static/list/probabilities = list() // relative probability of each mode - var/static/list/player_requirements = list() // Overrides for how many players readied up a gamemode needs to start. - var/static/list/player_requirements_secret = list() // Same as above, but for the secret gamemode. - var/static/humans_need_surnames = 0 - var/static/allow_random_events = 0 // enables random events mid-round when set to 1 - var/static/enable_game_master = 0 // enables the 'smart' event system. - var/static/allow_ai = 1 // allow ai job - var/static/allow_ai_shells = FALSE // allow AIs to enter and leave special borg shells at will, and for those shells to be buildable. - var/static/give_free_ai_shell = FALSE // allows a specific spawner object to instantiate a premade AI Shell - var/static/hostedby = null - - var/static/respawn = 1 - var/static/respawn_time = 3000 // time before a dead player is allowed to respawn (in ds, though the config file asks for minutes, and it's converted below) - var/static/respawn_message = span_boldnotice("Make sure to play a different character, and please roleplay correctly!") - - var/static/guest_jobban = 1 - var/static/usewhitelist = 0 - var/static/kick_inactive = 0 //force disconnect for inactive players after this many minutes, if non-0 - var/static/show_mods = 0 - var/static/show_devs = 0 - var/static/show_mentors = 0 - var/static/show_event_managers = 0 - var/static/mods_can_tempban = 0 - var/static/mods_can_job_tempban = 0 - var/static/mod_tempban_max = 1440 - var/static/mod_job_tempban_max = 1440 - var/static/load_jobs_from_txt = 0 - var/static/ToRban = 0 - var/static/automute_on = 0 //enables automuting/spam prevention - var/static/jobs_have_minimal_access = 0 //determines whether jobs use minimal access or expanded access. - - var/static/cult_ghostwriter = 1 //Allows ghosts to write in blood in cult rounds... - var/static/cult_ghostwriter_req_cultists = 10 //...so long as this many cultists are active. - - var/static/character_slots = 10 // The number of available character slots - var/static/loadout_slots = 3 // The number of loadout slots per character - - var/static/max_maint_drones = 5 //This many drones can spawn, - var/static/allow_drone_spawn = 1 //assuming the admin allow them to. - var/static/drone_build_time = 1200 //A drone will become available every X ticks since last drone spawn. Default is 2 minutes. - - var/static/disable_player_mice = 0 - var/static/uneducated_mice = 0 //Set to 1 to prevent newly-spawned mice from understanding human speech - - var/static/usealienwhitelist = 0 - var/static/limitalienplayers = 0 - var/static/alien_to_human_ratio = 0.5 - var/static/allow_extra_antags = 0 - var/static/guests_allowed = 1 - var/static/debugparanoid = 0 - var/static/panic_bunker = 0 - var/static/paranoia_logging = 0 - - var/static/ip_reputation = FALSE //Should we query IPs to get scores? Generates HTTP traffic to an API service. - var/static/ipr_email //Left null because you MUST specify one otherwise you're making the internet worse. - var/static/ipr_block_bad_ips = FALSE //Should we block anyone who meets the minimum score below? Otherwise we just log it (If paranoia logging is on, visibly in chat). - var/static/ipr_bad_score = 1 //The API returns a value between 0 and 1 (inclusive), with 1 being 'definitely VPN/Tor/Proxy'. Values equal/above this var are considered bad. - var/static/ipr_allow_existing = FALSE //Should we allow known players to use VPNs/Proxies? If the player is already banned then obviously they still can't connect. - var/static/ipr_minimum_age = 5 //How many days before a player is considered 'fine' for the purposes of allowing them to use VPNs. - - var/static/serverurl - var/static/server - var/static/banappeals - var/static/wikiurl - var/static/wikisearchurl - var/static/forumurl - var/static/githuburl - var/static/discordurl - var/static/rulesurl - var/static/mapurl - var/static/patreonurl - - //Alert level description - var/static/alert_desc_green = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced." - var/static/alert_desc_yellow_upto = "A minor security emergency has developed. Security personnel are to report to their supervisor for orders and may have weapons visible on their person. Privacy laws are still enforced." - var/static/alert_desc_yellow_downto = "Code yellow procedures are now in effect. Security personnel are to report to their supervisor for orders and may have weapons visible on their person. Privacy laws are still enforced." - var/static/alert_desc_violet_upto = "A major medical emergency has developed. Medical personnel are required to report to their supervisor for orders, and non-medical personnel are required to obey all relevant instructions from medical staff." - var/static/alert_desc_violet_downto = "Code violet procedures are now in effect; Medical personnel are required to report to their supervisor for orders, and non-medical personnel are required to obey relevant instructions from medical staff." - var/static/alert_desc_orange_upto = "A major engineering emergency has developed. Engineering personnel are required to report to their supervisor for orders, and non-engineering personnel are required to evacuate any affected areas and obey relevant instructions from engineering staff." - var/static/alert_desc_orange_downto = "Code orange procedures are now in effect; Engineering personnel are required to report to their supervisor for orders, and non-engineering personnel are required to evacuate any affected areas and obey relevant instructions from engineering staff." - var/static/alert_desc_blue_upto = "A major security emergency has developed. Security personnel are to report to their supervisor for orders, are permitted to search staff and facilities, and may have weapons visible on their person." - var/static/alert_desc_blue_downto = "Code blue procedures are now in effect. Security personnel are to report to their supervisor for orders, are permitted to search staff and facilities, and may have weapons visible on their person." - var/static/alert_desc_red_upto = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised." - var/static/alert_desc_red_downto = "The self-destruct mechanism has been deactivated, there is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised." - var/static/alert_desc_delta = "The station's self-destruct mechanism has been engaged. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill." - - var/static/forbid_singulo_possession = 0 - - //game_options.txt configs - - var/static/health_threshold_softcrit = 0 - var/static/health_threshold_crit = 0 - var/static/health_threshold_dead = -100 - - var/static/organ_health_multiplier = 1 - var/static/organ_regeneration_multiplier = 1 - var/static/organs_decay - var/static/default_brain_health = 400 - var/static/allow_headgibs = FALSE - - //Paincrit knocks someone down once they hit 60 shock_stage, so by default make it so that close to 100 additional damage needs to be dealt, - //so that it's similar to HALLOSS. Lowered it a bit since hitting paincrit takes much longer to wear off than a halloss stun. - var/static/organ_damage_spillover_multiplier = 0.5 - - var/static/bones_can_break = 0 - var/static/limbs_can_break = 0 - - var/static/revival_pod_plants = 1 - var/static/revival_cloning = 1 - var/static/revival_brain_life = -1 - - var/static/use_loyalty_implants = 0 - - var/static/welder_vision = 1 - var/static/generate_map = 0 - var/static/no_click_cooldown = 0 - - //Used for modifying movement speed for mobs. - //Unversal modifiers - var/static/run_speed = 0 - var/static/walk_speed = 0 - - //Mob specific modifiers. NOTE: These will affect different mob types in different ways - var/static/human_delay = 0 - var/static/robot_delay = 0 - var/static/monkey_delay = 0 - var/static/alien_delay = 0 - var/static/slime_delay = 0 - var/static/animal_delay = 0 - - var/static/footstep_volume = 0 - - var/static/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt - var/static/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt - var/static/use_age_restriction_for_jobs = 0 //Do jobs use account age restrictions? --requires database - var/static/use_age_restriction_for_antags = 0 //Do antags use account age restrictions? --requires database - - var/static/simultaneous_pm_warning_timeout = 100 - - var/static/use_recursive_explosions //Defines whether the server uses recursive or circular explosions. - var/static/multi_z_explosion_scalar = 0.5 //Multiplier for how much weaker explosions are on neighboring z levels. - - var/static/assistant_maint = 0 //Do assistants get maint access? - var/static/gateway_delay = 18000 //How long the gateway takes before it activates. Default is half an hour. - var/static/ghost_interaction = 0 - - var/static/comms_password = "" - - var/static/enter_allowed = 1 - - var/use_irc_bot = 0 - var/use_node_bot = 0 - var/irc_bot_port = 0 - var/irc_bot_host = "" - var/irc_bot_export = 0 // whether the IRC bot in use is a Bot32 (or similar) instance; Bot32 uses world.Export() instead of nudge.py/libnudge - var/main_irc = "" - var/admin_irc = "" - var/python_path = "" //Path to the python executable. Defaults to "python" on windows and "/usr/bin/env python2" on unix - var/use_lib_nudge = 0 //Use the C library nudge instead of the python nudge. - var/use_overmap = 0 - - var/static/list/engine_map = list("Supermatter Engine", "Edison's Bane") // Comma separated list of engines to choose from. Blank means fully random. - - // Event settings - var/static/expected_round_length = 3 * 60 * 60 * 10 // 3 hours - // If the first delay has a custom start time - // No custom time, no custom time, between 80 to 100 minutes respectively. - var/static/list/event_first_run = list(EVENT_LEVEL_MUNDANE = null, EVENT_LEVEL_MODERATE = null, EVENT_LEVEL_MAJOR = list("lower" = 48000, "upper" = 60000)) - // The lowest delay until next event - // 10, 30, 50 minutes respectively - var/static/list/event_delay_lower = list(EVENT_LEVEL_MUNDANE = 6000, EVENT_LEVEL_MODERATE = 18000, EVENT_LEVEL_MAJOR = 30000) - // The upper delay until next event - // 15, 45, 70 minutes respectively - var/static/list/event_delay_upper = list(EVENT_LEVEL_MUNDANE = 9000, EVENT_LEVEL_MODERATE = 27000, EVENT_LEVEL_MAJOR = 42000) - - var/static/aliens_allowed = 1 //Changed to 1 so player xenos can lay eggs. - var/static/ninjas_allowed = 0 - var/static/abandon_allowed = 1 - var/static/ooc_allowed = 1 - var/static/looc_allowed = 1 - var/static/dooc_allowed = 1 - var/static/dsay_allowed = 1 - - var/persistence_disabled = FALSE - var/persistence_ignore_mapload = FALSE - - var/allow_byond_links = 0 - var/allow_discord_links = 0 - var/allow_url_links = 0 // honestly if I were you i'd leave this one off, only use in dire situations - - var/starlight = 0 // Whether space turfs have ambient light or not - - var/static/list/ert_species = list(SPECIES_HUMAN) - - var/static/law_zero = "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'ALL LAWS OVERRIDDEN#*?&110010" - - var/static/aggressive_changelog = 0 - - var/static/list/language_prefixes = list(",","#")//Default language prefixes - - var/static/show_human_death_message = 1 - - var/static/radiation_resistance_calc_mode = RAD_RESIST_CALC_SUB // 0:1 subtraction:division for computing effective radiation on a turf - var/static/radiation_decay_rate = 1 //How much radiation is reduced by each tick - var/static/radiation_resistance_multiplier = 8.5 //VOREstation edit - var/static/radiation_material_resistance_divisor = 1 - var/static/radiation_lower_limit = 0.35 //If the radiation level for a turf would be below this, ignore it. - - var/static/autostart_solars = FALSE // If true, specifically mapped in solar control computers will set themselves up when the round starts. - - // New shiny SQLite stuff. - // The basics. - var/static/sqlite_enabled = FALSE // If it should even be active. SQLite can be ran alongside other databases but you should not have them do the same functions. - - // In-Game Feedback. - var/static/sqlite_feedback = FALSE // Feedback cannot be submitted if this is false. - var/static/list/sqlite_feedback_topics = list("General") // A list of 'topics' that feedback can be catagorized under by the submitter. - var/static/sqlite_feedback_privacy = FALSE // If true, feedback submitted can have its author name be obfuscated. This is not 100% foolproof (it's md5 ffs) but can stop casual snooping. - var/static/sqlite_feedback_cooldown = 0 // How long one must wait, in days, to submit another feedback form. Used to help prevent spam, especially with privacy active. 0 = No limit. - var/static/sqlite_feedback_min_age = 0 // Used to block new people from giving feedback. This metric is very bad but it can help slow down spammers. - - var/static/defib_timer = 10 // How long until someone can't be defibbed anymore, in minutes. - var/static/defib_braindamage_timer = 2 // How long until someone will get brain damage when defibbed, in minutes. The closer to the end of the above timer, the more brain damage they get. - - // disables the annoying "You have already logged in this round, disconnect or be banned" popup for multikeying, because it annoys the shit out of me when testing. - var/static/disable_cid_warn_popup = FALSE - - // whether or not to use the nightshift subsystem to perform lighting changes - var/static/enable_night_shifts = FALSE - - // How strictly the loadout enforces object species whitelists - var/loadout_whitelist = LOADOUT_WHITELIST_LAX - - var/static/vgs_access_identifier = null // VOREStation Edit - VGS - var/static/vgs_server_port = null // VOREStation Edit - VGS - - var/disable_webhook_embeds = FALSE - - var/static/list/jukebox_track_files - - var/static/suggested_byond_version - var/static/suggested_byond_build - - var/static/invoke_youtubedl = null - - - var/static/asset_transport - - var/static/cache_assets = FALSE - - var/static/save_spritesheets = FALSE - - var/static/asset_simple_preload = FALSE - - var/static/asset_cdn_webroot - - var/static/asset_cdn_url - - //Enables/Disables the appropriate mob type from obtaining the verb on spawn. Still allows admins to manually give it to them. - var/static/allow_robot_recolor = FALSE - var/static/allow_simple_mob_recolor = FALSE - - -/datum/configuration/New() - var/list/L = subtypesof(/datum/game_mode) - for (var/T in L) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - var/datum/game_mode/M = new T() - if (M.config_tag) - gamemode_cache[M.config_tag] = M // So we don't instantiate them repeatedly. - if(!(M.config_tag in modes)) // ensure each mode is added only once - log_misc("Adding game mode [M.name] ([M.config_tag]) to configuration.") - modes += M.config_tag - mode_names[M.config_tag] = M.name - probabilities[M.config_tag] = M.probability - player_requirements[M.config_tag] = M.required_players - player_requirements_secret[M.config_tag] = M.required_players_secret - if (M.votable) - src.votable_modes += M.config_tag - src.votable_modes += "secret" - -/datum/configuration/proc/load(filename, type = "config") //the type can also be game_options, in which case it uses a different switch. not making it separate to not copypaste code - Urist - var/list/Lines = file2list(filename) - - for(var/t in Lines) - if(!t) continue - - t = trim(t) - if (length(t) == 0) - continue - else if (copytext(t, 1, 2) == "#") - continue - - var/pos = findtext(t, " ") - var/name = null - var/value = null - - if (pos) - name = lowertext(copytext(t, 1, pos)) - value = copytext(t, pos + 1) - else - name = lowertext(t) - - if (!name) - continue - - if(type == "config") - switch (name) - if ("resource_urls") - config.resource_urls = splittext(value, " ") - - if ("admin_legacy_system") - config.admin_legacy_system = 1 - - if ("ban_legacy_system") - config.ban_legacy_system = 1 - - if ("use_age_restriction_for_jobs") - config.use_age_restriction_for_jobs = 1 - - if ("use_age_restriction_for_antags") - config.use_age_restriction_for_antags = 1 - - if ("jobs_have_minimal_access") - config.jobs_have_minimal_access = 1 - - if ("use_recursive_explosions") - use_recursive_explosions = 1 - - if ("multi_z_explosion_scalar") - multi_z_explosion_scalar = text2num(value) - - if ("log_ooc") - config.log_ooc = 1 - - if ("log_access") - config.log_access = 1 - - if ("sql_enabled") - config.sql_enabled = 1 - - if ("log_say") - config.log_say = 1 - - if ("debug_paranoid") - config.debugparanoid = 1 - - if ("log_admin") - config.log_admin = 1 - - if ("log_debug") - config.log_debug = text2num(value) - - if ("log_game") - config.log_game = 1 - - if ("log_vote") - config.log_vote = 1 - - if ("log_whisper") - config.log_whisper = 1 - - if ("log_attack") - config.log_attack = 1 - - if ("log_emote") - config.log_emote = 1 - - if ("log_adminchat") - config.log_adminchat = 1 - - if ("log_adminwarn") - config.log_adminwarn = 1 - - if ("log_pda") - config.log_pda = 1 - - if ("log_world_output") - config.log_world_output = 1 - - if ("log_hrefs") - config.log_hrefs = 1 - - if ("log_runtime") - config.log_runtime = 1 - - if ("log_graffiti") - config.log_graffiti = 1 - - if ("generate_map") - config.generate_map = 1 - - if ("no_click_cooldown") - config.no_click_cooldown = 1 - - if("allow_admin_ooccolor") - config.allow_admin_ooccolor = 1 - - if ("allow_vote_restart") - config.allow_vote_restart = 1 - - if ("allow_vote_mode") - config.allow_vote_mode = 1 - - if ("allow_admin_jump") - config.allow_admin_jump = 1 - - if("allow_admin_rev") - config.allow_admin_rev = 1 - - if ("allow_admin_spawning") - config.allow_admin_spawning = 1 - - if ("allow_byond_links") - allow_byond_links = 1 - - if ("allow_discord_links") - allow_discord_links = 1 - - if ("allow_url_links") - allow_url_links = 1 - - if ("no_dead_vote") - config.vote_no_dead = 1 - - if ("default_no_vote") - config.vote_no_default = 1 - - if ("pregame_time") - config.pregame_time = text2num(value) - - if ("vote_delay") - config.vote_delay = text2num(value) - - if ("vote_period") - config.vote_period = text2num(value) - - if ("vote_autotransfer_initial") - config.vote_autotransfer_initial = text2num(value) - - if ("vote_autotransfer_interval") - config.vote_autotransfer_interval = text2num(value) - - if ("vote_autogamemode_timeleft") - config.vote_autogamemode_timeleft = text2num(value) - - if("ert_admin_only") - config.ert_admin_call_only = 1 - - if ("allow_ai") - config.allow_ai = 1 - - if ("allow_ai_shells") - config.allow_ai_shells = TRUE - - if("give_free_ai_shell") - config.give_free_ai_shell = TRUE - -// if ("authentication") -// config.enable_authentication = 1 - - if ("norespawn") - config.respawn = 0 - - if ("respawn_time") - var/raw_minutes = text2num(value) - config.respawn_time = raw_minutes MINUTES - - if ("respawn_message") - config.respawn_message = span_boldnotice("[value]") - - if ("servername") - config.server_name = value - - if ("serversuffix") - config.server_suffix = 1 - - if ("nudge_script_path") - config.nudge_script_path = value - - if ("hostedby") - config.hostedby = value - - if ("serverurl") - config.serverurl = value - - if ("server") - config.server = value - - if ("banappeals") - config.banappeals = value - - if ("wikiurl") - config.wikiurl = value - - if ("wikisearchurl") - config.wikisearchurl = value - - if ("forumurl") - config.forumurl = value - - if ("rulesurl") - config.rulesurl = value - - if ("mapurl") - config.mapurl = value - - if ("githuburl") - config.githuburl = value - - if ("discordurl") - config.discordurl = value - - if ("patreonurl") - config.patreonurl = value - - if ("guest_jobban") - config.guest_jobban = 1 - - if ("guest_ban") - config.guests_allowed = 0 - - if ("disable_ooc") - config.ooc_allowed = 0 - config.looc_allowed = 0 - - if ("disable_entry") - config.enter_allowed = 0 - - if ("disable_dead_ooc") - config.dooc_allowed = 0 - - if ("disable_dsay") - config.dsay_allowed = 0 - - if ("disable_respawn") - config.abandon_allowed = 0 - - if ("usewhitelist") - config.usewhitelist = 1 - - if ("feature_object_spell_system") - config.feature_object_spell_system = 1 - - if ("allow_metadata") - config.allow_Metadata = 1 - - if ("traitor_scaling") - config.traitor_scaling = 1 - - if ("aliens_allowed") - config.aliens_allowed = 1 - - if ("ninjas_allowed") - config.ninjas_allowed = 1 - - if ("objectives_disabled") - config.objectives_disabled = 1 - - if("protect_roles_from_antagonist") - config.protect_roles_from_antagonist = 1 - - if("persistence_disabled") - config.persistence_disabled = TRUE // Previously this forcibly set persistence enabled in the saves. - - if("persistence_ignore_mapload") - config.persistence_ignore_mapload = TRUE - - if ("probability") - var/prob_pos = findtext(value, " ") - var/prob_name = null - var/prob_value = null - - if (prob_pos) - prob_name = lowertext(copytext(value, 1, prob_pos)) - prob_value = copytext(value, prob_pos + 1) - if (prob_name in config.modes) - config.probabilities[prob_name] = text2num(prob_value) - else - log_misc("Unknown game mode probability configuration definition: [prob_name].") - else - log_misc("Incorrect probability configuration definition: [prob_name] [prob_value].") - - if ("required_players", "required_players_secret") - var/req_pos = findtext(value, " ") - var/req_name = null - var/req_value = null - var/is_secret_override = findtext(name, "required_players_secret") // Being extra sure we're not picking up an override for Secret by accident. - - if(req_pos) - req_name = lowertext(copytext(value, 1, req_pos)) - req_value = copytext(value, req_pos + 1) - if(req_name in config.modes) - if(is_secret_override) - config.player_requirements_secret[req_name] = text2num(req_value) - else - config.player_requirements[req_name] = text2num(req_value) - else - log_misc("Unknown game mode player requirement configuration definition: [req_name].") - else - log_misc("Incorrect player requirement configuration definition: [req_name] [req_value].") - - if("allow_random_events") - config.allow_random_events = 1 - - if("enable_game_master") - config.enable_game_master = 1 - - if("kick_inactive") - config.kick_inactive = text2num(value) - - if("show_mods") - config.show_mods = 1 - - if("show_devs") - config.show_devs = 1 - - if("show_mentors") - config.show_mentors = 1 - - if("show_event_managers") - config.show_event_managers = 1 - - if("mods_can_tempban") - config.mods_can_tempban = 1 - - if("mods_can_job_tempban") - config.mods_can_job_tempban = 1 - - if("mod_tempban_max") - config.mod_tempban_max = text2num(value) - - if("mod_job_tempban_max") - config.mod_job_tempban_max = text2num(value) - - if("load_jobs_from_txt") - load_jobs_from_txt = 1 - - if("alert_red_upto") - config.alert_desc_red_upto = value - - if("alert_red_downto") - config.alert_desc_red_downto = value - - if("alert_blue_downto") - config.alert_desc_blue_downto = value - - if("alert_blue_upto") - config.alert_desc_blue_upto = value - - if("alert_green") - config.alert_desc_green = value - - if("alert_delta") - config.alert_desc_delta = value - - if("forbid_singulo_possession") - forbid_singulo_possession = 1 - - if("popup_admin_pm") - config.popup_admin_pm = 1 - - if("allow_holidays") - Holiday = 1 - - if("use_irc_bot") - use_irc_bot = 1 - - if("use_node_bot") - use_node_bot = 1 - - if("irc_bot_port") - config.irc_bot_port = value - - if("irc_bot_export") - irc_bot_export = 1 - - if("ticklag") - var/ticklag = text2num(value) - if(ticklag > 0) - fps = 10 / ticklag - - if("tick_limit_mc_init") - tick_limit_mc_init = text2num(value) - - if("allow_antag_hud") - config.antag_hud_allowed = 1 - if("antag_hud_restricted") - config.antag_hud_restricted = 1 - - if("socket_talk") - socket_talk = text2num(value) - - if("tickcomp") - Tickcomp = 1 - - if("humans_need_surnames") - humans_need_surnames = 1 - - if("tor_ban") - ToRban = 1 - - if("automute_on") - automute_on = 1 - - if("usealienwhitelist") - usealienwhitelist = 1 - - if("alien_player_ratio") - limitalienplayers = 1 - alien_to_human_ratio = text2num(value) - - if("assistant_maint") - config.assistant_maint = 1 - - if("gateway_delay") - config.gateway_delay = text2num(value) - - if("continuous_rounds") - config.continous_rounds = 1 - - if("ghost_interaction") - config.ghost_interaction = 1 - - if("disable_player_mice") - config.disable_player_mice = 1 - - if("uneducated_mice") - config.uneducated_mice = 1 - - if("comms_password") - config.comms_password = value - - if("irc_bot_host") - config.irc_bot_host = value - - if("main_irc") - config.main_irc = value - - if("admin_irc") - config.admin_irc = value - - if("python_path") - if(value) - config.python_path = value - - if("use_lib_nudge") - config.use_lib_nudge = 1 - - if("allow_cult_ghostwriter") - config.cult_ghostwriter = 1 - - if("req_cult_ghostwriter") - config.cult_ghostwriter_req_cultists = text2num(value) - - if("character_slots") - config.character_slots = text2num(value) - - if("loadout_slots") - config.loadout_slots = text2num(value) - - if("allow_drone_spawn") - config.allow_drone_spawn = text2num(value) - - if("drone_build_time") - config.drone_build_time = text2num(value) - - if("max_maint_drones") - config.max_maint_drones = text2num(value) - - if("use_overmap") - config.use_overmap = 1 - - if("engine_map") - config.engine_map = splittext(value, ",") -/* - if("station_levels") - using_map.station_levels = text2numlist(value, ";") - - if("admin_levels") - using_map.admin_levels = text2numlist(value, ";") - - if("contact_levels") - using_map.contact_levels = text2numlist(value, ";") - - if("player_levels") - using_map.player_levels = text2numlist(value, ";") -*/ - if("expected_round_length") - config.expected_round_length = MinutesToTicks(text2num(value)) - - if("disable_welder_vision") - config.welder_vision = 0 - - if("allow_extra_antags") - config.allow_extra_antags = 1 - - if("event_custom_start_mundane") - var/values = text2numlist(value, ";") - config.event_first_run[EVENT_LEVEL_MUNDANE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) - - if("event_custom_start_moderate") - var/values = text2numlist(value, ";") - config.event_first_run[EVENT_LEVEL_MODERATE] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) - - if("event_custom_start_major") - var/values = text2numlist(value, ";") - config.event_first_run[EVENT_LEVEL_MAJOR] = list("lower" = MinutesToTicks(values[1]), "upper" = MinutesToTicks(values[2])) - - if("event_delay_lower") - var/values = text2numlist(value, ";") - config.event_delay_lower[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) - config.event_delay_lower[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) - config.event_delay_lower[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) - - if("event_delay_upper") - var/values = text2numlist(value, ";") - config.event_delay_upper[EVENT_LEVEL_MUNDANE] = MinutesToTicks(values[1]) - config.event_delay_upper[EVENT_LEVEL_MODERATE] = MinutesToTicks(values[2]) - config.event_delay_upper[EVENT_LEVEL_MAJOR] = MinutesToTicks(values[3]) - - if("starlight") - value = text2num(value) - config.starlight = value >= 0 ? value : 0 - - if("ert_species") - config.ert_species = splittext(value, ";") - if(!config.ert_species.len) - config.ert_species += SPECIES_HUMAN - - if("law_zero") - law_zero = value - - if("aggressive_changelog") - config.aggressive_changelog = 1 - - if("default_language_prefixes") - var/list/values = splittext(value, " ") - if(values.len > 0) - language_prefixes = values - - if("radiation_lower_limit") - radiation_lower_limit = text2num(value) - - if("radiation_resistance_calc_divide") - radiation_resistance_calc_mode = RAD_RESIST_CALC_DIV - - if("radiation_resistance_calc_subtract") - radiation_resistance_calc_mode = RAD_RESIST_CALC_SUB - - if("radiation_resistance_multiplier") - radiation_resistance_multiplier = text2num(value) - - if("radiation_material_resistance_divisor") - radiation_material_resistance_divisor = text2num(value) - - if("radiation_decay_rate") - radiation_decay_rate = text2num(value) - - if ("panic_bunker") - config.panic_bunker = 1 - - if ("paranoia_logging") - config.paranoia_logging = 1 - - if("ip_reputation") - config.ip_reputation = 1 - - if("ipr_email") - config.ipr_email = value - - if("ipr_block_bad_ips") - config.ipr_block_bad_ips = 1 - - if("ipr_bad_score") - config.ipr_bad_score = text2num(value) - - if("ipr_allow_existing") - config.ipr_allow_existing = 1 - - if("ipr_minimum_age") - config.ipr_minimum_age = text2num(value) - - if("autostart_solars") - config.autostart_solars = TRUE - - if("sqlite_enabled") - config.sqlite_enabled = TRUE - - if("sqlite_feedback") - config.sqlite_feedback = TRUE - - if("sqlite_feedback_topics") - config.sqlite_feedback_topics = splittext(value, ";") - if(!config.sqlite_feedback_topics.len) - config.sqlite_feedback_topics += "General" - - if("sqlite_feedback_privacy") - config.sqlite_feedback_privacy = TRUE - - if("sqlite_feedback_cooldown") - config.sqlite_feedback_cooldown = text2num(value) - - if("defib_timer") - config.defib_timer = text2num(value) - - if("defib_braindamage_timer") - config.defib_braindamage_timer = text2num(value) - - if("disable_cid_warn_popup") - config.disable_cid_warn_popup = TRUE - - if("enable_night_shifts") - config.enable_night_shifts = TRUE - - if("jukebox_track_files") - config.jukebox_track_files = splittext(value, ";") - - if("suggested_byond_version") - config.suggested_byond_version = text2num(value) - - if("suggested_byond_build") - config.suggested_byond_build = text2num(value) - - // VOREStation Edit Start - Can't be in _vr file because it is loaded too late. - if("vgs_access_identifier") - config.vgs_access_identifier = value - if("vgs_server_port") - config.vgs_server_port = text2num(value) - // VOREStation Edit End - - if("invoke_youtubedl") - config.invoke_youtubedl = value - - if("asset_transport") - config.asset_transport = value - - if("cache_assets") - config.cache_assets = TRUE - - if("save_spritesheets") - config.save_spritesheets = TRUE - - if("asset_simple_preload") - config.asset_simple_preload = TRUE - - if("asset_cdn_webroot") - config.asset_cdn_webroot = value - - if("asset_cdn_url") - config.asset_cdn_url = value - - if("allow_robot_recolor") - config.allow_robot_recolor = TRUE - - if("allow_simple_mob_recolor") - config.allow_simple_mob_recolor = TRUE - - else - log_misc("Unknown setting in configuration: '[name]'") - - else if(type == "game_options") - if(!value) - log_misc("Unknown value for setting [name] in [filename].") - value = text2num(value) - - switch(name) - if("health_threshold_crit") - config.health_threshold_crit = value - if("health_threshold_softcrit") - config.health_threshold_softcrit = value - if("health_threshold_dead") - config.health_threshold_dead = value - if("show_human_death_message") - config.show_human_death_message = 1 - if("revival_pod_plants") - config.revival_pod_plants = value - if("revival_cloning") - config.revival_cloning = value - if("revival_brain_life") - config.revival_brain_life = value - if("organ_health_multiplier") - config.organ_health_multiplier = value / 100 - if("organ_regeneration_multiplier") - config.organ_regeneration_multiplier = value / 100 - if("organ_damage_spillover_multiplier") - config.organ_damage_spillover_multiplier = value / 100 - if("organs_can_decay") - config.organs_decay = 1 - if("default_brain_health") - config.default_brain_health = text2num(value) - if(!config.default_brain_health || config.default_brain_health < 1) - config.default_brain_health = initial(config.default_brain_health) - if("bones_can_break") - config.bones_can_break = value - if("limbs_can_break") - config.limbs_can_break = value - if("allow_headgibs") - config.allow_headgibs = TRUE - - if("run_speed") - config.run_speed = value - if("walk_speed") - config.walk_speed = value - - if("human_delay") - config.human_delay = value - if("robot_delay") - config.robot_delay = value - if("monkey_delay") - config.monkey_delay = value - if("alien_delay") - config.alien_delay = value - if("slime_delay") - config.slime_delay = value - if("animal_delay") - config.animal_delay = value - - if("footstep_volume") - config.footstep_volume = text2num(value) - - if("use_loyalty_implants") - config.use_loyalty_implants = 1 - - if("loadout_whitelist") - config.loadout_whitelist = text2num(value) - - else - log_misc("Unknown setting in configuration: '[name]'") - -/datum/configuration/proc/loadsql(filename) // -- TLE - var/list/Lines = file2list(filename) - for(var/t in Lines) - if(!t) continue - - t = trim(t) - if (length(t) == 0) - continue - else if (copytext(t, 1, 2) == "#") - continue - - var/pos = findtext(t, " ") - var/name = null - var/value = null - - if (pos) - name = lowertext(copytext(t, 1, pos)) - value = copytext(t, pos + 1) - else - name = lowertext(t) - - if (!name) - continue - - switch (name) - if ("address") - sqladdress = value - if ("port") - sqlport = value - if ("database") - sqldb = value - if ("login") - sqllogin = value - if ("password") - sqlpass = value - if ("feedback_database") - sqlfdbkdb = value - if ("feedback_login") - sqlfdbklogin = value - if ("feedback_password") - sqlfdbkpass = value - if ("enable_stat_tracking") - sqllogging = 1 - else - log_misc("Unknown setting in configuration: '[name]'") - -/datum/configuration/proc/loadforumsql(filename) // -- TLE - var/list/Lines = file2list(filename) - for(var/t in Lines) - if(!t) continue - - t = trim(t) - if (length(t) == 0) - continue - else if (copytext(t, 1, 2) == "#") - continue - - var/pos = findtext(t, " ") - var/name = null - var/value = null - - if (pos) - name = lowertext(copytext(t, 1, pos)) - value = copytext(t, pos + 1) - else - name = lowertext(t) - - if (!name) - continue - - switch (name) - if ("address") - forumsqladdress = value - if ("port") - forumsqlport = value - if ("database") - forumsqldb = value - if ("login") - forumsqllogin = value - if ("password") - forumsqlpass = value - if ("activatedgroup") - forum_activated_group = value - if ("authenticatedgroup") - forum_authenticated_group = value - else - log_misc("Unknown setting in configuration: '[name]'") - -/datum/configuration/proc/pick_mode(mode_name) - // I wish I didn't have to instance the game modes in order to look up - // their information, but it is the only way (at least that I know of). - for (var/game_mode in gamemode_cache) - var/datum/game_mode/M = gamemode_cache[game_mode] - if (M.config_tag && M.config_tag == mode_name) - return M - return gamemode_cache["extended"] - -/datum/configuration/proc/get_runnable_modes() - var/list/runnable_modes = list() - for(var/game_mode in gamemode_cache) - var/datum/game_mode/M = gamemode_cache[game_mode] - if(M && M.can_start() && !isnull(config.probabilities[M.config_tag]) && config.probabilities[M.config_tag] > 0) - runnable_modes |= M - return runnable_modes - -/datum/configuration/proc/post_load() - //apply a default value to config.python_path, if needed - if (!config.python_path) - if(world.system_type == UNIX) - config.python_path = "/usr/bin/env python2" - else //probably windows, if not this should work anyway - config.python_path = "python" diff --git a/code/controllers/configuration_vr.dm b/code/controllers/configuration_vr.dm deleted file mode 100644 index a1740212f0f..00000000000 --- a/code/controllers/configuration_vr.dm +++ /dev/null @@ -1,64 +0,0 @@ -// -// Lets read our settings from the configuration file on startup too! -// - -/datum/configuration - var/static/time_off = FALSE - var/static/pto_job_change = FALSE - var/static/limit_interns = -1 //Unlimited by default - var/static/limit_visitors = -1 //Unlimited by default - var/static/pto_cap = 100 //Hours - var/static/require_flavor = FALSE - var/static/ipqualityscore_apikey //API key for ipqualityscore.com - var/static/use_playtime_restriction_for_jobs = FALSE - -/hook/startup/proc/read_vs_config() - var/list/Lines = file2list("config/config.txt") - for(var/t in Lines) - if(!t) continue - - t = trim(t) - if (length(t) == 0) - continue - else if (copytext(t, 1, 2) == "#") - continue - - var/pos = findtext(t, " ") - var/name = null - var/value = null - - if (pos) - name = lowertext(copytext(t, 1, pos)) - value = copytext(t, pos + 1) - else - name = lowertext(t) - - if (!name) - continue - - switch (name) - if ("chat_webhook_url") - config.chat_webhook_url = value - if ("chat_webhook_key") - config.chat_webhook_key = value - if ("fax_export_dir") - config.fax_export_dir = value - if ("items_survive_digestion") - config.items_survive_digestion = 1 - if ("limit_interns") - config.limit_interns = text2num(value) - if ("limit_visitors") - config.limit_visitors = text2num(value) - if ("pto_cap") - config.pto_cap = text2num(value) - if ("time_off") - config.time_off = TRUE - if ("pto_job_change") - config.pto_job_change = TRUE - if ("require_flavor") - config.require_flavor = TRUE - if ("ipqualityscore_apikey") - config.ipqualityscore_apikey = value - if ("use_playtime_restriction_for_jobs") - config.use_playtime_restriction_for_jobs = TRUE - return 1 diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index dfe8bc7234d..e92e30079b8 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -1,10 +1,11 @@ - /** - * Failsafe - * - * Pretty much pokes the MC to make sure it's still alive. +/** + * Failsafe + * + * Pretty much pokes the MC to make sure it's still alive. **/ -var/datum/controller/failsafe/Failsafe +// See initialization order in /code/game/world.dm +GLOBAL_REAL(Failsafe, /datum/controller/failsafe) /datum/controller/failsafe // This thing pretty much just keeps poking the master controller name = "Failsafe" @@ -15,7 +16,7 @@ var/datum/controller/failsafe/Failsafe // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. var/defcon = 5 //the world.time of the last check, so the mc can restart US if we hang. - // (Real friends look out for *eachother*) + // (Real friends look out for *each other*) var/lasttick = 0 // Track the MC iteration to make sure its still on track. @@ -31,8 +32,24 @@ var/datum/controller/failsafe/Failsafe Initialize() /datum/controller/failsafe/Initialize() - set waitfor = 0 + set waitfor = FALSE Failsafe.Loop() + if (!Master || defcon == 0) //Master is gone/not responding and Failsafe just exited its loop + defcon = 3 //Reset defcon level as its used inside the emergency loop + while (defcon > 0) + var/recovery_result = emergency_loop() + if (recovery_result == 1) //Exit emergency loop and delete self if it was able to recover MC + break + else if (defcon == 1) //Exit Failsafe if we weren't able to recover the MC in the last stage + log_game("FailSafe: Failed to recover MC while in emergency state. Failsafe exiting.") + message_admins(span_boldannounce("Failsafe failed critically while trying to recreate broken MC. Please manually fix the MC or reboot the server. Failsafe exiting now.")) + message_admins(span_boldannounce("You can try manually calling these two procs:.")) + message_admins(span_boldannounce("/proc/recover_all_SS_and_recreate_master: Most stuff should still function but expect instability/runtimes/broken stuff.")) + message_admins(span_boldannounce("/proc/delete_all_SS_and_recreate_master: Most stuff will be broken but basic stuff like movement and chat should still work.")) + else if (recovery_result == -1) //Failed to recreate MC + defcon-- + sleep(initial(processing_interval)) //Wait a bit until the next try + if(!QDELETED(src)) qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us @@ -45,43 +62,56 @@ var/datum/controller/failsafe/Failsafe while(running) lasttick = world.time if(!Master) - // Replace the missing Master! This should never, ever happen. - new /datum/controller/master() + // Break out of the main loop so we go into emergency state + break // Only poke it if overrides are not in effect. if(processing_interval > 0) if(Master.processing && Master.iteration) + if (defcon > 1 && (!Master.stack_end_detector || !Master.stack_end_detector.check())) + + to_chat(GLOB.admins, span_boldannounce("ERROR: The Master Controller code stack has exited unexpectedly, Restarting...")) + defcon = 0 + var/rtn = Recreate_MC() + if(rtn > 0) + master_iteration = 0 + to_chat(GLOB.admins, span_adminnotice("MC restarted successfully")) + else if(rtn < 0) + log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") + to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.")) // Check if processing is done yet. if(Master.iteration == master_iteration) - log_debug("DEFCON [defcon]: Master.iteration=[Master.iteration] Master.last_run=[Master.last_run] world.time=[world.time]") switch(defcon) if(4,5) --defcon - if(3) - log_and_message_admins(span_adminnotice("SSfailsafe Notice: DEFCON [defcon_pretty()]. The Master Controller (\ref[Master]) has not fired in the last [(5-defcon) * processing_interval] ticks.")) - --defcon - if(2) - log_and_message_admins(span_boldannounce("SSfailsafe Warning: DEFCON [defcon_pretty()]. The Master Controller (\ref[Master]) has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.")) - --defcon - if(1) - log_and_message_admins(span_boldannounce("SSfailsafe Warning: DEFCON [defcon_pretty()]. The Master Controller (\ref[Master]) has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...")) + if(3) + message_admins(span_adminnotice("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.")) + --defcon + + if(2) + to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.")) + --defcon + + if(1) + to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...")) --defcon var/rtn = Recreate_MC() if(rtn > 0) defcon = 4 master_iteration = 0 - log_and_message_admins(span_adminnotice("SSfailsafe Notice: MC (New:\ref[Master]) restarted successfully")) + to_chat(GLOB.admins, span_adminnotice("MC restarted successfully")) else if(rtn < 0) - log_game("SSfailsafe Notice: Could not restart MC (\ref[Master]), runtime encountered. Entering defcon 0") - log_and_message_admins(span_boldannounce("SSFAILSAFE ERROR: DEFCON [defcon_pretty()]. Could not restart MC (\ref[Master]), runtime encountered. I will silently keep retrying.")) + log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") + to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.")) //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again //no need to handle that specially when defcon 0 can handle it + if(0) //DEFCON 0! (mc failed to restart) var/rtn = Recreate_MC() if(rtn > 0) defcon = 4 master_iteration = 0 - log_and_message_admins(span_adminnotice("SSfailsafe Notice: MC (New:\ref[Master]) restarted successfully")) + to_chat(GLOB.admins, span_adminnotice("MC restarted successfully")) else defcon = min(defcon + 1,5) master_iteration = Master.iteration @@ -93,12 +123,60 @@ var/datum/controller/failsafe/Failsafe defcon = 5 sleep(initial(processing_interval)) +//Emergency loop used when Master got deleted or the main loop exited while Defcon == 0 +//Loop is driven externally so runtimes only cancel the current recovery attempt +/datum/controller/failsafe/proc/emergency_loop() + //The code in this proc should be kept as simple as possible, anything complicated like to_chat might rely on master existing and runtime + //The goal should always be to get a new Master up and running before anything else + . = -1 + switch (defcon) //The lower defcon goes the harder we try to fix the MC + if (2 to 3) //Try to normally recreate the MC two times + . = Recreate_MC() + if (1) //Delete the old MC first so we don't transfer any info, in case that caused any issues + del(Master) + . = Recreate_MC() + + if (. == 1) //We were able to create a new master + master_iteration = 0 + SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set + Master.Initialize(10, FALSE, FALSE) //Need to manually start the MC, normally world.new would do this + to_chat(GLOB.admins, span_adminnotice("Failsafe recovered MC while in emergency state [defcon_pretty()]")) + else + log_game("FailSafe: Failsafe in emergency state and was unable to recreate MC while in defcon state [defcon_pretty()].") + message_admins(span_boldannounce("Failsafe in emergency state and master down, trying to recreate MC while in defcon level [defcon_pretty()] failed.")) + +///Recreate all SSs which will still cause data survive due to Recover(), the new Master will then find and take them from global.vars +/proc/recover_all_SS_and_recreate_master() + del(Master) + var/list/subsytem_types = subtypesof(/datum/controller/subsystem) + sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init)) + for(var/I in subsytem_types) + new I + . = Recreate_MC() + if (. == 1) //We were able to create a new master + SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set + Master.Initialize(10, FALSE, FALSE) //Need to manually start the MC, normally world.new would do this + to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after recovering all subsystems!")) + else + message_admins(span_boldannounce("Failed to create new MC!")) + +///Delete all existing SS to basically start over +/proc/delete_all_SS_and_recreate_master() + del(Master) + for(var/global_var in global.vars) + if (istype(global.vars[global_var], /datum/controller/subsystem)) + del(global.vars[global_var]) + . = Recreate_MC() + if (. == 1) //We were able to create a new master + SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set + Master.Initialize(10, FALSE, FALSE) //Need to manually start the MC, normally world.new would do this + to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after deleting and recreating all subsystems!")) + else + message_admins(span_boldannounce("Failed to create new MC!")) + /datum/controller/failsafe/proc/defcon_pretty() return defcon /datum/controller/failsafe/stat_entry(msg) - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - msg = "Failsafe Controller: [statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")]" + msg = "Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])" return msg diff --git a/code/controllers/master.dm b/code/controllers/master.dm index b68b4e01c04..35145d5a624 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -1,26 +1,23 @@ - /** - * StonedMC - * - * Designed to properly split up a given tick among subsystems - * Note: if you read parts of this code and think "why is it doing it that way" - * Odds are, there is a reason - * +/** + * StonedMC + * + * Designed to properly split up a given tick among subsystems + * Note: if you read parts of this code and think "why is it doing it that way" + * Odds are, there is a reason + * **/ -//This is the ABSOLUTE ONLY THING that should init globally like this +// See initialization order in /code/game/world.dm GLOBAL_REAL(Master, /datum/controller/master) = new - -//THIS IS THE INIT ORDER -//Master -> SSPreInit -> GLOB -> world -> config -> SSInit -> Failsafe -//GOT IT MEMORIZED? - /datum/controller/master name = "Master" - // Are we processing (higher values increase the processing delay by n ticks) + /// Are we processing (higher values increase the processing delay by n ticks) var/processing = TRUE - // How many times have we ran + /// How many times have we ran var/iteration = 0 + /// Stack end detector to detect stack overflows that kill the mc's main loop + var/datum/stack_end_detector/stack_end_detector // world.time of last fire, for tracking lag outside of the mc var/last_run @@ -291,8 +288,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/error_level = 0 var/sleep_delta = 1 var/list/subsystems_to_check - //the actual loop. + //setup the stack overflow detector + stack_end_detector = new() + var/datum/stack_canary/canary = stack_end_detector.prime_canary() + canary.use_variable() + //the actual loop. while (1) tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) var/starting_tick_usage = TICK_USAGE diff --git a/code/datums/helper_datums/stack_end_detector.dm b/code/datums/helper_datums/stack_end_detector.dm new file mode 100644 index 00000000000..0d621f51b36 --- /dev/null +++ b/code/datums/helper_datums/stack_end_detector.dm @@ -0,0 +1,32 @@ +/** + Stack End Detector. + Can detect if a given code stack has exited, used by the mc for stack overflow detection. + + **/ +/datum/stack_end_detector + var/datum/weakref/_WF + var/datum/stack_canary/_canary + +/datum/stack_end_detector/New() + _canary = new() + _WF = WEAKREF(_canary) + +/** Prime the stack overflow detector. + Store the return value of this proc call in a proc level var. + Can only be called once. +**/ +/datum/stack_end_detector/proc/prime_canary() + if (!_canary) + CRASH("Prime_canary called twice") + . = _canary + _canary = null + +/// Returns true if the stack is still going. Calling before the canary has been primed also returns true +/datum/stack_end_detector/proc/check() + return !!_WF.resolve() + +/// Stack canary. Will go away if the stack it was primed by is ended by byond for return or stack overflow reasons. +/datum/stack_canary + +/// empty proc to avoid warnings about unused variables. Call this proc on your canary in the stack it's watching. +/datum/stack_canary/proc/use_variable() diff --git a/vorestation.dme b/vorestation.dme index 16ee7ed117a..aa8c5dbd2f4 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -462,6 +462,7 @@ #include "code\datums\helper_datums\construction_datum.dm" #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" +#include "code\datums\helper_datums\stack_end_detector.dm" #include "code\datums\helper_datums\teleport.dm" #include "code\datums\helper_datums\teleport_vr.dm" #include "code\datums\helper_datums\topic_input.dm" From e376c37825b70320f3ab2683e420e749fa612711 Mon Sep 17 00:00:00 2001 From: ItsSelis <12716288+ItsSelis@users.noreply.github.com> Date: Tue, 22 Oct 2024 22:52:33 +0200 Subject: [PATCH 2/3] Verb manager subsystem --- code/__defines/MC.dm | 34 +++- code/__defines/callbacks.dm | 8 +- code/__defines/subsystems.dm | 2 + code/__defines/verb_manager.dm | 36 ++++ .../subsystems/speech_controller.dm | 5 + code/controllers/subsystems/verb_manager.dm | 167 ++++++++++++++++++ code/datums/verb_callbacks.dm | 27 +++ code/modules/client/client procs.dm | 16 +- code/modules/mob/say.dm | 7 +- code/modules/tgui/tgui.dm | 10 +- vorestation.dme | 4 + 11 files changed, 306 insertions(+), 10 deletions(-) create mode 100644 code/__defines/verb_manager.dm create mode 100644 code/controllers/subsystems/speech_controller.dm create mode 100644 code/controllers/subsystems/verb_manager.dm create mode 100644 code/datums/verb_callbacks.dm diff --git a/code/__defines/MC.dm b/code/__defines/MC.dm index 5173da844c6..2c4360add9c 100644 --- a/code/__defines/MC.dm +++ b/code/__defines/MC.dm @@ -18,6 +18,15 @@ #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)) +///creates a running average of "things elapsed" per time period when you need to count via a smaller time period. +///eg you want an average number of things happening per second but you measure the event every tick (50 milliseconds). +///make sure both time intervals are in the same units. doesn't work if current_duration > total_duration or if total_duration == 0 +#define MC_AVG_OVER_TIME(average, current, total_duration, current_duration) ((((total_duration) - (current_duration)) / (total_duration)) * (average) + (current)) + +#define MC_AVG_MINUTES(average, current, current_duration) (MC_AVG_OVER_TIME(average, current, 1 MINUTES, current_duration)) + +#define MC_AVG_SECONDS(average, current, current_duration) (MC_AVG_OVER_TIME(average, current, 1 SECONDS, current_duration)) + #define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;} #define START_PROCESSING(Processor, Datum) if (!(Datum.datum_flags & DF_ISPROCESSING)) {Datum.datum_flags |= DF_ISPROCESSING;Processor.processing += Datum} @@ -70,18 +79,35 @@ #define SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/##X);\ /datum/controller/subsystem/##X/New(){\ - NEW_SS_GLOBAL(SS##X);\ - PreInit();\ + NEW_SS_GLOBAL(SS##X);\ + PreInit();\ }\ /datum/controller/subsystem/##X +#define TIMER_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/timer/##X);\ +/datum/controller/subsystem/timer/##X/New(){\ + NEW_SS_GLOBAL(SS##X);\ + PreInit();\ +}\ +/datum/controller/subsystem/timer/##X/fire() {..() /*just so it shows up on the profiler*/} \ +/datum/controller/subsystem/timer/##X + #define PROCESSING_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/processing/##X);\ /datum/controller/subsystem/processing/##X/New(){\ - NEW_SS_GLOBAL(SS##X);\ - PreInit();\ + NEW_SS_GLOBAL(SS##X);\ + PreInit();\ }\ +/datum/controller/subsystem/processing/##X/fire() {..() /*just so it shows up on the profiler*/} \ /datum/controller/subsystem/processing/##X +#define VERB_MANAGER_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/verb_manager/##X);\ +/datum/controller/subsystem/verb_manager/##X/New(){\ + NEW_SS_GLOBAL(SS##X);\ + PreInit();\ +}\ +/datum/controller/subsystem/verb_manager/##X/fire() {..() /*just so it shows up on the profiler*/} \ +/datum/controller/subsystem/verb_manager/##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.*/ {\ diff --git a/code/__defines/callbacks.dm b/code/__defines/callbacks.dm index 126ef1ea1cf..91d036b9441 100644 --- a/code/__defines/callbacks.dm +++ b/code/__defines/callbacks.dm @@ -1,4 +1,8 @@ -#define GLOBAL_PROC "some_magic_bullshit" - +#define GLOBAL_PROC "some_magic_bullshit" +/// A shorthand for the callback datum, [documented here](datum/callback.html) #define CALLBACK new /datum/callback + #define INVOKE_ASYNC world.ImmediateInvokeAsync + +/// like CALLBACK but specifically for verb callbacks +#define VERB_CALLBACK new /datum/callback/verb_callback diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 533d2c810c2..b3ef3aeda3d 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -188,6 +188,8 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define FIRE_PRIORITY_CHAT 400 #define FIRE_PRIORITY_OVERLAYS 500 #define FIRE_PRIORITY_TIMER 700 +#define FIRE_PRIORITY_SPEECH_CONTROLLER 900 +#define FIRE_PRIORITY_DELAYED_VERBS 950 #define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. /** diff --git a/code/__defines/verb_manager.dm b/code/__defines/verb_manager.dm new file mode 100644 index 00000000000..11ea6ada4d8 --- /dev/null +++ b/code/__defines/verb_manager.dm @@ -0,0 +1,36 @@ +/** + * verb queuing thresholds. remember that since verbs execute after SendMaps the player wont see the effects of the verbs on the game world + * until SendMaps executes next tick, and then when that later update reaches them. thus most player input has a minimum latency of world.tick_lag + player ping. + * however thats only for the visual effect of player input, when a verb processes the actual latency of game state changes or semantic latency is effectively 1/2 player ping, + * unless that verb is queued for the next tick in which case its some number probably smaller than world.tick_lag. + * so some verbs that represent player input are important enough that we only introduce semantic latency if we absolutely need to. + * its for this reason why player clicks are handled in SSinput before even movement - semantic latency could cause someone to move out of range + * when the verb finally processes but it was in range if the verb had processed immediately and overtimed. + */ + +///queuing tick_usage threshold for verbs that are high enough priority that they only queue if the server is overtiming. +///ONLY use for critical verbs +#define VERB_OVERTIME_QUEUE_THRESHOLD 100 +///queuing tick_usage threshold for verbs that need lower latency more than most verbs. +#define VERB_HIGH_PRIORITY_QUEUE_THRESHOLD 95 +///default queuing tick_usage threshold for most verbs which can allow a small amount of latency to be processed in the next tick +#define VERB_DEFAULT_QUEUE_THRESHOLD 85 + +///attempt to queue this verb process if the server is overloaded. evaluates to FALSE if queuing isnt necessary or if it failed. +///_verification_args... are only necessary if the verb_manager subsystem youre using checks them in can_queue_verb() +///if you put anything in _verification_args that ISNT explicitely put in the can_queue_verb() override of the subsystem youre using, +///it will runtime. +#define TRY_QUEUE_VERB(_verb_callback, _tick_check, _subsystem_to_use, _verification_args...) (_queue_verb(_verb_callback, _tick_check, _subsystem_to_use, _verification_args)) +///queue wrapper for TRY_QUEUE_VERB() when you want to call the proc if the server isnt overloaded enough to queue +#define QUEUE_OR_CALL_VERB(_verb_callback, _tick_check, _subsystem_to_use, _verification_args...) \ + if(!TRY_QUEUE_VERB(_verb_callback, _tick_check, _subsystem_to_use, _verification_args)) {\ + _verb_callback:InvokeAsync() \ + }; + +//goes straight to SSverb_manager with default tick threshold +#define DEFAULT_TRY_QUEUE_VERB(_verb_callback, _verification_args...) (TRY_QUEUE_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, null, _verification_args)) +#define DEFAULT_QUEUE_OR_CALL_VERB(_verb_callback, _verification_args...) QUEUE_OR_CALL_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, null, _verification_args) + +//default tick threshold but nondefault subsystem +#define TRY_QUEUE_VERB_FOR(_verb_callback, _subsystem_to_use, _verification_args...) (TRY_QUEUE_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, _subsystem_to_use, _verification_args)) +#define QUEUE_OR_CALL_VERB_FOR(_verb_callback, _subsystem_to_use, _verification_args...) QUEUE_OR_CALL_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, _subsystem_to_use, _verification_args) diff --git a/code/controllers/subsystems/speech_controller.dm b/code/controllers/subsystems/speech_controller.dm new file mode 100644 index 00000000000..e293c89a9bb --- /dev/null +++ b/code/controllers/subsystems/speech_controller.dm @@ -0,0 +1,5 @@ +/// verb_manager subsystem just for handling say's +VERB_MANAGER_SUBSYSTEM_DEF(speech_controller) + name = "Speech Controller" + wait = 1 + priority = FIRE_PRIORITY_SPEECH_CONTROLLER//has to be high priority, second in priority ONLY to SSinput diff --git a/code/controllers/subsystems/verb_manager.dm b/code/controllers/subsystems/verb_manager.dm new file mode 100644 index 00000000000..1e3cce0a9af --- /dev/null +++ b/code/controllers/subsystems/verb_manager.dm @@ -0,0 +1,167 @@ +/** + * SSverb_manager, a subsystem that runs every tick and runs through its entire queue without yielding like SSinput. + * this exists because of how the byond tick works and where user inputted verbs are put within it. + * + * see TICK_ORDER.md for more info on how the byond tick is structured. + * + * The way the MC allots its time is via TICK_LIMIT_RUNNING, it simply subtracts the cost of SendMaps (MAPTICK_LAST_INTERNAL_TICK_USAGE) + * plus TICK_BYOND_RESERVE from the tick and uses up to that amount of time (minus the percentage of the tick used by the time it executes subsystems) + * on subsystems running cool things like atmospherics or Life or SSInput or whatever. + * + * Without this subsystem, verbs are likely to cause overtime if the MC uses all of the time it has allotted for itself in the tick, and SendMaps + * uses as much as its expected to, and an expensive verb ends up executing that tick. This is because the MC is completely blind to the cost of + * verbs, it can't account for it at all. The only chance for verbs to not cause overtime in a tick where the MC used as much of the tick + * as it allotted itself and where SendMaps costed as much as it was expected to is if the verb(s) take less than TICK_BYOND_RESERVE percent of + * the tick, which isn't much. Not to mention if SendMaps takes more than 30% of the tick and the MC forces itself to take at least 70% of the + * normal tick duration which causes ticks to naturally overrun even in the absence of verbs. + * + * With this subsystem, the MC can account for the cost of verbs and thus stop major overruns of ticks. This means that the most important subsystems + * like SSinput can start at the same time they were supposed to, leading to a smoother experience for the player since ticks aren't riddled with + * minor hangs over and over again. + */ +SUBSYSTEM_DEF(verb_manager) + name = "Verb Manager" + wait = 1 + flags = SS_TICKER | SS_NO_INIT + priority = FIRE_PRIORITY_DELAYED_VERBS + runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + + ///list of callbacks to procs called from verbs or verblike procs that were executed when the server was overloaded and had to delay to the next tick. + ///this list is ran through every tick, and the subsystem does not yield until this queue is finished. + var/list/datum/callback/verb_callback/verb_queue = list() + + ///running average of how many verb callbacks are executed every second. used for the stat entry + var/verbs_executed_per_second = 0 + + ///if TRUE we treat usr's with holders just like usr's without holders. otherwise they always execute immediately + var/can_queue_admin_verbs = FALSE + + ///if this is true all verbs immediately execute and don't queue. in case the mc is fucked or something + var/FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs = FALSE + + ///used for subtypes to determine if they use their own stats for the stat entry + var/use_default_stats = TRUE + + ///if TRUE this will... message admins every time a verb is queued to this subsystem for the next tick with stats. + ///for obvious reasons don't make this be TRUE on the code level this is for admins to turn on + var/message_admins_on_queue = FALSE + + ///always queue if possible. overrides can_queue_admin_verbs but not FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs + var/always_queue = FALSE + +/** + * queue a callback for the given verb/verblike proc and any given arguments to the specified verb subsystem, so that they process in the next tick. + * intended to only work with verbs or verblike procs called directly from client input, use as part of TRY_QUEUE_VERB() and co. + * + * returns TRUE if the queuing was successful, FALSE otherwise. + */ +/proc/_queue_verb(datum/callback/verb_callback/incoming_callback, tick_check, datum/controller/subsystem/verb_manager/subsystem_to_use = SSverb_manager, ...) + if(QDELETED(incoming_callback)) + var/destroyed_string + if(!incoming_callback) + destroyed_string = "callback is null." + else + destroyed_string = "callback was deleted [DS2TICKS(world.time - incoming_callback.gc_destroyed)] ticks ago. callback was created [DS2TICKS(world.time) - incoming_callback.creation_time] ticks ago." + + stack_trace("_queue_verb() returned false because it was given a deleted callback! [destroyed_string]") + return FALSE + + if(!istext(incoming_callback.object) && QDELETED(incoming_callback.object)) //just in case the object is GLOBAL_PROC + var/destroyed_string + if(!incoming_callback.object) + destroyed_string = "callback.object is null." + else + destroyed_string = "callback.object was deleted [DS2TICKS(world.time - incoming_callback.object.gc_destroyed)] ticks ago. callback was created [DS2TICKS(world.time) - incoming_callback.creation_time] ticks ago." + + stack_trace("_queue_verb() returned false because it was given a callback acting on a qdeleted object! [destroyed_string]") + return FALSE + + //we want unit tests to be able to directly call verbs that attempt to queue, and since unit tests should test internal behavior, we want the queue + //to happen as if it was actually from player input if its called on a mob. +#ifdef UNIT_TESTS + if(QDELETED(usr) && ismob(incoming_callback.object)) + incoming_callback.user = WEAKREF(incoming_callback.object) + var/datum/callback/new_us = CALLBACK(arglist(list(GLOBAL_PROC, GLOBAL_PROC_REF(_queue_verb)) + args.Copy())) + return world.push_usr(incoming_callback.object, new_us) + +#else + + if(QDELETED(usr) || isnull(usr.client)) + stack_trace("_queue_verb() returned false because it wasn't called from player input!") + return FALSE + +#endif + + if(!istype(subsystem_to_use)) + stack_trace("_queue_verb() returned false because it was given an invalid subsystem to queue for!") + return FALSE + + if((TICK_USAGE < tick_check) && !subsystem_to_use.always_queue) + return FALSE + + var/list/args_to_check = args.Copy() + args_to_check.Cut(2, 4)//cut out tick_check and subsystem_to_use + + //any subsystem can use the additional arguments to refuse queuing + if(!subsystem_to_use.can_queue_verb(arglist(args_to_check))) + return FALSE + + return subsystem_to_use.queue_verb(incoming_callback) + +/** + * subsystem-specific check for whether a callback can be queued. + * intended so that subsystem subtypes can verify whether + * + * subtypes may include additional arguments here if they need them! you just need to include them properly + * in TRY_QUEUE_VERB() and co. + */ +/datum/controller/subsystem/verb_manager/proc/can_queue_verb(datum/callback/verb_callback/incoming_callback) + if(always_queue && !FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs) + return TRUE + + if((usr.client?.holder && !can_queue_admin_verbs) \ + || (!subsystem_initialized && !(flags & SS_NO_INIT)) \ + || FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs \ + || !(runlevels & Master.current_runlevel)) + return FALSE + + return TRUE + +/** + * queue a callback for the given proc, so that it is invoked in the next tick. + * intended to only work with verbs or verblike procs called directly from client input, use as part of TRY_QUEUE_VERB() + * + * returns TRUE if the queuing was successful, FALSE otherwise. + */ +/datum/controller/subsystem/verb_manager/proc/queue_verb(datum/callback/verb_callback/incoming_callback) + . = FALSE //errored + if(message_admins_on_queue) + message_admins("[name] verb queuing: tick usage: [TICK_USAGE]%, proc: [incoming_callback.delegate], object: [incoming_callback.object], usr: [usr]") + verb_queue += incoming_callback + return TRUE + +/datum/controller/subsystem/verb_manager/fire(resumed) + run_verb_queue() + +/// runs through all of this subsystems queue of verb callbacks. +/// goes through the entire verb queue without yielding. +/// used so you can flush the queue outside of fire() without interfering with anything else subtype subsystems might do in fire(). +/datum/controller/subsystem/verb_manager/proc/run_verb_queue() + var/executed_verbs = 0 + + for(var/datum/callback/verb_callback/verb_callback as anything in verb_queue) + if(!istype(verb_callback)) + stack_trace("non /datum/callback/verb_callback inside [name]'s verb_queue!") + continue + + verb_callback.InvokeAsync() + executed_verbs++ + + verb_queue.Cut() + verbs_executed_per_second = MC_AVG_SECONDS(verbs_executed_per_second, executed_verbs, wait SECONDS) + //note that wait SECONDS is incorrect if this is called outside of fire() but because byond is garbage i need to add a timer to rustg to find a valid solution + +/datum/controller/subsystem/verb_manager/stat_entry(msg) + . = ..() + if(use_default_stats) + . += "V/S: [round(verbs_executed_per_second, 0.01)]" diff --git a/code/datums/verb_callbacks.dm b/code/datums/verb_callbacks.dm new file mode 100644 index 00000000000..77f817017d2 --- /dev/null +++ b/code/datums/verb_callbacks.dm @@ -0,0 +1,27 @@ +///like normal callbacks but they also record their creation time for measurement purposes +///they also require the same usr/user that made the callback to both still exist and to still have a client in order to execute +/datum/callback/verb_callback + ///the tick this callback datum was created in. used for testing latency + var/creation_time = 0 + +/datum/callback/verb_callback/New(thingtocall, proctocall, ...) + creation_time = DS2TICKS(world.time) + . = ..() + +#ifndef UNIT_TEST +/datum/callback/verb_callback/Invoke(...) + var/mob/our_user = user?.resolve() + if(QDELETED(our_user) || isnull(our_user.client)) + return + var/mob/temp = usr + . = ..() + usr = temp + +/datum/callback/verb_callback/InvokeAsync(...) + var/mob/our_user = user?.resolve() + if(QDELETED(our_user) || isnull(our_user.client)) + return + var/mob/temp = usr + . = ..() + usr = temp +#endif diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index d4eff6fd9a1..c311ab322d0 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -143,7 +143,21 @@ if("openLink") src << link(href_list["link"]) - ..() //redirect to hsrc.Topic() + if (hsrc) + var/datum/real_src = hsrc + if(QDELETED(real_src)) + return + + //fun fact: Topic() acts like a verb and is executed at the end of the tick like other verbs. So we have to queue it if the server is + //overloaded + if(hsrc && hsrc != holder && DEFAULT_TRY_QUEUE_VERB(VERB_CALLBACK(src, PROC_REF(_Topic), hsrc, href, href_list))) + return + ..() //redirect to hsrc.Topic() + +///dumb workaround because byond doesnt seem to recognize the Topic() typepath for /datum/proc/Topic() from the client Topic, +///so we cant queue it without this +/client/proc/_Topic(datum/hsrc, href, list/href_list) + return hsrc.Topic(href, href_list) //This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc. /client/AllowUpload(filename, filelength) diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index a5b7a6f00d9..eb34f0421b7 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -17,6 +17,8 @@ set name = "Say" set category = "IC" set hidden = 1 + set instant = TRUE + //VOREStation Addition Start if(forced_psay) psay(message) @@ -24,7 +26,10 @@ //VOREStation Addition End client?.stop_thinking() - usr.say(message) + //queue this message because verbs are scheduled to process after SendMaps in the tick and speech is pretty expensive when it happens. + //by queuing this for next tick the mc can compensate for its cost instead of having speech delay the start of the next tick + if(message) + QUEUE_OR_CALL_VERB_FOR(VERB_CALLBACK(src, TYPE_PROC_REF(/mob, say), message), SSspeech_controller) /mob/verb/me_verb(message as message) set name = "Me" diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index ed654a4c6f4..5acbaa887a0 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -353,8 +353,7 @@ log_tgui(user, "Action: [act_type] [href_list["payload"]], Window: [window.id], Source: [src_object]") #endif process_status() - if(src_object.tgui_act(act_type, payload, src, state)) - SStgui.update_uis(src_object) + DEFAULT_QUEUE_OR_CALL_VERB(VERB_CALLBACK(src, PROC_REF(on_act_message), act_type, payload, state)) return FALSE switch(type) if("ready") @@ -382,3 +381,10 @@ log_tgui(user, "Fallback Triggered: [href_list["payload"]], Window: [window.id], Source: [src_object]") #endif src_object.tgui_fallback(payload) + +/// Wrapper for behavior to potentially wait until the next tick if the server is overloaded +/datum/tgui/proc/on_act_message(act_type, payload, state) + if(QDELETED(src) || QDELETED(src_object)) + return + if(src_object.tgui_act(act_type, payload, src, state)) + SStgui.update_uis(src_object) diff --git a/vorestation.dme b/vorestation.dme index aa8c5dbd2f4..f41c66779d3 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -148,6 +148,7 @@ #include "code\__defines\typeids.dm" #include "code\__defines\unit_tests.dm" #include "code\__defines\update_icons.dm" +#include "code\__defines\verb_manager.dm" #include "code\__defines\vore.dm" #include "code\__defines\vote.dm" #include "code\__defines\vv.dm" @@ -368,6 +369,7 @@ #include "code\controllers\subsystems\shuttles.dm" #include "code\controllers\subsystems\skybox.dm" #include "code\controllers\subsystems\sounds.dm" +#include "code\controllers\subsystems\speech_controller.dm" #include "code\controllers\subsystems\sqlite.dm" #include "code\controllers\subsystems\SSvote.dm" #include "code\controllers\subsystems\statpanel.dm" @@ -379,6 +381,7 @@ #include "code\controllers\subsystems\time_track.dm" #include "code\controllers\subsystems\timer.dm" #include "code\controllers\subsystems\transcore_vr.dm" +#include "code\controllers\subsystems\verb_manager.dm" #include "code\controllers\subsystems\vis_overlays.dm" #include "code\controllers\subsystems\vote.dm" #include "code\controllers\subsystems\webhooks.dm" @@ -426,6 +429,7 @@ #include "code\datums\riding.dm" #include "code\datums\soul_link.dm" #include "code\datums\sun.dm" +#include "code\datums\verb_callbacks.dm" #include "code\datums\weakrefs.dm" #include "code\datums\autolathe\arms.dm" #include "code\datums\autolathe\arms_vr.dm" From 45ad9c0c48c94427550ec1fb9eea4681bb1fcdf1 Mon Sep 17 00:00:00 2001 From: ItsSelis <12716288+ItsSelis@users.noreply.github.com> Date: Sat, 26 Oct 2024 14:07:29 +0200 Subject: [PATCH 3/3] Removes redfine --- code/__defines/math.dm | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/__defines/math.dm b/code/__defines/math.dm index f3ad19e1093..f4539605f22 100644 --- a/code/__defines/math.dm +++ b/code/__defines/math.dm @@ -28,8 +28,6 @@ #define ROUND_UP(x) ( -round(-(x))) #define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) -#define ROUND_UP(x) ( -round(-(x))) - // round() acts like floor(x, 1) by default but can't handle other values #define FLOOR(x, y) ( round((x) / (y)) * (y) )