#![allow(clippy::let_and_return)]
use crate::prelude::*;
use slotmap::dense as sm;
visible_slotmap_key!{ ClientId(b'C') }
const MAX_CLIENT_INACTIVITY: Duration = Duration::from_secs(200);
const GAME_SAVE_LAG: Duration = Duration::from_millis(500);
const MAX_LOG_AGE: Duration = Duration::from_secs(10 * 86400);
#[derive(Hash,Ord,PartialOrd,Eq,PartialEq,Serialize)]
#[repr(transparent)]
pub struct RawTokenVal(str);
#[derive(Clone,Debug,Hash,Eq,PartialEq,Ord,PartialOrd)]
#[derive(Serialize,Deserialize)]
pub struct InstanceName {
pub account: AccountName,
pub game: String,
}
#[derive(Debug,Clone)]
pub struct InstanceRef(Arc<InstanceOuter>);
#[derive(Debug,Clone)]
pub struct InstanceWeakRef(std::sync::Weak<InstanceOuter>);
#[derive(Debug)]
pub struct InstanceOuter {
c: Mutex<InstanceContainer>,
b: Mutex<InstanceBundles>,
}
#[derive(Debug,Clone,Serialize,Deserialize,Default)]
#[derive(Deref,DerefMut)]
#[serde(transparent)]
pub struct LinksTable(pub EnumMap<LinkKind, Option<String>>);
pub struct Instance {
pub name: Arc<InstanceName>,
pub gs: GameState,
pub ipieces: IPieces,
pub pcaliases: PieceAliases,
pub ioccults: IOccults,
pub clients: DenseSlotMap<ClientId, Client>,
pub iplayers: SecondarySlotMap<PlayerId, PlayerRecord>,
pub tokens_players: TokenRegistry<PlayerId>,
pub tokens_clients: TokenRegistry<ClientId>,
pub acl: LoadedAcl<TablePermission>,
pub links: Arc<LinksTable>,
pub bundle_list: MgmtBundleList, pub bundle_specs: bundles::SpecsInBundles,
pub bundle_hashes: bundles::HashCache,
pub asset_url_key: AssetUrlKey,
pub local_libs: shapelib::Registry,
pub ifastsplits: IFastSplits,
}
pub struct PlayerRecord {
pub u: PlayerUpdates,
pub ipl: IPlayer,
pub account: Arc<AccountName>,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct IPlayer { pub acctid: AccountId,
pub tokens_revealed: HashMap<TokenRevelationKey, TokenRevelationValue>,
pub tz: Timezone,
}
#[derive(Debug,Serialize,Deserialize)]
#[derive(Deref)]
pub struct IPiece {
#[deref] pub p: IPieceTraitObj,
pub occilk: Option<IOccultIlk>,
#[serde(default)] pub special: PieceSpecialProperties,
}
#[derive(Debug,Serialize,Deserialize)]
#[serde(transparent)]
pub struct IPieces(ActualIPieces);
pub type ActualIPieces = SecondarySlotMap<PieceId, IPiece>;
#[derive(Copy,Clone,Debug)]
pub struct ModifyingPieces(());
#[derive(Debug,Serialize,Deserialize,Default)]
pub struct IOccults {
pub ilks: OccultIlks,
}
#[derive(Debug,Serialize,Deserialize,Default)]
#[derive(Deref)] #[serde(transparent)]
pub struct GPieces(pub(in crate::global) ActualGPieces);
type ActualGPieces = DenseSlotMap<PieceId, GPiece>;
#[derive(Debug)]
pub struct Client {
pub player: PlayerId,
pub lastseen: Instant,
}
pub type BundlesGuard<'b> = MutexGuard<'b, InstanceBundles>;
#[derive(Debug)]
pub struct InstanceGuard<'g> {
pub c: MutexGuard<'g, InstanceContainer>,
pub gref: InstanceRef,
}
#[derive(Debug,Default)]
pub struct TokenRegistry<Id: AccessId> {
tr: HashSet<RawToken>,
id: PhantomData<Id>,
}
#[derive(Clone,Debug)]
pub struct InstanceAccessDetails<Id> {
pub gref: InstanceRef,
pub ident: Id,
pub acctid: AccountId,
}
lazy_static! {
pub static ref GLOBAL: Global = default();
}
#[derive(Default)]
pub struct Global {
games_table: RwLock<GamesTable>,
dirty: Mutex<VecDeque<InstanceRef>>,
players: RwLock<TokenTable<PlayerId>>,
clients: RwLock<TokenTable<ClientId>>,
}
pub type GamesGuard = RwLockWriteGuard<'static, GamesTable>;
pub type GamesTable = HashMap<Arc<InstanceName>, InstanceRef>;
#[derive(Debug)]
pub struct InstanceContainer {
live: bool,
game_dirty: bool,
aux_dirty: bool,
g: Instance,
}
#[derive(Debug,Default,Serialize,Deserialize)]
struct InstanceSaveAuxiliary<RawTokenStr, PiecesLoadedRef, OccultIlksRef,
PieceAliasesRef, IFastSplitsRef> {
ipieces: PiecesLoadedRef,
ioccults: OccultIlksRef,
pcaliases: PieceAliasesRef,
tokens_players: Vec<(RawTokenStr, PlayerId)>,
aplayers: SecondarySlotMap<PlayerId, IPlayer>,
acl: Acl<TablePermission>,
pub links: Arc<LinksTable>,
asset_url_key: AssetUrlKey,
#[serde(default)] pub bundle_hashes: bundles::HashCache,
#[serde(default)] ifastsplits: IFastSplitsRef,
}
pub struct PrivateCaller(());
const PRIVATE_Y: PrivateCaller = PrivateCaller(());
impl<'s> From<&'s str> for &'s RawTokenVal {
fn from(s: &str) -> &RawTokenVal { unsafe { mem::transmute(s) } }
}
impl Borrow<RawTokenVal> for RawToken {
fn borrow(&self) -> &RawTokenVal { (&*self.0).into() }
}
impl Debug for RawTokenVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
crate::spec::imp::raw_token_debug_as_str(&self.0, f)
}
}
impl Debug for Instance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Instance {{ name: {:?}, .. }}", &self.name)
}
}
impl ModifyingPieces {
pub fn allow_without_necessarily_saving() -> ModifyingPieces {
ModifyingPieces(())
}
}
impl InstanceRef {
#[throws(GameBeingDestroyed)]
pub fn lock(&self) -> InstanceGuard<'_> {
let c = self.0.c.lock();
if !c.live { throw!(GameBeingDestroyed) }
InstanceGuard { c, gref: self.clone() }
}
pub fn lock_even_destroying(&self) -> MutexGuard<InstanceContainer> {
self.0.c.lock()
}
pub fn downgrade_to_weak(&self) -> InstanceWeakRef {
InstanceWeakRef(Arc::downgrade(&self.0))
}
pub fn lock_bundles(&self) -> MutexGuard<'_, InstanceBundles> {
self.0.b.lock()
}
}
impl InstanceWeakRef {
pub fn upgrade(&self) -> Option<InstanceRef> {
Some(InstanceRef(self.0.upgrade()?))
}
}
#[ext(pub)]
impl<A> Unauthorised<InstanceRef, A> {
#[throws(GameBeingDestroyed)]
fn lock<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
let must_not_escape = self.by_ref(Authorisation::promise_any());
Unauthorised::of(must_not_escape.lock()?)
}
fn lock_even_destroying<'r>(&'r self) -> Unauthorised<InstanceGuard<'r>, A> {
let must_not_escape = self.by_ref(Authorisation::promise_any());
Unauthorised::of(InstanceGuard {
c: must_not_escape.lock_even_destroying(),
gref: must_not_escape.clone(),
})
}
fn lock_bundles<'r>(&'r self) -> Unauthorised<BundlesGuard<'_>, A> {
let must_not_escape = self.by_ref(Authorisation::promise_any());
Unauthorised::of(must_not_escape.lock_bundles())
}
}
impl Instance {
#[allow(clippy::new_ret_no_self)]
#[throws(MgmtError)]
pub fn new(name: InstanceName, gs: GameState,
games: &mut GamesGuard,
acl: LoadedAcl<TablePermission>, _: Authorisation<InstanceName>)
-> InstanceRef {
let name = Arc::new(name);
let g = Instance {
name: name.clone(),
gs, acl,
ipieces: IPieces(default()),
pcaliases: default(),
ioccults: default(),
clients: default(),
iplayers: default(),
tokens_players: default(),
tokens_clients: default(),
links: default(),
bundle_list: default(),
bundle_specs: default(),
bundle_hashes: default(),
asset_url_key: AssetUrlKey::new_random()?,
local_libs: default(),
ifastsplits: default(),
};
let c = InstanceContainer {
live: true,
game_dirty: false,
aux_dirty: false,
g,
};
let c = Mutex::new(c);
let b = Mutex::new(InstanceBundles::new());
let gref = InstanceRef(Arc::new(InstanceOuter { c, b }));
let mut ig = gref.lock()?;
let entry = games.entry(name);
use hash_map::Entry::*;
let entry = match entry {
Vacant(ve) => ve,
Occupied(_) => throw!(ME::AlreadyExists),
};
ig.save_aux_now()?;
ig.save_game_now()?;
(||{
entry.insert(gref.clone());
})();
ig.gref
}
#[throws(MgmtError)]
pub fn lookup_by_name_locked_unauth
(games_table: &GamesTable, name: &InstanceName)
-> Unauthorised<InstanceRef, InstanceName>
{
Unauthorised::of(
games_table
.get(name)
.ok_or(ME::GameNotFound)?
.clone()
)
}
#[throws(MgmtError)]
pub fn lookup_by_name_locked(games: &GamesTable,
name: &InstanceName,
auth: Authorisation<InstanceName>)
-> InstanceRef {
Self::lookup_by_name_locked_unauth(games, name)?.by(auth)
}
#[throws(MgmtError)]
pub fn lookup_by_name_unauth(name: &InstanceName)
-> Unauthorised<InstanceRef, InstanceName>
{
let games = GLOBAL.games_table.read();
Self::lookup_by_name_locked_unauth(&games, name)?
}
#[throws(MgmtError)]
pub fn lookup_by_name(name: &InstanceName, auth: Authorisation<InstanceName>)
-> InstanceRef {
Self::lookup_by_name_unauth(name)?.by(auth)
}
#[throws(InternalError)]
pub fn destroy_game(games: &mut GamesGuard,
mut g: MutexGuard<InstanceContainer>,
_: Authorisation<InstanceName>) {
let a_savefile = savefilename(&g.g.name, "a-", "");
let b_dir = bundles::b_dir(&g.g.name);
let g_file = savefilename(&g.g.name, "g-", "");
fs::remove_file(&g_file).context("remove").context(g_file)?;
(||{ g.live = false;
games.remove(&g.g.name);
InstanceGuard::forget_all_tokens(&mut g.g.tokens_clients);
InstanceGuard::forget_all_tokens(&mut g.g.tokens_players);
InstanceBundles::truncate_all_besteffort(&g.g.name);
fn best_effort<F>(rm: F, path: &str, desc: &str)
where F: FnOnce(&str) -> Result<(), io::Error>
{
rm(path)
.unwrap_or_else(
|e| warn!("failed to delete stale {} {:?}: {:?}",
desc, path, e)
);
}
best_effort(|f| fs::remove_file(f), &a_savefile, "auth file");
best_effort(|f| fs::remove_dir_all(f), &b_dir, "bundles dir");
})(); }
pub fn list_names(account: Option<&AccountName>,
_: Authorisation<AccountName>)
-> Vec<Arc<InstanceName>> {
let games = GLOBAL.games_table.read();
let out: Vec<Arc<InstanceName>> =
games.keys()
.filter(|k| account == None || account == Some(&k.account))
.cloned()
.collect();
out
}
#[throws(InternalError)]
pub fn player_info_pane(&self) -> Html {
#[derive(Serialize,Debug)]
struct RenderContext<'r> {
players: Vec<RenderPlayer<'r>>,
}
#[derive(Serialize,Debug)]
struct RenderPlayer<'r> {
player_num: u32,
nick: &'r str,
account: &'r AccountName,
}
let players = self.gs.players.iter().filter_map(|(player, gpl)| {
let ipl = self.iplayers.get(player)?;
let (idx, _) = player.data().get_idx_version();
Some(RenderPlayer {
player_num: idx,
nick: &gpl.nick,
account: &ipl.account,
})
}).collect::<Vec<_>>();
let render = RenderContext { players };
let html = Html::from_html_string(
nwtemplates::render("player-info-pane.tera", &render)
.context("render player info template")?
);
html
}
pub fn dummy() -> Instance {
Instance {
name: Arc::new("server::".parse().unwrap()),
gs: GameState::dummy(),
ipieces: IPieces(default()),
pcaliases: default(),
ioccults: default(),
clients: default(),
tokens_players: default(),
tokens_clients: default(),
acl: default(),
links: default(),
bundle_list: default(),
bundle_specs: default(),
bundle_hashes: default(),
asset_url_key: AssetUrlKey::Dummy,
local_libs: default(),
iplayers: default(),
ifastsplits: default(),
}
}
}
pub fn games_lock() -> RwLockWriteGuard<'static, GamesTable> {
GLOBAL.games_table.write()
}
deref_to_field_mut!{InstanceGuard<'_>, Instance, c.g}
impl FromStr for AccountScope {
type Err = InvalidScopedName;
#[throws(InvalidScopedName)]
fn from_str(s: &str) -> Self {
let scope = AccountScope::parse_name(s, &mut [])?;
scope
}
}
impl FromStr for InstanceName {
type Err = InvalidScopedName;
#[throws(InvalidScopedName)]
fn from_str(s: &str) -> Self {
let mut names: [_;2] = default();
let scope = AccountScope::parse_name(s, &mut names)?;
let [subaccount, game] = names;
InstanceName {
account: AccountName { scope, subaccount },
game,
}
}
}
impl Display for InstanceName {
#[throws(fmt::Error)]
fn fmt(&self, f: &mut fmt::Formatter) {
self.account.scope.display_name(
&[ self.account.subaccount.as_str(), self.game.as_str() ],
|s| f.write_str(s),
)?
}
}
hformat_as_display!{InstanceName}
impl DebugIdentify for InstanceContainer {
#[throws(fmt::Error)]
fn debug_identify(&self, f: &mut fmt::Formatter) {
write!(f, "InstanceContainer({})", &self.g.name)?;
}
#[throws(fmt::Error)]
fn debug_identify_type(f: &mut fmt::Formatter) {
write!(f, "InstanceContainer")?;
}
}
impl DebugIdentify for InstanceRef {
#[throws(fmt::Error)]
fn debug_identify_type(f: &mut fmt::Formatter) {
write!(f, "InstanceRef")?;
}
}
fn link_a_href(k: &HtmlStr, v: &str) -> Html {
hformat!("<a href={}>{}</a>", v, k)
}
#[ext(pub)]
impl (LinkKind, &str) {
fn to_html(self) -> Html {
let (k, v) = self;
link_a_href(&k.to_html(), v)
}
}
impl From<&LinksTable> for Html {
fn from(links: &LinksTable) -> Html {
let mut s = links.iter()
.filter_map(|(k,v)| {
let v = v.as_ref()?;
Some((k, v.as_str()).to_html())
})
.chain(iter::once(
link_a_href(Html::lit("Shapelib").into(), "/_/shapelib.html")
))
.collect::<Vec<Html>>()
.iter()
.hjoin(&Html::lit(" "));
if s.len() != 0 { s = hformat!("links: {}", s) }
s
}
}
impl<Id> InstanceAccessDetails<Id>
where Id: AccessId, Fatal: From<Id::Error>
{
#[throws(Fatal)]
pub fn from_token(token: &RawTokenVal) -> InstanceAccessDetails<Id> {
let g = Id::global_tokens(PRIVATE_Y).read();
let i = g.get(token).ok_or(Id::ERROR)?;
i.clone()
}
}
impl<'ig> InstanceGuard<'ig> {
#[throws(IE)]
pub fn delete_piece<H,T>(&mut self, modperm: ModifyingPieces,
to_permute: &mut ToRecalculate,
piece: PieceId, hook: H)
-> (T, PieceUpdateOp<(),()>, UnpreparedUpdates)
where H: FnOnce(&IOccults, &GOccults,
Option<&IPiece>, Option<&GPiece>) -> T
{
let ig = &mut **self;
let gs = &mut ig.gs;
let gpc = gs.pieces.as_mut(modperm).get_mut(piece);
let mut xupdates = vec![];
if let Some(gpc) = gpc {
gpc.occult.passive_delete_hook(&mut gs.occults, piece);
if gpc.occult.is_active() {
xupdates.append(
&mut
remove_occultation(
&mut gs.gen.unique_gen(),
&mut gs.players,
&mut gs.pieces,
&mut gs.occults,
&ig.ipieces,
&ig.ioccults,
to_permute,
piece)?
);
}
}
let ioccults = &ig.ioccults;
let gpc = gs.pieces.as_mut(modperm).remove(piece);
let ipc = ig.ipieces.as_mut(modperm).remove(piece);
let hook_r = hook(ioccults, &gs.occults,
ipc.as_ref(), gpc.as_ref());
if let Some(ipc) = ipc {
if let Some(gpc) = gpc {
ipc.p.into_inner().delete_hook(&gpc, gs);
}
if let Some(occilk) = ipc.occilk {
ig.ioccults.ilks.dispose_iilk(occilk);
}
}
(hook_r, PieceUpdateOp::Delete(), xupdates.into_unprepared(None))
}
#[throws(MgmtError)]
pub fn player_new(&mut self, gnew: GPlayer, inew: IPlayer,
account: Arc<AccountName>, logentry: LogEntry)
-> (PlayerId, PreparedUpdateEntry, LogEntry) {
self.check_new_nick(&gnew.nick)?;
if self.c.g.iplayers.values().any(|r| r.ipl.acctid == inew.acctid) {
Err(ME::AlreadyExists)?;
}
let player = self.c.g.gs.players.insert(gnew);
let u = PlayerUpdates::start(&self.c.g.gs).for_player();
let record = PlayerRecord { u, account, ipl: inew, };
self.c.g.iplayers.insert(player, record);
let update = (||{
let update = self.prepare_set_player_update(player)?;
self.save_game_now()?;
self.save_aux_now()?;
Ok::<_,InternalError>(update)
})().map_err(|e|{
self.c.g.iplayers.remove(player);
self.c.g.gs.players.remove(player);
e
})?;
(||{
})(); (player, update, logentry)
}
#[throws(MgmtError)]
pub fn check_new_nick(&mut self, new_nick: &str) {
if self.c.g.gs.players.values().any(|old| old.nick == new_nick) {
Err(ME::NickCollision)?;
}
}
#[throws(IE)]
pub fn prepare_set_player_update(&self, player: PlayerId)
-> PreparedUpdateEntry {
let new_info_pane = Arc::new(self.player_info_pane()?);
PreparedUpdateEntry::SetPlayer {
player, new_info_pane,
data: DataLoadPlayer::from_player(self, player),
}
}
pub fn remove_clients(&mut self,
players: &HashSet<PlayerId>,
signal: ErrorSignaledViaUpdate<PUE_P, String>) {
let mut clients_to_remove = HashSet::new();
self.clients.retain(|k,v| {
let remove = players.contains(&v.player);
if remove { clients_to_remove.insert(k); }
!remove
});
let gen = self.c.g.gs.gen;
for &player in players {
if let Some(iplayer) = self.iplayers.get_mut(player) {
iplayer.u.push(PreparedUpdate {
gen,
when: Instant::now(),
us: vec![ PreparedUpdateEntry::Error(
signal.clone(),
)],
});
};
}
self.tokens_deregister_for_id(|id| clients_to_remove.contains(&id));
}
pub fn players_remove(&mut self, old_players_set: &HashSet<PlayerId>)
->
Result<Vec<
(Option<GPlayer>, Option<IPlayer>, PreparedUpdateEntry)
>, InternalError>
{
let mut players = self.c.g.gs.players.clone();
let old_players: Vec<_> = old_players_set.iter().cloned().collect();
let old_gpls: Vec<_> = old_players.iter().cloned().map(|oldplayer| {
players.remove(oldplayer)
}).collect();
let mut gs = GameState {
table_colour: self.c.g.gs.table_colour.clone(),
table_size: self.c.g.gs.table_size,
gen: self.c.g.gs.gen,
max_z: self.gs.max_z.clone(),
players,
log: default(),
pieces: default(),
occults: default(),
};
let held_by_old = |p: &GPiece| if_chain! {
if let Some(held) = p.held;
if old_players_set.contains(&held);
then { true }
else { false }
};
let mut updated_pieces = vec![];
let mut undo: Vec<Box<dyn FnOnce(&mut InstanceGuard)>> = vec![];
for (piece,p) in &mut self.c.g.gs.pieces {
if held_by_old(p) {
p.held = None;
updated_pieces.push(piece);
}
}
undo.push(Box::new(|ig| for &piece in &updated_pieces {
(||Some({
held_by_old(ig.c.g.gs.pieces.get_mut(piece)?);
}))();
}));
let mut swap_things = |ig: &mut InstanceGuard| {
mem::swap(&mut ig.c.g.gs.log, &mut gs.log );
mem::swap(&mut ig.c.g.gs.pieces, &mut gs.pieces);
mem::swap(&mut ig.c.g.gs.occults,&mut gs.occults);
mem::swap(&mut ig.c.g.gs, &mut gs, );
};
swap_things(self);
undo.push(Box::new(swap_things));
let new_info_pane = Arc::new(self.player_info_pane()?);
self.save_game_now().map_err(|e|{
#[allow(clippy::iter_with_drain)] for u in undo.drain(..).rev() {
u(self);
}
e
})?;
mem::drop(undo);
let old_ipls = (||{
for &piece in &updated_pieces {
(||Some({
self.c.g.gs.pieces.get_mut(piece)?.gen = self.c.g.gs.gen;
}))();
}
let estimate = updated_pieces.len() + 1;
let mut buf = PrepareUpdatesBuffer::new(self, Some(estimate));
for &piece in &updated_pieces {
buf.piece_update(piece, &None, PieceUpdateOp::Modify(()).into());
}
buf.finish();
self.remove_clients(old_players_set, ESVU::PlayerRemoved);
self.tokens_deregister_for_id(
|id:PlayerId| old_players_set.contains(&id)
);
let old_ipls: Vec<_> = old_players.iter().cloned().map(
|oldplayer| self.iplayers.remove(oldplayer)
.map(|ipr| ipr.ipl)
).collect();
self.save_aux_now().unwrap_or_else(
|e| warn!(
"trouble garbage collecting accesses for deleted player: {:?}",
&e)
);
old_ipls
})();
let updates = old_players.iter().cloned().map(
|player| PreparedUpdateEntry::RemovePlayer {
player,
new_info_pane: new_info_pane.clone(),
}
);
let old = izip!(
old_gpls,
old_ipls,
updates
).collect();
Ok(old)
}
#[throws(InternalError)]
pub fn invalidate_tokens(&mut self, player: PlayerId) {
let old_tokens = TokenRegistry {
tr: self.tokens_players.tr.clone(),
id: self.tokens_players.id,
};
self.tokens_deregister_for_id(|id:PlayerId| id==player);
self.save_aux_now().map_err(|e|{
self.tokens_players = old_tokens;
e
})?;
(||{
self.remove_clients(&[player].iter().cloned().collect(),
ESVU::TokenRevoked);
})(); }
#[throws(MgmtError)]
fn player_access_reset_redeliver(&mut self,
accounts: &mut AccountsGuard,
player: PlayerId,
_auth: Authorisation<AccountName>,
reset: bool)
-> AccessTokenReport {
let acctid = self.iplayers.byid(player)?.ipl.acctid;
let access = {
let (acct, _) = accounts.lookup(acctid)?;
let access = acct.access.clone();
let desc = access.describe_html();
let now = Timestamp::now();
let revk = TokenRevelationKey {
account: (*acct.account).clone(),
desc,
};
self.iplayers.byid_mut(player)?.ipl.tokens_revealed.entry(revk)
.or_insert(TokenRevelationValue {
latest: now,
earliest: now,
})
.latest = now;
access
};
let current_tokens: ArrayVec<&RawToken,2> = {
let players = GLOBAL.players.read();
self.tokens_players.tr.iter().
filter(|&token| (||{
let iad = players.get(token)?;
if iad.ident != player { return None }
if ! Arc::ptr_eq(&iad.gref.0, &self.gref.0) { return None }
Some(())
})() == Some(()))
.take(2)
.collect()
};
let reset = reset || current_tokens.is_empty();
let token: RawToken = if reset {
drop(current_tokens);
self.invalidate_tokens(player)?;
self.save_aux_now()?;
let token = access
.override_token()
.cloned()
.unwrap_or_else(||{
RawToken::new_random()
});
let iad = InstanceAccessDetails {
gref: self.gref.clone(),
ident: player,
acctid
};
self.token_register(token.clone(), iad);
self.save_aux_now()?;
token
} else {
let token = match current_tokens.as_slice() {
[] => panic!(), [token] => token,
_ => {
warn!("duplicate token for {}", player);
throw!(ME::ServerFailure("duplicate token".to_string()));
}
};
(*token).clone()
};
let ipl = &self.c.g.iplayers.byid(player)?.ipl;
let gpl = self.c.g.gs.players.byid(player)?;
let url = format!("{}/?{}",
&config().public_url.trim_end_matches('/'),
token.0);
let info = AccessTokenInfo { url };
let report = access.deliver(accounts, &self.c.g, gpl, ipl, info)?;
report
}
#[throws(MgmtError)]
pub fn player_access_reset(&mut self,
accounts: &mut AccountsGuard,
player: PlayerId,
auth: Authorisation<AccountName>)
-> AccessTokenReport {
self.player_access_reset_redeliver(accounts, player, auth, true)?
}
#[throws(MgmtError)]
pub fn player_access_redeliver(&mut self,
accounts: &mut AccountsGuard,
player: PlayerId,
auth: Authorisation<AccountName>)
-> AccessTokenReport {
self.player_access_reset_redeliver(accounts, player, auth, false)?
}
pub fn modify_pieces(&mut self) -> ModifyingPieces {
self.save_game_and_aux_later();
ModifyingPieces(())
}
pub fn modify_pieces_not_necessarily_saving_aux(&mut self)
-> ModifyingPieces {
self.save_game_later();
ModifyingPieces(())
}
fn token_register<Id:AccessId>(
&mut self,
token: RawToken,
iad: InstanceAccessDetails<Id>
) {
Id::tokens_registry(&mut self.c.g, PRIVATE_Y).tr.insert(token.clone());
Id::global_tokens(PRIVATE_Y).write().insert(token, iad);
}
fn forget_all_tokens<Id:AccessId>(tokens: &mut TokenRegistry<Id>) {
let global: &RwLock<TokenTable<Id>> = AccessId::global_tokens(PRIVATE_Y);
let mut global = global.write();
for t in tokens.tr.drain() { global.remove(&t); }
}
fn tokens_deregister_for_id<Id:AccessId, F: Fn(Id) -> bool
> (&mut self, oldid: F) {
let mut tokens = AccessId::global_tokens(PRIVATE_Y).write();
tokens.retain(|k,v| if_chain! {
if oldid(v.ident);
if Id::tokens_registry(self, PRIVATE_Y).tr.remove(k);
then { false }
else { true }
});
}
}
#[derive(Copy,Clone,Debug)]
enum SaveFileOrDir { File, Dir }
impl SaveFileOrDir {
#[throws(io::Error)]
fn remove<P:AsRef<std::path::Path>>(self, path: P) {
match self {
SaveFileOrDir::File => fs::remove_file(path)?,
SaveFileOrDir::Dir => fs::remove_dir_all(path)?,
}
}
}
#[derive(Debug)]
enum SavefilenameParseResult {
NotGameFile,
Auxiliary(SaveFileOrDir),
TempToDelete,
GameFile {
aux_leaves: Vec<Vec<u8>>,
name: InstanceName,
},
}
pub fn savefilename(name: &InstanceName, prefix: &str, suffix: &str)
-> String {
[ config().save_dir().as_str(), "/", prefix ]
.iter().map(Deref::deref)
.chain(iter::once( name.to_string().as_str() ))
.chain([ suffix ].iter().map(Deref::deref))
.collect()
}
#[throws(anyhow::Error)]
fn savefilename_parse(leaf: &[u8]) -> SavefilenameParseResult {
use SavefilenameParseResult::*;
if leaf.starts_with(b"a-") { return Auxiliary(SaveFileOrDir::File) }
if leaf.starts_with(b"b-") { return Auxiliary(SaveFileOrDir::Dir ) }
let rhs = match leaf.strip_prefix(b"g-") {
Some(rhs) => rhs,
None => return NotGameFile,
};
let after_ftype_prefix = rhs;
let rhs = str::from_utf8(rhs)?;
let rcomp = rhs.rsplitn(2, ':').next().unwrap();
if rcomp.find('.').is_some() { return TempToDelete }
let name = InstanceName::from_str(rhs)?;
let aux_leaves = [ b"a-", b"b-" ].iter().map(|prefix| {
let mut s: Vec<_> = (prefix[..]).into(); s.extend(after_ftype_prefix); s
}).collect();
GameFile { name, aux_leaves }
}
impl InstanceGuard<'_> {
#[throws(InternalError)]
fn save_something(
&self, prefix: &str,
w: fn(s: &Self, w: &mut BufWriter<fs::File>)
-> Result<(),rmp_serde::encode::Error>
) {
let tmp = savefilename(&self.name, prefix,".tmp");
let f = fs::File::create(&tmp)
.with_context(||format!("save: create {:?}",&tmp))?;
let mut f = BufWriter::new(f);
w(self, &mut f)?;
f.flush()
.with_context(||format!("save: flush {:?}",&tmp))?;
drop(
f.into_inner().map_err(|e| { let e: io::Error = e.into(); e })
.with_context(||format!("save: close {:?}",&tmp))?
);
let out = savefilename(&self.name, prefix,"");
fs::rename(&tmp, &out).context("install")
.with_context(||format!("save: install {:?} as {:?}", &tmp, &out))?;
debug!("saved to {}", &out);
}
#[throws(InternalError)]
pub fn save_game_now(&mut self) {
if self.c.aux_dirty {
self.save_aux_now()?;
}
self.save_something("g-", |s,w| {
rmp_serde::encode::write_named(w, &s.c.g.gs)
})?;
self.c.game_dirty = false;
debug!("saved (now) {}", &self.name);
}
#[throws(InternalError)]
pub fn save_aux_now(&mut self) {
self.save_something("a-", |s, w| {
let ipieces = &s.c.g.ipieces;
let ioccults = &s.c.g.ioccults;
let pcaliases = &s.c.g.pcaliases;
let ifastsplits = &s.c.g.ifastsplits;
let tokens_players: Vec<(&str, PlayerId)> = {
let global_players = GLOBAL.players.read();
s.c.g.tokens_players.tr
.iter()
.filter_map(|token|
global_players.get(token)
.map(|player| (token.0.as_str(), player.ident)))
.collect()
};
let aplayers = s.c.g.iplayers.iter().map(
|(player, PlayerRecord { ipl, .. })|
(player, ipl.clone())
).collect();
let acl = s.c.g.acl.clone().into();
let links = s.c.g.links.clone();
let asset_url_key = s.c.g.asset_url_key.clone();
let bundle_hashes = s.c.g.bundle_hashes.clone();
let isa = InstanceSaveAuxiliary {
ipieces, ioccults, tokens_players, aplayers, acl, links,
pcaliases, asset_url_key, bundle_hashes, ifastsplits,
};
rmp_serde::encode::write_named(w, &isa)
})?;
self.c.aux_dirty = false;
info!("saved aux for {}", &self.name);
}
#[throws(InternalError)]
fn load_something<T:DeserializeOwned>(name: &InstanceName, prefix: &str)
-> T {
let inp = savefilename(name, prefix, "");
let f = fs::File::open(&inp).with_context(|| inp.clone())?;
let mut f = BufReader::new(f);
let thing = rmp_serde::decode::from_read(&mut f)?;
debug!("loaded from {:?}", &inp);
thing
}
#[throws(StartupError)]
fn load_game(accounts: &AccountsGuard,
games: &mut GamesGuard,
name: InstanceName) -> Option<InstanceRef> {
let InstanceSaveAuxiliary::
<String,ActualIPieces,IOccults,PieceAliases,IFastSplits> {
tokens_players, mut ipieces, mut ioccults, mut aplayers, acl, links,
pcaliases, asset_url_key, bundle_hashes, mut ifastsplits,
} = match Self::load_something(&name, "a-") {
Ok(data) => data,
Err(e) => if (||{
let ae = match &e { InternalError::Anyhow(ae) => Some(ae), _=>None }?;
let ioe = ae.downcast_ref::<io::Error>()?;
let is_enoent = ioe.kind() == io::ErrorKind::NotFound;
is_enoent.as_option()
})().is_some() {
return None;
} else {
throw!(e);
},
};
let mut gs: GameState = Self::load_something(&name, "g-")?;
fn discard_mismatches<K:slotmap::Key, V1, V2>(
primary: &mut DenseSlotMap<K, V1>,
secondary: &mut SecondarySlotMap<K, V2>,
) {
primary.retain(|k,_v| secondary.contains_key(k));
secondary.retain(|k,_v| primary.contains_key(k));
}
for (piece, gpc) in &mut gs.pieces.0 {
if_let!{ Some(fsid) = gpc.fastsplit; else continue; }
if_chain!{
let ilks = &mut ioccults.ilks;
if let Some(recovered_ipc) = ifastsplits.recover_ipc(ilks, fsid);
then {
if_chain!{
if let Some(old_ipc) = ipieces.get_mut(piece);
if let Some(old_iilk) = old_ipc.occilk.take();
then { ilks.dispose_iilk(old_iilk); }
}
ipieces.insert(piece, recovered_ipc);
} else {
ipieces.remove(piece);
}
}
}
ifastsplits.cleanup(&mut ioccults.ilks);
discard_mismatches(&mut gs.players, &mut aplayers);
discard_mismatches(&mut gs.pieces.0, &mut ipieces);
let pu_bc = PlayerUpdates::start(&gs);
let iplayers: SecondarySlotMap<PlayerId, PlayerRecord> = {
let a = aplayers;
a.into_iter()
}.filter_map(|(player, ipl)| {
let u = pu_bc.for_player();
let account = accounts.lookup(ipl.acctid).ok()?.0.account.clone();
Some((player, PlayerRecord { u, ipl, account }))
}).collect();
for mut p in gs.pieces.values_mut() {
p.lastclient = default();
if let Some(held) = p.held {
if !gs.players.contains_key(held) { p.held = None }
}
}
let name = Arc::new(name);
let (tokens_players, acctids_players) = {
let mut tokens = Vec::with_capacity(tokens_players.len());
let mut acctids = Vec::with_capacity(tokens_players.len());
for (token, player) in tokens_players { if_chain! {
if let Some(record) = iplayers.get(player);
then {
tokens.push((token, player));
acctids.push(record.ipl.acctid);
}
}}
(tokens, accounts.bulk_check(&acctids))
};
let mut g = Instance {
gs, iplayers, links,
acl: acl.into(),
ipieces: IPieces(ipieces),
pcaliases,
ioccults,
name: name.clone(),
clients: default(),
tokens_clients: default(),
tokens_players: default(),
bundle_list: default(), local_libs: default(), bundle_specs: default(), asset_url_key,
bundle_hashes,
ifastsplits,
};
let b = InstanceBundles::reload_game_bundles(&mut g)?;
let b = Mutex::new(b);
let c = InstanceContainer {
live: true,
game_dirty: false,
aux_dirty: false,
g,
};
let c = Mutex::new(c);
let gref = InstanceRef(Arc::new(InstanceOuter { c, b }));
let mut g = gref.lock().unwrap();
let ig = &mut *g;
for (piece, ipc) in ig.ipieces.0.iter() {
ipc.direct_trait_access()
.save_reloaded_hook(piece, &mut ig.gs, &gref)?;
}
for (token, _) in &tokens_players {
g.tokens_players.tr.insert(RawToken(token.clone()));
}
let mut global = GLOBAL.players.write();
for ((token, player), acctid) in
tokens_players.into_iter()
.zip(acctids_players)
{ if_chain!{
if let Some(acctid) = acctid;
let iad = InstanceAccessDetails {
acctid,
gref: gref.clone(),
ident: player,
};
then { global.insert(RawToken(token), iad); }
} }
drop(global);
drop(g);
games.insert(name.clone(), gref.clone());
info!("loadewd {:?}", &name);
Some(gref)
}
}
#[throws(anyhow::Error)]
pub fn load_games(accounts: &mut AccountsGuard,
games: &mut GamesGuard) {
enum AFState { Found(PathBuf, SaveFileOrDir), Used }
use AFState::*;
use SavefilenameParseResult::*;
let mut a_leaves = HashMap::new();
let save_dir = config().save_dir().clone();
for de in fs::read_dir(&save_dir).context(save_dir)? {
let de = de?;
let leaf = de.file_name();
(||{
let leaf = leaf.as_bytes();
match savefilename_parse(leaf)? {
NotGameFile => {
}
TempToDelete => {
fs::remove_file(de.path())
.context("stale temporary file")?;
}
Auxiliary(fd) => {
a_leaves.entry(leaf.to_owned()).or_insert_with(
|| Found(de.path(), fd)
);
}
GameFile { aux_leaves, name } => {
InstanceGuard::load_game(accounts, games, name)?;
for aux_leaf in aux_leaves {
a_leaves.insert(aux_leaf, Used);
}
}
}
<Result<_,anyhow::Error>>::Ok(())
})().with_context(|| format!("leaf={:?}", leaf))?;
}
(||{
for (leaf, state) in &a_leaves {
if let Found(path, fd) = state {
fd.remove(&path)
.with_context(|| format!("leaf={:?}", leaf))?;
}
}
<Result<_,anyhow::Error>>::Ok(())
})().context("cleaning up stale files")?;
info!("loaded games");
}
pub type TokenTable<Id> = HashMap<RawToken, InstanceAccessDetails<Id>>;
pub trait AccessId: Copy + Clone + 'static {
type Error: Into<Fatal>;
const ERROR: Self::Error;
fn global_tokens(_:PrivateCaller) -> &'static RwLock<TokenTable<Self>>;
fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
-> &mut TokenRegistry<Self>;
}
#[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd)]
#[derive(Serialize,Deserialize)]
#[derive(Error)]
#[error("Player not found")]
pub struct PlayerNotFound;
impl AccessId for PlayerId {
type Error = PlayerNotFound;
const ERROR: PlayerNotFound = PlayerNotFound;
fn global_tokens(_: PrivateCaller) -> &'static RwLock<TokenTable<Self>> {
&GLOBAL.players
}
fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
-> &mut TokenRegistry<Self> {
&mut ig.tokens_players
}
}
impl AccessId for ClientId {
type Error = Fatal;
const ERROR: Fatal = NoClient;
fn global_tokens(_: PrivateCaller) -> &'static RwLock<TokenTable<Self>> {
&GLOBAL.clients
}
fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
-> &mut TokenRegistry<Self> {
&mut ig.tokens_clients
}
}
impl RawToken {
fn new_random() -> Self {
let mut rng = thread_rng();
let token = RawToken (
repeat_with(|| rng.sample(Alphanumeric))
.map(char::from)
.take(64).collect()
);
token
}
}
pub fn lookup_token<Id:AccessId>(s: &RawTokenVal)
-> Result<InstanceAccessDetails<Id>, Id::Error> {
Id::global_tokens(PRIVATE_Y).read().get(s).cloned()
.ok_or(Id::ERROR)
}
#[throws(Fatal)]
pub fn record_token<Id:AccessId> (
ig: &mut InstanceGuard,
iad: InstanceAccessDetails<Id>
) -> RawToken {
let token = RawToken::new_random();
ig.token_register(token.clone(), iad);
token
}
#[throws(E)]
pub fn process_all_players_for_account<
E: Error,
F: FnMut(&mut InstanceGuard<'_>, PlayerId) -> Result<(),E>
>
(games: &mut GamesGuard, acctid: AccountId, mut f: F)
{
for gref in games.values() {
let c = gref.lock_even_destroying();
let remove: Vec<_> = c.g.iplayers.iter().filter_map(|(player,pr)| {
if pr.ipl.acctid == acctid { Some(player) } else { None }
}).collect();
let mut ig = InstanceGuard { gref: gref.clone(), c };
for player in remove.into_iter() {
f(&mut ig, player)?;
}
}
}
impl IPieces {
pub fn get(&self, piece: PieceId) -> Option<&IPiece> {
self.0.get(piece)
}
pub fn as_mut(&mut self, _: ModifyingPieces) -> &mut ActualIPieces {
&mut self.0
}
pub fn is_empty(&self) -> bool {
let IPieces(actual) = self;
actual.is_empty()
}
}
impl GPieces {
pub fn get_mut(&mut self, piece: PieceId) -> Option<&mut GPiece> {
self.0.get_mut(piece)
}
pub fn values_mut(&mut self) -> sm::ValuesMut<PieceId, GPiece> {
self.0.values_mut()
}
pub fn as_mut(&mut self, _: ModifyingPieces) -> &mut ActualGPieces {
&mut self.0
}
#[cfg(test)]
pub fn as_mut_t(&mut self) -> &mut ActualGPieces { &mut self.0 }
}
impl ById for GPieces {
type Id = PieceId;
type Entry = GPiece;
type Error = Inapplicable;
#[throws(Ia)]
fn byid(&self, piece: PieceId) -> &GPiece {
self.get(piece).ok_or(Ia::PieceGone)?
}
#[throws(Ia)]
fn byid_mut(&mut self, piece: PieceId) -> &mut GPiece {
self.get_mut(piece).ok_or(Ia::PieceGone)?
}
}
impl<'p> IntoIterator for &'p mut GPieces {
type Item = (PieceId, &'p mut GPiece);
type IntoIter = sm::IterMut<'p, PieceId, GPiece>;
fn into_iter(self) -> Self::IntoIter { (&mut self.0).into_iter() }
}
impl InstanceGuard<'_> {
pub fn save_game_later(&mut self) {
if self.c.game_dirty { return }
GLOBAL.dirty.lock().push_back(self.gref.clone());
self.c.game_dirty = true;
}
pub fn save_game_and_aux_later(&mut self) {
if self.c.aux_dirty { return }
self.save_game_later();
self.c.aux_dirty = true;
}
}
pub fn game_flush_task() {
let mut inner_queue = VecDeque::new();
loop {
{
mem::swap(&mut inner_queue, &mut *GLOBAL.dirty.lock());
}
thread::sleep(GAME_SAVE_LAG);
for _ in 0..inner_queue.len() {
let ent = inner_queue.pop_front().unwrap();
let mut ig = match ent.lock() { Ok(ig) => ig, _ => continue };
if !ig.c.game_dirty { continue }
match ig.save_game_now() {
Ok(_) => {
assert!(!ig.c.game_dirty);
}
Err(e) => {
error!("save error! name={:?}: {}", &ig.name, &e);
mem::drop(ig);
inner_queue.push_back(ent); }
}
}
}
}
fn client_expire_old_clients() {
let mut expire = vec![];
let max_age = Instant::now() - MAX_CLIENT_INACTIVITY;
trait ClientIterator {
type Ret;
fn iter<'g>(&mut self, gref: &'g InstanceRef, max_age: Instant)
-> (MutexGuard<'g, InstanceContainer>, Option<Self::Ret>) {
let c = gref.lock_even_destroying();
let ret = 'ret: loop {
for (client, cl) in &c.g.clients {
if cl.lastseen > max_age { continue }
let ret = self.old(client);
if ret.is_some() { break 'ret ret }
}
break 'ret None;
};
(c,ret)
}
fn old(&mut self, client: ClientId) -> Option<Self::Ret>;
}
for gref in GLOBAL.games_table.read().values() {
struct Any;
impl ClientIterator for Any {
type Ret = ();
fn old(&mut self, _client: ClientId) -> Option<()> {
Some(())
}
}
if let (_, Some(())) = Any.iter(gref, max_age) {
expire.push(gref.clone());
}
}
for gref in expire.into_iter() {
#[derive(Debug)]
struct Now(HashSet<ClientId>);
impl ClientIterator for Now {
type Ret = Void;
fn old(&mut self, client: ClientId) -> Option<Void> {
self.0.insert(client);
None
}
}
let mut now = Now(default());
let (mut c, _) = now.iter(&gref, max_age);
c.g.clients.retain(|c,_| !now.0.contains(&c));
let mut gref = InstanceGuard { c, gref: gref.clone() };
debug!("expiring client {:?}", &now);
gref.tokens_deregister_for_id::<ClientId,_>(|c| now.0.contains(&c));
}
}
pub fn client_periodic_expiry() {
loop {
sleep(MAX_CLIENT_INACTIVITY);
client_expire_old_clients();
}
}
fn global_expire_old_logs() {
let cutoff = Timestamp(Timestamp::now().0 - MAX_LOG_AGE.as_secs());
let mut want_expire = vec![];
let read = GLOBAL.games_table.read();
for gref in read.values() {
if gref.lock_even_destroying().g.gs.want_expire_some_logs(cutoff) {
want_expire.push(gref.clone())
}
}
drop(read);
for gref in want_expire.into_iter() {
let mut g = gref.lock_even_destroying();
info!("expiring old log entries in {:?}", &g.g.name);
g.g.gs.do_expire_old_logs(cutoff);
}
}
pub fn logs_periodic_expiry() {
loop {
sleep(MAX_LOG_AGE / 10);
global_expire_old_logs();
}
}