Coverage for server/parts/lobby.py : 79%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2from __future__ import annotations
4# -- stdlib --
5from typing import Dict, Optional, Sequence, TYPE_CHECKING, Tuple
6import logging
8# -- third party --
9# -- own --
10from endpoint import Endpoint
11from server.base import Game
12from server.endpoint import Client
13from utils.events import FSM
14from utils.misc import BatchList, throttle
15import wire
17# -- typing --
18if TYPE_CHECKING:
19 from server.core import Core # noqa: F401
22# -- code --
23log = logging.getLogger('Lobby')
26class Lobby(object):
27 def __init__(self, core: Core):
28 self.core = core
30 core.events.client_connected += self.handle_client_connected
31 core.events.user_state_transition += self.handle_user_state_transition
32 core.events.game_ended += self.handle_game_ended
34 self.users: Dict[int, Client] = {} # all users
35 self.dropped_users: Dict[int, Client] = {} # passively dropped users
37 def __repr__(self) -> str:
38 return self.__class__.__name__
40 def handle_user_state_transition(self, ev: Tuple[Client, str, str]) -> Tuple[Client, str, str]:
41 c, f, t = ev
43 if (f, t) == ('uninitialized', 'freeslot'):
44 # Just don't bother, core is not running at this time
45 return ev
47 if (f, t) == ('connected', 'authed'):
48 self._user_join(c)
50 if t == 'dropped': 50 ↛ 51line 50 didn't jump to line 51, because the condition on line 50 was never true
51 if f != 'connected':
52 self._user_leave(c)
54 ul = [u for u in self.users.values() if u._[self]['state'] == 'lobby']
55 self._notify_online_users(ul)
57 return ev
59 def handle_client_connected(self, c: Client) -> Client:
60 core = self.core
61 c._[self] = {
62 'state': FSM(
63 c,
64 [
65 'initial',
66 'connected',
67 'authed',
68 'lobby',
69 'room',
70 'ready',
71 'game',
72 'finishing',
73 'ob',
74 'dropped',
75 ],
76 'initial',
77 FSM.to_evhub(core.events.user_state_transition),
78 )
79 }
80 c._[self]['state'].transit('connected')
81 return c
83 def handle_game_ended(self, g: Game) -> Game:
84 core = self.core
85 users = core.room.users_of(g)
87 for u in users:
88 self.dropped_users.pop(core.auth.uid_of(u), 0)
90 return g
92 # ----- Client Commands -----
93 # ----- Public Methods -----
94 def state_of(self, u: Client) -> FSM:
95 return u._[self]['state']
97 def all_users(self) -> BatchList[Client]:
98 return BatchList(self.users.values())
100 def get(self, uid: int) -> Optional[Client]:
101 return self.users.get(uid)
103 def init_freeslot(self, c: Client) -> None:
104 core = self.core
105 c._[self] = {
106 'state': FSM(
107 c, ['uninitialized', 'freeslot'], 'uninitialized',
108 FSM.to_evhub(core.events.user_state_transition),
109 )
110 }
112 # ----- Methods -----
113 def _user_join(self, u: Client) -> None:
114 core = self.core
115 uid = core.auth.uid_of(u)
116 name = core.auth.name_of(u)
118 old = None
120 if uid in self.users: 120 ↛ 122line 120 didn't jump to line 122, because the condition on line 120 was never true
121 # squeeze the original one out
122 log.info('%s[%s] has been squeezed out' % (name, uid))
123 old = self.users[uid]
125 if uid in self.dropped_users: 125 ↛ 126line 125 didn't jump to line 126, because the condition on line 125 was never true
126 log.info('%s[%s] rejoining dropped game' % (name, uid))
127 old = self.dropped_users.pop(uid)
129 # XXX
130 '''
131 @core.runner.spawn
132 def reconnect():
133 self.send_account_info(user)
134 '''
136 if old: 136 ↛ 137line 136 didn't jump to line 137, because the condition on line 136 was never true
137 u.pivot_to(old)
138 core.events.client_pivot.emit(old)
139 else:
140 self.users[uid] = u
141 self.state_of(u).transit('lobby')
143 log.info('User %s joined, online user %d' % (name, len(self.users)))
145 def _user_leave(self, u: Client) -> None:
146 core = self.core
147 uid = core.auth.uid_of(u)
148 name = core.auth.name_of(u)
149 self.users.pop(uid, 0)
150 log.info('User %s left, online user %d' % (name, len(self.users)))
152 @throttle(3)
153 def _notify_online_users(self, ul: Sequence[Client]) -> None:
154 core = self.core
155 lst = [core.view.User(u) for u in self.users.values()]
156 d = Endpoint.encode_bulk([wire.CurrentUsers(users=lst)])
158 @core.runner.spawn
159 def do_send() -> None:
160 for u in ul: 160 ↛ 161line 160 didn't jump to line 161, because the loop on line 160 never started
161 u.raw_write(d)