From: Ian Jackson Date: Sun, 26 Jul 2020 21:25:49 +0000 (+0100) Subject: addpieces command X-Git-Tag: otter-0.2.0~1240 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=891c091e2992c0342444a33e64459dd02a264aca;p=otter.git addpieces command --- diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index 1159d013..e38f7fc1 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -207,6 +207,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { players : Default::default(), log : Default::default(), gen : Generation(0), + max_z: ZCoord(0.), }; let name = InstanceName { @@ -226,18 +227,8 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse { Fine { } }, - +// let game = cs.lookup_game(&game)?; - /* - AddPiece(game, { pos,count,name,info }) => { - let game = cs.lookup_game(&game)?; - let count = spec.count.unwrap_or(1); - let pos = spec.ok_or(XXU("missing piece pos"))?; - let _xxx_name = spec.name; - let pc = info.load()?; - - } - }, // xxx*/ } } @@ -343,12 +334,47 @@ fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard, response } +const XXX_START_POS : Pos = [20,20]; +const XXX_DEFAULT_POSD : Pos = [5,5]; + +const CREATE_PIECES_MAX : u32 = 300; + #[throws(ME)] -fn execute_game_insn(_gs: &mut GameState, update: MgmtGameInstruction) +fn execute_game_insn(gs: &mut GameState, update: MgmtGameInstruction) -> (Vec<(PieceId,PieceUpdateOp<()>)>, Vec) { use MgmtGameInstruction::*; match update { Noop { } => (vec![], vec![]), + + AddPiece(PiecesSpec{ pos,posd,count,face,info }) => { + let count = count.unwrap_or(1); + if count > CREATE_PIECES_MAX { throw!(LimitExceeded) } + let posd = posd.unwrap_or(XXX_DEFAULT_POSD); + let face = info.resolve_spec_face(face)?; + + let mut updates = Vec::with_capacity(count as usize); + let mut pos = pos.unwrap_or(XXX_START_POS); + for _ in 0..count { + let p = info.load()?; + let z = ZCoord(gs.max_z.0 + (count + 1) as f64); + let pc = PieceState { + held: None, + zlevel: ZLevel { z, zg: gs.gen }, + lastclient: Default::default(), + gen_before_lastclient: Generation(0), + gen: gs.gen, + pos, p, face, + }; + let piece = gs.pieces.insert(pc); + updates.push((piece, PieceUpdateOp::Insert(()))); + pos[0] += posd[0]; + pos[1] += posd[1]; + } + + (updates, vec![ LogEntry { + html: format!("The facilitaror added {} pieces", count), + }]) + }, } } diff --git a/src/commands.rs b/src/commands.rs index 3a411005..a308c2fa 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -11,7 +11,7 @@ pub enum MgmtCommand { #[derive(Debug,Serialize,Deserialize)] pub enum MgmtGameInstruction { Noop { }, - // AddPiece(Box), + AddPiece(PiecesSpec), } #[derive(Debug,Serialize,Deserialize)] @@ -35,7 +35,9 @@ pub enum MgmtError { AlreadyExists, GameBeingDestroyed, GameCorrupted, + LimitExceeded, SVGProcessingFailed(#[from] SVGProcessingError), + GameError(#[from] GameError), } display_as_debug!{MgmtError} diff --git a/src/error.rs b/src/error.rs index e62837df..5d2a71b9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,13 @@ use crate::imports::*; -#[derive(Error,Debug)] +#[derive(Error,Debug,Serialize,Deserialize)] #[error("operation error {:?}",self)] pub enum GameError { Conflict, PieceGone, PieceHeld, + FaceNotFound, InternalErrorSVG(#[from] SVGProcessingError), } diff --git a/src/gamestate.rs b/src/gamestate.rs index ce6633e3..7e7459c4 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -10,6 +10,7 @@ slotmap::new_key_type!{ } define_index_type! { + #[derive(Default)] pub struct FaceId = u8; } @@ -24,7 +25,7 @@ visible_slotmap_key!{ VisiblePieceId('.') } #[derive(Serialize,Deserialize)] #[serde(into="f64")] #[serde(try_from="f64")] -pub struct ZCoord(f64); +pub struct ZCoord(pub f64); // ---------- general data types ---------- @@ -42,6 +43,7 @@ pub struct GameState { pub players : DenseSlotMap, pub gen : Generation, pub log : Vec<(Generation,Arc)>, + pub max_z : ZCoord, } #[derive(Debug,Serialize,Deserialize)] @@ -96,7 +98,9 @@ pub struct PieceRenderInstructions { #[typetag::serde(tag="type")] pub trait PieceSpec : Debug { - fn load(self) -> Result,SE>; + fn load(&self) -> Result,SE>; + fn resolve_spec_face(&self, face : Option) + -> Result; } // ========== implementations ========== @@ -202,5 +206,6 @@ pub fn xxx_gamestate_init() -> GameState { pieces.insert(pr); } GameState { pieces, gen, players : Default::default(), + max_z: ZCoord(0.), log : Default::default(), } } diff --git a/src/global.rs b/src/global.rs index 9bfdced9..0956a56f 100644 --- a/src/global.rs +++ b/src/global.rs @@ -11,7 +11,7 @@ const SAVE_DIRECTORY : &str = "save"; visible_slotmap_key!{ ClientId('C') } -#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Deserialize)] +#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Serialize,Deserialize)] #[serde(transparent)] pub struct RawToken (pub String); diff --git a/src/imports.rs b/src/imports.rs index 4013313b..4d6f239f 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -51,7 +51,7 @@ pub use rand::distributions::Alphanumeric; pub use slotmap::dense::DenseSlotMap; pub type SecondarySlotMap = slotmap::secondary::SecondaryMap; -pub use index_vec::{define_index_type,index_vec,IndexVec}; +pub use index_vec::{define_index_type,index_vec,IndexVec,IndexSlice}; pub use vecdeque_stableix::StableIndexVecDeque; @@ -65,7 +65,9 @@ pub use crate::error::*; pub use crate::commands::*; pub use crate::slotmap_slot_idx::*; pub use crate::cmdlistener::*; +pub use crate::spec::*; pub use crate::api::{Lens,TransparentLens}; +pub use crate::utils::OrdExt; pub use libc::uid_t; diff --git a/src/lib.rs b/src/lib.rs index 728c7888..5fc4ccad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,5 @@ pub mod api; pub mod spec; pub mod cmdlistener; pub mod commands; +pub mod utils; #[path="slotmap-slot-idx.rs"] pub mod slotmap_slot_idx; diff --git a/src/pieces.rs b/src/pieces.rs index b0ddddd4..de532ef3 100644 --- a/src/pieces.rs +++ b/src/pieces.rs @@ -110,11 +110,12 @@ impl Piece for SimpleShape { impl SimpleShape { fn new_from_path(desc: String, path: String, approx_dia: Coord, - mut faces: Vec) -> Result,SE> { + faces: &IndexVec) + -> Result,SE> { let scaled_path = svg_rescale_path(&path, SELECT_SCALE)?; let colours = faces - .iter_mut() - .map(|s| mem::take(s).try_into()) + .iter() + .map(|s| s.try_into()) .collect::>()?; Ok(Box::new(SimpleShape { scaled_path, desc, approx_dia, path, colours, @@ -127,45 +128,58 @@ impl SimpleShape { #[repr(transparent)] struct ColourSpec(String); -impl TryFrom for Colour { +impl TryFrom<&ColourSpec> for Colour { type Error = SE; #[throws(SE)] - fn try_from(spec: ColourSpec) -> Colour { + fn try_from(spec: &ColourSpec) -> Colour { // xxx check syntax - spec.0 + spec.0.clone() } } #[derive(Debug,Serialize,Deserialize)] struct Disc { diam : Coord, - faces : Vec, + faces : IndexVec, } #[derive(Debug,Serialize,Deserialize)] struct Square { size : Vec, - faces : Vec, + faces : IndexVec, +} + +#[throws(GameError)] +fn simple_resolve_spec_face(faces: &IndexSlice, + face: Option) + -> FaceId { + let face = face.unwrap_or_default(); + faces.get(face).ok_or(GameError::FaceNotFound)?; + face } #[typetag::serde] impl PieceSpec for Disc { #[throws(SE)] - fn load(self) -> Box { + fn load(&self) -> Box { let unit_path = "M 0 1 a 1 1 0 1 0 0 -2 \ a 1 1 0 1 0 0 2 z"; let scale = (self.diam as f64) * 0.5; let path = svg_rescale_path(&unit_path, scale)?; SimpleShape::new_from_path("circle".to_owned(), path, self.diam, - self.faces)? + &self.faces)? + } + #[throws(GameError)] + fn resolve_spec_face(&self, face: Option) -> FaceId { + simple_resolve_spec_face(&self.faces, face)? } } #[typetag::serde] impl PieceSpec for Square { #[throws(SE)] - fn load(self) -> Box { + fn load(&self) -> Box { let (x, y) = match self.size.as_slice() { &[s,] => (s,s), &[x, y] => (x,y), @@ -173,7 +187,12 @@ impl PieceSpec for Square { }; let path = format!("M {} {} h {} v {} h {} z", -(x as f64)*0.5, -(y as f64)*0.5, x, y, -x); - SimpleShape::new_from_path("square".to_owned(), path, (x+y+1)/2, self.faces)? + SimpleShape::new_from_path("square".to_owned(), path, (x+y+1)/2, + &self.faces)? + } + #[throws(GameError)] + fn resolve_spec_face(&self, face: Option) -> FaceId { + simple_resolve_spec_face(&self.faces, face)? } } @@ -182,12 +201,12 @@ pub fn xxx_make_pieces() -> Result)>,SE> { ([ 90, 80 ], Disc { diam : 20, - faces : vec![ ColourSpec("red".to_string()), ColourSpec("grey".to_string()) ], + faces : index_vec![ ColourSpec("red".to_string()), ColourSpec("grey".to_string()) ], }.load()?), ([ 90, 60 ], Square { size : vec![20], - faces : vec![ ColourSpec("blue".to_string()), ColourSpec("grey".to_string()) ], + faces : index_vec![ ColourSpec("blue".to_string()), ColourSpec("grey".to_string()) ], }.load()?), ]) } diff --git a/src/spec.rs b/src/spec.rs index aba45f45..fb284e98 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -3,37 +3,38 @@ use crate::imports::*; -#[derive(Debug,Deserialize)] -struct GameSpec { - table : Pos, - players : Vec, - pieces : Vec, +#[derive(Debug,Serialize,Deserialize)] +pub struct GameSpec { + pub table : Pos, + pub players : Vec, + pub pieces : Vec, } -#[derive(Debug,Deserialize)] -struct PlayerSpec { - nick: String, +#[derive(Debug,Serialize,Deserialize)] +pub struct PlayerSpec { + pub nick: String, #[serde(flatten)] - access: Box, + pub access: Box, } -#[derive(Debug,Deserialize)] -struct PiecesSpec { - pos : Option, - count : Option, - name : Option, +#[derive(Debug,Serialize,Deserialize)] +pub struct PiecesSpec { + pub pos : Option, + pub posd : Option, + pub count : Option, + pub face : Option, #[serde(flatten)] - info : Box, + pub info : Box, } -#[typetag::deserialize(tag="access")] -trait PlayerAccessSpec : Debug { +#[typetag::serde(tag="access")] +pub trait PlayerAccessSpec : Debug { #[throws(OE)] fn make_token(&self) -> RawToken { RawToken::new_random()? } fn deliver_token(&mut self) -> Result<(),OE>; } -#[typetag::deserialize] +#[typetag::serde] impl PlayerAccessSpec for RawToken { #[throws(OE)] fn make_token(&self) -> RawToken { self.clone() } diff --git a/src/sse.rs b/src/sse.rs index 6e4442de..f4d0b281 100644 --- a/src/sse.rs +++ b/src/sse.rs @@ -57,6 +57,7 @@ impl Read for UpdateReader { if next_len > buf.len() { break } let tu = next.for_transmit(self.client); + // xxx handle overflow by allocating write!(buf, "data: ")?; serde_json::to_writer(&mut buf, &tu)?; write!(buf, "\n\ diff --git a/src/updates.rs b/src/updates.rs index b7ba42d3..c157b550 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -184,8 +184,12 @@ impl<'r> PrepareUpdatesBuffer<'r> { lens: &dyn Lens) { let gs = &mut self.g.gs; + // xxx check pos is within range, everywhere + let (update, piece) = match gs.pieces.byid_mut(piece) { Ok(pc) => { + gs.max_z.update_max(pc.zlevel.z); + if self.by_client != pc.lastclient { pc.gen_before_lastclient = pc.gen; pc.lastclient = self.by_client; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..55117cb8 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,8 @@ + +pub trait OrdExt : Ord + Sized { + fn update_max(&mut self, new: Self) { + if new > *self { *self = new } + } +} +impl OrdExt for T where T : Ord + Sized { +}