Batches EXP updates for incredible efficiency (#15275)

* Batches EXP updates for incredible efficiency

* Review tweaks
This commit is contained in:
AffectedArc07
2021-01-15 13:04:32 -05:00
committed by GitHub
parent 2f6b0a9a0f
commit 8aa4b6884e
3 changed files with 145 additions and 100 deletions
+6 -3
View File
@@ -265,15 +265,17 @@ SUBSYSTEM_DEF(dbcore)
* * warn - Boolean to warn on query failure
* * qdel - Boolean to enable auto qdel of queries
* * assoc - Boolean to enable support for an associative list of queries
* * log - Do we want to generate logs for these queries
*/
/datum/controller/subsystem/dbcore/proc/MassExecute(list/querys, warn = FALSE, qdel = FALSE, assoc = FALSE)
/datum/controller/subsystem/dbcore/proc/MassExecute(list/querys, warn = FALSE, qdel = FALSE, assoc = FALSE, log = TRUE)
if(!islist(querys))
if(!istype(querys, /datum/db_query))
CRASH("Invalid query passed to MassExecute: [querys]")
querys = list(querys)
var/start_time = start_watch()
log_debug("Mass executing [length(querys)] queries...")
if(log)
log_debug("Mass executing [length(querys)] queries...")
for(var/thing in querys)
var/datum/db_query/query
@@ -296,7 +298,8 @@ SUBSYSTEM_DEF(dbcore)
if(qdel)
qdel(query)
log_debug("Executed [length(querys)] queries in [stop_watch(start_time)]s")
if(log)
log_debug("Executed [length(querys)] queries in [stop_watch(start_time)]s")
/**
* # db_query
+139 -3
View File
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(jobs)
name = "Jobs"
init_order = INIT_ORDER_JOBS // 12
wait = 3000 // 5 minutes (Deciseconds)
wait = 5 MINUTES // Dont ever make this a super low value since EXP updates are calculated from this value
runlevels = RUNLEVEL_GAME
offline_implications = "Job playtime hours will no longer be logged. No immediate action is needed."
@@ -25,9 +25,9 @@ SUBSYSTEM_DEF(jobs)
// Only fires every 5 minutes
/datum/controller/subsystem/jobs/fire()
if(!config.sql_enabled || !config.use_exp_tracking)
if(!SSdbcore.IsConnected() || !config.use_exp_tracking)
return
INVOKE_ASYNC(GLOBAL_PROC, /.proc/update_exp, 5, 0)
batch_update_player_exp(announce = FALSE) // Set this to true if you ever want to inform players about their EXP gains
/datum/controller/subsystem/jobs/proc/SetupOccupations(var/list/faction = list("Station"))
occupations = list()
@@ -726,3 +726,139 @@ SUBSYSTEM_DEF(jobs)
new_id_change_records["[id_change_counter]"] = thisrecord
id_change_counter++
id_change_records = new_id_change_records
// This proc will update all players EXP at once. It will calculate amount of time to add dynamically based on the SS fire time.
/datum/controller/subsystem/jobs/proc/batch_update_player_exp(announce = FALSE)
// Right off the bat
var/start_time = start_watch()
// First calculate minutes
var/divider = 10 // By default, 10 deciseconds in 1 second
if(flags & SS_TICKER)
divider = 20 // If this SS ever gets made into a ticker SS, account for that
var/minutes = (wait / divider) / 60 // Calculate minutes based on the SS wait time (How often this proc fires)
// Step 1: Get us a list of clients to process
var/list/client/clients_to_process = GLOB.clients.Copy() // This is copied so that clients joining in the middle of this dont break things
Debug("Starting EXP update for [length(clients_to_process)] clients. (Adding [minutes] minutes)")
var/list/datum/db_query/select_queries = list() // List of SELECT queries to mass grab EXP.
for(var/i in clients_to_process)
var/client/C = i
if(!C)
continue // If a client logs out in the middle of this
var/datum/db_query/exp_read = SSdbcore.NewQuery(
"SELECT exp FROM [format_table_name("player")] WHERE ckey=:ckey",
list("ckey" = C.ckey)
)
select_queries[C.ckey] = exp_read
var/list/read_records = list()
// Explanation for parameters:
// TRUE: We want warnings if these fail
// FALSE: Do NOT qdel() queries here, otherwise they wont be read. At all.
// TRUE: This is an assoc list, so it needs to prepare for that
// FALSE: We dont want to logspam
SSdbcore.MassExecute(select_queries, TRUE, FALSE, TRUE, FALSE) // Batch execute so we can take advantage of async magic
for(var/i in clients_to_process)
var/client/C = i
if(!C)
continue // If a client logs out in the middle of this
if(select_queries[C.ckey]) // This check should not be necessary, but I am paranoid
while(select_queries[C.ckey].NextRow())
read_records[C.ckey] = params2list(select_queries[C.ckey].item[1])
QDEL_LIST_ASSOC_VAL(select_queries) // Clean stuff up
var/list/play_records = list()
var/list/datum/db_query/player_update_queries = list() // List of queries to update player EXP
var/list/datum/db_query/playtime_history_update_queries = list() // List of queries to update the playtime history table
for(var/i in clients_to_process)
var/client/C = i
if(!C)
continue // If a client logs out in the middle of this
// Get us a container
play_records[C.ckey] = list()
for(var/rtype in GLOB.exp_jobsmap)
if(text2num(read_records[C.ckey][rtype]))
play_records[C.ckey][rtype] = text2num(read_records[C.ckey][rtype])
else
play_records[C.ckey][rtype] = 0
var/myrole
if(C.mob.mind)
if(C.mob.mind.playtime_role)
myrole = C.mob.mind.playtime_role
else if(C.mob.mind.assigned_role)
myrole = C.mob.mind.assigned_role
var/added_living = 0
var/added_ghost = 0
if(C.mob.stat == CONSCIOUS && myrole)
play_records[C.ckey][EXP_TYPE_LIVING] += minutes
added_living += minutes
if(announce)
to_chat(C.mob, "<span class='notice'>You got: [minutes] Living EXP!</span>")
for(var/category in GLOB.exp_jobsmap)
if(GLOB.exp_jobsmap[category]["titles"])
if(myrole in GLOB.exp_jobsmap[category]["titles"])
play_records[C.ckey][category] += minutes
if(announce)
to_chat(C.mob, "<span class='notice'>You got: [minutes] [category] EXP!</span>")
if(C.mob.mind.special_role)
play_records[C.ckey][EXP_TYPE_SPECIAL] += minutes
if(announce)
to_chat(C.mob, "<span class='notice'>You got: [minutes] Special EXP!</span>")
else if(isobserver(C.mob))
play_records[C.ckey][EXP_TYPE_GHOST] += minutes
added_ghost += minutes
if(announce)
to_chat(C.mob, "<span class='notice'>You got: [minutes] Ghost EXP!</span>")
else
continue
var/new_exp = list2params(play_records[C.ckey])
C.prefs.exp = new_exp
var/datum/db_query/update_query = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET exp =:newexp, lastseen=NOW() WHERE ckey=:ckey",
list(
"newexp" = new_exp,
"ckey" = C.ckey
)
)
player_update_queries += update_query
var/datum/db_query/update_query_history = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("playtime_history")] (ckey, date, time_living, time_ghost)
VALUES (:ckey, CURDATE(), :addedliving, :addedghost)
ON DUPLICATE KEY UPDATE time_living=time_living + VALUES(time_living), time_ghost=time_ghost + VALUES(time_ghost)"},
list(
"ckey" = C.ckey,
"addedliving" = added_living,
"addedghost" = added_ghost
)
)
playtime_history_update_queries += update_query_history
// warn=TRUE, qdel=TRUE, assoc=FALSE, log=FALSE
SSdbcore.MassExecute(player_update_queries, TRUE, TRUE, FALSE, FALSE) // Batch execute so we can take advantage of async magic
SSdbcore.MassExecute(playtime_history_update_queries, TRUE, TRUE, FALSE, FALSE)
Debug("Successfully updated all EXP data in [stop_watch(start_time)]s")