mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'Mckol/552-fix-persistence-crash' into 'master'
Fix #552 Closes #552 See merge request veloren/veloren!984
This commit is contained in:
commit
cbfe3d52fc
@ -42,6 +42,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event {
|
pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event {
|
||||||
|
let db_dir = &server.server_settings.persistence_db_dir.clone();
|
||||||
let state = server.state_mut();
|
let state = server.state_mut();
|
||||||
|
|
||||||
// Tell other clients to remove from player list
|
// Tell other clients to remove from player list
|
||||||
@ -77,7 +78,7 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
|
|||||||
state.read_storage::<comp::Stats>().get(entity),
|
state.read_storage::<comp::Stats>().get(entity),
|
||||||
) {
|
) {
|
||||||
if let Some(character_id) = player.character_id {
|
if let Some(character_id) = player.character_id {
|
||||||
persistence::stats::update(character_id, stats, None);
|
persistence::stats::update(character_id, stats, None, db_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +117,9 @@ impl Server {
|
|||||||
// Server-only components
|
// Server-only components
|
||||||
state.ecs_mut().register::<RegionSubscription>();
|
state.ecs_mut().register::<RegionSubscription>();
|
||||||
state.ecs_mut().register::<Client>();
|
state.ecs_mut().register::<Client>();
|
||||||
|
state.ecs_mut().insert(crate::settings::PersistenceDBDir(
|
||||||
|
settings.persistence_db_dir.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let world = World::generate(settings.world_seed, WorldOpts {
|
let world = World::generate(settings.world_seed, WorldOpts {
|
||||||
@ -236,7 +239,9 @@ impl Server {
|
|||||||
// Run pending DB migrations (if any)
|
// Run pending DB migrations (if any)
|
||||||
debug!("Running DB migrations...");
|
debug!("Running DB migrations...");
|
||||||
|
|
||||||
if let Some(error) = persistence::run_migrations().err() {
|
if let Some(error) =
|
||||||
|
persistence::run_migrations(&this.server_settings.persistence_db_dir).err()
|
||||||
|
{
|
||||||
log::info!("Migration error: {}", format!("{:#?}", error));
|
log::info!("Migration error: {}", format!("{:#?}", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@ type CharacterListResult = Result<Vec<CharacterItem>, Error>;
|
|||||||
///
|
///
|
||||||
/// After first logging in, and after a character is selected, we fetch this
|
/// After first logging in, and after a character is selected, we fetch this
|
||||||
/// data for the purpose of inserting their persisted data for the entity.
|
/// data for the purpose of inserting their persisted data for the entity.
|
||||||
pub fn load_character_data(character_id: i32) -> Result<comp::Stats, Error> {
|
pub fn load_character_data(character_id: i32, db_dir: &str) -> Result<comp::Stats, Error> {
|
||||||
let (character_data, body_data, stats_data) = schema::character::dsl::character
|
let (character_data, body_data, stats_data) = schema::character::dsl::character
|
||||||
.filter(schema::character::id.eq(character_id))
|
.filter(schema::character::id.eq(character_id))
|
||||||
.inner_join(schema::body::table)
|
.inner_join(schema::body::table)
|
||||||
.inner_join(schema::stats::table)
|
.inner_join(schema::stats::table)
|
||||||
.first::<(Character, Body, Stats)>(&establish_connection())?;
|
.first::<(Character, Body, Stats)>(&establish_connection(db_dir))?;
|
||||||
|
|
||||||
Ok(comp::Stats::from(StatsJoinData {
|
Ok(comp::Stats::from(StatsJoinData {
|
||||||
alias: &character_data.alias,
|
alias: &character_data.alias,
|
||||||
@ -37,13 +37,13 @@ pub fn load_character_data(character_id: i32) -> Result<comp::Stats, Error> {
|
|||||||
/// In the event that a join fails, for a character (i.e. they lack an entry for
|
/// In the event that a join fails, for a character (i.e. they lack an entry for
|
||||||
/// stats, body, etc...) the character is skipped, and no entry will be
|
/// stats, body, etc...) the character is skipped, and no entry will be
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn load_character_list(player_uuid: &str) -> CharacterListResult {
|
pub fn load_character_list(player_uuid: &str, db_dir: &str) -> CharacterListResult {
|
||||||
let data: Vec<(Character, Body, Stats)> = schema::character::dsl::character
|
let data: Vec<(Character, Body, Stats)> = schema::character::dsl::character
|
||||||
.filter(schema::character::player_uuid.eq(player_uuid))
|
.filter(schema::character::player_uuid.eq(player_uuid))
|
||||||
.order(schema::character::id.desc())
|
.order(schema::character::id.desc())
|
||||||
.inner_join(schema::body::table)
|
.inner_join(schema::body::table)
|
||||||
.inner_join(schema::stats::table)
|
.inner_join(schema::stats::table)
|
||||||
.load::<(Character, Body, Stats)>(&establish_connection())?;
|
.load::<(Character, Body, Stats)>(&establish_connection(db_dir))?;
|
||||||
|
|
||||||
Ok(data
|
Ok(data
|
||||||
.iter()
|
.iter()
|
||||||
@ -72,10 +72,11 @@ pub fn create_character(
|
|||||||
character_alias: String,
|
character_alias: String,
|
||||||
character_tool: Option<String>,
|
character_tool: Option<String>,
|
||||||
body: &comp::Body,
|
body: &comp::Body,
|
||||||
|
db_dir: &str,
|
||||||
) -> CharacterListResult {
|
) -> CharacterListResult {
|
||||||
check_character_limit(uuid)?;
|
check_character_limit(uuid, db_dir)?;
|
||||||
|
|
||||||
let connection = establish_connection();
|
let connection = establish_connection(db_dir);
|
||||||
|
|
||||||
connection.transaction::<_, diesel::result::Error, _>(|| {
|
connection.transaction::<_, diesel::result::Error, _>(|| {
|
||||||
use schema::{body, character, character::dsl::*, stats};
|
use schema::{body, character, character::dsl::*, stats};
|
||||||
@ -136,26 +137,26 @@ pub fn create_character(
|
|||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
load_character_list(uuid)
|
load_character_list(uuid, db_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a character. Returns the updated character list.
|
/// Delete a character. Returns the updated character list.
|
||||||
pub fn delete_character(uuid: &str, character_id: i32) -> CharacterListResult {
|
pub fn delete_character(uuid: &str, character_id: i32, db_dir: &str) -> CharacterListResult {
|
||||||
use schema::character::dsl::*;
|
use schema::character::dsl::*;
|
||||||
|
|
||||||
diesel::delete(character.filter(id.eq(character_id))).execute(&establish_connection())?;
|
diesel::delete(character.filter(id.eq(character_id))).execute(&establish_connection(db_dir))?;
|
||||||
|
|
||||||
load_character_list(uuid)
|
load_character_list(uuid, db_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_character_limit(uuid: &str) -> Result<(), Error> {
|
fn check_character_limit(uuid: &str, db_dir: &str) -> Result<(), Error> {
|
||||||
use diesel::dsl::count_star;
|
use diesel::dsl::count_star;
|
||||||
use schema::character::dsl::*;
|
use schema::character::dsl::*;
|
||||||
|
|
||||||
let character_count = character
|
let character_count = character
|
||||||
.select(count_star())
|
.select(count_star())
|
||||||
.filter(player_uuid.eq(uuid))
|
.filter(player_uuid.eq(uuid))
|
||||||
.load::<i64>(&establish_connection())?;
|
.load::<i64>(&establish_connection(db_dir))?;
|
||||||
|
|
||||||
match character_count.first() {
|
match character_count.first() {
|
||||||
Some(count) => {
|
Some(count) => {
|
||||||
|
@ -9,35 +9,37 @@ extern crate diesel;
|
|||||||
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel_migrations::embed_migrations;
|
use diesel_migrations::embed_migrations;
|
||||||
use std::{env, fs, path::Path};
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
// See: https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/macro.embed_migrations.html
|
// See: https://docs.rs/diesel_migrations/1.4.0/diesel_migrations/macro.embed_migrations.html
|
||||||
// This macro is called at build-time, and produces the necessary migration info
|
// This macro is called at build-time, and produces the necessary migration info
|
||||||
// for the `embedded_migrations` call below.
|
// for the `embedded_migrations` call below.
|
||||||
embed_migrations!();
|
embed_migrations!();
|
||||||
|
|
||||||
pub fn run_migrations() -> Result<(), diesel_migrations::RunMigrationsError> {
|
pub fn run_migrations(db_dir: &str) -> Result<(), diesel_migrations::RunMigrationsError> {
|
||||||
let _ = fs::create_dir(format!("{}/saves/", binary_absolute_path()));
|
let db_dir = &apply_saves_dir_override(db_dir);
|
||||||
embedded_migrations::run_with_output(&establish_connection(), &mut std::io::stdout())
|
let _ = fs::create_dir(format!("{}/", db_dir));
|
||||||
|
embedded_migrations::run_with_output(&establish_connection(db_dir), &mut std::io::stdout())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn establish_connection() -> SqliteConnection {
|
fn establish_connection(db_dir: &str) -> SqliteConnection {
|
||||||
let database_url = format!("{}/saves/db.sqlite", binary_absolute_path());
|
let db_dir = &apply_saves_dir_override(db_dir);
|
||||||
|
let database_url = format!("{}/db.sqlite", db_dir);
|
||||||
SqliteConnection::establish(&database_url)
|
SqliteConnection::establish(&database_url)
|
||||||
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the absolute path of the binary so that the database is always stored
|
fn apply_saves_dir_override(db_dir: &str) -> String {
|
||||||
// beside it, no matter where the binary is run from
|
if let Some(val) = env::var_os("VELOREN_SAVES_DIR") {
|
||||||
fn binary_absolute_path() -> String {
|
let path = PathBuf::from(val);
|
||||||
let binary_path;
|
if path.exists() || path.parent().map(|x| x.exists()).unwrap_or(false) {
|
||||||
match env::current_exe() {
|
// Only allow paths with valid unicode characters
|
||||||
Ok(exe_path) => binary_path = exe_path,
|
match path.to_str() {
|
||||||
Err(e) => panic!("Failed to get current exe path: {}", e),
|
Some(path) => return path.to_owned(),
|
||||||
};
|
None => {},
|
||||||
|
}
|
||||||
match Path::new(&binary_path.display().to_string()).parent() {
|
}
|
||||||
Some(path) => return path.display().to_string(),
|
log::warn!("VELOREN_SAVES_DIR points to an invalid path.");
|
||||||
None => panic!("Failed to get current exe parent path"),
|
}
|
||||||
};
|
db_dir.to_string()
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,18 @@ use super::{establish_connection, models::StatsUpdate, schema};
|
|||||||
use crate::comp;
|
use crate::comp;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
pub fn update(character_id: i32, stats: &comp::Stats, conn: Option<&SqliteConnection>) {
|
pub fn update(
|
||||||
|
character_id: i32,
|
||||||
|
stats: &comp::Stats,
|
||||||
|
conn: Option<&SqliteConnection>,
|
||||||
|
db_dir: &str,
|
||||||
|
) {
|
||||||
log::warn!("stats persisting...");
|
log::warn!("stats persisting...");
|
||||||
|
|
||||||
if let Err(error) =
|
if let Err(error) =
|
||||||
diesel::update(schema::stats::table.filter(schema::stats::character_id.eq(character_id)))
|
diesel::update(schema::stats::table.filter(schema::stats::character_id.eq(character_id)))
|
||||||
.set(&StatsUpdate::from(stats))
|
.set(&StatsUpdate::from(stats))
|
||||||
.execute(conn.unwrap_or(&establish_connection()))
|
.execute(conn.unwrap_or(&establish_connection(db_dir)))
|
||||||
{
|
{
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Failed to update stats for character: {:?}: {:?}",
|
"Failed to update stats for character: {:?}: {:?}",
|
||||||
@ -20,8 +25,8 @@ pub fn update(character_id: i32, stats: &comp::Stats, conn: Option<&SqliteConnec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn batch_update<'a>(updates: impl Iterator<Item = (i32, &'a comp::Stats)>) {
|
pub fn batch_update<'a>(updates: impl Iterator<Item = (i32, &'a comp::Stats)>, db_dir: &str) {
|
||||||
let connection = &establish_connection();
|
let connection = &establish_connection(db_dir);
|
||||||
|
|
||||||
updates.for_each(|(character_id, stats)| update(character_id, stats, Some(connection)));
|
updates.for_each(|(character_id, stats)| update(character_id, stats, Some(connection), db_dir));
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ pub struct ServerSettings {
|
|||||||
/// When set to None, loads the default map file (if available); otherwise,
|
/// When set to None, loads the default map file (if available); otherwise,
|
||||||
/// uses the value of the file options to decide how to proceed.
|
/// uses the value of the file options to decide how to proceed.
|
||||||
pub map_file: Option<FileOpts>,
|
pub map_file: Option<FileOpts>,
|
||||||
|
pub persistence_db_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerSettings {
|
impl Default for ServerSettings {
|
||||||
@ -56,6 +57,7 @@ impl Default for ServerSettings {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|n| n.to_string())
|
.map(|n| n.to_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
persistence_db_dir: "saves".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +97,7 @@ impl ServerSettings {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn singleplayer() -> Self {
|
pub fn singleplayer(persistence_db_dir: String) -> Self {
|
||||||
let load = Self::load();
|
let load = Self::load();
|
||||||
Self {
|
Self {
|
||||||
//BUG: theoretically another process can grab the port between here and server
|
//BUG: theoretically another process can grab the port between here and server
|
||||||
@ -121,9 +123,12 @@ impl ServerSettings {
|
|||||||
start_time: 9.0 * 3600.0,
|
start_time: 9.0 * 3600.0,
|
||||||
admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want
|
admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want
|
||||||
* to use admin commands or not */
|
* to use admin commands or not */
|
||||||
|
persistence_db_dir,
|
||||||
..load // Fill in remaining fields from server_settings.ron.
|
..load // Fill in remaining fields from server_settings.ron.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_settings_path() -> PathBuf { PathBuf::from(r"server_settings.ron") }
|
fn get_settings_path() -> PathBuf { PathBuf::from(r"server_settings.ron") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PersistenceDBDir(pub String);
|
||||||
|
@ -163,7 +163,10 @@ impl StateExt for State {
|
|||||||
// Grab persisted character data from the db and insert their associated
|
// Grab persisted character data from the db and insert their associated
|
||||||
// components. If for some reason the data can't be returned (missing
|
// components. If for some reason the data can't be returned (missing
|
||||||
// data, DB error), kick the client back to the character select screen.
|
// data, DB error), kick the client back to the character select screen.
|
||||||
match persistence::character::load_character_data(character_id) {
|
match persistence::character::load_character_data(
|
||||||
|
character_id,
|
||||||
|
&server_settings.persistence_db_dir,
|
||||||
|
) {
|
||||||
Ok(stats) => self.write_component(entity, stats),
|
Ok(stats) => self.write_component(entity, stats),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use super::SysTimer;
|
use super::SysTimer;
|
||||||
use crate::{auth_provider::AuthProvider, client::Client, persistence, CLIENT_TIMEOUT};
|
use crate::{
|
||||||
|
auth_provider::AuthProvider, client::Client, persistence, settings::PersistenceDBDir,
|
||||||
|
CLIENT_TIMEOUT,
|
||||||
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel},
|
comp::{Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
@ -24,6 +27,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
Read<'a, Time>,
|
Read<'a, Time>,
|
||||||
|
ReadExpect<'a, PersistenceDBDir>,
|
||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
Write<'a, SysTimer<Self>>,
|
Write<'a, SysTimer<Self>>,
|
||||||
ReadStorage<'a, Uid>,
|
ReadStorage<'a, Uid>,
|
||||||
@ -47,6 +51,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
entities,
|
entities,
|
||||||
server_event_bus,
|
server_event_bus,
|
||||||
time,
|
time,
|
||||||
|
persistence_db_dir,
|
||||||
terrain,
|
terrain,
|
||||||
mut timer,
|
mut timer,
|
||||||
uids,
|
uids,
|
||||||
@ -68,6 +73,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let time = time.0;
|
let time = time.0;
|
||||||
|
|
||||||
|
let persistence_db_dir = &persistence_db_dir.0;
|
||||||
|
|
||||||
let mut server_emitter = server_event_bus.emitter();
|
let mut server_emitter = server_event_bus.emitter();
|
||||||
|
|
||||||
let mut new_chat_msgs = Vec::new();
|
let mut new_chat_msgs = Vec::new();
|
||||||
@ -317,6 +324,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
if let Some(player) = players.get(entity) {
|
if let Some(player) = players.get(entity) {
|
||||||
match persistence::character::load_character_list(
|
match persistence::character::load_character_list(
|
||||||
&player.uuid().to_string(),
|
&player.uuid().to_string(),
|
||||||
|
persistence_db_dir,
|
||||||
) {
|
) {
|
||||||
Ok(character_list) => {
|
Ok(character_list) => {
|
||||||
client.notify(ServerMsg::CharacterListUpdate(character_list));
|
client.notify(ServerMsg::CharacterListUpdate(character_list));
|
||||||
@ -335,6 +343,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
alias,
|
alias,
|
||||||
tool,
|
tool,
|
||||||
&body,
|
&body,
|
||||||
|
persistence_db_dir,
|
||||||
) {
|
) {
|
||||||
Ok(character_list) => {
|
Ok(character_list) => {
|
||||||
client.notify(ServerMsg::CharacterListUpdate(character_list));
|
client.notify(ServerMsg::CharacterListUpdate(character_list));
|
||||||
@ -351,6 +360,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
match persistence::character::delete_character(
|
match persistence::character::delete_character(
|
||||||
&player.uuid().to_string(),
|
&player.uuid().to_string(),
|
||||||
character_id,
|
character_id,
|
||||||
|
persistence_db_dir,
|
||||||
) {
|
) {
|
||||||
Ok(character_list) => {
|
Ok(character_list) => {
|
||||||
client.notify(ServerMsg::CharacterListUpdate(character_list));
|
client.notify(ServerMsg::CharacterListUpdate(character_list));
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
persistence::stats,
|
persistence::stats,
|
||||||
|
settings::PersistenceDBDir,
|
||||||
sys::{SysScheduler, SysTimer},
|
sys::{SysScheduler, SysTimer},
|
||||||
};
|
};
|
||||||
use common::comp::{Player, Stats};
|
use common::comp::{Player, Stats};
|
||||||
use specs::{Join, ReadStorage, System, Write};
|
use specs::{Join, ReadExpect, ReadStorage, System, Write};
|
||||||
|
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
|
|
||||||
@ -11,11 +12,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
type SystemData = (
|
type SystemData = (
|
||||||
ReadStorage<'a, Player>,
|
ReadStorage<'a, Player>,
|
||||||
ReadStorage<'a, Stats>,
|
ReadStorage<'a, Stats>,
|
||||||
|
ReadExpect<'a, PersistenceDBDir>,
|
||||||
Write<'a, SysScheduler<Self>>,
|
Write<'a, SysScheduler<Self>>,
|
||||||
Write<'a, SysTimer<Self>>,
|
Write<'a, SysTimer<Self>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, (players, player_stats, mut scheduler, mut timer): Self::SystemData) {
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
(players, player_stats, persistence_db_dir, mut scheduler, mut timer): Self::SystemData,
|
||||||
|
) {
|
||||||
if scheduler.should_run() {
|
if scheduler.should_run() {
|
||||||
timer.start();
|
timer.start();
|
||||||
|
|
||||||
@ -23,6 +28,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
(&players, &player_stats)
|
(&players, &player_stats)
|
||||||
.join()
|
.join()
|
||||||
.filter_map(|(player, stats)| player.character_id.map(|id| (id, stats))),
|
.filter_map(|(player, stats)| player.character_id.map(|id| (id, stats))),
|
||||||
|
&persistence_db_dir.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
timer.end();
|
timer.end();
|
||||||
|
@ -32,7 +32,14 @@ impl Singleplayer {
|
|||||||
let (sender, receiver) = unbounded();
|
let (sender, receiver) = unbounded();
|
||||||
|
|
||||||
// Create server
|
// Create server
|
||||||
let settings = ServerSettings::singleplayer();
|
let settings = ServerSettings::singleplayer(
|
||||||
|
crate::settings::Settings::get_settings_path()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("saves")
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
let thread_pool = client.map(|c| c.thread_pool().clone());
|
let thread_pool = client.map(|c| c.thread_pool().clone());
|
||||||
let settings2 = settings.clone();
|
let settings2 = settings.clone();
|
||||||
|
Loading…
Reference in New Issue
Block a user