pubsub.lazy_guild: better algorithm for presence updates

this gives the separation between "complex" and "simple"
presence updates we can generalize on.
This commit is contained in:
Luna Mendes 2018-11-07 21:07:31 -03:00
parent 7be9d30f5d
commit 8b093d3d16
1 changed files with 97 additions and 33 deletions

View File

@ -245,6 +245,27 @@ class GuildMemberList:
] ]
self.list.group_info = {g.gid: g for g in role_groups} self.list.group_info = {g.gid: g for g in role_groups}
async def get_group(self, member_id: int,
roles: List[Union[str, int]],
status: str) -> int:
"""Return a fitting group ID for the user."""
member_roles = list(map(int, roles))
# get the member's permissions relative to the channel
# (accounting for channel overwrites)
member_perms = await get_permissions(
member_id, self.channel_id, storage=self.storage)
if not member_perms.bits.read_messages:
return None
# if the member is offline, we
# default give them the offline group.
group_id = ('offline' if status == 'offline'
else self._calc_member_group(member_roles, status))
return group_id
async def _pass_1(self, guild_presences: List[Presence]): async def _pass_1(self, guild_presences: List[Presence]):
"""First pass on generating the member list. """First pass on generating the member list.
@ -253,22 +274,9 @@ class GuildMemberList:
for presence in guild_presences: for presence in guild_presences:
member_id = int(presence['user']['id']) member_id = int(presence['user']['id'])
# list of roles for the member group_id = await self.get_group(
member_roles = list(map(int, presence['roles'])) member_id, presence['roles'], presence['status']
)
# get the member's permissions relative to the channel
# (accounting for channel overwrites)
member_perms = await get_permissions(
member_id, self.channel_id, storage=self.storage)
if not member_perms.bits.read_messages:
continue
# if the member is offline, we
# default give them the offline group.
status = presence['status']
group_id = ('offline' if status == 'offline'
else self._calc_member_group(member_roles, status))
self.list.data[group_id].append(presence) self.list.data[group_id].append(presence)
@ -460,24 +468,12 @@ class GuildMemberList:
await self._dispatch_sess([session_id], ops) await self._dispatch_sess([session_id], ops)
async def pres_update(self, user_id: int, async def _pres_update_simple(self, user_id: int):
partial_presence: Dict[str, Any]): def _get_id(item):
"""Update a presence inside the member listlist.""" # item can be a group item or a member item
await self._init_check() return item.get('member', {}).get('user', {}).get('id')
for _group, presences in self.list:
p_idx = index_by_func(
lambda p: p['user']['id'] == str(user_id),
presences)
if not p_idx:
continue
presences[p_idx].update(partial_presence)
def _get_id(p):
return p.get('member', {}).get('user', {}).get('id')
# get the updated item's index
item_index = index_by_func( item_index = index_by_func(
lambda p: _get_id(p) == str(user_id), lambda p: _get_id(p) == str(user_id),
self.items self.items
@ -490,6 +486,8 @@ class GuildMemberList:
item = self.items[item_index] item = self.items[item_index]
# only dispatch to sessions
# that are subscribed to the given item's index
def _is_in(sess_id): def _is_in(sess_id):
ranges = self.state[sess_id] ranges = self.state[sess_id]
@ -501,6 +499,8 @@ class GuildMemberList:
session_ids = filter(_is_in, self.state.keys()) session_ids = filter(_is_in, self.state.keys())
# simple update means we just give an UPDATE
# operation
return await self._dispatch_sess( return await self._dispatch_sess(
session_ids, session_ids,
[ [
@ -511,6 +511,70 @@ class GuildMemberList:
] ]
) )
async def _pres_update_complex(self, user_id: int,
old_group: str, new_group: str):
raise NotImplementedError
async def pres_update(self, user_id: int,
partial_presence: Dict[str, Any]):
"""Update a presence inside the member list.
There are 4 types of updates that can happen for a user in a group:
- from 'offline' to any
- from any to 'offline'
- from any to any
- from G to G (with G being any group)
any: 'online' | role_id
All first, second, and third updates are 'complex' updates,
which means we'll have to change the group the user is on
to account for them.
The fourth is a 'simple' change, since we're not changing
the group a user is on, and so there's less overhead
involved.
"""
await self._init_check()
old_group, old_presence = None, None
for group, presences in self.list:
p_idx = index_by_func(
lambda p: p['user']['id'] == str(user_id),
presences)
if not p_idx:
continue
# make a copy since we're modifying in-place
old_group = group.gid
old_presence = dict(presences[p_idx])
# be ready if it is a simple update
presences[p_idx].update(partial_presence)
break
if not old_group:
log.warning('pres update with unknown old group uid={}',
user_id)
return []
roles = partial_presence.get('roles', old_presence['roles'])
new_status = partial_presence.get('status', old_presence['status'])
new_group = await self.get_group(user_id, roles, new_status)
log.debug('pres update: gid={} cid={} old_g={} new_g={}',
self.guild_id, self.channel_id, old_group, new_group)
# if we're going to the same group,
# treat this as a simple update
if old_group == new_group:
return await self._pres_update_simple(user_id)
return await self._pres_update_complex(user_id, old_group, new_group)
async def dispatch(self, event: str, data: Any): async def dispatch(self, event: str, data: Any):
"""Modify the member list and dispatch the respective """Modify the member list and dispatch the respective
events to subscribed shards. events to subscribed shards.