chiark / gitweb /
Rename SetPlayer to AddPlayer
[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 new_info_pane = Arc::new(self.player_info_pane()?);
717
718       let update = PreparedUpdateEntry::SetPlayer {
719         player, new_info_pane,
720         data: DataLoadPlayer::from_player(self, player),
721       };
722
723       self.save_game_now()?;
724       self.save_aux_now()?;
725       Ok::<_,InternalError>(update)
726     })().map_err(|e|{
727       self.c.g.iplayers.remove(player);
728       self.c.g.gs.players.remove(player);
729       // Perhaps we leave the g-* file with this player recorded,
730       // but this will be ignored when we load.
731       e
732     })?;
733     (||{
734       
735     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
736     (player, update, logentry)
737   }
738
739   #[throws(MgmtError)]
740   pub fn check_new_nick(&mut self, new_nick: &str) {
741     if self.c.g.gs.players.values().any(|old| old.nick == new_nick) {
742       Err(ME::NickCollision)?;
743     }
744   }
745
746   pub fn remove_clients(&mut self,
747                         players: &HashSet<PlayerId>,
748                         signal: ErrorSignaledViaUpdate<PUE_P, String>) {
749     let mut clients_to_remove = HashSet::new();
750     self.clients.retain(|k,v| {
751       let remove = players.contains(&v.player);
752       if remove { clients_to_remove.insert(k); }
753       !remove
754     });
755
756     let gen = self.c.g.gs.gen;
757     for &player in players {
758       if let Some(iplayer) = self.iplayers.get_mut(player) {
759         iplayer.u.push(PreparedUpdate {
760           gen,
761           when: Instant::now(),
762           us: vec![ PreparedUpdateEntry::Error(
763             signal.clone(),
764           )],
765         });
766       };
767     }
768     self.tokens_deregister_for_id(|id| clients_to_remove.contains(&id));
769   }
770
771   //  #[throws(InternalError)]
772   //  https://github.com/withoutboats/fehler/issues/62
773   pub fn players_remove(&mut self, old_players_set: &HashSet<PlayerId>)
774                         ->
775     Result<Vec<
776         (Option<GPlayer>, Option<IPlayer>, PreparedUpdateEntry)
777         >, InternalError>
778   {
779     // We have to filter this player out of everything
780     // Then save
781     // Then send updates
782     // We make a copy so if the save fails, we can put everything back
783
784     let mut players = self.c.g.gs.players.clone();
785     let old_players: Vec<_> = old_players_set.iter().cloned().collect();
786     let old_gpls: Vec<_> = old_players.iter().cloned().map(|oldplayer| {
787       players.remove(oldplayer)
788     }).collect();
789
790     // New state
791     let mut gs = GameState {
792       // These parts are straightforward and correct
793       table_colour: self.c.g.gs.table_colour.clone(),
794       table_size: self.c.g.gs.table_size,
795       gen: self.c.g.gs.gen,
796       max_z: self.gs.max_z.clone(),
797       players,
798       // These have special handling
799       log: default(),
800       pieces: default(),
801       occults: default(),
802     };
803
804     let held_by_old = |p: &GPiece| if_chain! {
805       if let Some(held) = p.held;
806       if old_players_set.contains(&held);
807       then { true }
808       else { false }
809     };
810
811     let mut updated_pieces = vec![];
812
813     // drop order is reverse of creation order, so create undo
814     // after all the things it will reference
815     let mut undo: Vec<Box<dyn FnOnce(&mut InstanceGuard)>> = vec![];
816
817     // Arrange gs.pieces
818     for (piece,p) in &mut self.c.g.gs.pieces {
819       if held_by_old(p) {
820         p.held = None;
821         updated_pieces.push(piece);
822       }
823     }
824     undo.push(Box::new(|ig| for &piece in &updated_pieces {
825       (||Some({
826         held_by_old(ig.c.g.gs.pieces.get_mut(piece)?);
827       }))();
828     }));
829
830     // Handle gs.log:
831     // Installs gs as the new game state, stealing the log and pieces
832     let mut swap_things = |ig: &mut InstanceGuard| {
833       mem::swap(&mut ig.c.g.gs.log,    &mut gs.log   );
834       mem::swap(&mut ig.c.g.gs.pieces, &mut gs.pieces);
835       mem::swap(&mut ig.c.g.gs.occults,&mut gs.occults);
836       mem::swap(&mut ig.c.g.gs,        &mut gs,   );
837     };
838     swap_things(self);
839     undo.push(Box::new(swap_things));
840
841     let new_info_pane = Arc::new(self.player_info_pane()?);
842
843     self.save_game_now().map_err(|e|{
844       // oof
845       #[allow(clippy::iter_with_drain)] // don't eat undo, so we can drop it
846       for u in undo.drain(..).rev() {
847         u(self);
848       }
849       e
850     })?;
851
852     // point of no return
853     mem::drop(undo);
854
855     let old_ipls = (||{
856       for &piece in &updated_pieces {
857         (||Some({
858           self.c.g.gs.pieces.get_mut(piece)?.gen = self.c.g.gs.gen;
859         }))();
860       }
861
862       let estimate = updated_pieces.len() + 1;
863       let mut buf = PrepareUpdatesBuffer::new(self, Some(estimate));
864       for &piece in &updated_pieces {
865         buf.piece_update(piece, &None, PieceUpdateOp::Modify(()).into());
866       }
867       buf.finish();
868
869       self.remove_clients(old_players_set, ESVU::PlayerRemoved);
870       self.tokens_deregister_for_id(
871         |id:PlayerId| old_players_set.contains(&id)
872       );
873       let old_ipls: Vec<_> = old_players.iter().cloned().map(
874         |oldplayer| self.iplayers.remove(oldplayer)
875           .map(|ipr| ipr.ipl)
876       ).collect();
877       self.save_aux_now().unwrap_or_else(
878         |e| warn!(
879           "trouble garbage collecting accesses for deleted player: {:?}",
880           &e)
881       );
882       old_ipls
883     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
884
885     let updates = old_players.iter().cloned().map(
886       |player| PreparedUpdateEntry::RemovePlayer {
887         player,
888         new_info_pane: new_info_pane.clone(),
889       }
890     );
891
892     let old = izip!(
893       old_gpls,
894       old_ipls,
895       updates
896     ).collect();
897
898     Ok(old)
899   }
900
901   #[throws(InternalError)]
902   pub fn invalidate_tokens(&mut self, player: PlayerId) {
903     let old_tokens = TokenRegistry {
904       tr: self.tokens_players.tr.clone(),
905       id: self.tokens_players.id,
906     };
907     self.tokens_deregister_for_id(|id:PlayerId| id==player);
908     self.save_aux_now().map_err(|e|{
909       // oof, the tokens are already out of the global map, but
910       // not saved, so they might come back.  We need to leave
911       // them here so they can be deleted later.
912       self.tokens_players = old_tokens;
913       e
914     })?;
915     // ppoint of no return
916     (||{
917       self.remove_clients(&[player].iter().cloned().collect(),
918                           ESVU::TokenRevoked);
919     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
920   }
921
922   #[throws(MgmtError)]
923   fn player_access_reset_redeliver(&mut self,
924                                    accounts: &mut AccountsGuard,
925                                    player: PlayerId,
926                                    _auth: Authorisation<AccountName>,
927                                    reset: bool)
928                                    -> AccessTokenReport {
929     let acctid = self.iplayers.byid(player)?.ipl.acctid;
930
931     let access = {
932       let (acct, _) = accounts.lookup(acctid)?;
933       let access = acct.access.clone();
934       let desc = access.describe_html();
935       let now = Timestamp::now();
936       let revk = TokenRevelationKey {
937         account: (*acct.account).clone(),
938         desc,
939       };
940       self.iplayers.byid_mut(player)?.ipl.tokens_revealed.entry(revk)
941         .or_insert(TokenRevelationValue {
942           latest: now,
943           earliest: now,
944         })
945         .latest = now;
946       access
947     };
948
949     let current_tokens: ArrayVec<&RawToken,2> = {
950       let players = GLOBAL.players.read();
951       self.tokens_players.tr.iter().
952         filter(|&token| (||{
953           let iad = players.get(token)?;
954           if iad.ident != player { return None }
955           if ! Arc::ptr_eq(&iad.gref.0, &self.gref.0) { return None }
956           Some(())
957         })() == Some(()))
958         .take(2)
959         .collect()
960     };
961
962     let reset = reset || current_tokens.is_empty();
963
964     let token: RawToken = if reset {
965       drop(current_tokens);
966
967       self.invalidate_tokens(player)?;
968       self.save_aux_now()?;
969
970       let token = access
971         .override_token()
972         .cloned()
973         .unwrap_or_else(||{
974           RawToken::new_random()
975         });
976         
977       let iad = InstanceAccessDetails {
978         gref: self.gref.clone(),
979         ident: player,
980         acctid
981       };
982       self.token_register(token.clone(), iad);
983       self.save_aux_now()?;
984
985       token
986
987     } else {
988
989       let token = match current_tokens.as_slice() {
990         [] => panic!(), // this possibility was excluded earlier
991         [token] => token,
992         _ => {
993           warn!("duplicate token for {}", player);
994           throw!(ME::ServerFailure("duplicate token".to_string()));
995         }
996       };
997
998       (*token).clone()
999     };
1000
1001     let ipl = &self.c.g.iplayers.byid(player)?.ipl;
1002     let gpl = self.c.g.gs.players.byid(player)?;
1003
1004     let url = format!("{}/?{}",
1005                       &config().public_url.trim_end_matches('/'),
1006                       token.0);
1007     let info = AccessTokenInfo { url };
1008     let report = access.deliver(accounts, &self.c.g, gpl, ipl, info)?;
1009     report
1010   }
1011
1012   #[throws(MgmtError)]
1013   pub fn player_access_reset(&mut self,
1014                              accounts: &mut AccountsGuard,
1015                              player: PlayerId,
1016                              auth: Authorisation<AccountName>)
1017                              -> AccessTokenReport {
1018     self.player_access_reset_redeliver(accounts, player, auth, true)?
1019   }
1020
1021   #[throws(MgmtError)]
1022   pub fn player_access_redeliver(&mut self,
1023                                  accounts: &mut AccountsGuard,
1024                                  player: PlayerId,
1025                                  auth: Authorisation<AccountName>)
1026                                  -> AccessTokenReport {
1027     self.player_access_reset_redeliver(accounts, player, auth, false)?
1028   }
1029
1030   pub fn modify_pieces(&mut self) -> ModifyingPieces {
1031     self.save_game_and_aux_later();
1032     // want this to be borrowed from self, so that we tie it properly
1033     // to the same game.  But in practice we don't expect to write
1034     // bugs where we get different games mixed up.  Borrowing self
1035     // from the caller's pov is troublesome beczuse ultimately the
1036     // caller will need to manipulate various fields of Instance (so
1037     // we mustn't have a borrow of it).
1038     ModifyingPieces(())
1039   }
1040
1041   pub fn modify_pieces_not_necessarily_saving_aux(&mut self)
1042                                                   -> ModifyingPieces {
1043     self.save_game_later();
1044     ModifyingPieces(())
1045   }
1046
1047   fn token_register<Id:AccessId>(
1048     &mut self,
1049     token: RawToken,
1050     iad: InstanceAccessDetails<Id>
1051   ) {
1052     Id::tokens_registry(&mut self.c.g, PRIVATE_Y).tr.insert(token.clone());
1053     Id::global_tokens(PRIVATE_Y).write().insert(token, iad);
1054   }
1055
1056   fn forget_all_tokens<Id:AccessId>(tokens: &mut TokenRegistry<Id>) {
1057     let global: &RwLock<TokenTable<Id>> = AccessId::global_tokens(PRIVATE_Y);
1058     let mut global = global.write();
1059     for t in tokens.tr.drain() { global.remove(&t); }
1060   }
1061
1062   fn tokens_deregister_for_id<Id:AccessId, F: Fn(Id) -> bool
1063                               > (&mut self, oldid: F) {
1064     let mut tokens = AccessId::global_tokens(PRIVATE_Y).write();
1065     tokens.retain(|k,v| if_chain! {
1066       if oldid(v.ident);
1067       if Id::tokens_registry(self, PRIVATE_Y).tr.remove(k);
1068       then { false }
1069       else { true }
1070     });
1071   }
1072
1073 }
1074
1075 // ---------- save/load ----------
1076
1077 #[derive(Copy,Clone,Debug)]
1078 enum SaveFileOrDir { File, Dir }
1079
1080 impl SaveFileOrDir {
1081   #[throws(io::Error)]
1082   fn remove<P:AsRef<std::path::Path>>(self, path: P) {
1083     match self {
1084       SaveFileOrDir::File => fs::remove_file(path)?,
1085       SaveFileOrDir::Dir  => fs::remove_dir_all(path)?,
1086     }
1087   }
1088 }
1089
1090 #[derive(Debug)]
1091 enum SavefilenameParseResult {
1092   NotGameFile,
1093   Auxiliary(SaveFileOrDir),
1094   TempToDelete,
1095   GameFile {
1096     aux_leaves: Vec<Vec<u8>>,
1097     name: InstanceName,
1098   },
1099 }
1100
1101 pub fn savefilename(name: &InstanceName, prefix: &str, suffix: &str)
1102                     -> String {
1103   [ config().save_dir().as_str(), "/", prefix ]
1104     .iter().map(Deref::deref)
1105     .chain(iter::once( name.to_string().as_str() ))
1106     .chain([ suffix ].iter().map(Deref::deref))
1107     .collect()
1108 }
1109
1110 #[throws(anyhow::Error)]
1111 fn savefilename_parse(leaf: &[u8]) -> SavefilenameParseResult {
1112   use SavefilenameParseResult::*;
1113
1114   if leaf.starts_with(b"a-") { return Auxiliary(SaveFileOrDir::File) }
1115   if leaf.starts_with(b"b-") { return Auxiliary(SaveFileOrDir::Dir ) }
1116   let rhs = match leaf.strip_prefix(b"g-") {
1117     Some(rhs) => rhs,
1118     None => return NotGameFile,
1119   };
1120   let after_ftype_prefix = rhs;
1121   let rhs = str::from_utf8(rhs)?;
1122   let rcomp = rhs.rsplitn(2, ':').next().unwrap();
1123   if rcomp.find('.').is_some() { return TempToDelete }
1124
1125   let name = InstanceName::from_str(rhs)?;
1126
1127   let aux_leaves = [ b"a-", b"b-" ].iter().map(|prefix| {
1128     let mut s: Vec<_> = (prefix[..]).into(); s.extend(after_ftype_prefix); s
1129   }).collect();
1130   GameFile { name, aux_leaves }
1131 }
1132
1133 impl InstanceGuard<'_> {
1134   #[throws(InternalError)]
1135   fn save_something(
1136     &self, prefix: &str,
1137     w: fn(s: &Self, w: &mut BufWriter<fs::File>)
1138           -> Result<(),rmp_serde::encode::Error>
1139   ) {
1140     let tmp = savefilename(&self.name, prefix,".tmp");
1141     let f = fs::File::create(&tmp)
1142       .with_context(||format!("save: create {:?}",&tmp))?;
1143     let mut f = BufWriter::new(f);
1144     w(self, &mut f)?;
1145     f.flush()
1146       .with_context(||format!("save: flush {:?}",&tmp))?;
1147     drop(
1148       f.into_inner().map_err(|e| { let e: io::Error = e.into(); e })
1149         .with_context(||format!("save: close {:?}",&tmp))?
1150     );
1151     let out = savefilename(&self.name, prefix,"");
1152     fs::rename(&tmp, &out).context("install")
1153       .with_context(||format!("save: install {:?} as {:?}", &tmp, &out))?;
1154     debug!("saved to {}", &out);
1155   }
1156
1157   #[throws(InternalError)]
1158   pub fn save_game_now(&mut self) {
1159     if self.c.aux_dirty {
1160       self.save_aux_now()?;
1161     }
1162     self.save_something("g-", |s,w| {
1163       rmp_serde::encode::write_named(w, &s.c.g.gs)
1164     })?;
1165     self.c.game_dirty = false;
1166     debug!("saved (now) {}", &self.name);
1167   }
1168
1169   #[throws(InternalError)]
1170   pub fn save_aux_now(&mut self) {
1171     self.save_something("a-", |s, w| {
1172       let ipieces = &s.c.g.ipieces;
1173       let ioccults = &s.c.g.ioccults;
1174       let pcaliases = &s.c.g.pcaliases;
1175       let ifastsplits = &s.c.g.ifastsplits;
1176       let tokens_players: Vec<(&str, PlayerId)> = {
1177         let global_players = GLOBAL.players.read();
1178         s.c.g.tokens_players.tr
1179           .iter()
1180           .filter_map(|token|
1181                global_players.get(token)
1182                .map(|player| (token.0.as_str(), player.ident)))
1183           .collect()
1184       };
1185       let aplayers = s.c.g.iplayers.iter().map(
1186         |(player, PlayerRecord { ipl, .. })|
1187         (player, ipl.clone())
1188       ).collect();
1189       let acl = s.c.g.acl.clone().into();
1190       let links = s.c.g.links.clone();
1191       let asset_url_key = s.c.g.asset_url_key.clone();
1192       let bundle_hashes = s.c.g.bundle_hashes.clone();
1193       let isa = InstanceSaveAuxiliary {
1194         ipieces, ioccults, tokens_players, aplayers, acl, links,
1195         pcaliases, asset_url_key, bundle_hashes, ifastsplits,
1196       };
1197       rmp_serde::encode::write_named(w, &isa)
1198     })?;
1199     self.c.aux_dirty = false;
1200     info!("saved aux for {}", &self.name);
1201   }
1202
1203   #[throws(InternalError)]
1204   fn load_something<T:DeserializeOwned>(name: &InstanceName, prefix: &str)
1205                                         -> T {
1206     let inp = savefilename(name, prefix, "");
1207     let f = fs::File::open(&inp).with_context(|| inp.clone())?;
1208     let mut f = BufReader::new(f);
1209     let thing = rmp_serde::decode::from_read(&mut f)?;
1210     debug!("loaded from {:?}", &inp);
1211     thing
1212   }
1213
1214   #[throws(StartupError)]
1215   fn load_game(accounts: &AccountsGuard,
1216                games: &mut GamesGuard,
1217                name: InstanceName) -> Option<InstanceRef> {
1218     let InstanceSaveAuxiliary::
1219     <String,ActualIPieces,IOccults,PieceAliases,IFastSplits> {
1220       tokens_players, mut ipieces, mut ioccults, mut aplayers, acl, links,
1221       pcaliases, asset_url_key, bundle_hashes, mut ifastsplits,
1222     } = match Self::load_something(&name, "a-") {
1223       Ok(data) => data,
1224       Err(e) => if (||{
1225         let ae = match &e { InternalError::Anyhow(ae) => Some(ae), _=>None }?;
1226         let ioe = ae.downcast_ref::<io::Error>()?;
1227         let is_enoent = ioe.kind() == io::ErrorKind::NotFound;
1228         is_enoent.as_option()
1229       })().is_some() {
1230         return None;
1231       } else {
1232         throw!(e);
1233       },
1234     };
1235
1236     let mut gs: GameState = Self::load_something(&name, "g-")?;
1237
1238     fn discard_mismatches<K:slotmap::Key, V1, V2>(
1239       primary:   &mut DenseSlotMap<K, V1>,
1240       secondary: &mut SecondarySlotMap<K, V2>,
1241     ) {
1242       primary.retain(|k,_v| secondary.contains_key(k));
1243       secondary.retain(|k,_v| primary.contains_key(k));
1244     }
1245
1246     for (piece, gpc) in &mut gs.pieces.0 {
1247       if_let!{ Some(fsid) = gpc.fastsplit; else continue; }
1248
1249       // We must recover the ipc via the fastsplit table, or delete
1250       if_chain!{
1251         let ilks = &mut ioccults.ilks;
1252         if let Some(recovered_ipc) = ifastsplits.recover_ipc(ilks, fsid);
1253         then {
1254           if_chain!{
1255             if let Some(old_ipc) = ipieces.get_mut(piece);
1256             // We're about to overwrite this ipc, maybe owns some occilk.
1257             // If in fact we're the same kind, we've already acquired
1258             // another refcount from recover_ipc, above.
1259             if let Some(old_iilk) = old_ipc.occilk.take();
1260             then { ilks.dispose_iilk(old_iilk); }
1261           }
1262           ipieces.insert(piece, recovered_ipc);
1263         } else {
1264           // Not available in ipieces, fastsplit family must just have
1265           // been added.  This will get rid of it from gpieces, too, below.
1266           ipieces.remove(piece);
1267         }
1268       }
1269     }
1270     ifastsplits.cleanup(&mut ioccults.ilks);
1271
1272     discard_mismatches(&mut gs.players,  &mut aplayers);
1273     discard_mismatches(&mut gs.pieces.0, &mut ipieces);
1274   
1275     let pu_bc = PlayerUpdates::start(&gs);
1276
1277     let iplayers: SecondarySlotMap<PlayerId, PlayerRecord> = {
1278       let a = aplayers;
1279       a.into_iter()
1280     }.filter_map(|(player, ipl)| {
1281       let u = pu_bc.for_player();
1282       let account = accounts.lookup(ipl.acctid).ok()?.0.account.clone();
1283       Some((player, PlayerRecord { u, ipl, account }))
1284     }).collect();
1285
1286     for mut p in gs.pieces.values_mut() {
1287       p.lastclient = default();
1288       if let Some(held) = p.held {
1289         if !gs.players.contains_key(held) { p.held = None }
1290       }
1291     }
1292
1293     let name = Arc::new(name);
1294     let (tokens_players, acctids_players) = {
1295       let mut tokens = Vec::with_capacity(tokens_players.len());
1296       let mut acctids = Vec::with_capacity(tokens_players.len());
1297       for (token, player) in tokens_players { if_chain! {
1298         if let Some(record) = iplayers.get(player);
1299         then {
1300           tokens.push((token, player));
1301           acctids.push(record.ipl.acctid);
1302         }
1303       }}
1304       (tokens, accounts.bulk_check(&acctids))
1305     };
1306
1307     let mut g = Instance {
1308       gs, iplayers, links,
1309       acl: acl.into(),
1310       ipieces: IPieces(ipieces),
1311       pcaliases,
1312       ioccults,
1313       name: name.clone(),
1314       clients: default(),
1315       tokens_clients: default(),
1316       tokens_players: default(),
1317       bundle_list: default(), // set by load_game_bundles
1318       local_libs: default(), // set by load_game_bundles
1319       bundle_specs: default(), // set by load_game_bundles
1320       asset_url_key,
1321       bundle_hashes,
1322       ifastsplits,
1323     };
1324
1325     let b = InstanceBundles::reload_game_bundles(&mut g)?;
1326     let b = Mutex::new(b);
1327
1328     let c = InstanceContainer {
1329       live: true,
1330       game_dirty: false,
1331       aux_dirty: false,
1332       g,
1333     };
1334     let c = Mutex::new(c);
1335     let gref = InstanceRef(Arc::new(InstanceOuter { c, b }));
1336     let mut g = gref.lock().unwrap();
1337
1338     let ig = &mut *g;
1339     for (piece, ipc) in ig.ipieces.0.iter() {
1340       ipc.direct_trait_access()
1341         .save_reloaded_hook(piece, &mut ig.gs, &gref)?;
1342     }
1343
1344     for (token, _) in &tokens_players {
1345       g.tokens_players.tr.insert(RawToken(token.clone()));
1346     }
1347     let mut global = GLOBAL.players.write();
1348     for ((token, player), acctid) in
1349       tokens_players.into_iter()
1350       .zip(acctids_players)
1351     { if_chain!{
1352       if let Some(acctid) = acctid;
1353       let iad = InstanceAccessDetails {
1354         acctid,
1355         gref: gref.clone(),
1356         ident: player,
1357       };
1358       then { global.insert(RawToken(token), iad); }
1359     } }
1360     drop(global);
1361     drop(g);
1362     games.insert(name.clone(), gref.clone());
1363     info!("loadewd {:?}", &name);
1364     Some(gref)
1365   }
1366 }
1367
1368 #[throws(anyhow::Error)]
1369 pub fn load_games(accounts: &mut AccountsGuard,
1370                   games: &mut GamesGuard) {
1371   enum AFState { Found(PathBuf, SaveFileOrDir), Used }
1372   use AFState::*;
1373   use SavefilenameParseResult::*;
1374   let mut a_leaves = HashMap::new();
1375   let save_dir = config().save_dir().clone();
1376   for de in fs::read_dir(&save_dir).context(save_dir)? {
1377     let de = de?;
1378     let leaf = de.file_name();
1379     (||{
1380       let leaf = leaf.as_bytes();
1381       match savefilename_parse(leaf)? {
1382         NotGameFile => {
1383         }
1384         TempToDelete => {
1385           fs::remove_file(de.path())
1386             .context("stale temporary file")?;
1387         }
1388         Auxiliary(fd) => {
1389           a_leaves.entry(leaf.to_owned()).or_insert_with(
1390             || Found(de.path(), fd)
1391           );
1392         }
1393         GameFile { aux_leaves, name } => {
1394           InstanceGuard::load_game(accounts, games, name)?;
1395           for aux_leaf in aux_leaves {
1396             a_leaves.insert(aux_leaf, Used);
1397           }
1398         }
1399       }
1400       <Result<_,anyhow::Error>>::Ok(())
1401     })().with_context(|| format!("leaf={:?}", leaf))?;
1402   }
1403   (||{
1404     for (leaf, state) in &a_leaves {
1405       if let Found(path, fd) = state {
1406         fd.remove(&path)
1407           .with_context(|| format!("leaf={:?}", leaf))?;
1408       }
1409     }
1410     <Result<_,anyhow::Error>>::Ok(())
1411   })().context("cleaning up stale files")?;
1412   info!("loaded games");
1413 }
1414
1415 // ---------- Tokens / TokenTable / AccessId ----------
1416
1417 pub type TokenTable<Id> = HashMap<RawToken, InstanceAccessDetails<Id>>;
1418
1419 pub trait AccessId: Copy + Clone + 'static {
1420   type Error: Into<Fatal>;
1421   const ERROR: Self::Error;
1422   fn global_tokens(_:PrivateCaller) -> &'static RwLock<TokenTable<Self>>;
1423   fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
1424                      -> &mut TokenRegistry<Self>;
1425 }
1426
1427 #[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd)]
1428 #[derive(Serialize,Deserialize)]
1429 #[derive(Error)]
1430 #[error("Player not found")]
1431 pub struct PlayerNotFound;
1432
1433 impl AccessId for PlayerId {
1434   type Error = PlayerNotFound;
1435   const ERROR: PlayerNotFound = PlayerNotFound;
1436   fn global_tokens(_: PrivateCaller) -> &'static RwLock<TokenTable<Self>> {
1437     &GLOBAL.players
1438   }
1439   fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
1440                      -> &mut TokenRegistry<Self> {
1441     &mut ig.tokens_players
1442   }
1443 }
1444 impl AccessId for ClientId {
1445   type Error = Fatal;
1446   const ERROR: Fatal = NoClient;
1447   fn global_tokens(_: PrivateCaller) -> &'static RwLock<TokenTable<Self>> {
1448     &GLOBAL.clients
1449   }
1450   fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
1451                      -> &mut TokenRegistry<Self> {
1452     &mut ig.tokens_clients
1453   }
1454 }
1455
1456 impl RawToken {
1457   fn new_random() -> Self {
1458     let mut rng = thread_rng();
1459     let token = RawToken (
1460       repeat_with(|| rng.sample(Alphanumeric))
1461         .map(char::from)
1462         .take(64).collect()
1463     );
1464     token
1465   }
1466 }
1467
1468 pub fn lookup_token<Id:AccessId>(s: &RawTokenVal)
1469       -> Result<InstanceAccessDetails<Id>, Id::Error> {
1470   Id::global_tokens(PRIVATE_Y).read().get(s).cloned()
1471     .ok_or(Id::ERROR)
1472 }
1473
1474 #[throws(Fatal)]
1475 pub fn record_token<Id:AccessId> (
1476   ig: &mut InstanceGuard,
1477   iad: InstanceAccessDetails<Id>
1478 ) -> RawToken {
1479   let token = RawToken::new_random();
1480   ig.token_register(token.clone(), iad);
1481   token
1482 }
1483
1484 #[throws(E)]
1485 pub fn process_all_players_for_account<
1486     E: Error,
1487     F: FnMut(&mut InstanceGuard<'_>, PlayerId) -> Result<(),E>
1488     >
1489   (games: &mut GamesGuard, acctid: AccountId, mut f: F)
1490 {
1491   for gref in games.values() {
1492     let c = gref.lock_even_destroying();
1493     let remove: Vec<_> = c.g.iplayers.iter().filter_map(|(player,pr)| {
1494       if pr.ipl.acctid == acctid { Some(player) } else { None }
1495     }).collect();
1496     let mut ig = InstanceGuard { gref: gref.clone(), c };
1497     for player in remove.into_iter() {
1498       f(&mut ig, player)?;
1499     }
1500   }
1501 }
1502
1503 // ========== instance pieces data access ==========
1504
1505 impl IPieces {
1506   pub fn get(&self, piece: PieceId) -> Option<&IPiece> {
1507     self.0.get(piece)
1508   }
1509
1510   pub fn as_mut(&mut self, _: ModifyingPieces) -> &mut ActualIPieces {
1511     &mut self.0
1512   }
1513
1514   pub fn is_empty(&self) -> bool {
1515     let IPieces(actual) = self;
1516     actual.is_empty()
1517   }
1518 }
1519
1520 // ---------- gamestate pieces table ----------
1521
1522 impl GPieces {
1523   pub fn get_mut(&mut self, piece: PieceId) -> Option<&mut GPiece> {
1524     self.0.get_mut(piece)
1525   }
1526   pub fn values_mut(&mut self) -> sm::ValuesMut<PieceId, GPiece> {
1527     self.0.values_mut()
1528   }
1529   pub fn as_mut(&mut self, _: ModifyingPieces) -> &mut ActualGPieces {
1530     &mut self.0
1531   }
1532   #[cfg(test)]
1533   pub fn as_mut_t(&mut self) -> &mut ActualGPieces { &mut self.0 }
1534 }
1535
1536 impl ById for GPieces {
1537   type Id = PieceId;
1538   type Entry = GPiece;
1539   type Error = Inapplicable;
1540   #[throws(Ia)]
1541   fn byid(&self, piece: PieceId) -> &GPiece {
1542     self.get(piece).ok_or(Ia::PieceGone)?
1543   }
1544   #[throws(Ia)]
1545   fn byid_mut(&mut self, piece: PieceId) -> &mut GPiece {
1546     self.get_mut(piece).ok_or(Ia::PieceGone)?
1547   }
1548 }
1549
1550 /*impl<'p> IntoIterator for &'p Pieces {
1551   type Item = (PieceId, &'p PieceState);
1552   type IntoIter = sm::Iter<'p, PieceId, PieceState>;
1553   fn into_iter(self) -> Self::IntoIter { (&self.0).into_iter() }
1554 }*/
1555 impl<'p> IntoIterator for &'p mut GPieces {
1556   type Item = (PieceId, &'p mut GPiece);
1557   type IntoIter = sm::IterMut<'p, PieceId, GPiece>;
1558   fn into_iter(self) -> Self::IntoIter { (&mut self.0).into_iter() }
1559 }
1560
1561 // ========== background maintenance ==========
1562
1563 // ---------- delayed game save ----------
1564
1565 impl InstanceGuard<'_> {
1566   pub fn save_game_later(&mut self) {
1567     if self.c.game_dirty { return }
1568     GLOBAL.dirty.lock().push_back(self.gref.clone());
1569     self.c.game_dirty = true;
1570   }
1571
1572   pub fn save_game_and_aux_later(&mut self) {
1573     if self.c.aux_dirty { return }
1574     self.save_game_later();
1575     self.c.aux_dirty = true;
1576   }
1577 }
1578
1579 pub fn game_flush_task() {
1580   let mut inner_queue = VecDeque::new();
1581   loop {
1582     {
1583       mem::swap(&mut inner_queue, &mut *GLOBAL.dirty.lock());
1584     }
1585     thread::sleep(GAME_SAVE_LAG);
1586     for _ in 0..inner_queue.len() {
1587       let ent = inner_queue.pop_front().unwrap();
1588       let mut ig = match ent.lock() { Ok(ig) => ig, _ => continue/*ah well*/ };
1589       if !ig.c.game_dirty { continue }
1590       match ig.save_game_now() {
1591         Ok(_) => {
1592           assert!(!ig.c.game_dirty);
1593         }
1594         Err(e) => {
1595           // todo: notify the players
1596           error!("save error! name={:?}: {}", &ig.name, &e);
1597           mem::drop(ig);
1598           inner_queue.push_back(ent); // oof
1599         }
1600       }
1601     }
1602   }
1603 }
1604
1605 // ---------- client expiry ----------
1606
1607 fn client_expire_old_clients() {
1608   let mut expire = vec![];
1609   let max_age = Instant::now() - MAX_CLIENT_INACTIVITY;
1610
1611   trait ClientIterator {
1612     type Ret;
1613     fn iter<'g>(&mut self, gref: &'g InstanceRef, max_age: Instant)
1614             -> (MutexGuard<'g, InstanceContainer>, Option<Self::Ret>) {
1615       let c = gref.lock_even_destroying();
1616       let ret = 'ret: loop {
1617         for (client, cl) in &c.g.clients {
1618           if cl.lastseen > max_age { continue }
1619           let ret = self.old(client);
1620           if ret.is_some() { break 'ret ret }
1621         }
1622         break 'ret None;
1623       };
1624       (c,ret)
1625     }
1626     fn old(&mut self, client: ClientId) -> Option<Self::Ret>;
1627   }
1628
1629   for gref in GLOBAL.games_table.read().values() {
1630     struct Any;
1631     impl ClientIterator for Any {
1632       type Ret = ();
1633       fn old(&mut self, _client: ClientId) -> Option<()> {
1634         Some(())
1635       }
1636     }
1637     if let (_, Some(())) = Any.iter(gref, max_age) {
1638       expire.push(gref.clone());
1639     }
1640   }
1641   for gref in expire.into_iter() {
1642     #[derive(Debug)]
1643     struct Now(HashSet<ClientId>);
1644     impl ClientIterator for Now {
1645       type Ret = Void;
1646       fn old(&mut self, client: ClientId) -> Option<Void> {
1647         self.0.insert(client);
1648         None
1649       }
1650     }
1651
1652     let mut now = Now(default());
1653     let (mut c, _) = now.iter(&gref, max_age);
1654     c.g.clients.retain(|c,_| !now.0.contains(&c));
1655     let mut gref = InstanceGuard { c, gref: gref.clone() };
1656     debug!("expiring client {:?}", &now);
1657     gref.tokens_deregister_for_id::<ClientId,_>(|c| now.0.contains(&c));
1658   }
1659 }
1660
1661 pub fn client_periodic_expiry() {
1662   loop {
1663     sleep(MAX_CLIENT_INACTIVITY);
1664     client_expire_old_clients();
1665   }
1666 }
1667
1668 // ---------- log expiry ----------
1669
1670 fn global_expire_old_logs() {
1671   let cutoff = Timestamp(Timestamp::now().0 - MAX_LOG_AGE.as_secs());
1672
1673   let mut want_expire = vec![];
1674
1675   let read = GLOBAL.games_table.read();
1676   for gref in read.values() {
1677     if gref.lock_even_destroying().g.gs.want_expire_some_logs(cutoff) {
1678       want_expire.push(gref.clone())
1679     }
1680   }
1681   drop(read);
1682
1683   for gref in want_expire.into_iter() {
1684     let mut g = gref.lock_even_destroying();
1685     info!("expiring old log entries in {:?}", &g.g.name);
1686     g.g.gs.do_expire_old_logs(cutoff);
1687   }
1688 }
1689
1690 pub fn logs_periodic_expiry() {
1691   loop {
1692     sleep(MAX_LOG_AGE / 10);
1693     global_expire_old_logs();
1694   }
1695 }