Gather all characters stats in the system, build one big query, rather than per-character queries.

This commit is contained in:
Shane Handley 2020-05-11 02:28:11 +10:00
parent 7c6c9f4302
commit 0a6f9b860d
6 changed files with 99 additions and 47 deletions

View File

@ -108,7 +108,7 @@ impl Server {
state
.ecs_mut()
.insert(sys::StatsPersistenceScheduler::every(Duration::from_secs(
5,
60,
)));
// Server-only components

View File

@ -1,7 +1,7 @@
CREATE TABLE "stats" (
character_id INT NOT NULL PRIMARY KEY,
"level" INT NOT NULL DEFAULT 1,
"exp" INT NOT NULL DEFAULT 0,
exp INT NOT NULL DEFAULT 0,
endurance INT NOT NULL DEFAULT 0,
fitness INT NOT NULL DEFAULT 0,
willpower INT NOT NULL DEFAULT 0,

View File

@ -4,7 +4,7 @@ use std::fmt;
#[derive(Debug)]
pub enum Error {
// The player has alredy reached the max character limit
// The player has already reached the max character limit
CharacterLimitReached,
// An error occured when performing a database action
DatabaseError(diesel::result::Error),

View File

@ -73,7 +73,7 @@ impl From<&Body> for comp::Body {
}
/// `Stats` represents the stats for a character
#[derive(Associations, Identifiable, Queryable, Debug, Insertable)]
#[derive(Associations, AsChangeset, Identifiable, Queryable, Debug, Insertable)]
#[belongs_to(Character)]
#[primary_key(character_id)]
#[table_name = "stats"]
@ -110,9 +110,9 @@ impl From<StatsJoinData<'_>> for comp::Stats {
#[primary_key(character_id)]
#[table_name = "stats"]
pub struct StatsUpdate {
pub level: Option<i32>,
pub exp: Option<i32>,
pub endurance: Option<i32>,
pub fitness: Option<i32>,
pub willpower: Option<i32>,
pub level: i32,
pub exp: i32,
pub endurance: i32,
pub fitness: i32,
pub willpower: i32,
}

View File

@ -1,33 +1,83 @@
extern crate diesel;
use super::{establish_connection, models::StatsUpdate, schema};
use super::establish_connection;
use crate::comp;
use diesel::prelude::*;
pub fn update(
character_id: i32,
level: Option<i32>,
exp: Option<i32>,
endurance: Option<i32>,
fitness: Option<i32>,
willpower: Option<i32>,
) {
use schema::stats;
match diesel::update(stats::table)
.set(&StatsUpdate {
level,
exp,
endurance,
fitness,
willpower,
})
.execute(&establish_connection())
{
Err(error) => log::warn!(
"Failed to update stats for player with character_id: {:?}: {:?}",
character_id,
error
),
/// Update DB rows for stats given a Vec of (character_id, Stats) tuples
pub fn update(data: Vec<(i32, &comp::Stats)>) {
match establish_connection().execute(&build_query(data)) {
Err(diesel_error) => log::warn!("Error updating stats: {:?}", diesel_error),
_ => {},
};
}
}
/// Takes a Vec of (character_id, Stats) tuples and builds an SQL UPDATE query
/// Since there is apprently no sensible way to update > 1 row using diesel, we
/// just construct the raw SQL
fn build_query(data: Vec<(i32, &comp::Stats)>) -> String {
data.iter()
.map(|(character_id, stats)| {
String::from(format!(
"UPDATE stats SET level = {}, exp = {}, endurance = {}, fitness = {}, willpower = \
{} WHERE character_id = {};",
stats.level.level() as i32,
stats.exp.current() as i32,
stats.endurance as i32,
stats.fitness as i32,
stats.willpower as i32,
*character_id as i32
))
})
.collect::<Vec<String>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_query_for_multiple_characters() {
let mut stats_one = comp::Stats::new(
String::from("One"),
comp::Body::Humanoid(comp::humanoid::Body::random()),
);
stats_one.endurance = 1;
stats_one.fitness = 1;
stats_one.willpower = 1;
let mut stats_two = comp::Stats::new(
String::from("Two"),
comp::Body::Humanoid(comp::humanoid::Body::random()),
);
stats_two.endurance = 2;
stats_two.fitness = 2;
stats_two.willpower = 2;
let mut stats_three = comp::Stats::new(
String::from("Three"),
comp::Body::Humanoid(comp::humanoid::Body::random()),
);
stats_three.endurance = 3;
stats_three.fitness = 3;
stats_three.willpower = 3;
let data = vec![
(1_i32, &stats_one),
(2_i32, &stats_two),
(3_i32, &stats_three),
];
assert_eq!(
build_query(data),
"UPDATE stats SET level = 1, exp = 0, endurance = 1, fitness = 1, willpower = 1 WHERE \
character_id = 1; UPDATE stats SET level = 1, exp = 0, endurance = 2, fitness = 2, \
willpower = 2 WHERE character_id = 2; UPDATE stats SET level = 1, exp = 0, endurance \
= 3, fitness = 3, willpower = 3 WHERE character_id = 3;"
);
}
}

View File

@ -13,17 +13,19 @@ impl<'a> System<'a> for Sys {
fn run(&mut self, (players, player_stats, mut scheduler): Self::SystemData) {
if scheduler.should_run() {
for (player, stats) in (&players, &player_stats).join() {
if let Some(character_id) = player.character_id {
stats::update(
character_id,
Some(stats.level.level() as i32),
Some(stats.exp.current() as i32),
None,
None,
None,
);
}
let updates: Vec<(i32, &Stats)> = (&players, &player_stats)
.join()
.filter_map(|(player, stats)| {
if let Some(character_id) = player.character_id {
Some((character_id, stats))
} else {
None
}
})
.collect::<Vec<_>>();
if !updates.is_empty() {
stats::update(updates);
}
}
}