From 583dffcad4bf8edf8303dfac1668a517d92dd3e7 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Tue, 17 Aug 2021 11:57:08 +0100
Subject: [PATCH 01/19] Multi instance support
---
.../configuration/configuration_core.dm | 101 ++++++++++++++----
code/game/world.dm | 5 +-
2 files changed, 85 insertions(+), 21 deletions(-)
diff --git a/code/controllers/configuration/configuration_core.dm b/code/controllers/configuration/configuration_core.dm
index c4938d3efc6..c8b490e26cc 100644
--- a/code/controllers/configuration/configuration_core.dm
+++ b/code/controllers/configuration/configuration_core.dm
@@ -44,6 +44,8 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
var/datum/configuration_section/url_configuration/url
/// Holder for the voting configuration datum
var/datum/configuration_section/vote_configuration/vote
+ /// Raw data. Stored here to avoid passing data between procs constantly
+ var/list/raw_data = list()
/datum/server_configuration/Destroy(force)
@@ -51,6 +53,16 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
// This is going to stay existing. I dont care.
return QDEL_HINT_LETMELIVE
+/datum/server_configuration/vv_get_var(var_name)
+ if(var_name == "raw_data") // NO!
+ return FALSE
+ . = ..()
+
+/datum/server_configuration/vv_edit_var(var_name, var_value)
+ if(var_name == "raw_data") // NO!
+ return FALSE
+ . = ..()
+
/datum/server_configuration/CanProcCall(procname)
return FALSE // No thanks
@@ -83,32 +95,81 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
if(!fexists(config_file))
config_file = "config/example/config.toml" // Fallback to example if user hasnt setup config properly
var/raw_json = rustg_toml2json(config_file)
- var/list/raw_config_data = json_decode(raw_json)
+ raw_data = json_decode(raw_json)
// Now pass through all our stuff
- admin.load_data(raw_config_data["admin_configuration"])
- afk.load_data(raw_config_data["afk_configuration"])
- custom_sprites.load_data(raw_config_data["custom_sprites_configuration"])
- database.load_data(raw_config_data["database_configuration"])
- discord.load_data(raw_config_data["discord_configuration"])
- event.load_data(raw_config_data["event_configuration"])
- gamemode.load_data(raw_config_data["gamemode_configuration"])
- gateway.load_data(raw_config_data["gateway_configuration"])
- general.load_data(raw_config_data["general_configuration"])
- ipintel.load_data(raw_config_data["ipintel_configuration"])
- jobs.load_data(raw_config_data["job_configuration"])
- logging.load_data(raw_config_data["logging_configuration"])
- mc.load_data(raw_config_data["mc_configuration"])
- movement.load_data(raw_config_data["movement_configuration"])
- overflow.load_data(raw_config_data["overflow_configuration"])
- ruins.load_data(raw_config_data["ruin_configuration"])
- system.load_data(raw_config_data["system_configuration"])
- url.load_data(raw_config_data["url_configuration"])
- vote.load_data(raw_config_data["voting_configuration"])
+ safe_load(admin, "admin_configuration")
+ safe_load(afk, "afk_configuration")
+ safe_load(custom_sprites, "custom_sprites_configuration")
+ safe_load(database, "database_configuration")
+ safe_load(discord, "discord_configuration")
+ safe_load(event, "event_configuration")
+ safe_load(gamemode, "gamemode_configuration")
+ safe_load(gateway, "gateway_configuration")
+ safe_load(general, "general_configuration")
+ safe_load(ipintel, "ipintel_configuration")
+ safe_load(jobs, "job_configuration")
+ safe_load(logging, "logging_configuration")
+ safe_load(mc, "mc_configuration")
+ safe_load(movement, "movement_configuration")
+ safe_load(overflow, "overflow_configuration")
+ safe_load(ruins, "ruin_configuration")
+ safe_load(system, "system_configuration")
+ safe_load(url, "url_configuration")
+ safe_load(vote, "voting_configuration")
+
+ // Clear our list to save RAM
+ raw_data = list()
// And report the load
DIRECT_OUTPUT(world.log, "Config loaded in [stop_watch(start)]s")
+// Proc to load up instance-specific overrides
+/datum/server_configuration/proc/load_overrides()
+ var/override_file = "config/overrides_[world.port].toml"
+ if(!fexists(override_file))
+ DIRECT_OUTPUT(world.log, "Overrides not found for this instance.")
+ return
+
+ DIRECT_OUTPUT(world.log, "Overrides found for this instance. Loading them.")
+ var/start = start_watch() // Time tracking
+
+ var/raw_json = rustg_toml2json(override_file)
+ raw_data = json_decode(raw_json)
+
+ // Now safely load our overrides.
+ // Due to the nature of config wrappers, only vars that exist in the config file are applied to the config datums.
+ // This means that an override missing a key doesnt null it out from the main server
+ safe_load(admin, "admin_configuration")
+ safe_load(afk, "afk_configuration")
+ safe_load(custom_sprites, "custom_sprites_configuration")
+ safe_load(database, "database_configuration")
+ safe_load(discord, "discord_configuration")
+ safe_load(event, "event_configuration")
+ safe_load(gamemode, "gamemode_configuration")
+ safe_load(gateway, "gateway_configuration")
+ safe_load(general, "general_configuration")
+ safe_load(ipintel, "ipintel_configuration")
+ safe_load(jobs, "job_configuration")
+ safe_load(logging, "logging_configuration")
+ safe_load(mc, "mc_configuration")
+ safe_load(movement, "movement_configuration")
+ safe_load(overflow, "overflow_configuration")
+ safe_load(ruins, "ruin_configuration")
+ safe_load(system, "system_configuration")
+ safe_load(url, "url_configuration")
+ safe_load(vote, "voting_configuration")
+
+ // Clear our list to save RAM
+ raw_data = list()
+
+ // And report the load
+ DIRECT_OUTPUT(world.log, "Config overrides loaded in [stop_watch(start)]s")
+
+// Only loads the data for a config section if that key exists in the JSON
+/datum/server_configuration/proc/safe_load(datum/configuration_section/CS, section)
+ if(!isnull(raw_data[section]))
+ CS.load_data(raw_data[section])
/datum/configuration_section
/// See __config_defines.dm
diff --git a/code/game/world.dm b/code/game/world.dm
index a5ccc80fe7b..ac0234ab2dc 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -20,7 +20,10 @@ GLOBAL_LIST_INIT(map_transition_config, list(CC_TRANSITION_CONFIG))
//temporary file used to record errors with loading config and the database, moved to log directory once logging is set up
GLOB.config_error_log = GLOB.world_game_log = GLOB.world_runtime_log = GLOB.sql_log = "data/logs/config_error.log"
- GLOB.configuration.load_configuration()
+ GLOB.configuration.load_configuration() // Load up the base config.toml
+ // Load up overrides for this specific instance, based on port
+ // If this instance is listening on port 6666, the server will look for config/overrides_6666.toml
+ GLOB.configuration.load_overrides()
// Right off the bat, load up the DB
SSdbcore.CheckSchemaVersion() // This doesnt just check the schema version, it also connects to the db! This needs to happen super early! I cannot stress this enough!
From e19a83f9be3e709db408b33be3b68924147c335d Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Tue, 17 Aug 2021 14:05:46 +0100
Subject: [PATCH 02/19] SSinstancing (I need to test this with 2 machines)
---
.../configuration/configuration_core.dm | 39 +++++-------
.../sections/instancing_configuration.dm | 20 ++++++
code/controllers/subsystem/instancing.dm | 63 +++++++++++++++++++
code/datums/peer_server.dm | 32 ++++++++++
code/modules/admin/admin_verbs.dm | 3 +-
code/modules/admin/verbs/view_instances.dm | 22 +++++++
code/modules/world_topic/server_discovery.dm | 12 ++++
config/example/config.toml | 16 +++++
paradise.dme | 5 ++
9 files changed, 186 insertions(+), 26 deletions(-)
create mode 100644 code/controllers/configuration/sections/instancing_configuration.dm
create mode 100644 code/controllers/subsystem/instancing.dm
create mode 100644 code/datums/peer_server.dm
create mode 100644 code/modules/admin/verbs/view_instances.dm
create mode 100644 code/modules/world_topic/server_discovery.dm
diff --git a/code/controllers/configuration/configuration_core.dm b/code/controllers/configuration/configuration_core.dm
index c8b490e26cc..33f4e45d76c 100644
--- a/code/controllers/configuration/configuration_core.dm
+++ b/code/controllers/configuration/configuration_core.dm
@@ -24,6 +24,8 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
var/datum/configuration_section/gateway_configuration/gateway
/// Holder for the general configuration datum
var/datum/configuration_section/general_configuration/general
+ /// Holder for the instancing configuration datum
+ var/datum/configuration_section/instancing_configuration/instancing
/// Holder for the IPIntel configuration datum
var/datum/configuration_section/ipintel_configuration/ipintel
/// Holder for the job configuration datum
@@ -79,6 +81,7 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
gamemode = new()
gateway = new()
general = new()
+ instancing = new()
ipintel = new()
jobs = new()
logging = new()
@@ -98,6 +101,15 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
raw_data = json_decode(raw_json)
// Now pass through all our stuff
+ load_all_sections()
+
+ // Clear our list to save RAM
+ raw_data = list()
+
+ // And report the load
+ DIRECT_OUTPUT(world.log, "Config loaded in [stop_watch(start)]s")
+
+/datum/server_configuration/proc/load_all_sections()
safe_load(admin, "admin_configuration")
safe_load(afk, "afk_configuration")
safe_load(custom_sprites, "custom_sprites_configuration")
@@ -107,6 +119,7 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
safe_load(gamemode, "gamemode_configuration")
safe_load(gateway, "gateway_configuration")
safe_load(general, "general_configuration")
+ safe_load(instancing, "instancing_configuration")
safe_load(ipintel, "ipintel_configuration")
safe_load(jobs, "job_configuration")
safe_load(logging, "logging_configuration")
@@ -118,12 +131,6 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
safe_load(url, "url_configuration")
safe_load(vote, "voting_configuration")
- // Clear our list to save RAM
- raw_data = list()
-
- // And report the load
- DIRECT_OUTPUT(world.log, "Config loaded in [stop_watch(start)]s")
-
// Proc to load up instance-specific overrides
/datum/server_configuration/proc/load_overrides()
var/override_file = "config/overrides_[world.port].toml"
@@ -140,25 +147,7 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
// Now safely load our overrides.
// Due to the nature of config wrappers, only vars that exist in the config file are applied to the config datums.
// This means that an override missing a key doesnt null it out from the main server
- safe_load(admin, "admin_configuration")
- safe_load(afk, "afk_configuration")
- safe_load(custom_sprites, "custom_sprites_configuration")
- safe_load(database, "database_configuration")
- safe_load(discord, "discord_configuration")
- safe_load(event, "event_configuration")
- safe_load(gamemode, "gamemode_configuration")
- safe_load(gateway, "gateway_configuration")
- safe_load(general, "general_configuration")
- safe_load(ipintel, "ipintel_configuration")
- safe_load(jobs, "job_configuration")
- safe_load(logging, "logging_configuration")
- safe_load(mc, "mc_configuration")
- safe_load(movement, "movement_configuration")
- safe_load(overflow, "overflow_configuration")
- safe_load(ruins, "ruin_configuration")
- safe_load(system, "system_configuration")
- safe_load(url, "url_configuration")
- safe_load(vote, "voting_configuration")
+ load_all_sections()
// Clear our list to save RAM
raw_data = list()
diff --git a/code/controllers/configuration/sections/instancing_configuration.dm b/code/controllers/configuration/sections/instancing_configuration.dm
new file mode 100644
index 00000000000..bc5c6c8406a
--- /dev/null
+++ b/code/controllers/configuration/sections/instancing_configuration.dm
@@ -0,0 +1,20 @@
+/// Config holder for stuff relating to multi-server instances
+/datum/configuration_section/instancing_configuration
+ /// ID of this specific server
+ var/server_id = "paradise_main"
+ /// List of all peer servers
+ var/list/datum/peer_server/peers = list()
+
+/datum/configuration_section/instancing_configuration/load_data(list/data)
+ // Use the load wrappers here. That way the default isnt made 'null' if you comment out the config line
+ CONFIG_LOAD_STR(server_id, data["server_id"])
+
+ if(islist(data["peer_servers"]))
+ for(var/list/server in data["peer_servers"])
+ if(server["server_port"] == world.port) // Skip our own instance
+ continue
+ var/datum/peer_server/PS = new()
+ PS.internal_ip = server["internal_ip"]
+ PS.server_port = server["server_port"]
+ PS.commskey = server["commskey"]
+ peers.Add(PS)
diff --git a/code/controllers/subsystem/instancing.dm b/code/controllers/subsystem/instancing.dm
new file mode 100644
index 00000000000..ec4c289bc86
--- /dev/null
+++ b/code/controllers/subsystem/instancing.dm
@@ -0,0 +1,63 @@
+SUBSYSTEM_DEF(instancing)
+ name = "Instancing"
+ runlevels = RUNLEVEL_INIT | RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ /// Have we announced startup yet
+ var/startup_announced = FALSE
+ /// Is a check currently running?
+ var/check_running = FALSE
+
+/datum/controller/subsystem/instancing/Initialize(start_timeofday)
+ // Do an initial peer check
+ check_peers()
+ return ..()
+
+/datum/controller/subsystem/instancing/fire(resumed)
+ check_peers()
+
+
+/**
+ * Refreshes all peers on the server
+ *
+ * Called periodically during fire() as well as when a new peer reports itself as online
+ * Only one instance of this proc may run at a time
+ *
+ */
+/datum/controller/subsystem/instancing/proc/check_peers()
+ set waitfor = FALSE // This has sleeps if it cant topic so we dont want to bog things down
+ if(check_running)
+ return
+
+ check_running = TRUE // Dont allow multiple of these
+ // A NOTE TO ANYONE ELSE WHO LOOKS AT THIS
+ // THESE TIMINGS ARE A FUCKING NIGHTMARE AND YOU WILL NEED DEBUG LOGGING TO TEST THEM
+ // DO NOT FUCK WITH THE TIMINGS -aa
+ for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
+ // If the server hasnt been discovered and its been more than 5 minutes
+ if((!PS.discovered && PS.last_operation_time + 5 MINUTES > world.time))
+ continue
+
+ if(PS.last_operation_time + 1 MINUTES > world.time)
+ continue // Only run main operations once every minute anyway
+
+ var/peer_response = world.Export("byond://[PS.internal_ip]:[PS.server_port]?server_discovery&key=[PS.commskey]")
+ if(!peer_response)
+ PS.online = FALSE // Peer is offline
+ PS.last_operation_time = world.time
+ continue
+
+ // We got a response
+ PS.discovered = TRUE
+ PS.online = TRUE
+ var/list/peer_data = json_decode(peer_response)
+
+ PS.external_ip = peer_data["external_ip"]
+ PS.server_id = peer_data["server_id"]
+ PS.server_name = peer_data["server_name"]
+ PS.playercount = peer_data["playercount"]
+
+ PS.last_operation_time = world.time
+
+ check_running = FALSE
+
+/datum/controller/subsystem/instancing/proc/message_all_peers(include_offline = FALSE)
+ return
diff --git a/code/datums/peer_server.dm b/code/datums/peer_server.dm
new file mode 100644
index 00000000000..81f78be68b4
--- /dev/null
+++ b/code/datums/peer_server.dm
@@ -0,0 +1,32 @@
+/**
+ * # Peer server
+ *
+ * This datum is essentially a model class to represent another instance of Paracode running
+ *
+ * It contains communications passes, an internal IP to communicate to, an external IP for players to connect to
+ *
+ */
+/datum/peer_server
+ // The following options will be loaded from configuration
+ /// Peer server internal IP, used for communications
+ var/internal_ip
+ /// Port of the server
+ var/server_port = 0
+ /// Comms key for this server
+ var/commskey
+
+ // The following options will be pulled from the peer at runtime
+ /// Peer server external IP, used for routing players around
+ var/external_ip
+ /// Peer server ID, used internally. Pulled from the peer on startup.
+ var/server_id
+ /// Peer server name, presented to players. Pulled from the peer on startup.
+ var/server_name
+ /// Have we done initial data discovery yet?
+ var/discovered = FALSE
+ /// Is the peer server online?
+ var/online = FALSE
+ /// Playercount of the peer server on last ping
+ var/playercount
+ /// Last world.time that an operation was attempted
+ var/last_operation_time
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 598705668ae..6a3103172ee 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -67,7 +67,8 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
/client/proc/toggle_mentor_chat,
/client/proc/toggle_advanced_interaction, /*toggle admin ability to interact with not only machines, but also atoms such as buttons and doors*/
/client/proc/list_ssds_afks,
- /client/proc/ccbdb_lookup_ckey
+ /client/proc/ccbdb_lookup_ckey,
+ /client/proc/view_instances
))
GLOBAL_LIST_INIT(admin_verbs_ban, list(
/client/proc/ban_panel,
diff --git a/code/modules/admin/verbs/view_instances.dm b/code/modules/admin/verbs/view_instances.dm
new file mode 100644
index 00000000000..3aa83809516
--- /dev/null
+++ b/code/modules/admin/verbs/view_instances.dm
@@ -0,0 +1,22 @@
+/client/proc/view_instances()
+ set name = "View Server Instances"
+ set desc = "View the running server instances"
+ set category = "Server"
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ to_chat(usr, "Server instances info")
+ for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
+ // We havnt even been discovered, so we cant even be online
+ if(!PS.discovered)
+ to_chat(usr, "#[PS.server_port] (Undiscovered) - OFFLINE")
+ continue
+
+ // We exist but arent online at the moment
+ if(!PS.online)
+ to_chat(usr, "ID [PS.server_id] - OFFLINE")
+ continue
+
+ // If we are here, we are online, so we can do a rich report
+ to_chat(usr, "ID [PS.server_id] - ONLINE (Players: [PS.playercount])")
diff --git a/code/modules/world_topic/server_discovery.dm b/code/modules/world_topic/server_discovery.dm
new file mode 100644
index 00000000000..8558f5c380c
--- /dev/null
+++ b/code/modules/world_topic/server_discovery.dm
@@ -0,0 +1,12 @@
+/datum/world_topic_handler/server_discovery
+ topic_key = "server_discovery"
+ requires_commskey = TRUE
+
+/datum/world_topic_handler/server_discovery/execute(list/input, key_valid)
+ // We need to send the stuff to make a /datum/peer_server on the other side
+ var/list/server_data = list()
+ server_data["external_ip"] = world.internet_address
+ server_data["server_id"] = GLOB.configuration.instancing.server_id
+ server_data["server_name"] = GLOB.configuration.general.server_name
+ server_data["playercount"] = length(GLOB.clients)
+ return json_encode(server_data)
diff --git a/config/example/config.toml b/config/example/config.toml
index 254f793221e..b73e378be14 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -22,6 +22,7 @@
# - gamemode_configuration
# - gateway_configuration
# - general_configuration
+# - instancing_configuration
# - ipintel_configuration
# - job_configuration
# - logging_configuration
@@ -376,6 +377,21 @@ random_ai_lawset = true
################################################################
+[instancing_configuration]
+# This section contains all the information for instancing servers (Multiple paracode servers on the same database)
+
+# ID of this specific server. Override when needed
+server_id = "paradise_main"
+# List of all peer servers. Add all of the servers inside this configuration.
+# The code will ignore its own instance by port checking
+peer_servers = [
+ #{internal_ip = "10.0.0.30", server_port = 6666, commskey = "please_use_something_secure"}
+]
+
+
+################################################################
+
+
[ipintel_configuration]
# This section contains all the information for IPIntel (The Anti VPN system)
diff --git a/paradise.dme b/paradise.dme
index 11176b1befe..25499536c1c 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -196,6 +196,7 @@
#include "code\controllers\configuration\sections\gamemode_configuration.dm"
#include "code\controllers\configuration\sections\gateway_configuration.dm"
#include "code\controllers\configuration\sections\general_configuration.dm"
+#include "code\controllers\configuration\sections\instancing_configuration.dm"
#include "code\controllers\configuration\sections\ipintel_configuration.dm"
#include "code\controllers\configuration\sections\job_configuration.dm"
#include "code\controllers\configuration\sections\logging_configuration.dm"
@@ -228,6 +229,7 @@
#include "code\controllers\subsystem\icon_smooth.dm"
#include "code\controllers\subsystem\idlenpcpool.dm"
#include "code\controllers\subsystem\input.dm"
+#include "code\controllers\subsystem\instancing.dm"
#include "code\controllers\subsystem\ipintel.dm"
#include "code\controllers\subsystem\jobs.dm"
#include "code\controllers\subsystem\late_mapping.dm"
@@ -288,6 +290,7 @@
#include "code\datums\mind.dm"
#include "code\datums\mixed.dm"
#include "code\datums\mutable_appearance.dm"
+#include "code\datums\peer_server.dm"
#include "code\datums\periodic_news.dm"
#include "code\datums\pipe_datums.dm"
#include "code\datums\progressbar.dm"
@@ -1221,6 +1224,7 @@
#include "code\modules\admin\verbs\ticklag.dm"
#include "code\modules\admin\verbs\toggledebugverbs.dm"
#include "code\modules\admin\verbs\tripAI.dm"
+#include "code\modules\admin\verbs\view_instances.dm"
#include "code\modules\admin\verbs\vox_raiders.dm"
#include "code\modules\admin\verbs\SDQL2\SDQL_2.dm"
#include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm"
@@ -2508,6 +2512,7 @@
#include "code\modules\world_topic\manifest.dm"
#include "code\modules\world_topic\ping.dm"
#include "code\modules\world_topic\players.dm"
+#include "code\modules\world_topic\server_discovery.dm"
#include "code\modules\world_topic\status.dm"
#include "goon\code\datums\browserOutput.dm"
#include "interface\interface.dm"
From fdcc88b7ca3e8293cfbc4be556167b95d86dfd07 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Tue, 17 Aug 2021 14:30:00 +0100
Subject: [PATCH 03/19] Startup notifications
---
code/controllers/subsystem/instancing.dm | 46 +++++++++++++++++--
code/modules/admin/admin_verbs.dm | 3 +-
code/modules/admin/verbs/view_instances.dm | 10 ++++
code/modules/world_topic/instance_announce.dm | 7 +++
paradise.dme | 1 +
5 files changed, 61 insertions(+), 6 deletions(-)
create mode 100644 code/modules/world_topic/instance_announce.dm
diff --git a/code/controllers/subsystem/instancing.dm b/code/controllers/subsystem/instancing.dm
index ec4c289bc86..11ee97ef93f 100644
--- a/code/controllers/subsystem/instancing.dm
+++ b/code/controllers/subsystem/instancing.dm
@@ -3,6 +3,8 @@ SUBSYSTEM_DEF(instancing)
runlevels = RUNLEVEL_INIT | RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
/// Have we announced startup yet
var/startup_announced = FALSE
+ /// Has our initial check complete? Used as part of the above
+ var/initial_check_complete = FALSE
/// Is a check currently running?
var/check_running = FALSE
@@ -13,6 +15,10 @@ SUBSYSTEM_DEF(instancing)
/datum/controller/subsystem/instancing/fire(resumed)
check_peers()
+ if(initial_check_complete && !startup_announced)
+ startup_announced = TRUE
+ var/startup_msg = "The server [GLOB.configuration.general.server_name] is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name])"
+ INVOKE_ASYNC(src, .proc/message_all_peers, startup_msg) // Async
/**
@@ -21,8 +27,10 @@ SUBSYSTEM_DEF(instancing)
* Called periodically during fire() as well as when a new peer reports itself as online
* Only one instance of this proc may run at a time
*
+ * Arguments:
+ * * force - Do we want to force check all of them
*/
-/datum/controller/subsystem/instancing/proc/check_peers()
+/datum/controller/subsystem/instancing/proc/check_peers(force = FALSE)
set waitfor = FALSE // This has sleeps if it cant topic so we dont want to bog things down
if(check_running)
return
@@ -34,10 +42,13 @@ SUBSYSTEM_DEF(instancing)
for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
// If the server hasnt been discovered and its been more than 5 minutes
if((!PS.discovered && PS.last_operation_time + 5 MINUTES > world.time))
- continue
+ if(!force) // No, code review. This is not going in the same line.
+ continue
+ // Only run main operations once every minute anyway
if(PS.last_operation_time + 1 MINUTES > world.time)
- continue // Only run main operations once every minute anyway
+ if(!force)
+ continue
var/peer_response = world.Export("byond://[PS.internal_ip]:[PS.server_port]?server_discovery&key=[PS.commskey]")
if(!peer_response)
@@ -58,6 +69,31 @@ SUBSYSTEM_DEF(instancing)
PS.last_operation_time = world.time
check_running = FALSE
+ initial_check_complete = TRUE
-/datum/controller/subsystem/instancing/proc/message_all_peers(include_offline = FALSE)
- return
+/**
+ * Message all peers
+ *
+ * Wrapper for [topic_all_peers] to autoformat a message topic. Will send a server-wide announcement to the other servers
+ * including any relevant detail
+ * Arguments:
+ * * message - Message to send to the other servers
+ * * include_offline - Whether to topic offline servers on the off chance they came online
+ */
+/datum/controller/subsystem/instancing/proc/message_all_peers(message, include_offline = FALSE)
+ var/topic_string = "instance_announce&msg=[html_encode(message)]"
+ topic_all_peers(topic_string, include_offline)
+
+/**
+ * Sends a topic to all peers
+ *
+ * Sends a raw topic to the other servers. WILL APPEND &key=[commskey] ON THE END. PLEASE ACCOUNT FOR THIS.
+ *
+ * Arguments:
+ * * raw_topic - The raw topic to send to the other servers
+ * * include_offline - Whether to topic offline servers on the off chance they came online
+ */
+/datum/controller/subsystem/instancing/proc/topic_all_peers(raw_topic, include_offline = FALSE)
+ for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
+ if(PS.online || include_offline)
+ world.Export("byond://[PS.internal_ip]:[PS.server_port]?[raw_topic]&key=[PS.commskey]")
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 6a3103172ee..60758052863 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -138,7 +138,8 @@ GLOBAL_LIST_INIT(admin_verbs_server, list(
/client/proc/toggle_antagHUD_restrictions,
/client/proc/set_ooc,
/client/proc/reset_ooc,
- /client/proc/set_next_map
+ /client/proc/set_next_map,
+ /client/proc/refresh_instances
))
GLOBAL_LIST_INIT(admin_verbs_debug, list(
/client/proc/cmd_admin_list_open_jobs,
diff --git a/code/modules/admin/verbs/view_instances.dm b/code/modules/admin/verbs/view_instances.dm
index 3aa83809516..3cedf9614a0 100644
--- a/code/modules/admin/verbs/view_instances.dm
+++ b/code/modules/admin/verbs/view_instances.dm
@@ -20,3 +20,13 @@
// If we are here, we are online, so we can do a rich report
to_chat(usr, "ID [PS.server_id] - ONLINE (Players: [PS.playercount])")
+
+/client/proc/refresh_instances()
+ set name = "Force Refresh Server Instances"
+ set desc = "Force refresh the local cache of server instances"
+ set category = "Server"
+
+ if(!check_rights(R_SERVER))
+ return
+
+ SSinstancing.check_peers(TRUE) // Force check
diff --git a/code/modules/world_topic/instance_announce.dm b/code/modules/world_topic/instance_announce.dm
new file mode 100644
index 00000000000..e63ce9a290c
--- /dev/null
+++ b/code/modules/world_topic/instance_announce.dm
@@ -0,0 +1,7 @@
+/datum/world_topic_handler/instance_announce
+ topic_key = "instance_announce"
+ requires_commskey = TRUE
+
+/datum/world_topic_handler/instance_announce/execute(list/input, key_valid)
+ var/msg = input["msg"]
+ to_chat(world, "Attention: [msg]")
diff --git a/paradise.dme b/paradise.dme
index 25499536c1c..4f3e928c357 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -2509,6 +2509,7 @@
#include "code\modules\world_topic\_topic_base.dm"
#include "code\modules\world_topic\adminmsg.dm"
#include "code\modules\world_topic\announce.dm"
+#include "code\modules\world_topic\instance_announce.dm"
#include "code\modules\world_topic\manifest.dm"
#include "code\modules\world_topic\ping.dm"
#include "code\modules\world_topic\players.dm"
From 3560f8fceabd8d8cbed4aa897e382fd4eed0f3f5 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Tue, 17 Aug 2021 14:36:09 +0100
Subject: [PATCH 04/19] Behaviour tweak
---
code/controllers/subsystem/instancing.dm | 12 ++++--------
tgui/packages/common/target/npmlist.json | 1 +
2 files changed, 5 insertions(+), 8 deletions(-)
create mode 100644 tgui/packages/common/target/npmlist.json
diff --git a/code/controllers/subsystem/instancing.dm b/code/controllers/subsystem/instancing.dm
index 11ee97ef93f..ffc8808e427 100644
--- a/code/controllers/subsystem/instancing.dm
+++ b/code/controllers/subsystem/instancing.dm
@@ -1,9 +1,7 @@
SUBSYSTEM_DEF(instancing)
name = "Instancing"
runlevels = RUNLEVEL_INIT | RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
- /// Have we announced startup yet
- var/startup_announced = FALSE
- /// Has our initial check complete? Used as part of the above
+ /// Has our initial check complete? Used to halt init but not lag the server
var/initial_check_complete = FALSE
/// Is a check currently running?
var/check_running = FALSE
@@ -11,15 +9,13 @@ SUBSYSTEM_DEF(instancing)
/datum/controller/subsystem/instancing/Initialize(start_timeofday)
// Do an initial peer check
check_peers()
+ UNTIL(initial_check_complete) // Wait here a bit
+ var/startup_msg = "The server [GLOB.configuration.general.server_name] is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name])"
+ message_all_peers(startup_msg)
return ..()
/datum/controller/subsystem/instancing/fire(resumed)
check_peers()
- if(initial_check_complete && !startup_announced)
- startup_announced = TRUE
- var/startup_msg = "The server [GLOB.configuration.general.server_name] is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name])"
- INVOKE_ASYNC(src, .proc/message_all_peers, startup_msg) // Async
-
/**
* Refreshes all peers on the server
diff --git a/tgui/packages/common/target/npmlist.json b/tgui/packages/common/target/npmlist.json
new file mode 100644
index 00000000000..f9c06d0a205
--- /dev/null
+++ b/tgui/packages/common/target/npmlist.json
@@ -0,0 +1 @@
+{"name":"common","version":"3.0.0"}
\ No newline at end of file
From 6381e692dc9f50b7e75f7879284ae15cc2f94681 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Tue, 17 Aug 2021 15:14:25 +0100
Subject: [PATCH 05/19] FUCKING VALUES
---
code/datums/peer_server.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/datums/peer_server.dm b/code/datums/peer_server.dm
index 81f78be68b4..ed7abc97f6a 100644
--- a/code/datums/peer_server.dm
+++ b/code/datums/peer_server.dm
@@ -29,4 +29,4 @@
/// Playercount of the peer server on last ping
var/playercount
/// Last world.time that an operation was attempted
- var/last_operation_time
+ var/last_operation_time = 0
From ac6b9a4b10555cada7d4897092c2f2fde4bf4ce7 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Tue, 17 Aug 2021 15:27:42 +0100
Subject: [PATCH 06/19] *scream
---
code/controllers/subsystem/instancing.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/controllers/subsystem/instancing.dm b/code/controllers/subsystem/instancing.dm
index ffc8808e427..7abe4ba2b09 100644
--- a/code/controllers/subsystem/instancing.dm
+++ b/code/controllers/subsystem/instancing.dm
@@ -8,7 +8,7 @@ SUBSYSTEM_DEF(instancing)
/datum/controller/subsystem/instancing/Initialize(start_timeofday)
// Do an initial peer check
- check_peers()
+ check_peers(TRUE) // Force because of time memes
UNTIL(initial_check_complete) // Wait here a bit
var/startup_msg = "The server [GLOB.configuration.general.server_name] is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name])"
message_all_peers(startup_msg)
From bbbe8c1d453e69bec29283ad1888292e1cb77227 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Tue, 17 Aug 2021 16:45:29 +0100
Subject: [PATCH 07/19] Even more stuff
---
code/controllers/subsystem/instancing.dm | 12 ++++++------
code/modules/world_topic/instance_announce.dm | 9 ++++++++-
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/code/controllers/subsystem/instancing.dm b/code/controllers/subsystem/instancing.dm
index 7abe4ba2b09..87ad3f06e7c 100644
--- a/code/controllers/subsystem/instancing.dm
+++ b/code/controllers/subsystem/instancing.dm
@@ -10,8 +10,8 @@ SUBSYSTEM_DEF(instancing)
// Do an initial peer check
check_peers(TRUE) // Force because of time memes
UNTIL(initial_check_complete) // Wait here a bit
- var/startup_msg = "The server [GLOB.configuration.general.server_name] is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name])"
- message_all_peers(startup_msg)
+ var/startup_msg = "The server [GLOB.configuration.general.server_name] is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name]). You can connect with the Switch Server verb."
+ message_all_peers(startup_msg, send_reping = TRUE)
return ..()
/datum/controller/subsystem/instancing/fire(resumed)
@@ -76,9 +76,9 @@ SUBSYSTEM_DEF(instancing)
* * message - Message to send to the other servers
* * include_offline - Whether to topic offline servers on the off chance they came online
*/
-/datum/controller/subsystem/instancing/proc/message_all_peers(message, include_offline = FALSE)
+/datum/controller/subsystem/instancing/proc/message_all_peers(message, include_offline = FALSE, send_reping = FALSE)
var/topic_string = "instance_announce&msg=[html_encode(message)]"
- topic_all_peers(topic_string, include_offline)
+ topic_all_peers(topic_string, include_offline, send_reping)
/**
* Sends a topic to all peers
@@ -89,7 +89,7 @@ SUBSYSTEM_DEF(instancing)
* * raw_topic - The raw topic to send to the other servers
* * include_offline - Whether to topic offline servers on the off chance they came online
*/
-/datum/controller/subsystem/instancing/proc/topic_all_peers(raw_topic, include_offline = FALSE)
+/datum/controller/subsystem/instancing/proc/topic_all_peers(raw_topic, include_offline = FALSE, send_reping = FALSE)
for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
if(PS.online || include_offline)
- world.Export("byond://[PS.internal_ip]:[PS.server_port]?[raw_topic]&key=[PS.commskey]")
+ world.Export("byond://[PS.internal_ip]:[PS.server_port]?[raw_topic]&key=[PS.commskey][send_reping ? "&repoll=1" : ""]")
diff --git a/code/modules/world_topic/instance_announce.dm b/code/modules/world_topic/instance_announce.dm
index e63ce9a290c..250046cd53b 100644
--- a/code/modules/world_topic/instance_announce.dm
+++ b/code/modules/world_topic/instance_announce.dm
@@ -4,4 +4,11 @@
/datum/world_topic_handler/instance_announce/execute(list/input, key_valid)
var/msg = input["msg"]
- to_chat(world, "Attention: [msg]")
+ if(input["repoll"]) // Repoll peers
+ UNTIL(!SSinstancing.check_running) // If we are running, wait
+ SSinstancing.check_peers(TRUE) // Manually check, with FORCE
+ UNTIL(!SSinstancing.check_running) // Wait for completion
+
+ // Now that we repolled, we can tell the playerbase
+ to_chat(world, "
[GLOB.configuration.general.server_name] is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name]). You can connect with the Switch Server verb."
- message_all_peers(startup_msg, send_reping = TRUE)
+ message_all_peers(startup_msg)
return ..()
/datum/controller/subsystem/instancing/fire(resumed)
- check_peers()
+ update_heartbeat()
+ update_playercache()
/**
- * Refreshes all peers on the server
+ * Playercache updater
*
- * Called periodically during fire() as well as when a new peer reports itself as online
- * Only one instance of this proc may run at a time
- *
- * Arguments:
- * * force - Do we want to force check all of them
+ * Updates the player cache in the DB. Different from heartbeat so we can force invoke it on player operations
*/
-/datum/controller/subsystem/instancing/proc/check_peers(force = FALSE)
- set waitfor = FALSE // This has sleeps if it cant topic so we dont want to bog things down
- if(check_running)
+/datum/controller/subsystem/instancing/proc/update_playercache(optional_ckey)
+ // You may be wondering, why the fuck is an "optional ckey" variable here
+ // Well, this is invoked in client/New(), and needs to read from GLOB.clients
+ // However, this proc sleeps, and if you sleep during client/New() once the client is in GLOB.clients, stuff breaks bad
+ // (See my comment rambling in client/New())
+ // By passing the ckey through, we can sleep in this proc and still get the data
+ if(!SSdbcore.IsConnected())
return
+ // First iterate clients to get ckeys
+ var/list/ckeys = list()
+ for(var/client/C in GLOB.clients) // No code review. I am not doing the `as anything` bullshit, because we *do* need the type checks here to avoid null clients which do happen sometimes
+ ckeys += C.ckey
+ // Add our optional
+ if(optional_ckey)
+ ckeys += optional_ckey
+ // Note: We dont have to sort the list here. The only time this is read for is a search,
+ // and since BYOND lists are linked lists internally, order doesnt matter
+ var/ckey_json = json_encode(ckeys)
- check_running = TRUE // Dont allow multiple of these
- // A NOTE TO ANYONE ELSE WHO LOOKS AT THIS
- // THESE TIMINGS ARE A FUCKING NIGHTMARE AND YOU WILL NEED DEBUG LOGGING TO TEST THEM
- // DO NOT FUCK WITH THE TIMINGS -aa
- for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
- // If the server hasnt been discovered and its been more than 5 minutes
- if((!PS.discovered && PS.last_operation_time + 5 MINUTES > world.time))
- if(!force) // No, code review. This is not going in the same line.
- continue
+ // Yes I care about performance savings this much here to mass execute this shit
+ var/list/datum/db_query/queries = list()
+ queries += SSdbcore.NewQuery("UPDATE instance_data_cache SET key_value=:json WHERE key_name='playerlist' AND server_id=:sid", list(
+ "json" = ckey_json,
+ "sid" = GLOB.configuration.system.instance_id
+ ))
+ queries += SSdbcore.NewQuery("UPDATE instance_data_cache SET key_value=:count WHERE key_name='playercount' AND server_id=:sid", list(
+ "count" = length(ckeys),
+ "sid" = GLOB.configuration.system.instance_id
+ ))
- // Only run main operations once every minute anyway
- if(PS.last_operation_time + 1 MINUTES > world.time)
- if(!force)
- continue
+ SSdbcore.MassExecute(queries, TRUE, TRUE, FALSE, FALSE)
- var/peer_response = world.Export("byond://[PS.internal_ip]:[PS.server_port]?server_discovery&key=[PS.commskey]")
- if(!peer_response)
- PS.online = FALSE // Peer is offline
- PS.last_operation_time = world.time
- continue
+/**
+ * Heartbeat updater
+ *
+ * Updates the heartbeat in the DB. Used so other servers can see when this one was alive
+ */
+/datum/controller/subsystem/instancing/proc/update_heartbeat()
+ // this could probably just go in fire() but clean code and profiler ease who cares
+ var/datum/db_query/dbq = SSdbcore.NewQuery("UPDATE instance_data_cache SET key_value=NOW() WHERE key_name='heartbeat' AND server_id=:sid", list(
+ "sid" = GLOB.configuration.system.instance_id
+ ))
+ dbq.warn_execute()
+ qdel(dbq)
- // We got a response
- PS.discovered = TRUE
- PS.online = TRUE
- var/list/peer_data = json_decode(peer_response)
+/**
+ * Seed data
+ *
+ * Seeds all our data into the DB for other servers to discover from.
+ * This is called during world/New() instead of on initialize so it can be done *instantly*
+ */
+/datum/controller/subsystem/instancing/proc/seed_data()
+ // We need to seed a lot of keys, so lets just use a key-value-pair-map to do this easily
+ var/list/kvp_map = list()
+ kvp_map["server_name"] = GLOB.configuration.general.server_name // Name of the server
+ kvp_map["server_port"] = world.port // Server port (used for redirection and topics)
+ kvp_map["topic_key"] = GLOB.configuration.system.topic_key // Server topic key (used for topics)
+ kvp_map["internal_ip"] = GLOB.configuration.system.internal_ip // Server internal IP (used for topics)
+ kvp_map["playercount"] = length(GLOB.clients) // Server client count (used for status info)
+ kvp_map["playerlist"] = json_encode(list()) // Server client list. Used for dupe login checks. This gets filled in later
+ kvp_map["heartbeat"] = SQLtime() // SQL timestamp for heartbeat purposes. Any server without a heartbeat in the last 60 seconds can be considered dead
+ // Also note for above. You may say "But AA you dont need to JSON encode it, just use "\[]"."
+ // Well to that I say, no. This is meant to be JSON regardless, and it should represent that. This proc is ran once during world/New()
+ // An extra nanosecond of load will make zero difference.
- PS.external_ip = peer_data["external_ip"]
- PS.server_id = peer_data["server_id"]
- PS.server_name = peer_data["server_name"]
- PS.playercount = peer_data["playercount"]
+ for(var/key in kvp_map)
+ var/datum/db_query/dbq = SSdbcore.NewQuery("INSERT INTO instance_data_cache (server_id, key_name, key_value) VALUES (:sid, :kn, :kv) ON DUPLICATE KEY UPDATE key_value=:kv2", // Is this necessary? Who knows!
+ list(
+ "sid" = GLOB.configuration.system.instance_id,
+ "kn" = key,
+ "kv" = "[kvp_map[key]]", // String encoding IS necessary since these tables use strings, not ints
+ "kv2" = "[kvp_map[key]]", // Dont know if I need the second but better to be safe
+ )
+ )
+ dbq.warn_execute(FALSE) // Do NOT async execute here because world/New() shouldnt sleep. EVER. You get issues if you do.
+ qdel(dbq)
- PS.last_operation_time = world.time
-
- check_running = FALSE
- initial_check_complete = TRUE
/**
* Message all peers
*
- * Wrapper for [topic_all_peers] to autoformat a message topic. Will send a server-wide announcement to the other servers
- * including any relevant detail
+ * Wrapper for [topic_all_peers] to format the input into a message topic. Will send a server-wide announcement to the other servers
+ *
* Arguments:
* * message - Message to send to the other servers
* * include_offline - Whether to topic offline servers on the off chance they came online
*/
-/datum/controller/subsystem/instancing/proc/message_all_peers(message, include_offline = FALSE, send_reping = FALSE)
+/datum/controller/subsystem/instancing/proc/message_all_peers(message)
+ if(!SSdbcore.IsConnected())
+ return
var/topic_string = "instance_announce&msg=[html_encode(message)]"
- topic_all_peers(topic_string, include_offline, send_reping)
+ topic_all_peers(topic_string)
/**
* Sends a topic to all peers
@@ -89,7 +129,28 @@ SUBSYSTEM_DEF(instancing)
* * raw_topic - The raw topic to send to the other servers
* * include_offline - Whether to topic offline servers on the off chance they came online
*/
-/datum/controller/subsystem/instancing/proc/topic_all_peers(raw_topic, include_offline = FALSE, send_reping = FALSE)
- for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
- if(PS.online || include_offline)
- world.Export("byond://[PS.internal_ip]:[PS.server_port]?[raw_topic]&key=[PS.commskey][send_reping ? "&repoll=1" : ""]")
+/datum/controller/subsystem/instancing/proc/topic_all_peers(raw_topic)
+ // Someone here is going to say "AA you shouldnt put load on the DB server you can do sorting in BYOND"
+ // Well let me put it this way. The DB server is an entirely different machine to BYOND,w ith this entire dataset being stored in its RAM, not even on disk
+ // By making the DB server do the work, we can offload from BYOND, which is already strained
+ var/datum/db_query/dbq1 = SSdbcore.NewQuery({"
+ SELECT server_id, key_name, key_value FROM instance_data_cache WHERE server_id IN
+ (SELECT server_id FROM instance_data_cache WHERE server_id !=:sid AND
+ key_name='heartbeat' AND last_updated BETWEEN NOW() - INTERVAL 60 SECOND AND NOW())
+ AND key_name IN ("topic_key", "internal_ip", "server_port")"}, list(
+ "sid" = GLOB.configuration.system.instance_id
+ ))
+ if(!dbq1.warn_execute())
+ qdel(dbq1)
+ return
+
+ var/servers_outer = list()
+ while(dbq1.NextRow())
+ if(!servers_outer[dbq1.item[1]])
+ servers_outer[dbq1.item[1]] = list()
+
+ servers_outer[dbq1.item[1]][dbq1.item[2]] = dbq1.item[3] // This should assoc load our data
+
+ for(var/server in servers_outer)
+ var/server_data = servers_outer[server]
+ world.Export("byond://[server_data["internal_ip"]]:[server_data["server_port"]]?[raw_topic]&key=[server_data["topic_key"]]")
diff --git a/code/game/world.dm b/code/game/world.dm
index 2153a5517b5..e8d048c2d4e 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -30,6 +30,7 @@ GLOBAL_LIST_INIT(map_transition_config, list(CC_TRANSITION_CONFIG))
// Right off the bat, load up the DB
SSdbcore.CheckSchemaVersion() // This doesnt just check the schema version, it also connects to the db! This needs to happen super early! I cannot stress this enough!
SSdbcore.SetRoundID() // Set the round ID here
+ SSinstancing.seed_data() // Set us up in the DB
// Setup all log paths and stamp them with startups, including round IDs
SetupLogs()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 2a1d77da5e1..3768ccbf833 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -139,7 +139,6 @@ GLOBAL_LIST_INIT(admin_verbs_server, list(
/client/proc/set_ooc,
/client/proc/reset_ooc,
/client/proc/set_next_map,
- /client/proc/refresh_instances,
/client/proc/manage_queue,
/client/proc/add_queue_server_bypass
))
diff --git a/code/modules/admin/verbs/view_instances.dm b/code/modules/admin/verbs/view_instances.dm
index 3cedf9614a0..bbef0b184dc 100644
--- a/code/modules/admin/verbs/view_instances.dm
+++ b/code/modules/admin/verbs/view_instances.dm
@@ -7,6 +7,8 @@
return
to_chat(usr, "Server instances info")
+ to_chat(usr, "AA you need to finish this you lazy oaf")
+ /*
for(var/datum/peer_server/PS in GLOB.configuration.instancing.peers)
// We havnt even been discovered, so we cant even be online
if(!PS.discovered)
@@ -20,13 +22,5 @@
// If we are here, we are online, so we can do a rich report
to_chat(usr, "ID [PS.server_id] - ONLINE (Players: [PS.playercount])")
+ */
-/client/proc/refresh_instances()
- set name = "Force Refresh Server Instances"
- set desc = "Force refresh the local cache of server instances"
- set category = "Server"
-
- if(!check_rights(R_SERVER))
- return
-
- SSinstancing.check_peers(TRUE) // Force check
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index c841c759229..9e7858e20b1 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -326,12 +326,16 @@
if(length(related_accounts_cid))
log_admin("[key_name(src)] Alts by CID: [jointext(related_accounts_cid, " ")]")
+ // This sleeps so it has to go here. Dont fucking move it.
+ SSinstancing.update_playercache(ckey)
+
// This has to go here to avoid issues
// If you sleep past this point, you will get SSinput errors as well as goonchat errors
// DO NOT STUFF RANDOM SQL QUERIES BELOW THIS POINT WITHOUT USING `INVOKE_ASYNC()` OR SIMILAR
// YOU WILL BREAK STUFF. SERIOUSLY. -aa07
GLOB.clients += src
+
spawn() // Goonchat does some non-instant checks in start()
chatOutput.start()
@@ -434,6 +438,7 @@
GLOB.admins -= src
GLOB.directory -= ckey
GLOB.clients -= src
+ SSinstancing.update_playercache() // Clear us out
QDEL_NULL(chatOutput)
if(movingmob)
movingmob.client_mobs_in_contents -= mob
diff --git a/code/modules/world_topic/instance_announce.dm b/code/modules/world_topic/instance_announce.dm
index 250046cd53b..d4ecaa1ba85 100644
--- a/code/modules/world_topic/instance_announce.dm
+++ b/code/modules/world_topic/instance_announce.dm
@@ -3,12 +3,5 @@
requires_commskey = TRUE
/datum/world_topic_handler/instance_announce/execute(list/input, key_valid)
- var/msg = input["msg"]
- if(input["repoll"]) // Repoll peers
- UNTIL(!SSinstancing.check_running) // If we are running, wait
- SSinstancing.check_peers(TRUE) // Manually check, with FORCE
- UNTIL(!SSinstancing.check_running) // Wait for completion
-
- // Now that we repolled, we can tell the playerbase
- to_chat(world, "[server] - [players] player[players == 1 ? "" : "s"] online.")
+ to_chat(usr, "Offline instances are not reported")
diff --git a/paradise.dme b/paradise.dme
index 0b2b1346c29..259a5eac574 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1162,6 +1162,7 @@
#include "code\game\verbs\ooc.dm"
#include "code\game\verbs\randomtip.dm"
#include "code\game\verbs\suicide.dm"
+#include "code\game\verbs\switch_server.dm"
#include "code\game\verbs\webmap.dm"
#include "code\game\verbs\who.dm"
#include "code\modules\admin\admin.dm"
From b105b44bede933553cf906399042258ba7752a1b Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Sun, 17 Oct 2021 16:30:19 +0100
Subject: [PATCH 15/19] Lets give this a go
---
code/modules/admin/admin_verbs.dm | 1 +
code/modules/admin/verbs/gsay.dm | 27 +++++++++++++++++++++++++++
code/modules/world_topic/gsay.dm | 18 ++++++++++++++++++
paradise.dme | 2 ++
4 files changed, 48 insertions(+)
create mode 100644 code/modules/admin/verbs/gsay.dm
create mode 100644 code/modules/world_topic/gsay.dm
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 019ccedade9..a78f3219e47 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -45,6 +45,7 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
/datum/admins/proc/toggleemoji, /*toggles using emoji in ooc for everyone*/
/client/proc/game_panel, /*game panel, allows to change game-mode etc*/
/client/proc/cmd_admin_say, /*admin-only ooc chat*/
+ /client/proc/gsay, /*cross-server asay*/
/datum/admins/proc/PlayerNotes,
/client/proc/cmd_mentor_say,
/datum/admins/proc/show_player_notes,
diff --git a/code/modules/admin/verbs/gsay.dm b/code/modules/admin/verbs/gsay.dm
new file mode 100644
index 00000000000..d777fa422b2
--- /dev/null
+++ b/code/modules/admin/verbs/gsay.dm
@@ -0,0 +1,27 @@
+// GSAY
+// Like asay but global between instances!
+// *Insert changeling hivemind :g joke here*
+
+/client/proc/gsay(msg as text)
+ set name = "gsay"
+ set hidden = TRUE
+ if(!check_rights(R_ADMIN))
+ return
+
+ if(!msg)
+ return
+ msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN))
+
+ // To whoever says "Why dont you just topic the full message with formatting"
+ // This lets us use this in other apps, like a discord bot, without HTML parsing
+ // It also removes a way to put whatever HTML we want in the chat window
+ var/built_topic = "gsay&msg=[url_encode(msg)]&usr=[url_encode(usr.ckey)]&src=[url_encode(GLOB.configuration.system.instance_id)]"
+
+ // Send to peers
+ SSinstancing.topic_all_peers(built_topic)
+ // Send to online admins
+ for(var/client/C in GLOB.admins)
+ if(R_ADMIN & C.holder.rights)
+ to_chat(C, "GSAY: [usr.ckey]@[GLOB.configuration.system.instance_id]: [msg]")
+
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "gsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/world_topic/gsay.dm b/code/modules/world_topic/gsay.dm
new file mode 100644
index 00000000000..971375d8658
--- /dev/null
+++ b/code/modules/world_topic/gsay.dm
@@ -0,0 +1,18 @@
+// Just dumps the text in the admin chat box
+/datum/world_topic_handler/gsay
+ topic_key = "gsay"
+ requires_commskey = TRUE
+
+/datum/world_topic_handler/gsay/execute(list/input, key_valid)
+ if(!input["msg"] || !input["usr"] || !input["src"])
+ return json_encode(list("error" = "Malformed request"))
+
+ var/message = input["msg"]
+ var/user = input["usr"]
+ var/source = input["source"]
+
+ // Send to online admins
+ for(var/client/C in GLOB.admins)
+ if(R_ADMIN & C.holder.rights)
+ to_chat(C, "GSAY: [user]@[source]: [message]")
+
diff --git a/paradise.dme b/paradise.dme
index 259a5eac574..5488d42c65c 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1210,6 +1210,7 @@
#include "code\modules\admin\verbs\freeze.dm"
#include "code\modules\admin\verbs\getlogs.dm"
#include "code\modules\admin\verbs\gimmick_team.dm"
+#include "code\modules\admin\verbs\gsay.dm"
#include "code\modules\admin\verbs\infiltratorteam_syndicate.dm"
#include "code\modules\admin\verbs\logging_view.dm"
#include "code\modules\admin\verbs\manage_queue.dm"
@@ -2522,6 +2523,7 @@
#include "code\modules\world_topic\_topic_base.dm"
#include "code\modules\world_topic\adminmsg.dm"
#include "code\modules\world_topic\announce.dm"
+#include "code\modules\world_topic\gsay.dm"
#include "code\modules\world_topic\instance_announce.dm"
#include "code\modules\world_topic\manifest.dm"
#include "code\modules\world_topic\ping.dm"
From 767d8e08410e4a6448477ec74a8bb156250da280 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Sun, 17 Oct 2021 16:37:40 +0100
Subject: [PATCH 16/19] Fix
---
code/modules/world_topic/gsay.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/world_topic/gsay.dm b/code/modules/world_topic/gsay.dm
index 971375d8658..16378cd32eb 100644
--- a/code/modules/world_topic/gsay.dm
+++ b/code/modules/world_topic/gsay.dm
@@ -9,7 +9,7 @@
var/message = input["msg"]
var/user = input["usr"]
- var/source = input["source"]
+ var/source = input["src"]
// Send to online admins
for(var/client/C in GLOB.admins)
From 1d750acf78a9795611feba11547fc079bb9e0ab2 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Sun, 17 Oct 2021 18:59:54 +0100
Subject: [PATCH 17/19] Flatty fixes
---
code/controllers/subsystem/instancing.dm | 6 ++----
config/example/config.toml | 2 +-
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/code/controllers/subsystem/instancing.dm b/code/controllers/subsystem/instancing.dm
index 6849d71f1fd..8fd80a9bad7 100644
--- a/code/controllers/subsystem/instancing.dm
+++ b/code/controllers/subsystem/instancing.dm
@@ -39,7 +39,7 @@ SUBSYSTEM_DEF(instancing)
if(optional_ckey)
ckeys += optional_ckey
// Note: We dont have to sort the list here. The only time this is read for is a search,
- // and since BYOND lists are linked lists internally, order doesnt matter
+ // and order doesnt matter for that.
var/ckey_json = json_encode(ckeys)
// Yes I care about performance savings this much here to mass execute this shit
@@ -108,7 +108,6 @@ SUBSYSTEM_DEF(instancing)
*
* Arguments:
* * message - Message to send to the other servers
- * * include_offline - Whether to topic offline servers on the off chance they came online
*/
/datum/controller/subsystem/instancing/proc/message_all_peers(message)
if(!SSdbcore.IsConnected())
@@ -123,7 +122,6 @@ SUBSYSTEM_DEF(instancing)
*
* Arguments:
* * raw_topic - The raw topic to send to the other servers
- * * include_offline - Whether to topic offline servers on the off chance they came online
*/
/datum/controller/subsystem/instancing/proc/topic_all_peers(raw_topic)
// Someone here is going to say "AA you shouldnt put load on the DB server you can do sorting in BYOND"
@@ -163,7 +161,7 @@ SUBSYSTEM_DEF(instancing)
* ckey - The ckey to check if they are logged into another server
*/
/datum/controller/subsystem/instancing/proc/check_player(ckey)
- // Please see above rant on L133
+ // Please see above rant on L127
var/datum/db_query/dbq1 = SSdbcore.NewQuery({"
SELECT server_id, key_value FROM instance_data_cache WHERE server_id IN
(SELECT server_id FROM instance_data_cache WHERE server_id != :sid AND
diff --git a/config/example/config.toml b/config/example/config.toml
index a929c964de5..b341021d4ec 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -727,7 +727,7 @@ topic_ip_ratelimit_bypass = ["127.0.0.1"]
is_production = false
# Server instance ID. This is used for tagging the server in the database
# You do NOT want to change this once you are running in production
-instance_id = "aa_debug"
+instance_id = "paradise_main"
# Server internal IP. Used if you are splitting instances over multiple internal IPs.
# In most cases this is just 127.0.0.1
internal_ip = "127.0.0.1"
From 4e40353cfa9375bcf2ebd1182e4591663d36fedc Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Sun, 17 Oct 2021 21:06:02 +0100
Subject: [PATCH 18/19] Check your conflict resolution people
---
code/controllers/configuration/configuration_core.dm | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/code/controllers/configuration/configuration_core.dm b/code/controllers/configuration/configuration_core.dm
index 0550280527b..c96e96016e8 100644
--- a/code/controllers/configuration/configuration_core.dm
+++ b/code/controllers/configuration/configuration_core.dm
@@ -97,8 +97,7 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
var/config_file = "config/config.toml"
if(!fexists(config_file))
config_file = "config/example/config.toml" // Fallback to example if user hasnt setup config properly
- var/raw_json = rustg_read_toml_file(config_file)
- raw_data = json_decode(raw_json)
+ raw_data = rustg_read_toml_file(config_file)
// Now pass through all our stuff
load_all_sections()
@@ -141,8 +140,7 @@ GLOBAL_DATUM_INIT(configuration, /datum/server_configuration, new())
DIRECT_OUTPUT(world.log, "Overrides found for this instance. Loading them.")
var/start = start_watch() // Time tracking
- var/raw_json = rustg_read_toml_file(override_file)
- raw_data = json_decode(raw_json)
+ raw_data = rustg_read_toml_file(override_file)
// Now safely load our overrides.
// Due to the nature of config wrappers, only vars that exist in the config file are applied to the config datums.
From de48c081bfbda4753e0e87e9b659b9c3d50a4c1d Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Wed, 20 Oct 2021 17:50:43 +0100
Subject: [PATCH 19/19] Elastic logic
---
code/controllers/subsystem/metrics.dm | 1 +
1 file changed, 1 insertion(+)
diff --git a/code/controllers/subsystem/metrics.dm b/code/controllers/subsystem/metrics.dm
index 95055f99216..181f24cfb08 100644
--- a/code/controllers/subsystem/metrics.dm
+++ b/code/controllers/subsystem/metrics.dm
@@ -28,6 +28,7 @@ SUBSYSTEM_DEF(metrics)
out["elapsed_real"] = (REALTIMEOFDAY - world_init_time)
out["client_count"] = length(GLOB.clients)
out["round_id"] = text2num(GLOB.round_id) // This is so we can filter the metrics by a single round ID
+ out["server_id"] = GLOB.configuration.system.instance_id // And this is so we can filter by instance ID
// Funnel in all SS metrics
var/list/ss_data = list()