chiark / gitweb /
Break out prepare_set_player_update
[otter.git] / src / global.rs
1 #![allow(clippy::let_and_return)]
2
3 use crate::prelude::*;
4
5 use slotmap::dense as sm;
6
7 // ---------- newtypes and type aliases ----------
8
9 visible_slotmap_key!{ ClientId(b'C') }
10
11 const MAX_CLIENT_INACTIVITY: Duration = Duration::from_secs(200);
12
13 const GAME_SAVE_LAG: Duration = Duration::from_millis(500);
14
15 const MAX_LOG_AGE: Duration = Duration::from_secs(10 * 86400);
16
17 #[derive(Hash,Ord,PartialOrd,Eq,PartialEq,Serialize)]
18 #[repr(transparent)]
19 pub struct RawTokenVal(str);
20
21 // ---------- public data structure ----------
22
23 #[derive(Clone,Debug,Hash,Eq,PartialEq,Ord,PartialOrd)]
24 #[derive(Serialize,Deserialize)]
25 pub struct InstanceName {
26   pub account: AccountName,
27   pub game: String,
28 }
29
30 #[derive(Debug,Clone)]
31 pub struct InstanceRef(Arc<InstanceOuter>);
32
33 #[derive(Debug,Clone)]
34 pub struct InstanceWeakRef(std::sync::Weak<InstanceOuter>);
35
36 #[derive(Debug)]
37 pub struct InstanceOuter {
38   c: Mutex<InstanceContainer>,
39   b: Mutex<InstanceBundles>,
40 }
41
42 #[derive(Debug,Clone,Serialize,Deserialize,Default)]
43 #[derive(Deref,DerefMut)]
44 #[serde(transparent)]
45 pub struct LinksTable(pub EnumMap<LinkKind, Option<String>>);
46
47 pub struct Instance {
48   pub name: Arc<InstanceName>,
49   pub gs: GameState,
50   pub ipieces: IPieces,
51   pub pcaliases: PieceAliases,
52   pub ioccults: IOccults,
53   pub clients: DenseSlotMap<ClientId, Client>,
54   pub iplayers: SecondarySlotMap<PlayerId, PlayerRecord>,
55   pub tokens_players: TokenRegistry<PlayerId>,
56   pub tokens_clients: TokenRegistry<ClientId>,
57   pub acl: LoadedAcl<TablePermission>,
58   pub links: Arc<LinksTable>,
59   pub bundle_list: MgmtBundleList, // copy for easy access
60   pub bundle_specs: bundles::SpecsInBundles,
61   pub bundle_hashes: bundles::HashCache,
62   pub asset_url_key: AssetUrlKey,
63   pub local_libs: shapelib::Registry,
64   pub ifastsplits: IFastSplits,
65 }
66
67 pub struct PlayerRecord {
68   pub u: PlayerUpdates,
69   pub ipl: IPlayer,
70   pub account: Arc<AccountName>,
71 }
72
73 #[derive(Debug,Clone,Serialize,Deserialize)]
74 pub struct IPlayer { // usual variable: ipl
75   pub acctid: AccountId,
76   pub tokens_revealed: HashMap<TokenRevelationKey, TokenRevelationValue>,
77   pub tz: Timezone,
78 }
79
80 /// Strange ownership and serialisation rules, like `OccultIlkOwningId`
81 #[derive(Debug,Serialize,Deserialize)]
82 #[derive(Deref)]
83 pub struct IPiece {
84   #[deref] pub p: IPieceTraitObj,
85   pub occilk: Option<IOccultIlk>,
86   #[serde(default)] pub special: PieceSpecialProperties,
87 }
88
89 #[derive(Debug,Serialize,Deserialize)]
90 #[serde(transparent)]
91 pub struct IPieces(ActualIPieces);
92 pub type ActualIPieces = SecondarySlotMap<PieceId, IPiece>;
93
94 /// Proof token that it is OK to modify the array
95 ///
96 /// This exists to prevent bugs where we forget to save aux
97 #[derive(Copy,Clone,Debug)]
98 pub struct ModifyingPieces(());
99
100 #[derive(Debug,Serialize,Deserialize,Default)]
101 pub struct IOccults {
102   pub ilks: OccultIlks,
103 }
104
105 #[derive(Debug,Serialize,Deserialize,Default)]
106 #[derive(Deref)] // No DerefMut to make sure we send updates, save, etc.
107 #[serde(transparent)]
108 pub struct GPieces(pub(in crate::global) ActualGPieces);
109 type ActualGPieces = DenseSlotMap<PieceId, GPiece>;
110
111 #[derive(Debug)]
112 pub struct Client {
113   pub player: PlayerId,
114   pub lastseen: Instant,
115 }
116
117 pub type BundlesGuard<'b> = MutexGuard<'b, InstanceBundles>;
118
119 // KINDS OF PERSISTENT STATE
120 //
121 //               TokenTable   TokenTable    GameState    Instance GameState
122 //                <ClientId>   <PlayerId>    .players    .pieces  .pieces
123 //
124 //   Saved        No           a-*           g-*         a-*      g-*
125 //   Spec TOML    Absent       table, ish    table       game     game
126 //
127 //
128 // UPDATE RELIABILITY/PERSISTENCE RULES
129 //
130 // From the caller's point of view
131 //
132 // We offer atomic creation/modification/destruction of:
133 //
134 //    * Games (roughtly, a map from InstanceName to GameState;
135 //             includes any player)
136 //
137 //    * Player access tokens, for an existing (game, player)
138 //
139 //    * Clients (for an existing (game, player)
140 //
141 // We also offer atomic destruction of:
142 //
143 //    * Games
144 //
145 //    * Players
146 //
147 // All of the above, except clients, are persistent, in the sense
148 // that a server restart will preserve them.  See above.
149 //
150 // The general code gets mutable access to the GameState.  We offer
151 // post-hoc saving of a modified game.  This should not be used for
152 // player changes.  For other changes, if the save fails, a server
153 // restart may be a rewind.  This relaxation of the rules is
154 // necessary avoid complicated three-phase commit on GameState update
155 // for routine game events.
156 //
157 // IMPLEMENTATION
158 //
159 // Games are created in this order:
160 //
161 //  save/a-<nameforfs>    MessagePack of InstanceSaveAuxiliary
162 //     if, on reload, this does not exist, the game is considered
163 //     not to exist, so at this stage the game conclusively does
164 //     not exist
165 //
166 //  save/g-<nameforfs>    MessagePack of GameState
167 //
168 //  games
169 //     can always be done right after g-<nameforfs>
170 //     since games update is infallible (barring unexpected panic)
171 //
172 // For the access elements such as player tokens and client
173 // tokents, we
174 //   1. check constraints against existing state
175 //   2. save to disk
176 //   3. modify in memory (infallibly)
177 //
178 // A consequence is that game creation or deletion means running
179 // much of this code (including game save/load) with a write lock
180 // onto `games`.
181 //
182 // The Instance object is inside Arc<Mutex>.  Because of Arc the Mutex
183 // may persist beyond the lifetime of the actual game.  So within the
184 // Mutex we have a field `live' that tells us if the thing is dead.
185 // This allows a lockholder to infallibly declare the thing dead,
186 // atomically from the pov of anyone else who has got a reference
187 // to it.  We prevent the caller from ever seeing an Instance whosae
188 // `live` is `false`.
189 #[derive(Debug)]
190 pub struct InstanceGuard<'g> {
191   pub c: MutexGuard<'g, InstanceContainer>,
192   pub gref: InstanceRef,
193 }
194
195 #[derive(Debug,Default)]
196 pub struct TokenRegistry<Id: AccessId> {
197   tr: HashSet<RawToken>,
198   id: PhantomData<Id>,
199 }
200
201 #[derive(Clone,Debug)]
202 pub struct InstanceAccessDetails<Id> {
203   pub gref: InstanceRef,
204   pub ident: Id,
205   pub acctid: AccountId,
206 }
207
208 // ========== internal data structures ==========
209
210 lazy_static! {
211   pub static ref GLOBAL: Global = default();
212 }
213
214 #[derive(Default)]
215 pub struct Global {
216   // lock hierarchy: all of these are in order of acquisition
217   // (in order of lock acquisition (L first), so also in order of criticality
218   // (perf impact); outermost first, innermost last)
219
220   // <- accounts::accounts ->
221   games_table: RwLock<GamesTable>,
222
223   // per-game lock:
224   // <- InstanceContainer ->
225   // inner locks which the game needs:
226   dirty: Mutex<VecDeque<InstanceRef>>,
227
228   // fast global lookups
229   players: RwLock<TokenTable<PlayerId>>,
230   clients: RwLock<TokenTable<ClientId>>,
231 }
232
233 pub type GamesGuard = RwLockWriteGuard<'static, GamesTable>;
234 pub type GamesTable = HashMap<Arc<InstanceName>, InstanceRef>;
235
236 #[derive(Debug)]
237 pub struct InstanceContainer {
238   live: bool,
239   game_dirty: bool,
240   aux_dirty: bool,
241   g: Instance,
242 }
243
244 #[derive(Debug,Default,Serialize,Deserialize)]
245 struct InstanceSaveAuxiliary<RawTokenStr, PiecesLoadedRef, OccultIlksRef,
246                              PieceAliasesRef, IFastSplitsRef> {
247   ipieces: PiecesLoadedRef,
248   ioccults: OccultIlksRef,
249   pcaliases: PieceAliasesRef,
250   tokens_players: Vec<(RawTokenStr, PlayerId)>,
251   aplayers: SecondarySlotMap<PlayerId, IPlayer>,
252   acl: Acl<TablePermission>,
253   pub links: Arc<LinksTable>,
254   asset_url_key: AssetUrlKey,
255   #[serde(default)] pub bundle_hashes: bundles::HashCache,
256   #[serde(default)] ifastsplits: IFastSplitsRef,
257 }
258
259 pub struct PrivateCaller(());
260 // outsiders cannot construct this
261 // workaround for inability to have private trait methods
262 const PRIVATE_Y: PrivateCaller = PrivateCaller(());
263
264 // ========== implementations ==========
265
266 impl<'s> From<&'s str> for &'s RawTokenVal {
267   // str is [u8] with a funny hat on, so &str is pointer + byte count.
268   // nomicon says &SomeStruct([T]) is pointer plus number of elements.
269   // So &str and &SomeStruct(str) have the same layout
270   fn from(s: &str) -> &RawTokenVal { unsafe { mem::transmute(s) } }
271 }
272
273 impl Borrow<RawTokenVal> for RawToken {
274   fn borrow(&self) -> &RawTokenVal { (&*self.0).into() }
275 }
276
277 impl Debug for RawTokenVal {
278   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279     crate::spec::imp::raw_token_debug_as_str(&self.0, f)
280   }
281 }
282
283 impl Debug for Instance {
284   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285     write!(f, "Instance {{ name: {:?}, .. }}", &self.name)
286   }
287 }
288
289 impl ModifyingPieces {
290   pub fn allow_without_necessarily_saving() -> ModifyingPieces {
291     ModifyingPieces(())
292   }
293 }
294
295 // ---------- Main API for instance lifecycle ----------
296
297 impl InstanceRef {
298   #[throws(GameBeingDestroyed)]
299   pub fn lock(&self) -> InstanceGuard<'_> {
300     let c = self.0.c.lock();
301     if !c.live { throw!(GameBeingDestroyed) }
302     InstanceGuard { c, gref: self.clone() }
303   }
304
305   pub fn lock_even_destroying(&self) -> MutexGuard<InstanceContainer> {
306     self.0.c.lock()
307   }
308
309   pub fn downgrade_to_weak(&self) -> InstanceWeakRef {
310     InstanceWeakRef(Arc::downgrade(&self.0))
311   }
312
313   pub fn lock_bundles(&self) -> MutexGuard<'_, InstanceBundles> {
314     self.0.b.lock()
315   }
316 }
317
318 impl InstanceWeakRef {
319   pub fn upgrade(&self) -> Option<InstanceRef> {
320     Some(InstanceRef(self.0.upgrade()?))
321   }
322 }
323
324 #[ext(pub)]
325 impl<A> Unauthorised<InstanceRef, A> {
326   #[throws(GameBeingDestroyed)]
327   fn lock<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
328     let must_not_escape = self.by_ref(Authorisation::promise_any());
329     Unauthorised::of(must_not_escape.lock()?)
330   }
331
332   fn lock_even_destroying<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
333     let must_not_escape = self.by_ref(Authorisation::promise_any());
334     Unauthorised::of(InstanceGuard {
335       c: must_not_escape.lock_even_destroying(),
336       gref: must_not_escape.clone(),
337     })
338   }
339
340   fn lock_bundles<'r>(&'r self) -> Unauthorised<BundlesGuard<'_>, A> {
341     let must_not_escape = self.by_ref(Authorisation::promise_any());
342     Unauthorised::of(must_not_escape.lock_bundles())
343   }
344 }
345
346 impl Instance {
347   /// Returns `None` if a game with this name already exists
348   #[allow(clippy::new_ret_no_self)]
349   #[throws(MgmtError)]
350   pub fn new(name: InstanceName, gs: GameState,
351              games: &mut GamesGuard,
352              acl: LoadedAcl<TablePermission>, _: Authorisation<InstanceName>)
353              -> InstanceRef {
354     let name = Arc::new(name);
355
356     let g = Instance {
357       name: name.clone(),
358       gs, acl,
359       ipieces: IPieces(default()),
360       pcaliases: default(),
361       ioccults: default(),
362       clients: default(),
363       iplayers: default(),
364       tokens_players: default(),
365       tokens_clients: default(),
366       links: default(),
367       bundle_list: default(),
368       bundle_specs: default(),
369       bundle_hashes: default(),
370       asset_url_key: AssetUrlKey::new_random()?,
371       local_libs: default(),
372       ifastsplits: default(),
373     };
374
375     let c = InstanceContainer {
376       live: true,
377       game_dirty: false,
378       aux_dirty: false,
379       g,
380     };
381
382     let c = Mutex::new(c);
383     let b = Mutex::new(InstanceBundles::new());
384     let gref = InstanceRef(Arc::new(InstanceOuter { c, b }));
385     let mut ig = gref.lock()?;
386
387     let entry = games.entry(name);
388
389     use hash_map::Entry::*;
390     let entry = match entry {
391       Vacant(ve) => ve,
392       Occupied(_) => throw!(ME::AlreadyExists),
393     };
394
395     ig.save_aux_now()?;
396     ig.save_game_now()?;
397
398     (||{
399       entry.insert(gref.clone());
400     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
401
402     ig.gref
403   }
404
405   #[throws(MgmtError)]
406   pub fn lookup_by_name_locked_unauth
407     (games_table: &GamesTable, name: &InstanceName)
408      -> Unauthorised<InstanceRef, InstanceName>
409   {
410     Unauthorised::of(
411       games_table
412         .get(name)
413         .ok_or(ME::GameNotFound)?
414         .clone()
415     )
416   }
417
418   #[throws(MgmtError)]
419   pub fn lookup_by_name_locked(games: &GamesTable,
420                                name: &InstanceName,
421                                auth: Authorisation<InstanceName>)
422                                -> InstanceRef {
423     Self::lookup_by_name_locked_unauth(games, name)?.by(auth)
424   }
425
426   #[throws(MgmtError)]
427   pub fn lookup_by_name_unauth(name: &InstanceName)
428       -> Unauthorised<InstanceRef, InstanceName>
429   {
430     let games = GLOBAL.games_table.read();
431     Self::lookup_by_name_locked_unauth(&games, name)?
432   }
433
434   #[throws(MgmtError)]
435   pub fn lookup_by_name(name: &InstanceName, auth: Authorisation<InstanceName>)
436                         -> InstanceRef {
437     Self::lookup_by_name_unauth(name)?.by(auth)
438   }
439
440   #[throws(InternalError)]
441   pub fn destroy_game(games: &mut GamesGuard,
442                       mut g: MutexGuard<InstanceContainer>,
443                       _: Authorisation<InstanceName>) {
444     let a_savefile = savefilename(&g.g.name, "a-", "");
445     let b_dir = bundles::b_dir(&g.g.name);
446
447     let g_file = savefilename(&g.g.name, "g-", "");
448     fs::remove_file(&g_file).context("remove").context(g_file)?;
449
450     (||{ // Infallible:
451       g.live = false;
452       games.remove(&g.g.name);
453       InstanceGuard::forget_all_tokens(&mut g.g.tokens_clients);
454       InstanceGuard::forget_all_tokens(&mut g.g.tokens_players);
455
456       InstanceBundles::truncate_all_besteffort(&g.g.name);
457
458       fn best_effort<F>(rm: F, path: &str, desc: &str)
459       where F: FnOnce(&str) -> Result<(), io::Error>
460       {
461         rm(path)
462           .unwrap_or_else(
463             |e| warn!("failed to delete stale {} {:?}: {:?}",
464                       desc, path, e)
465             // apart from that, ignore the error
466             // load_games will clean it up later.
467           );
468       }
469       best_effort(|f| fs::remove_file(f), &a_savefile, "auth file");
470       best_effort(|f| fs::remove_dir_all(f), &b_dir, "bundles dir");
471
472     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
473   }
474
475   pub fn list_names(account: Option<&AccountName>,
476                     _: Authorisation<AccountName>)
477                     -> Vec<Arc<InstanceName>> {
478     let games = GLOBAL.games_table.read();
479     let out: Vec<Arc<InstanceName>> =
480       games.keys()
481       .filter(|k| account == None || account == Some(&k.account))
482       .cloned()
483       .collect();
484     out
485   }
486
487   #[throws(InternalError)]
488   pub fn player_info_pane(&self) -> Html {
489     #[derive(Serialize,Debug)]
490     struct RenderContext<'r> {
491       players: Vec<RenderPlayer<'r>>,
492     }
493     #[derive(Serialize,Debug)]
494     struct RenderPlayer<'r> {
495       player_num: u32,
496       nick: &'r str,
497       account: &'r AccountName,
498     }
499     let players = self.gs.players.iter().filter_map(|(player, gpl)| {
500       let ipl = self.iplayers.get(player)?;
501       let (idx, _) = player.data().get_idx_version();
502       Some(RenderPlayer {
503         player_num: idx,
504         nick: &gpl.nick,
505         account: &ipl.account,
506       })
507     }).collect::<Vec<_>>();
508     let render = RenderContext { players };
509     let html = Html::from_html_string(
510       nwtemplates::render("player-info-pane.tera", &render)
511         .context("render player info template")?
512     );
513     html
514   }
515
516   pub fn dummy() -> Instance {
517     Instance {
518       name: Arc::new("server::".parse().unwrap()),
519       gs: GameState::dummy(),
520       ipieces: IPieces(default()),
521       pcaliases: default(),
522       ioccults: default(),
523       clients: default(),
524       tokens_players: default(),
525       tokens_clients: default(),
526       acl: default(),
527       links: default(),
528       bundle_list: default(),
529       bundle_specs: default(),
530       bundle_hashes: default(),
531       asset_url_key: AssetUrlKey::Dummy,
532       local_libs: default(),
533       iplayers: default(),
534       ifastsplits: default(),
535     }
536   }
537
538 }
539
540 pub fn games_lock() -> RwLockWriteGuard<'static, GamesTable> {
541   GLOBAL.games_table.write()
542 }
543
544 // ---------- Simple trait implementations ----------
545
546 deref_to_field_mut!{InstanceGuard<'_>, Instance, c.g}
547
548 impl FromStr for AccountScope {
549   type Err = InvalidScopedName;
550   #[throws(InvalidScopedName)]
551   fn from_str(s: &str) -> Self {
552     let scope = AccountScope::parse_name(s, &mut [])?;
553     scope
554   }
555 }
556
557 impl FromStr for InstanceName {
558   type Err = InvalidScopedName;
559   #[throws(InvalidScopedName)]
560   fn from_str(s: &str) -> Self {
561     let mut names: [_;2] = default();
562     let scope = AccountScope::parse_name(s, &mut names)?;
563     let [subaccount, game] = names;
564     InstanceName {
565       account: AccountName { scope, subaccount },
566       game,
567     }
568   }
569 }
570
571 impl Display for InstanceName {
572   #[throws(fmt::Error)]
573   fn fmt(&self, f: &mut fmt::Formatter) {
574     self.account.scope.display_name(
575       &[ self.account.subaccount.as_str(), self.game.as_str() ],
576       |s| f.write_str(s),
577     )?
578   }
579 }
580 hformat_as_display!{InstanceName}
581
582 impl DebugIdentify for InstanceContainer {
583   #[throws(fmt::Error)]
584   fn debug_identify(&self, f: &mut fmt::Formatter) {
585     write!(f, "InstanceContainer({})", &self.g.name)?;
586   }
587   #[throws(fmt::Error)]
588   fn debug_identify_type(f: &mut fmt::Formatter) {
589     write!(f, "InstanceContainer")?;
590   }
591 }
592
593 impl DebugIdentify for InstanceRef {
594   #[throws(fmt::Error)]
595   fn debug_identify_type(f: &mut fmt::Formatter) {
596     write!(f, "InstanceRef")?;
597   }
598 }
599
600 fn link_a_href(k: &HtmlStr, v: &str) -> Html {
601   hformat!("<a href={}>{}</a>", v, k)
602 }
603 #[ext(pub)]
604 impl (LinkKind, &str) {
605   fn to_html(self) -> Html {
606     let (k, v) = self;
607     link_a_href(&k.to_html(), v)
608   }
609 }
610
611 impl From<&LinksTable> for Html {
612   fn from(links: &LinksTable) -> Html {
613     let mut s = links.iter()
614       .filter_map(|(k,v)| {
615         let v = v.as_ref()?;
616         Some((k, v.as_str()).to_html())
617       })
618       .chain(iter::once(
619         link_a_href(Html::lit("Shapelib").into(), "/_/shapelib.html")
620       ))
621       .collect::<Vec<Html>>()
622       .iter()
623       .hjoin(&Html::lit(" "));
624     if s.len() != 0 { s = hformat!("links: {}", s) }
625     s
626   }
627 }
628
629 // ---------- Player and token functionality ----------
630
631 impl<Id> InstanceAccessDetails<Id>
632   where Id: AccessId, Fatal: From<Id::Error>
633 {
634   #[throws(Fatal)]
635   pub fn from_token(token: &RawTokenVal) -> InstanceAccessDetails<Id> {
636     let g = Id::global_tokens(PRIVATE_Y).read();
637     let i = g.get(token).ok_or(Id::ERROR)?;
638     i.clone()
639   }
640 }
641
642 impl<'ig> InstanceGuard<'ig> {
643   /// Core of piece deletion
644   ///
645   /// Caller is completely responsible for the necessary log entries.
646   ///
647   /// Idempotent (so does not detect if the piece didn't exist,
648   /// other than by passing `None`s to the callback.
649   #[throws(IE)]
650   pub fn delete_piece<H,T>(&mut self, modperm: ModifyingPieces,
651                              to_permute: &mut ToRecalculate,
652                              piece: PieceId, hook: H)
653                              -> (T, PieceUpdateOp<(),()>, UnpreparedUpdates)
654   where H: FnOnce(&IOccults, &GOccults,
655                   Option<&IPiece>, Option<&GPiece>) -> T
656   {
657     let ig = &mut **self;
658     let gs = &mut ig.gs;
659     let gpc = gs.pieces.as_mut(modperm).get_mut(piece);
660     let mut xupdates = vec![];
661     if let Some(gpc) = gpc {
662       gpc.occult.passive_delete_hook(&mut gs.occults, piece);
663       if gpc.occult.is_active() {
664         xupdates.append(
665           &mut
666             remove_occultation(
667               &mut gs.gen.unique_gen(),
668               &mut gs.players,
669               &mut gs.pieces,
670               &mut gs.occults,
671               &ig.ipieces,
672               &ig.ioccults,
673               to_permute,
674               piece)?
675         );
676       }
677     }
678     let ioccults = &ig.ioccults;
679     let gpc = gs.pieces.as_mut(modperm).remove(piece);
680     let ipc = ig.ipieces.as_mut(modperm).remove(piece);
681
682     let hook_r = hook(ioccults, &gs.occults,
683                       ipc.as_ref(), gpc.as_ref());
684
685     if let Some(ipc) = ipc {
686       if let Some(gpc) = gpc {
687         ipc.p.into_inner().delete_hook(&gpc, gs);
688       }
689       if let Some(occilk) = ipc.occilk {
690         ig.ioccults.ilks.dispose_iilk(occilk);
691       }
692     }
693
694     (hook_r, PieceUpdateOp::Delete(), xupdates.into_unprepared(None))
695   }
696
697   /// caller is responsible for logging; threading it through
698   /// proves the caller has a log entry.
699   #[throws(MgmtError)]
700   pub fn player_new(&mut self, gnew: GPlayer, inew: IPlayer,
701                     account: Arc<AccountName>, logentry: LogEntry)
702                     -> (PlayerId, PreparedUpdateEntry, LogEntry) {
703     // saving is fallible, but we can't attempt to save unless
704     // we have a thing to serialise with the player in it
705     self.check_new_nick(&gnew.nick)?;
706     if self.c.g.iplayers.values().any(|r| r.ipl.acctid == inew.acctid) {
707       Err(ME::AlreadyExists)?;
708     }
709     let player = self.c.g.gs.players.insert(gnew);
710     let u = PlayerUpdates::start(&self.c.g.gs).for_player();
711     let record = PlayerRecord { u, account, ipl: inew, };
712
713     self.c.g.iplayers.insert(player, record);
714
715     let update = (||{
716       let update = self.prepare_set_player_update(player)?;
717       self.save_game_now()?;
718       self.save_aux_now()?;
719       Ok::<_,InternalError>(update)
720     })().map_err(|e|{
721       self.c.g.iplayers.remove(player);
722       self.c.g.gs.players.remove(player);
723       // Perhaps we leave the g-* file with this player recorded,
724       // but this will be ignored when we load.
725       e
726     })?;
727     (||{
728       
729     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
730     (player, update, logentry)
731   }
732
733   #[throws(MgmtError)]
734   pub fn check_new_nick(&mut self, new_nick: &str) {
735     if self.c.g.gs.players.values().any(|old| old.nick == new_nick) {
736       Err(ME::NickCollision)?;
737     }
738   }
739
740   #[throws(IE)]
741   pub fn prepare_set_player_update(&self, player: PlayerId)
742                                    -> PreparedUpdateEntry {
743     let new_info_pane = Arc::new(self.player_info_pane()?);
744     PreparedUpdateEntry::SetPlayer {
745       player, new_info_pane,
746       data: DataLoadPlayer::from_player(self, player),
747     }
748   }
749
750   pub fn remove_clients(&mut self,
751                         players: &HashSet<PlayerId>,
752                         signal: ErrorSignaledViaUpdate<PUE_P, String>) {
753     let mut clients_to_remove = HashSet::new();
754     self.clients.retain(|k,v| {
755       let remove = players.contains(&v.player);
756       if remove { clients_to_remove.insert(k); }
757       !remove
758     });
759
760     let gen = self.c.g.gs.gen;
761     for &player in players {
762       if let Some(iplayer) = self.iplayers.get_mut(player) {
763         iplayer.u.push(PreparedUpdate {
764           gen,
765           when: Instant::now(),
766           us: vec![ PreparedUpdateEntry::Error(
767             signal.clone(),
768           )],
769         });
770       };
771     }
772     self.tokens_deregister_for_id(|id| clients_to_remove.contains(&id));
773   }
774
775   //  #[throws(InternalError)]
776   //  https://github.com/withoutboats/fehler/issues/62
777   pub fn players_remove(&mut self, old_players_set: &HashSet<PlayerId>)
778                         ->
779     Result<Vec<
780         (Option<GPlayer>, Option<IPlayer>, PreparedUpdateEntry)
781         >, InternalError>
782   {
783     // We have to filter this player out of everything
784     // Then save
785     // Then send updates
786     // We make a copy so if the save fails, we can put everything back
787
788     let mut players = self.c.g.gs.players.clone();
789     let old_players: Vec<_> = old_players_set.iter().cloned().collect();
790     let old_gpls: Vec<_> = old_players.iter().cloned().map(|oldplayer| {
791       players.remove(oldplayer)
792     }).collect();
793
794     // New state
795     let mut gs = GameState {
796       // These parts are straightforward and correct
797       table_colour: self.c.g.gs.table_colour.clone(),
798       table_size: self.c.g.gs.table_size,
799       gen: self.c.g.gs.gen,
800       max_z: self.gs.max_z.clone(),
801       players,
802       // These have special handling
803       log: default(),
804       pieces: default(),
805       occults: default(),
806     };
807
808     let held_by_old = |p: &GPiece| if_chain! {
809       if let Some(held) = p.held;
810       if old_players_set.contains(&held);
811       then { true }
812       else { false }
813     };
814
815     let mut updated_pieces = vec![];
816
817     // drop order is reverse of creation order, so create undo
818     // after all the things it will reference
819     let mut undo: Vec<Box<dyn FnOnce(&mut InstanceGuard)>> = vec![];
820
821     // Arrange gs.pieces
822     for (piece,p) in &mut self.c.g.gs.pieces {
823       if held_by_old(p) {
824         p.held = None;
825         updated_pieces.push(piece);
826       }
827     }
828     undo.push(Box::new(|ig| for &piece in &updated_pieces {
829       (||Some({
830         held_by_old(ig.c.g.gs.pieces.get_mut(piece)?);
831       }))();
832     }));
833
834     // Handle gs.log:
835     // Installs gs as the new game state, stealing the log and pieces
836     let mut swap_things = |ig: &mut InstanceGuard| {
837       mem::swap(&mut ig.c.g.gs.log,    &mut gs.log   );
838       mem::swap(&mut ig.c.g.gs.pieces, &mut gs.pieces);
839       mem::swap(&mut ig.c.g.gs.occults,&mut gs.occults);
840       mem::swap(&mut ig.c.g.gs,        &mut gs,   );
841     };
842     swap_things(self);
843     undo.push(Box::new(swap_things));
844
845     let new_info_pane = Arc::new(self.player_info_pane()?);
846
847     self.save_game_now().map_err(|e|{
848       // oof
849       #[allow(clippy::iter_with_drain)] // don't eat undo, so we can drop it
850       for u in undo.drain(..).rev() {
851         u(self);
852       }
853       e
854     })?;
855
856     // point of no return
857     mem::drop(undo);
858
859     let old_ipls = (||{
860       for &piece in &updated_pieces {
861         (||Some({
862           self.c.g.gs.pieces.get_mut(piece)?.gen = self.c.g.gs.gen;
863         }))();
864       }
865
866       let estimate = updated_pieces.len() + 1;
867       let mut buf = PrepareUpdatesBuffer::new(self, Some(estimate));
868       for &piece in &updated_pieces {
869         buf.piece_update(piece, &None, PieceUpdateOp::Modify(()).into());
870       }
871       buf.finish();
872
873       self.remove_clients(old_players_set, ESVU::PlayerRemoved);
874       self.tokens_deregister_for_id(
875         |id:PlayerId| old_players_set.contains(&id)
876       );
877       let old_ipls: Vec<_> = old_players.iter().cloned().map(
878         |oldplayer| self.iplayers.remove(oldplayer)
879           .map(|ipr| ipr.ipl)
880       ).collect();
881       self.save_aux_now().unwrap_or_else(
882         |e| warn!(
883           "trouble garbage collecting accesses for deleted player: {:?}",
884           &e)
885       );
886       old_ipls
887     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
888
889     let updates = old_players.iter().cloned().map(
890       |player| PreparedUpdateEntry::RemovePlayer {
891         player,
892         new_info_pane: new_info_pane.clone(),
893       }
894     );
895
896     let old = izip!(
897       old_gpls,
898       old_ipls,
899       updates
900     ).collect();
901
902     Ok(old)
903   }
904
905   #[throws(InternalError)]
906   pub fn invalidate_tokens(&mut self, player: PlayerId) {
907     let old_tokens = TokenRegistry {
908       tr: self.tokens_players.tr.clone(),
909       id: self.tokens_players.id,
910     };
911     self.tokens_deregister_for_id(|id:PlayerId| id==player);
912     self.save_aux_now().map_err(|e|{
913       // oof, the tokens are already out of the global map, but
914       // not saved, so they might come back.  We need to leave
915       // them here so they can be deleted later.
916       self.tokens_players = old_tokens;
917       e
918     })?;
919     // ppoint of no return
920     (||{
921       self.remove_clients(&[player].iter().cloned().collect(),
922                           ESVU::TokenRevoked);
923     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
924   }
925
926   #[throws(MgmtError)]
927   fn player_access_reset_redeliver(&mut self,
928                                    accounts: &mut AccountsGuard,
929                                    player: PlayerId,
930                                    _auth: Authorisation<AccountName>,
931                                    reset: bool)
932                                    -> AccessTokenReport {
933     let acctid = self.iplayers.byid(player)?.ipl.acctid;
934
935     let access = {
936       let (acct, _) = accounts.lookup(acctid)?;
937       let access = acct.access.clone();
938       let desc = access.describe_html();
939       let now = Timestamp::now();
940       let revk = TokenRevelationKey {
941         account: (*acct.account).clone(),
942         desc,
943       };
944       self.iplayers.byid_mut(player)?.ipl.tokens_revealed.entry(revk)
945         .or_insert(TokenRevelationValue {
946           latest: now,
947           earliest: now,
948         })
949         .latest = now;
950       access
951     };
952
953     let current_tokens: ArrayVec<&RawToken,2> = {
954       let players = GLOBAL.players.read();
955       self.tokens_players.tr.iter().
956         filter(|&token| (||{
957           let iad = players.get(token)?;
958           if iad.ident != player { return None }
959           if ! Arc::ptr_eq(&iad.gref.0, &self.gref.0) { return None }
960           Some(())
961         })() == Some(()))
962         .take(2)
963         .collect()
964     };
965
966     let reset = reset || current_tokens.is_empty();
967
968     let token: RawToken = if reset {
969       drop(current_tokens);
970
971       self.invalidate_tokens(player)?;
972       self.save_aux_now()?;
973
974       let token = access
975         .override_token()
976         .cloned()
977         .unwrap_or_else(||{
978           RawToken::new_random()
979         });
980         
981       let iad = InstanceAccessDetails {
982         gref: self.gref.clone(),
983         ident: player,
984         acctid
985       };
986       self.token_register(token.clone(), iad);
987       self.save_aux_now()?;
988
989       token
990
991     } else {
992
993       let token = match current_tokens.as_slice() {
994         [] => panic!(), // this possibility was excluded earlier
995         [token] => token,
996         _ => {
997           warn!("duplicate token for {}", player);
998           throw!(ME::ServerFailure("duplicate token".to_string()));
999         }
1000       };
1001
1002       (*token).clone()
1003     };
1004
1005     let ipl = &self.c.g.iplayers.byid(player)?.ipl;
1006     let gpl = self.c.g.gs.players.byid(player)?;
1007
1008     let url = format!("{}/?{}",
1009                       &config().public_url.trim_end_matches('/'),
1010                       token.0);
1011     let info = AccessTokenInfo { url };
1012     let report = access.deliver(accounts, &self.c.g, gpl, ipl, info)?;
1013     report
1014   }
1015
1016   #[throws(MgmtError)]
1017   pub fn player_access_reset(&mut self,
1018                              accounts: &mut AccountsGuard,
1019                              player: PlayerId,
1020                              auth: Authorisation<AccountName>)
1021                              -> AccessTokenReport {
1022     self.player_access_reset_redeliver(accounts, player, auth, true)?
1023   }
1024
1025   #[throws(MgmtError)]
1026   pub fn player_access_redeliver(&mut self,
1027                                  accounts: &mut AccountsGuard,
1028                                  player: PlayerId,
1029                                  auth: Authorisation<AccountName>)
1030                                  -> AccessTokenReport {
1031     self.player_access_reset_redeliver(accounts, player, auth, false)?
1032   }
1033
1034   pub fn modify_pieces(&mut self) -> ModifyingPieces {
1035     self.save_game_and_aux_later();
1036     // want this to be borrowed from self, so that we tie it properly
1037     // to the same game.  But in practice we don't expect to write
1038     // bugs where we get different games mixed up.  Borrowing self
1039     // from the caller's pov is troublesome beczuse ultimately the
1040     // caller will need to manipulate various fields of Instance (so
1041     // we mustn't have a borrow of it).
1042     ModifyingPieces(())
1043   }
1044
1045   pub fn modify_pieces_not_necessarily_saving_aux(&mut self)
1046                                                   -> ModifyingPieces {
1047     self.save_game_later();
1048     ModifyingPieces(())
1049   }
1050
1051   fn token_register<Id:AccessId>(
1052     &mut self,
1053     token: RawToken,
1054     iad: InstanceAccessDetails<Id>
1055   ) {
1056     Id::tokens_registry(&mut self.c.g, PRIVATE_Y).tr.insert(token.clone());
1057     Id::global_tokens(PRIVATE_Y).write().insert(token, iad);
1058   }
1059
1060   fn forget_all_tokens<Id:AccessId>(tokens: &mut TokenRegistry<Id>) {
1061     let global: &RwLock<TokenTable<Id>> = AccessId::global_tokens(PRIVATE_Y);
1062     let mut global = global.write();
1063     for t in tokens.tr.drain() { global.remove(&t); }
1064   }
1065
1066   fn tokens_deregister_for_id<Id:AccessId, F: Fn(Id) -> bool
1067                               > (&mut self, oldid: F) {
1068     let mut tokens = AccessId::global_tokens(PRIVATE_Y).write();
1069     tokens.retain(|k,v| if_chain! {
1070       if oldid(v.ident);
1071       if Id::tokens_registry(self, PRIVATE_Y).tr.remove(k);
1072       then { false }
1073       else { true }
1074     });
1075   }
1076
1077 }
1078
1079 // ---------- save/load ----------
1080
1081 #[derive(Copy,Clone,Debug)]
1082 enum SaveFileOrDir { File, Dir }
1083
1084 impl SaveFileOrDir {
1085   #[throws(io::Error)]
1086   fn remove<P:AsRef<std::path::Path>>(self, path: P) {
1087     match self {
1088       SaveFileOrDir::File => fs::remove_file(path)?,
1089       SaveFileOrDir::Dir  => fs::remove_dir_all(path)?,
1090     }
1091   }
1092 }
1093
1094 #[derive(Debug)]
1095 enum SavefilenameParseResult {
1096   NotGameFile,
1097   Auxiliary(SaveFileOrDir),
1098   TempToDelete,
1099   GameFile {
1100     aux_leaves: Vec<Vec<u8>>,
1101     name: InstanceName,
1102   },
1103 }
1104
1105 pub fn savefilename(name: &InstanceName, prefix: &str, suffix: &str)
1106                     -> String {
1107   [ config().save_dir().as_str(), "/", prefix ]
1108     .iter().map(Deref::deref)
1109     .chain(iter::once( name.to_string().as_str() ))
1110     .chain([ suffix ].iter().map(Deref::deref))
1111     .collect()
1112 }
1113
1114 #[throws(anyhow::Error)]
1115 fn savefilename_parse(leaf: &[u8]) -> SavefilenameParseResult {
1116   use SavefilenameParseResult::*;
1117
1118   if leaf.starts_with(b"a-") { return Auxiliary(SaveFileOrDir::File) }
1119   if leaf.starts_with(b"b-") { return Auxiliary(SaveFileOrDir::Dir ) }
1120   let rhs = match leaf.strip_prefix(b"g-") {
1121     Some(rhs) => rhs,
1122     None => return NotGameFile,
1123   };
1124   let after_ftype_prefix = rhs;
1125   let rhs = str::from_utf8(rhs)?;
1126   let rcomp = rhs.rsplitn(2, ':').next().unwrap();
1127   if rcomp.find('.').is_some() { return TempToDelete }
1128
1129   let name = InstanceName::from_str(rhs)?;
1130
1131   let aux_leaves = [ b"a-", b"b-" ].iter().map(|prefix| {
1132     let mut s: Vec<_> = (prefix[..]).into(); s.extend(after_ftype_prefix); s
1133   }).collect();
1134   GameFile { name, aux_leaves }
1135 }
1136
1137 impl InstanceGuard<'_> {
1138   #[throws(InternalError)]
1139   fn save_something(
1140     &self, prefix: &str,
1141     w: fn(s: &Self, w: &mut BufWriter<fs::File>)
1142           -> Result<(),rmp_serde::encode::Error>
1143   ) {
1144     let tmp = savefilename(&self.name, prefix,".tmp");
1145     let f = fs::File::create(&tmp)
1146       .with_context(||format!("save: create {:?}",&tmp))?;
1147     let mut f = BufWriter::new(f);
1148     w(self, &mut f)?;
1149     f.flush()
1150       .with_context(||format!("save: flush {:?}",&tmp))?;
1151     drop(
1152       f.into_inner().map_err(|e| { let e: io::Error = e.into(); e })
1153         .with_context(||format!("save: close {:?}",&tmp))?
1154     );
1155     let out = savefilename(&self.name, prefix,"");
1156     fs::rename(&tmp, &out).context("install")
1157       .with_context(||format!("save: install {:?} as {:?}", &tmp, &out))?;
1158     debug!("saved to {}", &out);
1159   }
1160
1161   #[throws(InternalError)]
1162   pub fn save_game_now(&mut self) {
1163     if self.c.aux_dirty {
1164       self.save_aux_now()?;
1165     }
1166     self.save_something("g-", |s,w| {
1167       rmp_serde::encode::write_named(w, &s.c.g.gs)
1168     })?;
1169     self.c.game_dirty = false;
1170     debug!("saved (now) {}", &self.name);
1171   }
1172
1173   #[throws(InternalError)]
1174   pub fn save_aux_now(&mut self) {
1175     self.save_something("a-", |s, w| {
1176       let ipieces = &s.c.g.ipieces;
1177       let ioccults = &s.c.g.ioccults;
1178       let pcaliases = &s.c.g.pcaliases;
1179       let ifastsplits = &s.c.g.ifastsplits;
1180       let tokens_players: Vec<(&str, PlayerId)> = {
1181         let global_players = GLOBAL.players.read();
1182         s.c.g.tokens_players.tr
1183           .iter()
1184           .filter_map(|token|
1185                global_players.get(token)
1186                .map(|player| (token.0.as_str(), player.ident)))
1187           .collect()
1188       };
1189       let aplayers = s.c.g.iplayers.iter().map(
1190         |(player, PlayerRecord { ipl, .. })|
1191         (player, ipl.clone())
1192       ).collect();
1193       let acl = s.c.g.acl.clone().into();
1194       let links = s.c.g.links.clone();
1195       let asset_url_key = s.c.g.asset_url_key.clone();
1196       let bundle_hashes = s.c.g.bundle_hashes.clone();
1197       let isa = InstanceSaveAuxiliary {
1198         ipieces, ioccults, tokens_players, aplayers, acl, links,
1199         pcaliases, asset_url_key, bundle_hashes, ifastsplits,
1200       };
1201       rmp_serde::encode::write_named(w, &isa)
1202     })?;
1203     self.c.aux_dirty = false;
1204     info!("saved aux for {}", &self.name);
1205   }
1206
1207   #[throws(InternalError)]
1208   fn load_something<T:DeserializeOwned>(name: &InstanceName, prefix: &str)
1209                                         -> T {
1210     let inp = savefilename(name, prefix, "");
1211     let f = fs::File::open(&inp).with_context(|| inp.clone())?;
1212     let mut f = BufReader::new(f);
1213     let thing = rmp_serde::decode::from_read(&mut f)?;
1214     debug!("loaded from {:?}", &inp);
1215     thing
1216   }
1217
1218   #[throws(StartupError)]
1219   fn load_game(accounts: &AccountsGuard,
1220                games: &mut GamesGuard,
1221                name: InstanceName) -> Option<InstanceRef> {
1222     let InstanceSaveAuxiliary::
1223     <String,ActualIPieces,IOccults,PieceAliases,IFastSplits> {
1224       tokens_players, mut ipieces, mut ioccults, mut aplayers, acl, links,
1225       pcaliases, asset_url_key, bundle_hashes, mut ifastsplits,
1226     } = match Self::load_something(&name, "a-") {
1227       Ok(data) => data,
1228       Err(e) => if (||{
1229         let ae = match &e { InternalError::Anyhow(ae) => Some(ae), _=>None }?;
1230         let ioe = ae.downcast_ref::<io::Error>()?;
1231         let is_enoent = ioe.kind() == io::ErrorKind::NotFound;
1232         is_enoent.as_option()
1233       })().is_some() {
1234         return None;
1235       } else {
1236         throw!(e);
1237       },
1238     };
1239
1240     let mut gs: GameState = Self::load_something(&name, "g-")?;
1241
1242     fn discard_mismatches<K:slotmap::Key, V1, V2>(
1243       primary:   &mut DenseSlotMap<K, V1>,
1244       secondary: &mut SecondarySlotMap<K, V2>,
1245     ) {
1246       primary.retain(|k,_v| secondary.contains_key(k));
1247       secondary.retain(|k,_v| primary.contains_key(k));
1248     }
1249
1250     for (piece, gpc) in &mut gs.pieces.0 {
1251       if_let!{ Some(fsid) = gpc.fastsplit; else continue; }
1252
1253       // We must recover the ipc via the fastsplit table, or delete
1254       if_chain!{
1255         let ilks = &mut ioccults.ilks;
1256         if let Some(recovered_ipc) = ifastsplits.recover_ipc(ilks, fsid);
1257         then {
1258           if_chain!{
1259             if let Some(old_ipc) = ipieces.get_mut(piece);
1260             // We're about to overwrite this ipc, maybe owns some occilk.
1261             // If in fact we're the same kind, we've already acquired
1262             // another refcount from recover_ipc, above.
1263             if let Some(old_iilk) = old_ipc.occilk.take();
1264             then { ilks.dispose_iilk(old_iilk); }
1265           }
1266           ipieces.insert(piece, recovered_ipc);
1267         } else {
1268           // Not available in ipieces, fastsplit family must just have
1269           // been added.  This will get rid of it from gpieces, too, below.
1270           ipieces.remove(piece);
1271         }
1272       }
1273     }
1274     ifastsplits.cleanup(&mut ioccults.ilks);
1275
1276     discard_mismatches(&mut gs.players,  &mut aplayers);
1277     discard_mismatches(&mut gs.pieces.0, &mut ipieces);
1278   
1279     let pu_bc = PlayerUpdates::start(&gs);
1280
1281     let iplayers: SecondarySlotMap<PlayerId, PlayerRecord> = {
1282       let a = aplayers;
1283       a.into_iter()
1284     }.filter_map(|(player, ipl)| {
1285       let u = pu_bc.for_player();
1286       let account = accounts.lookup(ipl.acctid).ok()?.0.account.clone();
1287       Some((player, PlayerRecord { u, ipl, account }))
1288     }).collect();
1289
1290     for mut p in gs.pieces.values_mut() {
1291       p.lastclient = default();
1292       if let Some(held) = p.held {
1293         if !gs.players.contains_key(held) { p.held = None }
1294       }
1295     }
1296
1297     let name = Arc::new(name);
1298     let (tokens_players, acctids_players) = {
1299       let mut tokens = Vec::with_capacity(tokens_players.len());
1300       let mut acctids = Vec::with_capacity(tokens_players.len());
1301       for (token, player) in tokens_players { if_chain! {
1302         if let Some(record) = iplayers.get(player);
1303         then {
1304           tokens.push((token, player));
1305           acctids.push(record.ipl.acctid);
1306         }
1307       }}
1308       (tokens, accounts.bulk_check(&acctids))
1309     };
1310
1311     let mut g = Instance {
1312       gs, iplayers, links,
1313       acl: acl.into(),
1314       ipieces: IPieces(ipieces),
1315       pcaliases,
1316       ioccults,
1317       name: name.clone(),
1318       clients: default(),
1319       tokens_clients: default(),
1320       tokens_players: default(),
1321       bundle_list: default(), // set by load_game_bundles
1322       local_libs: default(), // set by load_game_bundles
1323       bundle_specs: default(), // set by load_game_bundles
1324       asset_url_key,
1325       bundle_hashes,
1326       ifastsplits,
1327     };
1328
1329     let b = InstanceBundles::reload_game_bundles(&mut g)?;
1330     let b = Mutex::new(b);
1331
1332     let c = InstanceContainer {
1333       live: true,
1334       game_dirty: false,
1335       aux_dirty: false,
1336       g,
1337     };
1338     let c = Mutex::new(c);
1339     let gref = InstanceRef(Arc::new(InstanceOuter { c, b }));
1340     let mut g = gref.lock().unwrap();
1341
1342     let ig = &mut *g;
1343     for (piece, ipc) in ig.ipieces.0.iter() {
1344       ipc.direct_trait_access()
1345         .save_reloaded_hook(piece, &mut ig.gs, &gref)?;
1346     }
1347
1348     for (token, _) in &tokens_players {
1349       g.tokens_players.tr.insert(RawToken(token.clone()));
1350     }
1351     let mut global = GLOBAL.players.write();
1352     for ((token, player), acctid) in
1353       tokens_players.into_iter()
1354       .zip(acctids_players)
1355     { if_chain!{
1356       if let Some(acctid) = acctid;
1357       let iad = InstanceAccessDetails {
1358         acctid,
1359         gref: gref.clone(),
1360         ident: player,
1361       };
1362       then { global.insert(RawToken(token), iad); }
1363     } }
1364     drop(global);
1365     drop(g);
1366     games.insert(name.clone(), gref.clone());
1367     info!("loadewd {:?}", &name);
1368     Some(gref)
1369   }
1370 }
1371
1372 #[throws(anyhow::Error)]
1373 pub fn load_games(accounts: &mut AccountsGuard,
1374                   games: &mut GamesGuard) {
1375   enum AFState { Found(PathBuf, SaveFileOrDir), Used }
1376   use AFState::*;
1377   use SavefilenameParseResult::*;
1378   let mut a_leaves = HashMap::new();
1379   let save_dir = config().save_dir().clone();
1380   for de in fs::read_dir(&save_dir).context(save_dir)? {
1381     let de = de?;
1382     let leaf = de.file_name();
1383     (||{
1384       let leaf = leaf.as_bytes();
1385       match savefilename_parse(leaf)? {
1386         NotGameFile => {
1387         }
1388         TempToDelete => {
1389           fs::remove_file(de.path())
1390             .context("stale temporary file")?;
1391         }
1392         Auxiliary(fd) => {
1393           a_leaves.entry(leaf.to_owned()).or_insert_with(
1394             || Found(de.path(), fd)
1395           );
1396         }
1397         GameFile { aux_leaves, name } => {
1398           InstanceGuard::load_game(accounts, games, name)?;
1399           for aux_leaf in aux_leaves {
1400             a_leaves.insert(aux_leaf, Used);
1401           }
1402         }
1403       }
1404       <Result<_,anyhow::Error>>::Ok(())
1405     })().with_context(|| format!("leaf={:?}", leaf))?;
1406   }
1407   (||{
1408     for (leaf, state) in &a_leaves {
1409       if let Found(path, fd) = state {
1410         fd.remove(&path)
1411           .with_context(|| format!("leaf={:?}", leaf))?;
1412       }
1413     }
1414     <Result<_,anyhow::Error>>::Ok(())
1415   })().context("cleaning up stale files")?;
1416   info!("loaded games");
1417 }
1418
1419 // ---------- Tokens / TokenTable / AccessId ----------
1420
1421 pub type TokenTable<Id> = HashMap<RawToken, InstanceAccessDetails<Id>>;
1422
1423 pub trait AccessId: Copy + Clone + 'static {
1424   type Error: Into<Fatal>;
1425   const ERROR: Self::Error;
1426   fn global_tokens(_:PrivateCaller) -> &'static RwLock<TokenTable<Self>>;
1427   fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
1428                      -> &mut TokenRegistry<Self>;
1429 }
1430
1431 #[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd)]
1432 #[derive(Serialize,Deserialize)]
1433 #[derive(Error)]
1434 #[error("Player not found")]
1435 pub struct PlayerNotFound;
1436
1437 impl AccessId for PlayerId {
1438   type Error = PlayerNotFound;
1439   const ERROR: PlayerNotFound = PlayerNotFound;
1440   fn global_tokens(_: PrivateCaller) -> &'static RwLock<TokenTable<Self>> {
1441     &GLOBAL.players
1442   }
1443   fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
1444                      -> &mut TokenRegistry<Self> {
1445     &mut ig.tokens_players
1446   }
1447 }
1448 impl AccessId for ClientId {
1449   type Error = Fatal;
1450   const ERROR: Fatal = NoClient;
1451   fn global_tokens(_: PrivateCaller) -> &'static RwLock<TokenTable<Self>> {
1452     &GLOBAL.clients
1453   }
1454   fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
1455                      -> &mut TokenRegistry<Self> {
1456     &mut ig.tokens_clients
1457   }
1458 }
1459
1460 impl RawToken {
1461   fn new_random() -> Self {
1462     let mut rng = thread_rng();
1463     let token = RawToken (
1464       repeat_with(|| rng.sample(Alphanumeric))
1465         .map(char::from)
1466         .take(64).collect()
1467     );
1468     token
1469   }
1470 }
1471
1472 pub fn lookup_token<Id:AccessId>(s: &RawTokenVal)
1473       -> Result<InstanceAccessDetails<Id>, Id::Error> {
1474   Id::global_tokens(PRIVATE_Y).read().get(s).cloned()
1475     .ok_or(Id::ERROR)
1476 }
1477
1478 #[throws(Fatal)]
1479 pub fn record_token<Id:AccessId> (
1480   ig: &mut InstanceGuard,
1481   iad: InstanceAccessDetails<Id>
1482 ) -> RawToken {
1483   let token = RawToken::new_random();
1484   ig.token_register(token.clone(), iad);
1485   token
1486 }
1487
1488 #[throws(E)]
1489 pub fn process_all_players_for_account<
1490     E: Error,
1491     F: FnMut(&mut InstanceGuard<'_>, PlayerId) -> Result<(),E>
1492     >
1493   (games: &mut GamesGuard, acctid: AccountId, mut f: F)
1494 {
1495   for gref in games.values() {
1496     let c = gref.lock_even_destroying();
1497     let remove: Vec<_> = c.g.iplayers.iter().filter_map(|(player,pr)| {
1498       if pr.ipl.acctid == acctid { Some(player) } else { None }
1499     }).collect();
1500     let mut ig = InstanceGuard { gref: gref.clone(), c };
1501     for player in remove.into_iter() {
1502       f(&mut ig, player)?;
1503     }
1504   }
1505 }
1506
1507 // ========== instance pieces data access ==========
1508
1509 impl IPieces {
1510   pub fn get(&self, piece: PieceId) -> Option<&IPiece> {
1511     self.0.get(piece)
1512   }
1513
1514   pub fn as_mut(&mut self, _: ModifyingPieces) -> &mut ActualIPieces {
1515     &mut self.0
1516   }
1517
1518   pub fn is_empty(&self) -> bool {
1519     let IPieces(actual) = self;
1520     actual.is_empty()
1521   }
1522 }
1523
1524 // ---------- gamestate pieces table ----------
1525
1526 impl GPieces {
1527   pub fn get_mut(&mut self, piece: PieceId) -> Option<&mut GPiece> {
1528     self.0.get_mut(piece)
1529   }
1530   pub fn values_mut(&mut self) -> sm::ValuesMut<PieceId, GPiece> {
1531     self.0.values_mut()
1532   }
1533   pub fn as_mut(&mut self, _: ModifyingPieces) -> &mut ActualGPieces {
1534     &mut self.0
1535   }
1536   #[cfg(test)]
1537   pub fn as_mut_t(&mut self) -> &mut ActualGPieces { &mut self.0 }
1538 }
1539
1540 impl ById for GPieces {
1541   type Id = PieceId;
1542   type Entry = GPiece;
1543   type Error = Inapplicable;
1544   #[throws(Ia)]
1545   fn byid(&self, piece: PieceId) -> &GPiece {
1546     self.get(piece).ok_or(Ia::PieceGone)?
1547   }
1548   #[throws(Ia)]
1549   fn byid_mut(&mut self, piece: PieceId) -> &mut GPiece {
1550     self.get_mut(piece).ok_or(Ia::PieceGone)?
1551   }
1552 }
1553
1554 /*impl<'p> IntoIterator for &'p Pieces {
1555   type Item = (PieceId, &'p PieceState);
1556   type IntoIter = sm::Iter<'p, PieceId, PieceState>;
1557   fn into_iter(self) -> Self::IntoIter { (&self.0).into_iter() }
1558 }*/
1559 impl<'p> IntoIterator for &'p mut GPieces {
1560   type Item = (PieceId, &'p mut GPiece);
1561   type IntoIter = sm::IterMut<'p, PieceId, GPiece>;
1562   fn into_iter(self) -> Self::IntoIter { (&mut self.0).into_iter() }
1563 }
1564
1565 // ========== background maintenance ==========
1566
1567 // ---------- delayed game save ----------
1568
1569 impl InstanceGuard<'_> {
1570   pub fn save_game_later(&mut self) {
1571     if self.c.game_dirty { return }
1572     GLOBAL.dirty.lock().push_back(self.gref.clone());
1573     self.c.game_dirty = true;
1574   }
1575
1576   pub fn save_game_and_aux_later(&mut self) {
1577     if self.c.aux_dirty { return }
1578     self.save_game_later();
1579     self.c.aux_dirty = true;
1580   }
1581 }
1582
1583 pub fn game_flush_task() {
1584   let mut inner_queue = VecDeque::new();
1585   loop {
1586     {
1587       mem::swap(&mut inner_queue, &mut *GLOBAL.dirty.lock());
1588     }
1589     thread::sleep(GAME_SAVE_LAG);
1590     for _ in 0..inner_queue.len() {
1591       let ent = inner_queue.pop_front().unwrap();
1592       let mut ig = match ent.lock() { Ok(ig) => ig, _ => continue/*ah well*/ };
1593       if !ig.c.game_dirty { continue }
1594       match ig.save_game_now() {
1595         Ok(_) => {
1596           assert!(!ig.c.game_dirty);
1597         }
1598         Err(e) => {
1599           // todo: notify the players
1600           error!("save error! name={:?}: {}", &ig.name, &e);
1601           mem::drop(ig);
1602           inner_queue.push_back(ent); // oof
1603         }
1604       }
1605     }
1606   }
1607 }
1608
1609 // ---------- client expiry ----------
1610
1611 fn client_expire_old_clients() {
1612   let mut expire = vec![];
1613   let max_age = Instant::now() - MAX_CLIENT_INACTIVITY;
1614
1615   trait ClientIterator {
1616     type Ret;
1617     fn iter<'g>(&mut self, gref: &'g InstanceRef, max_age: Instant)
1618             -> (MutexGuard<'g, InstanceContainer>, Option<Self::Ret>) {
1619       let c = gref.lock_even_destroying();
1620       let ret = 'ret: loop {
1621         for (client, cl) in &c.g.clients {
1622           if cl.lastseen > max_age { continue }
1623           let ret = self.old(client);
1624           if ret.is_some() { break 'ret ret }
1625         }
1626         break 'ret None;
1627       };
1628       (c,ret)
1629     }
1630     fn old(&mut self, client: ClientId) -> Option<Self::Ret>;
1631   }
1632
1633   for gref in GLOBAL.games_table.read().values() {
1634     struct Any;
1635     impl ClientIterator for Any {
1636       type Ret = ();
1637       fn old(&mut self, _client: ClientId) -> Option<()> {
1638         Some(())
1639       }
1640     }
1641     if let (_, Some(())) = Any.iter(gref, max_age) {
1642       expire.push(gref.clone());
1643     }
1644   }
1645   for gref in expire.into_iter() {
1646     #[derive(Debug)]
1647     struct Now(HashSet<ClientId>);
1648     impl ClientIterator for Now {
1649       type Ret = Void;
1650       fn old(&mut self, client: ClientId) -> Option<Void> {
1651         self.0.insert(client);
1652         None
1653       }
1654     }
1655
1656     let mut now = Now(default());
1657     let (mut c, _) = now.iter(&gref, max_age);
1658     c.g.clients.retain(|c,_| !now.0.contains(&c));
1659     let mut gref = InstanceGuard { c, gref: gref.clone() };
1660     debug!("expiring client {:?}", &now);
1661     gref.tokens_deregister_for_id::<ClientId,_>(|c| now.0.contains(&c));
1662   }
1663 }
1664
1665 pub fn client_periodic_expiry() {
1666   loop {
1667     sleep(MAX_CLIENT_INACTIVITY);
1668     client_expire_old_clients();
1669   }
1670 }
1671
1672 // ---------- log expiry ----------
1673
1674 fn global_expire_old_logs() {
1675   let cutoff = Timestamp(Timestamp::now().0 - MAX_LOG_AGE.as_secs());
1676
1677   let mut want_expire = vec![];
1678
1679   let read = GLOBAL.games_table.read();
1680   for gref in read.values() {
1681     if gref.lock_even_destroying().g.gs.want_expire_some_logs(cutoff) {
1682       want_expire.push(gref.clone())
1683     }
1684   }
1685   drop(read);
1686
1687   for gref in want_expire.into_iter() {
1688     let mut g = gref.lock_even_destroying();
1689     info!("expiring old log entries in {:?}", &g.g.name);
1690     g.g.gs.do_expire_old_logs(cutoff);
1691   }
1692 }
1693
1694 pub fn logs_periodic_expiry() {
1695   loop {
1696     sleep(MAX_LOG_AGE / 10);
1697     global_expire_old_logs();
1698   }
1699 }