diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index 23ede3c084b..8de6b77cdbb 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -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 diff --git a/code/controllers/subsystem/jobs.dm b/code/controllers/subsystem/jobs.dm index 56e877f6f8d..f82964ad4e0 100644 --- a/code/controllers/subsystem/jobs.dm +++ b/code/controllers/subsystem/jobs.dm @@ -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, "You got: [minutes] Living EXP!") + + 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, "You got: [minutes] [category] EXP!") + + if(C.mob.mind.special_role) + play_records[C.ckey][EXP_TYPE_SPECIAL] += minutes + if(announce) + to_chat(C.mob, "You got: [minutes] Special EXP!") + + else if(isobserver(C.mob)) + play_records[C.ckey][EXP_TYPE_GHOST] += minutes + added_ghost += minutes + if(announce) + to_chat(C.mob, "You got: [minutes] Ghost EXP!") + 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") diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm index f0483e5c17a..54b5201baa0 100644 --- a/code/game/jobs/job_exp.dm +++ b/code/game/jobs/job_exp.dm @@ -224,97 +224,3 @@ GLOBAL_LIST_INIT(role_playtime_requirements, list( else return "none" -/proc/update_exp(mins = 0, ann = 0) - if(!SSdbcore.IsConnected()) - return - for(var/client/L in GLOB.clients) - if(L.inactivity >= (10 MINUTES)) - continue - L.update_exp_client(mins, ann) - CHECK_TICK - -/client/proc/update_exp_client(minutes = 0, announce_changes = 0) - if(!src || !ckey || !SSdbcore.IsConnected()) - return - - var/datum/db_query/exp_read = SSdbcore.NewQuery( - "SELECT exp FROM [format_table_name("player")] WHERE ckey=:ckey", - list("ckey" = ckey) - ) - - if(!exp_read.warn_execute()) - qdel(exp_read) - return FALSE - - var/list/read_records = list() - var/hasread = FALSE - while(exp_read.NextRow()) - read_records = params2list(exp_read.item[1]) - hasread = TRUE - - qdel(exp_read) - - if(!hasread) - return - - var/list/play_records = list() - for(var/rtype in GLOB.exp_jobsmap) - if(text2num(read_records[rtype])) - play_records[rtype] = text2num(read_records[rtype]) - else - play_records[rtype] = 0 - var/myrole - if(mob.mind) - if(mob.mind.playtime_role) - myrole = mob.mind.playtime_role - else if(mob.mind.assigned_role) - myrole = mob.mind.assigned_role - var/added_living = 0 - var/added_ghost = 0 - if(mob.stat == CONSCIOUS && myrole) - play_records[EXP_TYPE_LIVING] += minutes - added_living += minutes - if(announce_changes) - to_chat(mob,"You got: [minutes] Living EXP!") - for(var/category in GLOB.exp_jobsmap) - if(GLOB.exp_jobsmap[category]["titles"]) - if(myrole in GLOB.exp_jobsmap[category]["titles"]) - play_records[category] += minutes - if(announce_changes) - to_chat(mob,"You got: [minutes] [category] EXP!") - if(mob.mind.special_role) - play_records[EXP_TYPE_SPECIAL] += minutes - if(announce_changes) - to_chat(mob,"You got: [minutes] Special EXP!") - else if(isobserver(mob)) - play_records[EXP_TYPE_GHOST] += minutes - added_ghost += minutes - if(announce_changes) - to_chat(mob,"You got: [minutes] Ghost EXP!") - else - return - var/new_exp = list2params(play_records) - 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" = ckey - ) - ) - update_query.warn_execute() - qdel(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" = ckey, - "addedliving" = added_living, - "addedghost" = added_ghost - ) - ) - update_query_history.warn_execute() - qdel(update_query_history)