Files
Paradise/code/controllers/subsystem/SSinstancing.dm
Contrabang 3f87165a03 CI now bans files with the same name (PART 2) (#21051)
* CI change

* world.dm

* .dme world.dm

* subsystem renaming

* .dme for subsystems

* ai_laws.dm

* armor.dm

* emote.dm

* logging.dm

* spell.dm

* air_alarm.dm

* crew.dm

* decal.dm

* emissive_blocker.dm

* footstep.dm

* spawner.dm

* fire.dm

* carbon.dm

* living.dm

* mob.dm

* movement.dm

* thermal_drill.dm

* plasmamen.dm

* lavaland.dm

* chaplain.dm

* lightning.dm

* magnet.dm

* mimic.dm

* wizard.dm

* morph.dm

* vampire.dm

* click.dm

* self.dm

* radiation_storm.dm

* airlock.dm

* autolathe.dm

* mulebot.dm

* nuclearbomb.dm

* particle_accelerator.dm

* smartfridge.dm

* syndicatebomb.dm

* vending.dm

* wires.dm

* sound.dm

* mining.dm

* syndicate_space_base.dm

* monkey.dm

* guardian.dm

* bomb.dm

* standard.dm

* nuclear.dm

* pinpointer.dm

* access.dm

* departments.dm

* job.dm

* science.dm

* buttons.dm

* cloning.dm

* igniter.dm

* wishgranter.dm

* atmos_control.dm

* message.dm

* power_monitor.dm

* mecha.dm

* combat.dm

* mining_tools.dm

* meteors.dm

* spiders.dm

* contraband.dm

* aliens.dm

* uplinks.dm

* voice.dm

* intercom.dm

* lights.dm

* robot_items.dm

* mineral.dm

* dice.dm

* extinguisher.dm

* paint.dm

* signs.dm

* staff.dm

* smokebomb.dm

* boxes.dm

* random.dm

* janicart.dm

* statue.dm

* cargo.dm

* asteroid.dm

* headslug.dm

* fulton.dm

* atmospherics.dm

* pump.dm

* corpse.dm

* oldstation.dm

* gps.dm

* preferences.dm

* clothing.dm

* ears.dm

* glasses.dm

* boxing.dm

* color.dm

* renames ninja gear files

* recipes.dm

* error_handler.dm

* anomaly.dm

* floorcluwne.dm

* undead.dm

* overmind.dm

* shield.dm

* bottle.dm

* organ.dm

* piano.dm

* plasma_fist.dm

* language.dm

* mob_defines.dm

* mob_helpers.dm

* damage_procs.dm

* _defines.dm

* empress.dm and queen.dm

* brain.dm

* organ file renaming

* subsystems.dm

* constructs.dm

* bot.dm

* pet.dm

* nature.dm

* magic.dm

* colors.dm

* drugs.dm

* medicine.dm

* toxins.dm

* shuttle.dm

* surgery.dm

* moves a bunch of define files

* traits.dm

* names.dm

* other_mobs.dm

* flags.dm

* some final define files

* well turns out contractor_pinpointer.dm was  taken

* I forgot to remove this file

* how in the hell did this get unticked

* I DID INCLUDE IT, but there was a "w" there

* swaps the world definitions

* camera renamed to SScamera

* examine -> alien_examine
2023-06-02 14:30:17 -05:00

185 lines
7.8 KiB
Plaintext

SUBSYSTEM_DEF(instancing)
name = "Instancing"
runlevels = RUNLEVEL_INIT | RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 30 SECONDS
flags = SS_KEEP_TIMING
cpu_display = SS_CPUDISPLAY_LOW
// 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()
// 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 || !GLOB.configuration.system.enable_multi_instance_support)
flags |= SS_NO_FIRE
return
// 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)
/datum/controller/subsystem/instancing/fire(resumed)
update_heartbeat()
update_playercache()
/datum/controller/subsystem/instancing/proc/execute_command(source, command, list/arguments)
// We aint enabled. Dont bother.
if(!GLOB.configuration.system.enable_multi_instance_support)
return
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
*
* Updates the player cache in the DB. Different from heartbeat so we can force invoke it on player operations
*/
/datum/controller/subsystem/instancing/proc/update_playercache(optional_ckey)
// We aint enabled. Dont bother.
if(!GLOB.configuration.system.enable_multi_instance_support)
return
// 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 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
var/datum/db_query/dbq = 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
))
dbq.warn_execute(FALSE)
qdel(dbq)
var/datum/db_query/dbq2 = 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
))
dbq2.warn_execute(FALSE)
qdel(dbq2)
/**
* 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)
/**
* 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 aint enabled. Dont bother.
if(!GLOB.configuration.system.enable_multi_instance_support)
return
// 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.
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)
/**
* Player checker
*
* Check all connected peers to see if a player exists in the player cache
* This is used to make sure players dont log into 2 servers at once
* Arguments:
* ckey - The ckey to check if they are logged into another server
*/
/datum/controller/subsystem/instancing/proc/check_player(ckey)
// We aint enabled. Dont bother.
if(!GLOB.configuration.system.enable_multi_instance_support)
return
// 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
key_name='heartbeat' AND last_updated BETWEEN NOW() - INTERVAL 60 SECOND AND NOW())
AND key_name IN ("playerlist")"}, list(
"sid" = GLOB.configuration.system.instance_id
))
if(!dbq1.warn_execute())
qdel(dbq1)
return
while(dbq1.NextRow())
var/list/other_server_cache = json_decode(dbq1.item[2])
if(ckey in other_server_cache)
var/target_server = dbq1.item[1] // Yes. This var is necessary.
qdel(dbq1)
return target_server
qdel(dbq1)
return null // If we are here, it means we didnt find our player on another server