From: Ian Jackson Date: Sat, 22 Aug 2020 19:42:08 +0000 (+0100) Subject: reset compiles X-Git-Tag: otter-0.2.0~1108 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=c9e1e0fd6bcad5d78cbf2b3c9d03a7972d218618;p=otter.git reset compiles --- diff --git a/src/bin/otter.rs b/src/bin/otter.rs index 07c66655..8fc2db41 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -69,15 +69,14 @@ inventory::collect!(Subcommand); struct ArgumentParseError(String); display_as_debug!(ArgumentParseError); -fn parse_args( +fn parse_args( args: Vec, apmaker: &F, - completer: &C, + completer: Option<&dyn Fn(&mut T) -> Result<(), ArgumentParseError>>, extra_help: Option<&dyn Fn(&mut dyn Write) -> Result<(), io::Error>>, ) -> T where T: Default, F: Fn(&mut T) -> ArgumentParser, - C: Fn(&mut T) -> Result<(), ArgumentParseError>, { let mut parsed = Default::default(); let ap = apmaker(&mut parsed); @@ -103,11 +102,13 @@ where T: Default, }); } mem::drop(ap); - completer(&mut parsed).unwrap_or_else(|e:ArgumentParseError| { - let ap = apmaker(&mut parsed); - ap.error(&us, &e.0, &mut stderr); - exit(EXIT_USAGE); - }); + if let Some(completer) = completer { + completer(&mut parsed).unwrap_or_else(|e:ArgumentParseError| { + let ap = apmaker(&mut parsed); + ap.error(&us, &e.0, &mut stderr); + exit(EXIT_USAGE); + }); + } parsed } @@ -118,7 +119,7 @@ fn main() { subcommand: String, subargs: Vec, }; - let ma = parse_args::( + let ma = parse_args::( env::args().collect(), &|ma|{ use argparse::*; @@ -148,7 +149,7 @@ fn main() { verbose.add_option(&["-v","--verbose"], IncrBy(1), "increase verbosity (default is short progress messages)"); ap - }, &|ma| { + }, Some(&|ma| { if let ref mut scope @None = ma.opts.scope { let user = env::var("USER").map_err(|e| ArgumentParseError( format!("--scope-unix needs USER env var: {}", &e) @@ -156,7 +157,7 @@ fn main() { *scope = Some(ManagementScope::Unix { user }); } Ok(()) - }, Some(&|w|{ + }), Some(&|w|{ writeln!(w, "\nSubcommands:")?; let maxlen = inventory::iter::.into_iter() .map(|Subcommand(verb,_,_)| verb.len()) @@ -226,7 +227,7 @@ impl DerefMut for ConnForGame { impl ConnForGame { #[throws(AE)] fn alter_game(&mut self, insns: Vec, - f: &mut dyn FnMut(&MgmtGameResponse) -> Result<(),AE>) + f: Option<&mut dyn FnMut(&MgmtGameResponse) -> Result<(),AE>>) -> Vec { let insns_len = insns.len(); let cmd = MgmtCommand::AlterGame { @@ -241,25 +242,28 @@ impl ConnForGame { wat => Err(anyhow!("unexpected AlterGame response: {:?} => {:?}", &cmd, &wat))?, }; - for response in &responses { - f(response)?; + if let Some(f) = f { + for response in &responses { + f(response)?; + } } responses } - fn get_players(&mut self) -> - Result<(PlayerMap, HashMap),AE> + fn get_info(&mut self) -> Result< + (MgmtGameResponseGameInfo, HashMap + ),AE> { let mut players = self.alter_game( - vec![ MgmtGameInstruction::GetPlayers { } ], - &mut |_|Ok(()) + vec![ MgmtGameInstruction::Info ], + None, )?; - let players = match players.pop() { - Some(MgmtGameResponse::Players(players)) => players, + let info = match players.pop() { + Some(MgmtGameResponse::Info(info)) => info, wat => Err(anyhow!("GetGstate got {:?}", &wat))?, }; let mut nick2id = HashMap::new(); - for (player, pstate) in players.iter() { + for (player, pstate) in info.players.iter() { use hash_map::Entry::*; match nick2id.entry(pstate.nick.clone()) { Occupied(oe) => Err(anyhow!("game has duplicate nick {:?}, {} {}", @@ -267,7 +271,7 @@ impl ConnForGame { Vacant(ve) => ve.insert(player), }; } - Ok((players, nick2id)) + Ok((info, nick2id)) } } @@ -285,7 +289,7 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> { // create missing players let (added_players,) = { - let (_, nick2id) = chan.get_players()?; + let (_, nick2id) = chan.get_info()?; let mut insns = vec![]; for pspec in &spec.players { @@ -296,7 +300,7 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> { } } let mut added_players = HashSet::new(); - chan.alter_game(insns, &mut |response| { + chan.alter_game(insns, Some(&mut |response| { let player = match response { &MgmtGameResponse::AddPlayer(player) => player, _ => Err(anyhow!("AddPlayer strange answer {:?}", @@ -304,14 +308,14 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> { }; added_players.insert(player); Ok(()) - })?; + }))?; (added_players,) }; // ensure players have access tokens { - let (_, nick2id) = chan.get_players()?; + let (_, nick2id) = chan.get_info()?; let mut insns = vec![]; let mut resetreport = vec![]; let mut resetspecs = vec![]; @@ -338,12 +342,12 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> { players: resetreport.clone(), }); let mut got_tokens = None; - chan.alter_game(insns, &mut |response| { + chan.alter_game(insns, Some(&mut |response| { if let MgmtGameResponse::PlayerAccessTokens(tokens) = response { got_tokens = Some(tokens.clone()); } Ok(()) - })?; + }))?; let got_tokens = match got_tokens { Some(t) if t.len() == resetreport.len() => t, wat => Err(anyhow!("Did not get expected ReportPlayerAccesses! {:?}", @@ -371,6 +375,8 @@ fn read_spec(filename: &str, what: &str) -> T { })().with_context(|| format!("read {} {:?}", what, filename))? } +//---------- create-game ---------- + mod create_table { use super::*; @@ -381,28 +387,19 @@ mod create_table { } fn subargs(sa: &mut Args) -> ArgumentParser { - use argparse::*; - let mut ap = ArgumentParser::new(); - ap.refer(&mut sa.name).required() - .add_argument("TABLE-NAME",Store,"table name"); - ap.refer(&mut sa.file).required() - .add_argument("TABLE-SPEC-TOML",Store,"table spec"); - ap + use argparse::*; + let mut ap = ArgumentParser::new(); + ap.refer(&mut sa.name).required() + .add_argument("TABLE-NAME",Store,"table name"); + ap.refer(&mut sa.file).required() + .add_argument("TABLE-SPEC-TOML",Store,"table spec"); + ap } - #[throws(ArgumentParseError)] - fn complete(_sa: &mut Args) { } - #[throws(E)] fn call(_sc: &Subcommand, ma: MainOpts, args: Vec) { - let args = parse_args::(args, &subargs, &complete, None); - - if ma.verbose >= 2 { - eprintln!("CREATE-TABLE {:?} {:?}", &ma, &args); - } - - let spec : TableSpec = read_spec(&args.file, "game spec")?; - + let args = parse_args::(args, &subargs, None, None); + let spec : TableSpec = read_spec(&args.file, "table spec")?; let mut chan = connect(&ma)?; chan.cmd(&MgmtCommand::CreateGame { @@ -429,8 +426,81 @@ mod create_table { )} } +//---------- reset-game ---------- + +type Insn = MgmtGameInstruction; + +mod reset_game { + use super::*; -/* -impl Default for Args { - fn default() -> Args { Args { name: String::new(), file: String::new() }} -}*/ + #[derive(Default,Debug)] + struct Args { + name: String, + game_file: String, + table_file: Option, + } + + fn subargs(sa: &mut Args) -> ArgumentParser { + use argparse::*; + let mut ap = ArgumentParser::new(); + ap.refer(&mut sa.table_file) + .add_option(&["--reset-table"],StoreOption, + "reset the players and access too"); + ap.refer(&mut sa.name).required() + .add_argument("TABLE-NAME",Store,"table name"); + ap.refer(&mut sa.game_file).required() + .add_argument("GAME-SPEC-TOML",Store,"game spec"); + ap + } + + fn call(_sc: &Subcommand, ma: MainOpts, args: Vec) ->Result<(),AE> { + let args = parse_args::(args, &subargs, None, None); + let mut chan = ConnForGame { + conn: connect(&ma)?, + name: args.name.clone(), + how: MgmtGameUpdateMode::Bulk, + }; + let game : GameSpec = read_spec(&args.game_file, "game spec")?; + if let Some(table_file) = args.table_file { + let table_spec = read_spec(&table_file, "table spec")?; + setup_table(&mut chan, &table_spec)?; + } + + let mut insns = vec![]; + + chan.alter_game( + vec![ MgmtGameInstruction::ListPieces ], + Some(&mut |response|{ match response { + MgmtGameResponse::Pieces(pieces) => { + for p in pieces { + insns.push(MgmtGameInstruction::DeletePiece(p.piece)); + } + Ok(()) + }, + wat => Err(anyhow!("ListPieces => {:?}", &wat))?, + }}) + )?; + + if let Some(size) = game.table_size { + insns.push(Insn::SetTableSize(size)); + } + + let mut game = game; + for pspec in game.pieces.drain(..) { + insns.push(Insn::AddPieces(pspec)); + } + + chan.alter_game(insns, None)?; + + if ma.verbose >= 0 { + eprintln!("reset successful."); + } + Ok(()) + } + + inventory::submit!{Subcommand( + "reset", + "Reset the state of the game table", + call, + )} +} diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index dbf9dc50..444a668b 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -204,6 +204,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { CreateGame { name, insns } => { let gs = crate::gamestate::GameState { + table_size : DEFAULT_TABLE_SIZE, pieces : Default::default(), players : Default::default(), log : Default::default(), @@ -262,6 +263,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { struct UpdateHandlerBulk { pieces : slotmap::SparseSecondaryMap>, logs : bool, + raw : Vec, } #[derive(Debug)] @@ -282,7 +284,8 @@ impl UpdateHandler { #[throws(SVGProcessingError)] fn accumulate(&mut self, g: &mut Instance, upieces: Vec<(PieceId,PieceUpdateOp<()>)>, - ulogs: Vec) { + ulogs: Vec, + mut raw: Vec) { use UpdateHandler::*; match self { Bulk(bulk) => { @@ -301,6 +304,7 @@ impl UpdateHandler { }; } bulk.logs |= ulogs.len() != 0; + bulk.raw.append(&mut raw); }, Online => { let estimate = upieces.len() + ulogs.len(); @@ -310,6 +314,7 @@ impl UpdateHandler { buf.piece_update(upiece, uuop, &lens); } buf.log_updates(ulogs); + buf.raw_updates(raw); }, } } @@ -331,6 +336,8 @@ impl UpdateHandler { // xxx use cs.desc }]); } + + buf.raw_updates(bulk.raw); }, Online => { }, } @@ -345,8 +352,8 @@ fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard, let mut responses = Vec::with_capacity(insns.len()); let ok = (||{ for insn in insns.drain(0..) { - let (upieces, ulogs, resp) = execute_game_insn(cs, ig, insn)?; - uh.accumulate(ig, upieces, ulogs)?; + let (upieces, ulogs, raw, resp) = execute_game_insn(cs, ig, insn)?; + uh.accumulate(ig, upieces, ulogs, raw.unwrap_or_default())?; responses.push(resp); } uh.complete(cs,ig)?; @@ -363,47 +370,85 @@ const XXX_DEFAULT_POSD : Pos = [5,5]; const CREATE_PIECES_MAX : u32 = 300; +type ExecuteGameInsnResults = ( + Vec<(PieceId,PieceUpdateOp<()>)>, + Vec, + Option>, + MgmtGameResponse, +); + #[throws(ME)] fn execute_game_insn(cs: &CommandStream, ig: &mut InstanceGuard, update: MgmtGameInstruction) - -> (Vec<(PieceId,PieceUpdateOp<()>)>, - Vec, - MgmtGameResponse) { + -> ExecuteGameInsnResults { use MgmtGameInstruction::*; use MgmtGameResponse::*; + type Insn = MgmtGameInstruction; + type Resp = MgmtGameResponse; + fn readonly(_ig: &InstanceGuard, resp: Resp) -> ExecuteGameInsnResults { + (vec![], vec![], None, resp) + } + match update { - Noop { } => (vec![], vec![], Fine), + Noop { } => readonly(ig, Fine), + + Insn::DeletePiece(_) => panic!(),//xxx + + MgmtGameInstruction::SetTableSize(size) => { + ig.gs.table_size = size; + (vec![], + vec![ LogEntry { + html: format!("The table was resized to {}x{}", size[0], size[1]), + }], + Some(vec![ PreparedUpdateEntry::SetTableSize(size) ]), + Fine) + } MgmtGameInstruction::AddPlayer(pl) => { + if ig.gs.players.values().any(|p| p.nick == pl.nick) { + Err(ME::AlreadyExists)?; + } let player = ig.player_new(pl)?; #[allow(clippy::useless_format)] // xxx below - // xxx check for duplicate players (vec![], vec![ LogEntry { html: format!("The facilitator added a player xxx"), } ], + None, MgmtGameResponse::AddPlayer(player)) }, + Insn::ListPieces => readonly(ig, { + let pieces = ig.gs.pieces.iter().map(|(piece,p)|{ + let &PieceState { pos, face, .. } = p; + let desc_html = p.p.describe_html(None) + .unwrap_or_else(|_|"[html formatting failed!]".to_string()); + MgmtGamePieceInfo { piece, pos, face, desc_html } + }).collect(); + Resp::Pieces(pieces) + }), + RemovePlayer(player) => { ig.player_remove(player)?; - (vec![], vec![], Fine) + (vec![], vec![], None, Fine) }, - GetPlayers { } => { + MgmtGameInstruction::Info { } => { let players = ig.gs.players.clone(); - (vec![], vec![], Players(players)) + let table_size = ig.gs.table_size; + let info = MgmtGameResponseGameInfo { table_size, players }; + (vec![], vec![], None, Resp::Info(info)) }, ResetPlayerAccesses { players } => { let tokens = ig.players_access_reset(&players)? .drain(0..).map(|token| vec![token]).collect(); - (vec![], vec![], PlayerAccessTokens(tokens)) + (vec![], vec![], None, PlayerAccessTokens(tokens)) } ReportPlayerAccesses { players } => { let tokens = ig.players_access_report(&players)?; - (vec![], vec![], PlayerAccessTokens(tokens)) + (vec![], vec![], None, PlayerAccessTokens(tokens)) } SetFixedPlayerAccess { player, token } => { @@ -416,10 +461,10 @@ fn execute_game_insn(cs: &CommandStream, ig.player_access_register_fixed( player, token, authorised )?; - (vec![], vec![], Fine) + (vec![], vec![], None, Fine) } - AddPiece(PiecesSpec{ pos,posd,count,face,info }) => { + AddPieces(PiecesSpec{ pos,posd,count,face,info }) => { let gs = &mut ig.gs; let count = count.unwrap_or(1); if count > CREATE_PIECES_MAX { throw!(LimitExceeded) } @@ -445,9 +490,11 @@ fn execute_game_insn(cs: &CommandStream, pos[1] += posd[1]; } - (updates, vec![ LogEntry { - html: format!("The facilitaror added {} pieces", count), - }], + (updates, + vec![ LogEntry { + html: format!("The facilitaror added {} pieces", count), + }], + None, Fine ) }, diff --git a/src/commands.rs b/src/commands.rs index cdd3b5f3..a61b6ae3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -24,11 +24,15 @@ pub enum MgmtResponse { #[derive(Debug,Serialize,Deserialize)] pub enum MgmtGameInstruction { Noop, - AddPiece(PiecesSpec), - // todo: RemovePiece + Info, + SetTableSize(Pos), + + ListPieces, + AddPieces(PiecesSpec), + DeletePiece(PieceId), + AddPlayer(PlayerState), RemovePlayer(PlayerId), - GetPlayers, ResetPlayerAccesses { players: Vec }, ReportPlayerAccesses { players: Vec }, SetFixedPlayerAccess { player: PlayerId, token: RawToken }, @@ -37,9 +41,26 @@ pub enum MgmtGameInstruction { #[derive(Debug,Serialize,Deserialize)] pub enum MgmtGameResponse { Fine, + Info(MgmtGameResponseGameInfo), + + Pieces(Vec), + AddPlayer(PlayerId), PlayerAccessTokens(Vec>), - Players(PlayerMap), +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct MgmtGameResponseGameInfo { + pub table_size: Pos, + pub players: PlayerMap, +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct MgmtGamePieceInfo { + pub piece: PieceId, + pub pos: Pos, + pub face: FaceId, + pub desc_html: String, } #[derive(Debug,Copy,Clone,Serialize,Deserialize)] diff --git a/src/gamestate.rs b/src/gamestate.rs index 35aa6051..905f5cb6 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -27,6 +27,8 @@ visible_slotmap_key!{ VisiblePieceId('.') } #[serde(try_from="f64")] pub struct ZCoord(pub f64); +pub const DEFAULT_TABLE_SIZE : Pos = [ 200, 100 ]; + // ---------- general data types ---------- #[derive(Debug,Copy,Clone,Serialize,Deserialize,Eq,PartialEq,Ord,PartialOrd)] @@ -39,6 +41,7 @@ pub struct ZLevel { #[derive(Debug,Serialize,Deserialize)] pub struct GameState { + pub table_size : Pos, // xxx send to client etc. pub pieces : DenseSlotMap, pub players : PlayerMap, pub gen : Generation, @@ -207,7 +210,8 @@ pub fn xxx_gamestate_init() -> GameState { gen.increment(); pieces.insert(pr); } - GameState { pieces, gen, players : Default::default(), + GameState { table_size : DEFAULT_TABLE_SIZE, + pieces, gen, players : Default::default(), max_z: ZCoord(0.), log : Default::default(), } } diff --git a/src/global.rs b/src/global.rs index 6edd2df4..7bc960b6 100644 --- a/src/global.rs +++ b/src/global.rs @@ -334,6 +334,7 @@ impl InstanceGuard<'_> { // New state let mut gs = GameState { // These parts are straightforward and correct + table_size : self.c.g.gs.table_size, gen : self.c.g.gs.gen, max_z : self.gs.max_z, players, diff --git a/src/spec.rs b/src/spec.rs index 50826499..71717f0b 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -17,7 +17,7 @@ pub struct PlayerSpec { #[derive(Debug,Serialize,Deserialize)] pub struct GameSpec { - pub table : Pos, + pub table_size : Option, pub pieces : Vec, } diff --git a/src/updates.rs b/src/updates.rs index dd43cf34..910d3274 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -36,6 +36,7 @@ pub enum PreparedUpdateEntry { piece : VisiblePieceId, op : PieceUpdateOp, }, + SetTableSize(Pos), Log (Arc), Error (ErrorSignaledViaUpdate), } @@ -79,6 +80,7 @@ enum TransmitUpdateEntry<'u> { piece : VisiblePieceId, op : &'u PieceUpdateOp, }, + SetTableSize(Pos), Log (&'u LogEntry), Error(ErrorSignaledViaUpdate), } @@ -122,6 +124,7 @@ impl PreparedUpdateEntry { Log(logent) => { logent.html.as_bytes().len() * 3 }, + SetTableSize(_) | Error(_) => { 100 }, @@ -260,6 +263,10 @@ impl<'r> PrepareUpdatesBuffer<'r> { self.us.push(update); } + pub fn raw_updates(&mut self, mut raw: Vec) { + self.us.append(&mut raw) + } + pub fn log_updates(&mut self, logents: Vec) { for logentry in logents { let logentry = Arc::new(logentry); @@ -292,8 +299,9 @@ impl PreparedUpdate { pub fn for_transmit(&self, dest : ClientId) -> TransmitUpdate { let mut ents = vec![]; for u in &self.us { + type Prep = PreparedUpdateEntry; let ue = match u { - &PreparedUpdateEntry::Piece + &Prep::Piece { piece, client, sameclient_cseq : cseq, ref op } if client == dest => { let zg = op.new_z_generation(); @@ -305,6 +313,9 @@ impl PreparedUpdate { PreparedUpdateEntry::Log(logent) => { TransmitUpdateEntry::Log(&logent) }, + &PreparedUpdateEntry::SetTableSize(size) => { + TransmitUpdateEntry::SetTableSize(size) + }, &PreparedUpdateEntry::Error(e) => { TransmitUpdateEntry::Error(e) }