struct ArgumentParseError(String);
display_as_debug!(ArgumentParseError);
-fn parse_args<T,F,C>(
+fn parse_args<T,F>(
args: Vec<String>,
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);
});
}
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
}
subcommand: String,
subargs: Vec<String>,
};
- let ma = parse_args::<MainArgs,_,_>(
+ let ma = parse_args::<MainArgs,_>(
env::args().collect(),
&|ma|{
use argparse::*;
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)
*scope = Some(ManagementScope::Unix { user });
}
Ok(())
- }, Some(&|w|{
+ }), Some(&|w|{
writeln!(w, "\nSubcommands:")?;
let maxlen = inventory::iter::<Subcommand>.into_iter()
.map(|Subcommand(verb,_,_)| verb.len())
impl ConnForGame {
#[throws(AE)]
fn alter_game(&mut self, insns: Vec<MgmtGameInstruction>,
- f: &mut dyn FnMut(&MgmtGameResponse) -> Result<(),AE>)
+ f: Option<&mut dyn FnMut(&MgmtGameResponse) -> Result<(),AE>>)
-> Vec<MgmtGameResponse> {
let insns_len = insns.len();
let cmd = MgmtCommand::AlterGame {
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<String,PlayerId>),AE>
+ fn get_info(&mut self) -> Result<
+ (MgmtGameResponseGameInfo, HashMap<String,PlayerId>
+ ),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 {:?}, {} {}",
Vacant(ve) => ve.insert(player),
};
}
- Ok((players, nick2id))
+ Ok((info, nick2id))
}
}
// 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 {
}
}
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 {:?}",
};
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![];
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! {:?}",
})().with_context(|| format!("read {} {:?}", what, filename))?
}
+//---------- create-game ----------
+
mod create_table {
use super::*;
}
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<String>) {
- let args = parse_args::<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,_>(args, &subargs, None, None);
+ let spec : TableSpec = read_spec(&args.file, "table spec")?;
let mut chan = connect(&ma)?;
chan.cmd(&MgmtCommand::CreateGame {
)}
}
+//---------- 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<String>,
+ }
+
+ 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<String>) ->Result<(),AE> {
+ let args = parse_args::<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,
+ )}
+}
CreateGame { name, insns } => {
let gs = crate::gamestate::GameState {
+ table_size : DEFAULT_TABLE_SIZE,
pieces : Default::default(),
players : Default::default(),
log : Default::default(),
struct UpdateHandlerBulk {
pieces : slotmap::SparseSecondaryMap<PieceId, PieceUpdateOp<()>>,
logs : bool,
+ raw : Vec<PreparedUpdateEntry>,
}
#[derive(Debug)]
#[throws(SVGProcessingError)]
fn accumulate(&mut self, g: &mut Instance,
upieces: Vec<(PieceId,PieceUpdateOp<()>)>,
- ulogs: Vec<LogEntry>) {
+ ulogs: Vec<LogEntry>,
+ mut raw: Vec<PreparedUpdateEntry>) {
use UpdateHandler::*;
match self {
Bulk(bulk) => {
};
}
bulk.logs |= ulogs.len() != 0;
+ bulk.raw.append(&mut raw);
},
Online => {
let estimate = upieces.len() + ulogs.len();
buf.piece_update(upiece, uuop, &lens);
}
buf.log_updates(ulogs);
+ buf.raw_updates(raw);
},
}
}
// xxx use cs.desc
}]);
}
+
+ buf.raw_updates(bulk.raw);
},
Online => { },
}
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)?;
const CREATE_PIECES_MAX : u32 = 300;
+type ExecuteGameInsnResults = (
+ Vec<(PieceId,PieceUpdateOp<()>)>,
+ Vec<LogEntry>,
+ Option<Vec<PreparedUpdateEntry>>,
+ MgmtGameResponse,
+);
+
#[throws(ME)]
fn execute_game_insn(cs: &CommandStream,
ig: &mut InstanceGuard, update: MgmtGameInstruction)
- -> (Vec<(PieceId,PieceUpdateOp<()>)>,
- Vec<LogEntry>,
- 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 } => {
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) }
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
)
},
#[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<PlayerId> },
ReportPlayerAccesses { players: Vec<PlayerId> },
SetFixedPlayerAccess { player: PlayerId, token: RawToken },
#[derive(Debug,Serialize,Deserialize)]
pub enum MgmtGameResponse {
Fine,
+ Info(MgmtGameResponseGameInfo),
+
+ Pieces(Vec<MgmtGamePieceInfo>),
+
AddPlayer(PlayerId),
PlayerAccessTokens(Vec<Vec<RawToken>>),
- 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)]
#[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)]
#[derive(Debug,Serialize,Deserialize)]
pub struct GameState {
+ pub table_size : Pos, // xxx send to client etc.
pub pieces : DenseSlotMap<PieceId,PieceState>,
pub players : PlayerMap,
pub gen : Generation,
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(), }
}
// 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,
#[derive(Debug,Serialize,Deserialize)]
pub struct GameSpec {
- pub table : Pos,
+ pub table_size : Option<Pos>,
pub pieces : Vec<PiecesSpec>,
}
piece : VisiblePieceId,
op : PieceUpdateOp<PreparedPieceState>,
},
+ SetTableSize(Pos),
Log (Arc<LogEntry>),
Error (ErrorSignaledViaUpdate),
}
piece : VisiblePieceId,
op : &'u PieceUpdateOp<PreparedPieceState>,
},
+ SetTableSize(Pos),
Log (&'u LogEntry),
Error(ErrorSignaledViaUpdate),
}
Log(logent) => {
logent.html.as_bytes().len() * 3
},
+ SetTableSize(_) |
Error(_) => {
100
},
self.us.push(update);
}
+ pub fn raw_updates(&mut self, mut raw: Vec<PreparedUpdateEntry>) {
+ self.us.append(&mut raw)
+ }
+
pub fn log_updates(&mut self, logents: Vec<LogEntry>) {
for logentry in logents {
let logentry = Arc::new(logentry);
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();
PreparedUpdateEntry::Log(logent) => {
TransmitUpdateEntry::Log(&logent)
},
+ &PreparedUpdateEntry::SetTableSize(size) => {
+ TransmitUpdateEntry::SetTableSize(size)
+ },
&PreparedUpdateEntry::Error(e) => {
TransmitUpdateEntry::Error(e)
}