Start to implement a static item (rooms etc...) system.

This commit is contained in:
Condorra 2022-12-27 20:16:35 +11:00
parent 672eb1ee75
commit c4f9577645
7 changed files with 155 additions and 1 deletions

16
Cargo.lock generated
View File

@ -112,6 +112,7 @@ dependencies = [
"deadpool", "deadpool",
"deadpool-postgres", "deadpool-postgres",
"futures", "futures",
"itertools",
"log", "log",
"nix", "nix",
"nom", "nom",
@ -392,6 +393,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "enum-ordinalize" name = "enum-ordinalize"
version = "3.1.12" version = "3.1.12"
@ -751,6 +758,15 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.4" version = "1.0.4"

View File

@ -32,3 +32,4 @@ ouroboros = "0.15.5"
chrono = { version = "0.4.23", features = ["serde"] } chrono = { version = "0.4.23", features = ["serde"] }
bcrypt = "0.13.0" bcrypt = "0.13.0"
validator = "0.16.0" validator = "0.16.0"
itertools = "0.10.5"

View File

@ -10,6 +10,7 @@ use crate::message_handler::ListenerSession;
use crate::DResult; use crate::DResult;
use crate::models::{session::Session, user::User, item::Item}; use crate::models::{session::Session, user::User, item::Item};
use tokio_postgres::types::ToSql; use tokio_postgres::types::ToSql;
use std::collections::BTreeSet;
use serde_json; use serde_json;
use futures::FutureExt; use futures::FutureExt;
@ -133,6 +134,24 @@ impl DBPool {
Ok(()) Ok(())
} }
pub async fn find_static_item_types(self: &Self) -> DResult<Box<BTreeSet<String>>> {
Ok(Box::new(
self
.get_conn().await?
.query("SELECT DISTINCT details->>'item_type' AS item_type \
FROM items WHERE details->>'is_static' = 'true'", &[]).await?
.iter()
.map(|r| r.get("item_type"))
.collect()))
}
pub async fn delete_static_items_by_type(self: &Self, item_type: &str) -> DResult<()> {
self.get_conn().await?.query(
"DELETE FROM items WHERE details->>'is_static' = 'true' AND details->>'item_type' = {}",
&[&item_type]).await?;
Ok(())
}
pub async fn get_conn(self: &DBPool) -> pub async fn get_conn(self: &DBPool) ->
DResult<Object> { DResult<Object> {
let conn = self.pool.get().await?; let conn = self.pool.get().await?;
@ -243,6 +262,29 @@ impl DBTrans {
Ok(()) Ok(())
} }
pub async fn find_static_items_by_type(self: &Self, item_type: &str) ->
DResult<Box<BTreeSet<String>>> {
Ok(Box::new(
self.pg_trans()?
.query("SELECT DISTINCT details->>'item_code' AS item_code FROM items WHERE \
details->>'is_static' = 'true' AND \
details->>'item_type' = $1", &[&item_type])
.await?
.into_iter()
.map(|v| v.get("item_code"))
.collect()))
}
pub async fn delete_static_items_by_code(self: &Self, item_type: &str,
item_code: &str) -> DResult<()> {
self.pg_trans()?.query(
"DELETE FROM items WHERE details->>'is_static' = 'true' AND \
details->>'item_type' = {} AND \
details->>'item_code' = {}",
&[&item_type, &item_code]).await?;
Ok(())
}
pub async fn commit(mut self: Self) -> DResult<()> { pub async fn commit(mut self: Self) -> DResult<()> {
let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None)); let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None));
if let Some(trans) = trans_opt { if let Some(trans) = trans_opt {

View File

@ -13,6 +13,7 @@ mod version_cutover;
mod av; mod av;
mod regular_tasks; mod regular_tasks;
mod models; mod models;
mod static_content;
pub type DResult<T> = Result<T, Box<dyn Error + Send + Sync>>; pub type DResult<T> = Result<T, Box<dyn Error + Send + Sync>>;
@ -53,6 +54,8 @@ async fn main() -> DResult<()> {
} }
).await?; ).await?;
static_content::refresh_static_content(&pool).await?;
version_cutover::replace_old_gameserver(&config.pidfile)?; version_cutover::replace_old_gameserver(&config.pidfile)?;
regular_tasks::start_regular_tasks(&pool, listener_map)?; regular_tasks::start_regular_tasks(&pool, listener_map)?;

View File

@ -0,0 +1,87 @@
use crate::DResult;
use crate::db::DBPool;
use crate::models::item::Item;
use log::error;
use itertools::Itertools;
use std::collections::{BTreeSet, BTreeMap};
mod room;
pub struct StaticItem {
pub item_code: &'static str,
pub initial_item: fn () -> Item
}
struct StaticItemTypeGroup {
item_type: &'static str,
items: fn () -> Box<dyn Iterator<Item = StaticItem>>
}
fn static_item_registry() -> Vec<StaticItemTypeGroup> {
vec!(
// Must have no duplicates.
StaticItemTypeGroup {
item_type: "npc",
items: || Box::new(vec!().into_iter())
},
StaticItemTypeGroup {
item_type: "room",
items: || room::static_items()
},
)
}
async fn refresh_static_items(pool: &DBPool) -> DResult<()> {
let mut registry = static_item_registry();
registry.sort_unstable_by(|x, y| x.item_type.cmp(y.item_type));
let duplicates: Vec<&'static str> =
registry.iter()
.group_by(|x| x.item_type).into_iter()
.filter_map(|(k, v)| if v.count() <= 1 { None } else { Some(k) })
.collect();
if duplicates.len() > 0 {
error!("static_item_registry has duplicate item_types: {:}", duplicates.join(", "));
Err("Duplicate item_types in static_item_registry")?;
}
let expected_type: BTreeSet<String> =
registry.iter().map(|x| x.item_type.to_owned()).collect();
let cur_types: Box<BTreeSet<String>> = pool.find_static_item_types().await?;
for item_type in cur_types.difference(&expected_type) {
pool.delete_static_items_by_type(item_type).await?;
}
for type_group in registry.iter() {
let iterator : Box<dyn Iterator<Item = StaticItem>> = (type_group.items)();
let duplicates: Vec<&'static str> = iterator
.group_by(|x| x.item_code)
.into_iter()
.filter_map(|(k, v)| if v.count() <= 1 { None } else { Some(k) })
.collect();
if duplicates.len() > 0 {
error!("static_item_registry has duplicate item_codes for {}: {:}",
type_group.item_type,
duplicates.join(", "));
Err("Duplicate item_types in static_item_registry")?;
}
let tx = pool.start_transaction().await?;
let existing_items = tx.find_static_items_by_type(type_group.item_type).await?;
let expected_items: BTreeMap<String, StaticItem> =
(type_group.items)().map(|x| (x.item_code.to_owned(), x)).collect();
let expected_set: BTreeSet<String> = expected_items.keys().map(|x|x.to_owned()).collect();
for unwanted_item in existing_items.difference(&expected_set) {
tx.delete_static_items_by_code(type_group.item_type, unwanted_item).await?;
}
for new_item_code in expected_set.difference(&existing_items) {
tx.create_item(&(expected_items.get(new_item_code)
.unwrap().initial_item)()).await?;
}
}
Ok(())
}
pub async fn refresh_static_content(pool: &DBPool) -> DResult<()> {
refresh_static_items(pool).await?;
Ok(())
}

View File

@ -0,0 +1,5 @@
use super::StaticItem;
pub fn static_items() -> Box<dyn Iterator<Item = StaticItem>> {
Box::new(vec!().into_iter())
}

View File

@ -21,7 +21,7 @@ CREATE TABLE items (
item_id BIGSERIAL NOT NULL PRIMARY KEY, item_id BIGSERIAL NOT NULL PRIMARY KEY,
details JSONB NOT NULL details JSONB NOT NULL
); );
CREATE UNIQUE INDEX item_index ON items ((details->>'item_code'), (details->>'item_type')); CREATE UNIQUE INDEX item_index ON items ((details->>'item_type'), (details->>'item_code'));
CREATE INDEX item_by_loc ON items ((details->>'location')); CREATE INDEX item_by_loc ON items ((details->>'location'));
CREATE INDEX item_by_static ON items ((cast(details->>'is_static' as boolean))); CREATE INDEX item_by_static ON items ((cast(details->>'is_static' as boolean)));