pub const SVG_SCALE: f64 = 6.;
+pub const MOVEHIST_LENS: &[usize] = &[ 1, 3, 10, 0];
+pub const MOVEHIST_LEN_MAX: usize = 10;
+
+#[test]
+fn movehist_len_max() { assert_eq!(
+ MOVEHIST_LENS.iter().max(),
+ Some(&MOVEHIST_LEN_MAX),
+) }
+
pub fn timestring_abbreviate<'x>(base: &str, this: &'x str)
-> (&'x str, bool)
{
pub nick: String,
pub layout: PresentationLayout,
pub idmap: PerPlayerIdMap,
+ #[serde(default)] pub moveheld: SparseSecondaryMap<PieceId, GMoveHistLast>,
+ #[serde(default)] pub movehist: VecDeque<MoveHistEnt>,
}
#[derive(Debug,Serialize,Deserialize)]
pub moveable: PieceMoveable,
}
+#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
+pub struct MoveHistPosx { // usual variable: posx
+ pub pos: Pos,
+ pub angle: CompassAngle,
+ pub facehint: Option<FaceId>,
+}
+
+#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
+pub struct GMoveHistLast {
+ pub held: PlayerId,
+ pub posx: MoveHistPosx,
+}
+
+#[derive(Debug,Clone,Serialize,Deserialize)]
+pub struct MoveHistEnt {
+ pub held: PlayerId,
+ pub posx: OldNew<MoveHistPosx>,
+}
+
pub type PieceXDataState = Option<Box<dyn PieceXData>>;
#[derive(Debug,Serialize,Deserialize)]
pub use serde::{Deserializer, Serializer};
pub use serde_with::DeserializeFromStr;
pub use serde_with::SerializeDisplay;
-pub use slotmap::{dense::DenseSlotMap, Key as _};
+pub use slotmap::{dense::DenseSlotMap, SparseSecondaryMap, Key as _};
pub use strum::{EnumString, EnumIter, EnumProperty};
pub use strum::{IntoEnumIterator, IntoStaticStr};
pub use thiserror::Error;
pub enum PreparedUpdateEntry {
Piece(PreparedUpdateEntry_Piece),
Image(PreparedUpdateEntry_Image),
+ MoveHistEnt(SecondarySlotMap<PlayerId, MoveHistEnt>),
SetTableSize(Pos),
SetTableColour(Colour),
SetLinks(Arc<LinksTable>),
},
Piece(TransmitUpdateEntry_Piece<'u>),
Image(TransmitUpdateEntry_Image<'u>),
+ MoveHistEnt(&'u MoveHistEnt),
RecordedUnpredictable {
piece: VisiblePieceId,
cseq: ClientSequence,
Image(ims) => {
ims.json_len(player)
}
+ MoveHistEnt(ents) => {
+ match ents.get(player) { None => 0, Some(_) => 100 }
+ }
Log(logent) => {
logent.logent.html.json_len() * 28
}
piece, &e);
PreparedUpdateEntry::Error(ErrorSignaledViaUpdate::InternalError)
});
+
+ // We're track this on behalf of the client, based on the updates
+ // we are sending. That means we don't ahve to worry about
+ // occultation, etc. etc.
+ let movehist_update = if let PUE::Piece(PUE_P { ops,.. }) = &update {
+ let mut pu = SecondarySlotMap::new();
+ for (player, PreparedPieceUpdate { op,.. }) in ops { if_chain! {
+ if let Some(ns) = op.new_state();
+ if let Some(gpl) = wants!( self.g.gs.players.get_mut(player), ?player);
+ if let Some(mut ent) = wants!( gpl.moveheld.entry(piece), ?piece);
+ let &PreparedPieceState { pos, angle, facehint, .. } = ns;
+ let new_posx = MoveHistPosx { pos, angle, facehint };
+
+ then {
+ if let slotmap::sparse_secondary::Entry::Occupied(ref mut oe) = ent {
+ let last = oe.get();
+ if ns.held == Some(last.held) { continue }
+
+ // Generate an update
+ let histent = MoveHistEnt {
+ held: last.held,
+ posx: OldNew::from([last.posx, new_posx]),
+ };
+ gpl.movehist.reserve(MOVEHIST_LEN_MAX);
+ if gpl.movehist.len() == MOVEHIST_LEN_MAX {
+ gpl.movehist.pop_front();
+ }
+ gpl.movehist.push_back(histent.clone());
+ pu.insert(player, histent);
+ }
+
+ if let Some(held) = ns.held {
+ ent.insert(GMoveHistLast { held: held, posx: new_posx });
+ } else {
+ ent.remove();
+ }
+ }
+ } }
+
+ if pu.is_empty() {
+ None
+ } else {
+ Some(PUE::MoveHistEnt(pu))
+ }
+ } else {
+ None
+ };
self.us.push(update);
+ self.us.extend(movehist_update);
}
#[throws(InternalError)]
_ => continue,
}
}
+ PUE::MoveHistEnt(ents) => {
+ match ents.get(player) {
+ Some(mhe) => TUE::MoveHistEnt(mhe),
+ _ => continue,
+ }
+ }
PUE::Log(logent) => {
TUE::Log((&tz, &logent))
}