From 97cd3108f6ba7f65ff505c7e891b8ed365cca360 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Thu, 3 Sep 2020 00:48:49 +0100 Subject: [PATCH] much error reorg --- src/api.rs | 74 ++++++++++++++++++++++++++++++++++-------------- src/commands.rs | 7 ++--- src/error.rs | 73 ++++++++++++++++++++++++++++------------------- src/gamestate.rs | 19 +++++++------ src/global.rs | 15 +++++----- src/http.rs | 7 ++--- src/pieces.rs | 33 +++++++++++---------- src/session.rs | 5 ++-- src/spec.rs | 18 +++++++++--- src/updates.rs | 16 ++++++----- 10 files changed, 164 insertions(+), 103 deletions(-) diff --git a/src/api.rs b/src/api.rs index a3224a9c..5ab15b7c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -11,12 +11,19 @@ struct ApiPiece { op : O, } trait ApiPieceOp : Debug { - #[throws(GameError)] + #[throws(ApiPieceOpError)] fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId, lens: &dyn Lens /* used for LogEntry and PieceId but not Pos */) -> (PieceUpdateOp<()>, Vec); } +#[derive(Error,Debug)] +enum ApiPieceOpError { + ReportViaResponse(#[from] OnlineError), + ReportViaUpdate(#[from] PieceOpError), +} +display_as_debug!(ApiPieceOpError); + pub trait Lens : Debug { fn pieceid2visible(&self, piece: PieceId) -> VisiblePieceId; fn log_pri(&self, piece: PieceId, pc: &PieceState) @@ -82,24 +89,15 @@ fn api_piece_op(form : Json>) eprintln!("Q_GEN={:?} U_GEN={:?}", u_gen, q_gen); - if u_gen > q_gen { throw!(GameError::Conflict) } + if u_gen > q_gen { throw!(PieceOpError::Conflict) } if pc.held != None && pc.held != Some(player) { - throw!(GameError::PieceHeld) + throw!(OnlineError::PieceHeld) }; let (update, logents) = form.op.op(gs,player,piece,&lens)?; - Ok((update, logents)) + Ok::<_,ApiPieceOpError>((update, logents)) })() { - Err(err) => { - let err : GameError = err; - if let GameError::InternalErrorSVG(svg) = err { throw!(svg) } - eprintln!("API {:?} => {:?}", &form, &err); - // Restating the state of this piece (with a new generation) - // will forcibly synchronise the client which made the failing - // request. - let mut buf = PrepareUpdatesBuffer::new(g, None, None); - buf.piece_update(piece, PieceUpdateOp::Modify(()), &lens); - throw!(err); - }, + Err(err) => err.report(&mut ig, piece, player, client, &lens)?, + Ok((update, logents)) => { let mut buf = PrepareUpdatesBuffer::new(g, Some((client, form.cseq)), Some(1 + logents.len())); @@ -113,6 +111,38 @@ fn api_piece_op(form : Json>) "" } +impl ApiPieceOpError { + pub fn report(self, ig: &mut InstanceGuard, + piece: PieceId, player: PlayerId, client: ClientId, + lens: &dyn Lens) -> Result<(),OE> { + use ApiPieceOpError::*; + + match self { + ReportViaUpdate(poe) => { + let gen = ig.gs.gen; + ig.updates.get_mut(player) + .ok_or(OE::NoPlayer)? + .push(Arc::new(PreparedUpdate { + gen, + us : vec![ PreparedUpdateEntry::Error( + Some(client), + ErrorSignaledViaUpdate::PieceOpError(piece, poe), + )], + })); + + let mut buf = PrepareUpdatesBuffer::new(ig, None, None); + buf.piece_update(piece, PieceUpdateOp::Modify(()), lens); + }, + + ReportViaResponse(err) => { + eprintln!("API ERROR => {:?}", &err); + Err(err)?; + }, + } + Ok(()) + } +} + #[derive(Debug,Serialize,Deserialize)] struct ApiPieceGrab { } @@ -123,14 +153,14 @@ fn api_grab(form : Json>) api_piece_op(form) } impl ApiPieceOp for ApiPieceGrab { - #[throws(GameError)] + #[throws(ApiPieceOpError)] fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId, lens: &dyn Lens) -> (PieceUpdateOp<()>, Vec) { let pl = gs.players.byid(player).unwrap(); let pc = gs.pieces.byid_mut(piece).unwrap(); - if pc.held.is_some() { throw!(GameError::PieceHeld) } + if pc.held.is_some() { throw!(OnlineError::PieceHeld) } pc.held = Some(player); let update = PieceUpdateOp::Modify(()); @@ -155,14 +185,14 @@ fn api_ungrab(form : Json>) api_piece_op(form) } impl ApiPieceOp for ApiPieceUngrab { - #[throws(GameError)] + #[throws(ApiPieceOpError)] fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId, lens: &dyn Lens) -> (PieceUpdateOp<()>, Vec) { let pl = gs.players.byid(player).unwrap(); let pc = gs.pieces.byid_mut(piece).unwrap(); - if pc.held != Some(player) { throw!(GameError::PieceHeld) } + if pc.held != Some(player) { throw!(OnlineError::PieceHeld) } pc.held = None; let update = PieceUpdateOp::Modify(()); @@ -188,7 +218,7 @@ fn api_raise(form : Json>) api_piece_op(form) } impl ApiPieceOp for ApiPieceRaise { - #[throws(GameError)] + #[throws(ApiPieceOpError)] fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId, _: &dyn Lens) -> (PieceUpdateOp<()>, Vec) { @@ -207,13 +237,13 @@ fn api_move(form : Json>) -> impl response::Responder<'st api_piece_op(form) } impl ApiPieceOp for ApiPieceMove { - #[throws(GameError)] + #[throws(ApiPieceOpError)] fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId, _lens: &dyn Lens) -> (PieceUpdateOp<()>, Vec) { let pc = gs.pieces.byid_mut(piece).unwrap(); if let (_,true) = self.0.clamped(gs.table_size) { - throw!(GameError::PosOffTable); + Err(ApiPieceOpError::ReportViaUpdate(PieceOpError::PosOffTable))?; } pc.pos = self.0; let update = PieceUpdateOp::Move(self.0); diff --git a/src/commands.rs b/src/commands.rs index 1936bb81..2fbc49e0 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -82,9 +82,8 @@ pub enum MgmtError { PlayerNotFound, PieceNotFound, LimitExceeded, - SVGProcessingFailed(#[from] SVGProcessingError), - GameError(#[from] GameError), ServerFailure(String), + BadSpec(#[from] SpecError), } impl Display for MgmtError { #[throws(fmt::Error)] @@ -97,8 +96,8 @@ impl Display for MgmtError { } } -impl From for MgmtError { - fn from(e: ServerFailure) -> MgmtError { +impl From for MgmtError { + fn from(e: InternalError) -> MgmtError { MgmtError::ServerFailure(format!("ServerFailure {}\n", &e)) } } diff --git a/src/error.rs b/src/error.rs index 8aa36586..a860909e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,59 +1,67 @@ use crate::imports::*; -#[derive(Error,Clone,Debug,Serialize,Deserialize)] -#[error("operation error {:?}",self)] -pub enum GameError { - Conflict, - PieceGone, - PieceHeld, - FaceNotFound, - PosOffTable, - InternalErrorSVG(#[from] SVGProcessingError), -} - #[derive(Error,Debug)] pub enum OnlineError { #[error("Game in process of being destroyed")] GameBeingDestroyed, - #[error("Game corrupted by previous crash - consult administrator")] - GameCorrupted, #[error("client session not recognised (terminated by server?)")] NoClient, #[error("player not part of game (removed?)")] NoPlayer, - #[error("game operation error")] - GameError(#[from] GameError), #[error("invalid Z coordinate")] InvalidZCoord, - #[error("JSON~ serialisation error: {0:?}")] - JSONSerializeFailed(#[from] serde_json::error::Error), - #[error("SVG processing/generation error {0:?}")] - SVGProcessingFailed(#[from] SVGProcessingError), - #[error("Server operational problems: {0:?}")] - ServerFailure(#[from] ServerFailure), + #[error("improper piece hold status for op (client should have known)")] + PieceHeld, + #[error("Server operational problems - consult administrator: {0:?}")] + ServerFailure(#[from] InternalError), + #[error("JSON deserialisation error: {0:?}")] + BadJSON(serde_json::Error), } from_instance_lock_error!{OnlineError} #[derive(Error,Debug)] -pub enum ServerFailure { +pub enum InternalError { + #[error("Game corrupted by previous crash")] + GameCorrupted, #[error("Server MessagePack encoding error {0}")] MessagePackEncodeFail(#[from] rmp_serde::encode::Error), #[error("Server MessagePack decoding error (game load failed) {0}")] MessagePackDecodeFail(#[from] rmp_serde::decode::Error), #[error("Server internal logic error {0}")] InternalLogicError(String), + #[error("SVG processing/generation error {0:?}")] + SVGProcessingFailed(#[from] SVGProcessingError), + #[error("String formatting error {0}")] + StringFormatting(#[from] fmt::Error), + #[error("JSON deserialisation error: {0:?}")] + JSONEncode(serde_json::Error), #[error("Server error {0:?}")] Anyhow(#[from] anyhow::Error), } +impl From for SpecError { + fn from(ie: InternalError) -> SpecError { + SpecError::InternalError(format!("{:?}",ie)) + } +} + #[derive(Error,Debug,Serialize,Copy,Clone)] pub enum ErrorSignaledViaUpdate { - RenderingError, + InternalError, PlayerRemoved, + PieceOpError(PieceId, PieceOpError), } display_as_debug!{ErrorSignaledViaUpdate} +#[derive(Error,Debug,Serialize,Copy,Clone)] +pub enum PieceOpError { + Gone, + Conflict, + PosOffTable, +} +display_as_debug!{PieceOpError} + pub type StartupError = anyhow::Error; pub use OnlineError::{NoClient,NoPlayer}; @@ -70,8 +78,8 @@ macro_rules! from_instance_lock_error { fn from(e: InstanceLockError) -> $into { use InstanceLockError::*; match e { - GameCorrupted => $into::GameCorrupted, GameBeingDestroyed => $into::GameBeingDestroyed, + GameCorrupted => InternalError::GameCorrupted.into(), } } } @@ -120,16 +128,17 @@ impl IdForById for T where T : AccessId { } impl IdForById for PieceId { - type Error = GameError; - const ERROR : GameError = GameError::PieceGone; + type Error = PieceOpError; + const ERROR : PieceOpError = PieceOpError::Gone; } #[macro_export] macro_rules! display_as_debug { {$x:ty} => { - impl Display for $x { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - ::fmt(self, f) + impl std::fmt::Display for $x { + #[throws(std::fmt::Error)] + fn fmt(&self, f: &mut std::fmt::Formatter) { + ::fmt(self, f)? } } } @@ -145,3 +154,9 @@ macro_rules! error_from_losedetails { } } pub use crate::error_from_losedetails; + +impl From for SpecError { + fn from(se: SVGProcessingError) -> SpecError { + InternalError::SVGProcessingFailed(se).into() + } +} diff --git a/src/gamestate.rs b/src/gamestate.rs index f860430a..1b3b0cbe 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -70,20 +70,21 @@ pub struct LogEntry { // ---------- piece trait, and rendering ---------- +type IE = InternalError; +type IR = Result<(),IE>; type SE = SVGProcessingError; -type SR = Result<(),SE>; #[typetag::serde] pub trait Piece : Send + Debug { // #[throws] doesn't work here for some reason - fn svg_piece(&self, f: &mut String, pri: &PieceRenderInstructions) -> SR; + fn svg_piece(&self, f: &mut String, pri: &PieceRenderInstructions) -> IR; - #[throws(SE)] + #[throws(IE)] fn surround_path(&self, pri : &PieceRenderInstructions) -> String; - fn svg_x_defs(&self, f: &mut String, pri : &PieceRenderInstructions) -> SR; + fn svg_x_defs(&self, f: &mut String, pri : &PieceRenderInstructions) -> IR; - #[throws(SE)] + #[throws(IE)] fn thresh_dragraise(&self, pri : &PieceRenderInstructions) -> Option; @@ -103,9 +104,9 @@ pub struct PieceRenderInstructions { #[typetag::serde(tag="type")] pub trait PieceSpec : Debug { - fn load(&self) -> Result,SE>; + fn load(&self) -> Result,SpecError>; fn resolve_spec_face(&self, face : Option) - -> Result; + -> Result; } // ========== implementations ========== @@ -172,7 +173,7 @@ impl ClampTable for Pos { // ---------- game state - rendering etc. ---------- impl PieceState { - #[throws(SE)] + #[throws(IE)] pub fn make_defs(&self, pri : &PieceRenderInstructions) -> String { let pr = self; let mut defs = String::new(); @@ -193,7 +194,7 @@ impl PieceState { defs } - #[throws(SE)] + #[throws(IE)] pub fn prep_piecestate(&self, pri : &PieceRenderInstructions) -> PreparedPieceState { PreparedPieceState { diff --git a/src/global.rs b/src/global.rs index 5501b3cc..0123ace6 100644 --- a/src/global.rs +++ b/src/global.rs @@ -252,7 +252,7 @@ impl Instance { .clone() } - #[throws(ServerFailure)] + #[throws(InternalError)] pub fn destroy_game(mut g: InstanceGuard) { let a_savefile = savefilename(&g.name, "a-", ""); @@ -327,7 +327,7 @@ impl InstanceGuard<'_> { // #[throws(ServerFailure)] // https://github.com/withoutboats/fehler/issues/62 pub fn player_remove(&mut self, oldplayer: PlayerId) - -> Result,ServerFailure> { + -> Result,InternalError> { // We have to filter this player out of everything // Then save // Then send updates @@ -408,6 +408,7 @@ impl InstanceGuard<'_> { updates. push(PreparedUpdate { gen: self.c.g.gs.gen, us : vec![ PreparedUpdateEntry::Error( + None, ErrorSignaledViaUpdate::PlayerRemoved )], }); @@ -571,7 +572,7 @@ fn savefilename_parse(leaf: &[u8]) -> SavefilenameParseResult { } impl InstanceGuard<'_> { - #[throws(ServerFailure)] + #[throws(InternalError)] fn save_something( &self, prefix: &str, w: fn(s: &Self, w: &mut BufWriter) @@ -594,7 +595,7 @@ impl InstanceGuard<'_> { eprintln!("saved to {}", &out); } - #[throws(ServerFailure)] + #[throws(InternalError)] pub fn save_game_now(&mut self) { self.save_something("g-", |s,w| { rmp_serde::encode::write_named(w, &s.c.g.gs) @@ -602,7 +603,7 @@ impl InstanceGuard<'_> { self.c.game_dirty = false; } - #[throws(ServerFailure)] + #[throws(InternalError)] fn save_access_now(&mut self) { self.save_something("a-", |s,w| { let tokens_players : Vec<(&str, PlayerId)> = { @@ -620,7 +621,7 @@ impl InstanceGuard<'_> { })?; } - #[throws(ServerFailure)] + #[throws(InternalError)] fn load_something(name: &InstanceName, prefix: &str) -> T { let inp = savefilename(name, prefix, ""); @@ -653,7 +654,7 @@ impl InstanceGuard<'_> { let mut access_load : InstanceSaveAccesses = Self::load_something(&name, "a-") .or_else(|e| { - if let ServerFailure::Anyhow(ae) = &e { + if let InternalError::Anyhow(ae) = &e { if let Some(ioe) = ae.downcast_ref::() { if ioe.kind() == io::ErrorKind::NotFound { return Ok(Default::default()) diff --git a/src/http.rs b/src/http.rs index baaad11f..494bae62 100644 --- a/src/http.rs +++ b/src/http.rs @@ -14,11 +14,10 @@ impl<'r> Responder<'r> for OnlineError { use rocket::http::Status; use OnlineError::*; let status = match self { - GameCorrupted | JSONSerializeFailed(_) | SVGProcessingFailed(_) - | ServerFailure(_) - => Status::InternalServerError, + ServerFailure(_) => Status::InternalServerError, NoClient | NoPlayer | GameBeingDestroyed => Status::NotFound, - InvalidZCoord | OnlineError::GameError(_) => Status::BadRequest, + InvalidZCoord | BadJSON(_) | OnlineError::PieceHeld + => Status::BadRequest, }; let mut resp = Responder::respond_to(msg,req).unwrap(); resp.set_status(status); diff --git a/src/pieces.rs b/src/pieces.rs index e0a41ecd..ea8df9b6 100644 --- a/src/pieces.rs +++ b/src/pieces.rs @@ -22,14 +22,17 @@ pub enum SVGProcessingError { BadNumber, WriteFail, NegativeDragraise, - ImproperSizeSpec, - UnsupportedColourSpec, } display_as_debug!{SVGProcessingError} error_from_losedetails!{SVGProcessingError,WriteFail,fmt::Error} error_from_losedetails!{SVGProcessingError,BadNumber,std::num::ParseFloatError} +impl From for MgmtError { + fn from(se: SVGProcessingError) -> MgmtError { se.into() } +} + +type IE = InternalError; type SE = SVGProcessingError; #[throws(SE)] @@ -83,22 +86,22 @@ eprintln!("rescaled by {}: {} as {}",scale,&input,&out); #[typetag::serde] impl Piece for SimpleShape { - #[throws(SE)] + #[throws(IE)] fn svg_piece(&self, f: &mut String, pri: &PieceRenderInstructions) { write!(f, r##""##, self.colours[pri.face], &self.path)?; } - #[throws(SE)] + #[throws(IE)] fn surround_path(&self, _pri : &PieceRenderInstructions) -> String { self.scaled_path.clone() } - #[throws(SE)] + #[throws(IE)] fn thresh_dragraise(&self, _pri : &PieceRenderInstructions) -> Option { Some(self.approx_dia / 2) } - #[throws(SE)] + #[throws(IE)] fn svg_x_defs(&self, _f: &mut String, _pri : &PieceRenderInstructions) { } fn describe_html(&self, face : Option) -> String { @@ -113,30 +116,30 @@ impl Piece for SimpleShape { impl SimpleShape { fn new_from_path(desc: String, path: String, approx_dia: Coord, faces: &IndexVec) - -> Result,SE> { + -> Result,SpecError> { let scaled_path = svg_rescale_path(&path, SELECT_SCALE)?; let colours = faces .iter() .map(|s| s.try_into()) - .collect::>()?; + .collect::>()?; Ok(Box::new(SimpleShape { scaled_path, desc, approx_dia, path, colours, })) } } -#[throws(GameError)] +#[throws(SpecError)] fn simple_resolve_spec_face(faces: &IndexSlice, face: Option) -> FaceId { let face = face.unwrap_or_default(); - faces.get(face).ok_or(GameError::FaceNotFound)?; + faces.get(face).ok_or(SpecError::FaceNotFound)?; face } #[typetag::serde] impl PieceSpec for piece_specs::Disc { - #[throws(SE)] + #[throws(SpecError)] fn load(&self) -> Box { let unit_path = "M 0 1 a 1 1 0 1 0 0 -2 \ @@ -146,7 +149,7 @@ impl PieceSpec for piece_specs::Disc { SimpleShape::new_from_path("circle".to_owned(), path, self.diam, &self.faces)? } - #[throws(GameError)] + #[throws(SpecError)] fn resolve_spec_face(&self, face: Option) -> FaceId { simple_resolve_spec_face(&self.faces, face)? } @@ -154,19 +157,19 @@ impl PieceSpec for piece_specs::Disc { #[typetag::serde] impl PieceSpec for piece_specs::Square { - #[throws(SE)] + #[throws(SpecError)] fn load(&self) -> Box { let (x, y) = match *self.size.as_slice() { [s,] => (s,s), [x, y] => (x,y), - _ => throw!(SE::ImproperSizeSpec), + _ => throw!(SpecError::ImproperSizeSpec), }; 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)? } - #[throws(GameError)] + #[throws(SpecError)] fn resolve_spec_face(&self, face: Option) -> FaceId { simple_resolve_spec_face(&self.faces, face)? } diff --git a/src/session.rs b/src/session.rs index fdaaa615..a65924c8 100644 --- a/src/session.rs +++ b/src/session.rs @@ -105,7 +105,8 @@ fn session(form : Json) -> Result { let for_piece = SessionPieceContext { id: pri.id, pos : pr.pos, - info : serde_json::to_string(&for_info)?, + info : serde_json::to_string(&for_info) + .map_err(|e| InternalError::JSONEncode(e))?, }; uses.push(for_piece); } @@ -121,7 +122,7 @@ fn session(form : Json) -> Result { nick : pl.nick.clone(), load : serde_json::to_string(&DataLoad { players : load_players, - })?, + }).map_err(|e| InternalError::JSONEncode(e))?, }; eprintln!("SRC {:?}", &src); src diff --git a/src/spec.rs b/src/spec.rs index 178682ce..a2e5e349 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -6,6 +6,8 @@ use index_vec::{define_index_type,IndexVec}; use crate::gamestate::PieceSpec; use std::fmt::Debug; use implementation::PlayerAccessSpec; +use thiserror::Error; +use crate::error::display_as_debug; //---------- common types ---------- @@ -27,6 +29,15 @@ define_index_type! { #[repr(transparent)] pub struct ColourSpec(String); +#[derive(Error,Clone,Serialize,Deserialize,Debug)] +pub enum SpecError { + ImproperSizeSpec, + UnsupportedColourSpec, + FaceNotFound, + InternalError(String), +} +display_as_debug!{SpecError} + //---------- Table TOML file ---------- #[derive(Debug,Serialize,Deserialize)] @@ -91,7 +102,6 @@ mod implementation { use super::*; use crate::imports::*; type Insn = crate::commands::MgmtGameInstruction; - type SE = SVGProcessingError; #[typetag::serde(tag="access")] pub trait PlayerAccessSpec : Debug { @@ -125,8 +135,8 @@ mod implementation { } impl TryFrom<&ColourSpec> for Colour { - type Error = SE; - #[throws(SE)] + type Error = SpecError; + #[throws(SpecError)] fn try_from(spec: &ColourSpec) -> Colour { lazy_static! { static ref RE: Regex = Regex::new(concat!( @@ -138,7 +148,7 @@ mod implementation { } let s = &spec.0; if !RE.is_match(s) { - throw!(SVGProcessingError::UnsupportedColourSpec); + throw!(SpecError::UnsupportedColourSpec); } spec.0.clone() } diff --git a/src/updates.rs b/src/updates.rs index 4b04ef99..5328a01f 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -45,7 +45,7 @@ pub enum PreparedUpdateEntry { }, SetTableSize(Pos), Log (Arc), - Error (ErrorSignaledViaUpdate), + Error (Option /* none: all */, ErrorSignaledViaUpdate), } #[derive(Debug,Serialize)] @@ -132,7 +132,7 @@ impl PreparedUpdateEntry { logent.html.as_bytes().len() * 3 }, SetTableSize(_) | - Error(_) => { + Error(_,_) => { 100 }, } @@ -209,7 +209,7 @@ impl<'r> PrepareUpdatesBuffer<'r> { } } - #[throws(SVGProcessingError)] + #[throws(InternalError)] fn piece_update_fallible(&mut self, piece: PieceId, update: PieceUpdateOp<()>, lens: &dyn Lens) -> PreparedUpdateEntry { @@ -234,13 +234,13 @@ impl<'r> PrepareUpdatesBuffer<'r> { |_|{ let mut ns = pc.prep_piecestate(&pri_for_all)?; lens.massage_prep_piecestate(&mut ns); - >::Ok(ns) + >::Ok(ns) }, )?; (update, pri_for_all.id) }, - Err(GameError::PieceGone) => { + Err(PieceOpError::Gone) => { (PieceUpdateOp::Delete(), lens.pieceid2visible(piece)) } Err(e) => { @@ -265,7 +265,8 @@ impl<'r> PrepareUpdatesBuffer<'r> { .unwrap_or_else(|e| { eprintln!("piece update error! piece={:?} lens={:?} error={:?}", piece, &lens, &e); - PreparedUpdateEntry::Error(ErrorSignaledViaUpdate::RenderingError) + PreparedUpdateEntry::Error(None, + ErrorSignaledViaUpdate::InternalError) }); self.us.push(update); } @@ -323,7 +324,8 @@ impl PreparedUpdate { &PreparedUpdateEntry::SetTableSize(size) => { TransmitUpdateEntry::SetTableSize(size) }, - &PreparedUpdateEntry::Error(e) => { + &PreparedUpdateEntry::Error(c, e) => { + if let Some(c) = c { if c != dest { continue } } TransmitUpdateEntry::Error(e) } }; -- 2.30.2