Migrates SSinstancing to Redis from world.Export() (#17679)

This commit is contained in:
AffectedArc07
2022-05-03 04:29:56 +01:00
committed by GitHub
parent d5e5d9e62b
commit 7bc1bee63a
10 changed files with 136 additions and 93 deletions
+2
View File
@@ -511,3 +511,5 @@
if(istype(I, /datum/mind))
var/datum/mind/B = I
return B.current.client
#define SERVER_MESSAGES_REDIS_CHANNEL "byond.servermessages"
+39 -58
View File
@@ -3,21 +3,55 @@ SUBSYSTEM_DEF(instancing)
runlevels = RUNLEVEL_INIT | RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 30 SECONDS
flags = SS_KEEP_TIMING
// This SS has the default init value since it needs to happen after the DB & redis
/// Associative list of registered commands. K = command name | V = command datum
var/list/datum/server_command/registered_commands = list()
/datum/controller/subsystem/instancing/Initialize(start_timeofday)
// Dont even bother if we arent connected
if(!SSdbcore.IsConnected())
// Make sure no one broke things. This check will trip up CI
if(init_order >= SSredis.init_order)
CRASH("SSinstancing was set to init before SSredis. Who broke it?")
// Dont even bother if we arent connected to redis or the DB
if(!SSdbcore.IsConnected() || !SSredis.connected)
flags |= SS_NO_FIRE
return ..()
update_heartbeat() // Make sure you do this before announcing to peers, or no one will hear your announcement
var/startup_msg = "The server <code>[GLOB.configuration.general.server_name]</code> is now starting up. The map is [SSmapping.map_datum.fluff_name] ([SSmapping.map_datum.technical_name]). You can connect with the <code>Switch Server</code> verb."
message_all_peers(startup_msg)
// Setup our commands
for(var/sct in subtypesof(/datum/server_command))
var/datum/server_command/SC = new sct()
if(isnull(SC.command_name))
stack_trace("[SC.type] has no comamnd name set!")
continue
if(SC.command_name in registered_commands)
stack_trace("A command with the name '[SC.command_name]' already exists!")
registered_commands[SC.command_name] = SC
var/amount_registered = length(registered_commands)
log_startup_progress("Registered [amount_registered] server command[amount_registered == 1 ? "" : "s"].")
// Announce startup to peers
var/datum/server_command/new_round_announce/NRA = registered_commands["new_round_announce"]
NRA.custom_dispatch(GLOB.configuration.general.server_name, SSmapping.map_datum.fluff_name, SSmapping.map_datum.technical_name)
return ..()
/datum/controller/subsystem/instancing/fire(resumed)
update_heartbeat()
update_playercache()
/datum/controller/subsystem/instancing/proc/execute_command(source, command, list/arguments)
var/datum/server_command/SC = registered_commands[command]
if(!SC)
CRASH("Attempted to execute command with ID '[command]' from [source], but that command didnt exist!")
if((source == GLOB.configuration.system.instance_id) && SC.ignoreself)
return // Dont self respond
SC.execute(source, arguments)
/**
* Playercache updater
*
@@ -102,59 +136,6 @@ SUBSYSTEM_DEF(instancing)
dbq.warn_execute(FALSE) // Do NOT async execute here because world/New() shouldnt sleep. EVER. You get issues if you do.
qdel(dbq)
/**
* Message all peers
*
* 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
*/
/datum/controller/subsystem/instancing/proc/message_all_peers(message)
if(!SSdbcore.IsConnected())
return
var/topic_string = "instance_announce&msg=[url_encode(message)]"
topic_all_peers(topic_string)
/**
* 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
*/
/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, with 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
qdel(dbq1)
for(var/server in servers_outer)
var/server_data = servers_outer[server]
// TODO: Move this to redis PubSub. world.Export() cannot be trusted. Redis is more reliable anyway
world.Export("byond://[server_data["internal_ip"]]:[server_data["server_port"]]?[raw_topic]&key=[server_data["topic_key"]]")
/**
* Player checker
*
+7 -7
View File
@@ -2,6 +2,8 @@
// Like asay but global between instances!
// *Insert changeling hivemind :g joke here*
// TODO - May as well fold this into regular asay. We already have it global enough with the discord integration.
// Same with msay
/client/proc/gsay(msg as text)
set name = "gsay"
set hidden = TRUE
@@ -10,18 +12,16 @@
if(!msg)
return
// Sanitize it all
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)]"
var/datum/server_command/gsay/GS = SSinstancing.registered_commands["gsay"]
GS.custom_dispatch(usr.ckey, msg)
// 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)
if(C.holder.rights & R_ADMIN)
to_chat(C, "<span class='admin_channel'>GSAY: [usr.ckey]@[GLOB.configuration.system.instance_id]: [msg]</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "gsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -0,0 +1,8 @@
/datum/redis_callback/server_messages
channel = SERVER_MESSAGES_REDIS_CHANNEL
/datum/redis_callback/server_messages/on_message(message)
// Decode
var/list/data = json_decode(message)
// And fire
SSinstancing.execute_command(data["src"], data["cmd"], data["args"])
@@ -0,0 +1,18 @@
/datum/server_command/gsay
command_name = "gsay"
/datum/server_command/gsay/execute(source, command_args)
var/message = command_args["msg"]
var/user = command_args["usr"]
// Send to online admins
for(var/client/C in GLOB.admins)
if(C.holder.rights & R_ADMIN)
to_chat(C, "<span class='admin_channel'>GSAY: [user]@[source]: [message]</span>")
/datum/server_command/gsay/custom_dispatch(ackey, message)
var/list/cmd_args = list()
cmd_args["usr"] = ackey
cmd_args["msg"] = message
dispatch(cmd_args)
@@ -0,0 +1,20 @@
/datum/server_command/new_round_announce
command_name = "new_round_announce"
/datum/server_command/new_round_announce/execute(source, command_args)
var/server_name = command_args["sname"]
var/map_name = command_args["mname"]
var/map_fluff = command_args["mfluff"]
var/startup_msg = "The server <code>[server_name] ([source])</code> is now starting up. The map is [map_fluff] ([map_name]). You can connect with the <code>Switch Server</code> verb."
to_chat(world, "<center><span class='boldannounce'><big>Attention</big></span></center><hr>[startup_msg]<hr>")
SEND_SOUND(world, sound('sound/misc/notice2.ogg')) // Same as captains priority announce
/datum/server_command/new_round_announce/custom_dispatch(sname, mname, mfluff)
var/list/cmd_args = list()
cmd_args["sname"] = sname
cmd_args["mname"] = mname
cmd_args["mfluff"] = mfluff
dispatch(cmd_args)
+38
View File
@@ -0,0 +1,38 @@
/**
* # Server Command
*
* Datum to handle both sending and receiving of server commands
*
* This datum is an extension of the redis callback and is designed for tighter integration with the BYOND servers.
* This list is registered and managed by SSintancing, not SSredis.
* NOTE: These commands are "fire and forget". If you need specific data from each server, use world/Topic still
*/
/datum/server_command
/// Does the sending server want to ignore this command? This is almost always yes unless you are doing testing stuff
var/ignoreself = TRUE
/// The source BYOND server for this message
var/source = null
/// The command name (must be unique)
var/command_name = null
/// Associative list of command args
var/list/command_args = list()
/datum/server_command/proc/execute(source, command_args)
CRASH("execute(source, command_args) not overriden for [type]!")
/datum/server_command/proc/dispatch(command_args)
SHOULD_NOT_OVERRIDE(TRUE) // No messing with
// Aight get serializing
var/list/serializeable_data = list()
serializeable_data["src"] = GLOB.configuration.system.instance_id
serializeable_data["cmd"] = command_name
serializeable_data["args"] = command_args
var/payload = json_encode(serializeable_data)
SSredis.publish(SERVER_MESSAGES_REDIS_CHANNEL, payload)
// Override this if you want a cleaner method for putting together dispatch args
/datum/server_command/proc/custom_dispatch()
CRASH("custom_dispatch() not overriden for [type]!")
-18
View File
@@ -1,18 +0,0 @@
// 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["src"]
// Send to online admins
for(var/client/C in GLOB.admins)
if(R_ADMIN & C.holder.rights)
to_chat(C, "<span class='admin_channel'>GSAY: [user]@[source]: [message]</span>")
@@ -1,8 +0,0 @@
/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, "<center><span class='boldannounce'><big>Attention</big></span></center><hr>[msg]<hr>")
SEND_SOUND(world, sound('sound/misc/notice2.ogg')) // Same as captains priority announce