From: Ian Jackson Date: Sun, 26 Jul 2020 11:59:45 +0000 (+0100) Subject: creategame command etc. X-Git-Tag: otter-0.2.0~1248 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=a826b7f55febf4b0226d25c50f2f83461b9e2382;p=otter.git creategame command etc. --- diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index bd83b506..3ee23199 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -31,8 +31,9 @@ struct CommandStream<'d> { euid : Result, read : io::Lines>, write : CSWrite, - scope : Option, desc : &'d str, + scope : Option, + amu : Option, } type CSE = anyhow::Error; @@ -195,10 +196,25 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { Fine { } }, -/* - CreateGame(game) => { - + CreateGame(name) => { + let gs = GameState { + pieces : Default::default(), + players : Default::default(), + log : Default::default(), + gen : Generation(0), + }; + + let name = InstanceName { + scope : cs.scope.as_ref().ok_or(NoScope)?.clone(), + scoped_name : name, + }; + + cs.amu = Some(Instance::new(name, gs)?); + + Fine { } }, + + /* AddPiece(game, { pos,count,name,info }) => { let game = cs.lookup_game(&game)?; let count = spec.count.unwrap_or(1); @@ -271,7 +287,7 @@ impl CommandListener { let write = BufWriter::new(write); let cs = CommandStream { - scope: None, desc: &desc, + scope: None, amu: None, desc: &desc, read, write, euid, }; cs.mainloop()?; diff --git a/src/commands.rs b/src/commands.rs index 1436516b..516ca2e3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -5,6 +5,7 @@ use crate::imports::*; pub enum MgmtCommand { Noop { }, SetScope(ManagementScope), + CreateGame(String), // AddPiece(Box), } @@ -18,6 +19,8 @@ pub enum MgmtResponse { pub enum MgmtError { ParseFailed(String), AuthorisationError, + NoScope, + AlreadyExists, XXXU(&'static str), } display_as_debug!{MgmtError} diff --git a/src/global.rs b/src/global.rs index d4512f00..b5a72cf7 100644 --- a/src/global.rs +++ b/src/global.rs @@ -15,10 +15,17 @@ pub struct RawToken (pub String); // ---------- data structure ---------- +#[derive(Debug,Serialize,Deserialize)] +#[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] +pub struct InstanceName { + pub scope: ManagementScope, + pub scoped_name: String, +} +pub type InstanceRef = Arc>; + #[derive(Debug)] pub struct Instance { - pub scope : ManagementScope, - pub scoped_name : String, + pub name : Arc, pub gs : GameState, pub clients : DenseSlotMap, pub updates : SecondarySlotMap, @@ -26,7 +33,8 @@ pub struct Instance { pub tokens_clients : TokenRegistry, } -#[derive(Debug,Deserialize,Serialize)] +#[derive(Debug,Clone,Deserialize,Serialize)] +#[derive(Eq,PartialEq,Ord,PartialOrd,Hash)] pub enum ManagementScope { XXX, Unix { user : String /* username, so filename-safe */ }, @@ -46,6 +54,7 @@ pub struct InstanceGuard<'g> { #[derive(Default)] struct Global { // lock hierarchy: this is the innermost lock + games : RwLock,InstanceRef>>, players : RwLock>, clients : RwLock>, // xxx delete instances at some point! @@ -107,17 +116,32 @@ lazy_static! { // ---------- Player and instance API ---------- impl Instance { - #[throws(OE)] - pub fn new(gs: GameState, - scope : ManagementScope, - scoped_name: String) -> Instance { - Instance { - scope, scoped_name, gs, + /// Returns `None` if a game with this name already exists + #[throws(MgmtError)] + pub fn new(name: InstanceName, gs: GameState) -> InstanceRef { + let name = Arc::new(name); + + let inst = Instance { + name : name.clone(), + gs, clients : Default::default(), updates : Default::default(), tokens_players : Default::default(), tokens_clients : Default::default(), - } + }; + + let mut games = GLOBAL.games.write().unwrap(); + let entry = games.entry(name); + + use hash_map::Entry::*; + let entry = match entry { + Vacant(ve) => ve, + Occupied(_) => throw!(MgmtError::AlreadyExists), + }; + + let ami = Arc::new(Mutex::new(inst)); + entry.insert(ami.clone()); + ami } #[throws(OE)] @@ -170,15 +194,14 @@ impl InstanceGuard<'_> { for t in tokens.tr.drain() { global.remove(&t); } } - fn savefile(scope: &ManagementScope, scoped_name: &str, - prefix: &str, suffix: &str) -> String { - let scope_prefix = { use ManagementScope::*; match scope { - XXX => format!(""), - Unix{user} => { format!("{}:", user) }, + fn savefile(name: &InstanceName, prefix: &str, suffix: &str) -> String { + let scope_prefix = { use ManagementScope::*; match &name.scope { + XXX => format!(""), + Unix{user} => { format!("{}:", user) }, } }; iter::once(prefix) .chain( iter::once(scope_prefix.as_ref()) ) - .chain( utf8_percent_encode(scoped_name, + .chain( utf8_percent_encode(&name.scoped_name, &percent_encoding::NON_ALPHANUMERIC) ) .chain( iter::once(suffix) ) .collect() @@ -189,12 +212,12 @@ impl InstanceGuard<'_> { w: fn(s: &Self, w: &mut BufWriter) -> Result<(),rmp_serde::encode::Error> ) { - let tmp = Self::savefile(&self.scope, &self.scoped_name, prefix,".tmp"); + let tmp = Self::savefile(&self.name, prefix,".tmp"); let mut f = BufWriter::new(fs::File::create(&tmp)?); w(self, &mut f)?; f.flush()?; drop( f.into_inner().map_err(|e| { let e : io::Error = e.into(); e })? ); - let out = Self::savefile(&self.scope, &self.scoped_name, prefix,""); + let out = Self::savefile(&self.name, prefix,""); fs::rename(&tmp, &out)?; eprintln!("saved to {}", &out); } @@ -224,33 +247,33 @@ impl InstanceGuard<'_> { } #[throws(OE)] - fn load_something( - scope: &ManagementScope, - scoped_name: &str, - prefix: &str - ) -> T { - let inp = Self::savefile(scope, scoped_name, prefix, ""); + fn load_something(name: &InstanceName, prefix: &str) -> T { + let inp = Self::savefile(name, prefix, ""); let mut f = BufReader::new(fs::File::open(&inp)?); // xxx handle ENOENT specially, own OE variant rmp_serde::decode::from_read(&mut f)? } #[throws(OE)] - pub fn load(scope: ManagementScope, scoped_name: String) - -> Arc> { - let gs : GameState = Self::load_something(&scope, &scoped_name, "g-")?; + pub fn load(name: InstanceName) -> Arc> { + // xxx scan on startup, rather than asking caller to specify names + // xxx should take a file lock on save area + let gs : GameState = Self::load_something(&name, "g-")?; let mut al : InstanceSaveAccesses - = Self::load_something(&scope, &scoped_name, "a-")?; + = Self::load_something(&name, "a-")?; let mut updates : SecondarySlotMap<_,_> = Default::default(); for player in gs.players.keys() { updates.insert(player, Default::default()); } + let name = Arc::new(name); + let inst = Instance { - scope, scoped_name, gs, updates, + name, gs, updates, clients : Default::default(), tokens_clients : Default::default(), tokens_players : Default::default(), }; + // xxx record in GLOBAL.games let amu = Arc::new(Mutex::new(inst)); let mut ig = amu.lock().unwrap(); for (token, _) in &al.tokens_players { @@ -344,9 +367,11 @@ const XXX_PLAYERS_TOKENS : &[(&str, &str)] = &[ #[throws(OE)] pub fn xxx_global_setup() { let gs = xxx_gamestate_init(); - let gi = Instance::new(gs, ManagementScope::XXX, "dummy".to_string())?; - let amu = Arc::new(Mutex::new(gi)); - let mut ig = Instance::lock(&amu)?; + let ami = Instance::new(InstanceName { + scope: ManagementScope::XXX, + scoped_name: "dummy".to_string() + }, gs).expect("xxx create dummy"); + let mut ig = Instance::lock(&ami)?; for (token, nick) in XXX_PLAYERS_TOKENS { let player = ig.player_new(PlayerState { nick : nick.to_string(), diff --git a/src/imports.rs b/src/imports.rs index 5507ff23..5d810839 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -8,7 +8,7 @@ pub use std::fmt::{self,Display,Debug}; pub use std::thread; pub use std::time::Duration; pub use std::sync::{Arc,Mutex,MutexGuard,RwLock,Condvar}; -pub use std::collections::{HashMap,HashSet}; +pub use std::collections::{HashMap,hash_map,HashSet}; pub use std::borrow::Borrow; pub use std::convert::{TryFrom,TryInto}; pub use std::str;