trait ApiPieceOp : Debug {
#[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
+ p: &dyn Piece,
lens: &dyn Lens /* used for LogEntry and PieceId but not Pos */)
-> (PieceUpdateOp<()>, Vec<LogEntry>);
}
cl.lastseen = Instant::now();
let player = cl.player;
let gs = &mut g.gs;
+ let g_pieces = &g.pieces;
let _ = gs.players.byid(player)?;
let lens = TransparentLens { };
let piece = lens.decode_visible_pieceid(form.piece, player);
use ApiPieceOpError::*;
match (||{
+ let p = g_pieces.get(piece).ok_or(OnlineError::PieceGone)?;
let pc = gs.pieces.byid_mut(piece)
.map_err(|()| OnlineError::PieceGone)?;
if pc.held != None && pc.held != Some(player) {
throw!(OnlineError::PieceHeld)
};
- let (update, logents) = form.op.op(gs,player,piece,&lens)?;
+ let (update, logents) = form.op.op(gs,player,piece,p.as_ref(),&lens)?;
Ok::<_,ApiPieceOpError>((update, logents))
})() {
Err(ReportViaUpdate(poe)) => {
impl ApiPieceOp for ApiPieceGrab {
#[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- lens: &dyn Lens)
+ p: &dyn Piece, lens: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
let pl = gs.players.byid(player).unwrap();
let pc = gs.pieces.byid_mut(piece).unwrap();
let logent = LogEntry {
html : Html(format!("{} grasped {}",
&htmlescape::encode_minimal(&pl.nick),
- pc.describe_html(&lens.log_pri(piece, pc)).0)),
+ p.describe_pri(&lens.log_pri(piece, pc)).0)),
};
(update, vec![logent])
impl ApiPieceOp for ApiPieceUngrab {
#[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
- lens: &dyn Lens)
+ p: &dyn Piece, lens: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
let pl = gs.players.byid(player).unwrap();
let pc = gs.pieces.byid_mut(piece).unwrap();
let logent = LogEntry {
html : Html(format!("{} released {}",
&htmlescape::encode_minimal(&pl.nick),
- pc.describe_html(&lens.log_pri(piece, pc)).0)),
+ p.describe_pri(&lens.log_pri(piece, pc)).0)),
};
(update, vec![logent])
impl ApiPieceOp for ApiPieceRaise {
#[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
- _: &dyn Lens)
+ _p: &dyn Piece, _: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
let pc = gs.pieces.byid_mut(piece).unwrap();
pc.zlevel = ZLevel { z : self.z, zg : gs.gen };
impl ApiPieceOp for ApiPieceMove {
#[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
- _lens: &dyn Lens)
+ _p: &dyn Piece, _lens: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
let pc = gs.pieces.byid_mut(piece).unwrap();
let (pos, clamped) = self.0.clamped(gs.table_size);
scoped_name : name,
};
- let gref = Instance::new(name, gs)?;
+ let gref = Instance::new(name, gs, Default::default())?;
let mut ig = gref.lock()?;
execute_for_game(cs, &mut ig, insns, MgmtGameUpdateMode::Bulk)
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);
- MgmtGamePieceInfo { piece, pos, face, desc_html }
- }).collect();
+ let desc_html = ig.pieces.get(piece)?.describe_html(None);
+ Some(MgmtGamePieceInfo { piece, pos, face, desc_html })
+ }).flatten().collect();
Resp::Pieces(pieces)
}),
}
DeletePiece(piece) => {
+ let p = ig.pieces.remove(piece).ok_or(ME::PieceNotFound)?;
let gs = &mut ig.gs;
- let p = gs.pieces.remove(piece).ok_or(ME::PieceNotFound)?;
- let desc_html = p.p.describe_html(Some(Default::default()));
- p.p.delete_hook(&p, gs);
+ let pc = gs.pieces.remove(piece);
+ let desc_html = p.describe_html(Some(Default::default()));
+ if let Some(pc) = pc { p.delete_hook(&pc, gs); }
(U{ pcs: vec![(piece, PieceUpdateOp::Delete())],
log: vec![ LogEntry {
html: Html(format!("A piece {} was removed from the game",
},
AddPieces(PiecesSpec{ pos,posd,count,face,info }) => {
+ let ig = &mut **ig;
let gs = &mut ig.gs;
let count = count.unwrap_or(1);
if count > CREATE_PIECES_MAX { throw!(LimitExceeded) }
lastclient: Default::default(),
gen_before_lastclient: Generation(0),
gen: gs.gen,
- pos, p, face,
+ pos, face,
};
if let (_, true) = pc.pos.clamped(gs.table_size) {
throw!(SpecError::PosOffTable);
}
let piece = gs.pieces.insert(pc);
+ ig.pieces.insert(piece, p);
updates.push((piece, PieceUpdateOp::Insert(())));
pos[0] += posd[0];
pos[1] += posd[1];
#[derive(Debug,Serialize,Deserialize)]
pub struct PieceState {
pub pos : Pos,
- pub p : Box<dyn Piece>,
pub face : FaceId,
pub held : Option<PlayerId>,
pub zlevel : ZLevel,
impl PieceState {
#[throws(IE)]
- pub fn make_defs(&self, pri : &PieceRenderInstructions) -> Html {
- let pr = self;
+ pub fn prep_piecestate(&self, p: &dyn Piece, pri : &PieceRenderInstructions)
+ -> PreparedPieceState {
+ PreparedPieceState {
+ pos : self.pos,
+ held : self.held,
+ svg : p.make_defs(pri)?,
+ z : self.zlevel.z,
+ zg : self.zlevel.zg,
+ }
+ }
+}
+
+pub trait PieceExt {
+ fn make_defs(&self, pri : &PieceRenderInstructions) -> Result<Html,IE>;
+ fn describe_pri(&self, pri : &PieceRenderInstructions) -> Html;
+}
+
+impl<T> PieceExt for T where T: Piece + ?Sized {
+ #[throws(IE)]
+ fn make_defs(&self, pri : &PieceRenderInstructions) -> Html {
let mut defs = Html(String::new());
- let dragraise = match pr.p.thresh_dragraise(pri)? {
+ let dragraise = match self.thresh_dragraise(pri)? {
Some(n) if n < 0 => throw!(SE::NegativeDragraise),
Some(n) => n,
None => -1,
write!(&mut defs.0,
r##"<g id="piece{}" data-dragraise="{}">"##,
pri.id, dragraise)?;
- pr.p.svg_piece(&mut defs, &pri)?;
+ self.svg_piece(&mut defs, &pri)?;
write!(&mut defs.0, r##"</g>"##)?;
write!(&mut defs.0,
r##"<path id="surround{}" d="{}"/>"##,
- pri.id, pr.p.surround_path(&pri)?.0)?;
- pr.p.svg_x_defs(&mut defs, &pri)?;
+ pri.id, self.surround_path(&pri)?.0)?;
+ self.svg_x_defs(&mut defs, &pri)?;
defs
}
- #[throws(IE)]
- pub fn prep_piecestate(&self, pri : &PieceRenderInstructions)
- -> PreparedPieceState {
- PreparedPieceState {
- pos : self.pos,
- held : self.held,
- svg : self.make_defs(pri)?,
- z : self.zlevel.z,
- zg : self.zlevel.zg,
- }
- }
-
- pub fn describe_html(&self, pri : &PieceRenderInstructions) -> Html {
- self.p.describe_html(Some(pri.face))
+ fn describe_pri(&self, pri : &PieceRenderInstructions) -> Html {
+ self.describe_html(Some(pri.face))
}
}
#[repr(transparent)]
pub struct RawTokenVal(str);
+pub type PiecesLoaded = SecondarySlotMap<PieceId,Box<dyn Piece>>;
+
// ---------- public data structure ----------
#[derive(Debug,Serialize,Deserialize)]
pub struct Instance {
pub name : Arc<InstanceName>,
pub gs : GameState,
+ pub pieces : PiecesLoaded,
pub clients : DenseSlotMap<ClientId,Client>,
pub updates : SecondarySlotMap<PlayerId, PlayerUpdates>,
pub tokens_players : TokenRegistry<PlayerId>,
/// KINDS OF PERSISTENT STATE
///
-/// TokenTable TokenTable GameState GameState
-/// <ClientId> <PlayerId> .players .pieces
+/// TokenTable TokenTable GameState Instance GameState
+/// <ClientId> <PlayerId> .players .pieces .pieces
///
-/// Saved No a-* g-* g-*
-/// Spec TOML Absent table, ish table game
+/// Saved No a-* g-* a-* g-*
+/// Spec TOML Absent table, ish table game game
///
///
/// UPDATE RELIABILITY/PERSISTENCE RULES
}
#[derive(Debug,Default,Serialize,Deserialize)]
-struct InstanceSaveAccesses<RawTokenStr> {
- tokens_players : Vec<(RawTokenStr, PlayerId)>,
+struct InstanceSaveAccesses<RawTokenStr, PiecesLoadedRef> {
+ pieces: PiecesLoadedRef,
+ tokens_players: Vec<(RawTokenStr, PlayerId)>,
}
display_as_debug!{InstanceLockError}
/// Returns `None` if a game with this name already exists
#[allow(clippy::new_ret_no_self)]
#[throws(MgmtError)]
- pub fn new(name: InstanceName, gs: GameState) -> InstanceRef {
+ pub fn new(name: InstanceName, gs: GameState, pieces: PiecesLoaded)
+ -> InstanceRef {
let name = Arc::new(name);
let g = Instance {
name : name.clone(),
- gs,
+ gs, pieces,
clients : Default::default(),
updates : Default::default(),
tokens_players : Default::default(),
#[throws(InternalError)]
fn save_access_now(&mut self) {
self.save_something("a-", |s,w| {
+ let pieces = &s.c.g.pieces;
let tokens_players : Vec<(&str, PlayerId)> = {
let global_players = GLOBAL.players.read().unwrap();
s.c.g.tokens_players.tr
.flatten()
.collect()
};
- let isa = InstanceSaveAccesses { tokens_players };
+ let isa = InstanceSaveAccesses { pieces, tokens_players };
rmp_serde::encode::write_named(w, &isa)
})?;
info!("saved accesses for {:?}", &self.name);
})().context(lockfile).context("lock global save area")?);
}
}
- let gs = {
- let mut gs : GameState = Self::load_something(&name, "g-")?;
- for mut p in gs.pieces.values_mut() {
- p.lastclient = Default::default();
- }
- gs
- };
-
- let mut access_load : InstanceSaveAccesses<String>
- = Self::load_something(&name, "a-")
+ let InstanceSaveAccesses::<String,PiecesLoaded>
+ { mut tokens_players, mut pieces } = Self::load_something(&name, "a-")
.or_else(|e| {
if let InternalError::Anyhow(ae) = &e {
if let Some(ioe) = ae.downcast_ref::<io::Error>() {
}
Err(e)
})?;
+
+ let gs = {
+ let mut gs : GameState = Self::load_something(&name, "g-")?;
+ for mut p in gs.pieces.values_mut() {
+ p.lastclient = Default::default();
+ }
+ gs.pieces.retain(|k,_v| pieces.contains_key(k));
+ pieces.retain(|k,_v| gs.pieces.contains_key(k));
+
+ gs
+ };
+
let mut updates : SecondarySlotMap<_,_> = Default::default();
let pu_bc = PlayerUpdates::new_begin(&gs);
for player in gs.players.keys() {
updates.insert(player, pu_bc.new());
}
let name = Arc::new(name);
- access_load.tokens_players.retain(
+ tokens_players.retain(
|&(_,player)| gs.players.contains_key(player)
);
let g = Instance {
- gs, updates,
+ gs, pieces, updates,
name: name.clone(),
clients : Default::default(),
tokens_clients : Default::default(),
};
let gref = InstanceRef(Arc::new(Mutex::new(cont)));
let mut g = gref.lock().unwrap();
- for (token, _) in &access_load.tokens_players {
+ for (token, _) in &tokens_players {
g.tokens_players.tr.insert(RawToken(token.clone()));
}
let mut global = GLOBAL.players.write().unwrap();
- for (token, player) in access_load.tokens_players.drain(0..) {
+ for (token, player) in tokens_players.drain(0..) {
let iad = InstanceAccessDetails {
gref : gref.clone(),
ident : player,
id : make_pieceid_visible(gpid),
face : pr.face,
};
- let defs = pr.make_defs(&pri)?;
+ let p = if let Some(p) = ig.pieces.get(gpid) { p }
+ else { continue /* was deleted */ };
+ let defs = p.make_defs(&pri)?;
alldefs.push((pri.id, defs));
let for_info = SessionPieceLoadJson {
lens: &dyn Lens) -> PreparedUpdateEntry {
let gs = &mut self.g.gs;
- let (update, piece) = match gs.pieces.byid_mut(piece) {
- Ok(pc) => {
+ let (update, piece) = match (
+ gs.pieces.byid_mut(piece),
+ self.g.pieces.byid(piece),
+ ) {
+ (Ok(pc), Ok(p)) => {
gs.max_z.update_max(pc.zlevel.z);
if self.by_client != pc.lastclient {
let update = update.try_map_new_state(
|_|{
- let mut ns = pc.prep_piecestate(&pri_for_all)?;
+ let mut ns = pc.prep_piecestate(p.as_ref(), &pri_for_all)?;
lens.massage_prep_piecestate(&mut ns);
<Result<_,InternalError>>::Ok(ns)
},
(update, pri_for_all.id)
},
- Err(()) => {
+ _ => {
(PieceUpdateOp::Delete(), lens.pieceid2visible(piece))
}
};