Browse Source

Optimise VoiceState for memory.

Instead of storing one VoiceState per Member, only store them if
necessary. This should bring down the number of instances
significantly.
pull/447/head
Rapptz 9 years ago
parent
commit
31229a53e9
  1. 3
      discord/calls.py
  2. 80
      discord/member.py
  3. 51
      discord/server.py
  4. 25
      discord/state.py

3
discord/calls.py

@ -117,8 +117,7 @@ class GroupCall:
if data['channel_id'] is None: if data['channel_id'] is None:
self._voice_states.pop(user_id, None) self._voice_states.pop(user_id, None)
else: else:
data['voice_channel'] = self.channel self._voice_states[user_id] = VoiceState(data=data, channel=self.channel)
self._voice_states[user_id] = VoiceState(**data)
@property @property
def connected(self): def connected(self):

80
discord/member.py

@ -31,9 +31,6 @@ from . import utils
from .enums import Status, ChannelType, try_enum from .enums import Status, ChannelType, try_enum
from .colour import Colour from .colour import Colour
import copy
import inspect
class VoiceState: class VoiceState:
"""Represents a Discord user's voice state. """Represents a Discord user's voice state.
@ -49,32 +46,25 @@ class VoiceState:
Indicates if the user is currently deafened by their own accord. Indicates if the user is currently deafened by their own accord.
is_afk: bool is_afk: bool
Indicates if the user is currently in the AFK channel in the server. Indicates if the user is currently in the AFK channel in the server.
voice_channel: Optional[Union[:class:`Channel`, :class:`PrivateChannel`]] channel: Optional[Union[:class:`Channel`, :class:`PrivateChannel`]]
The voice channel that the user is currently connected to. None if the user The voice channel that the user is currently connected to. None if the user
is not currently in a voice channel. is not currently in a voice channel.
""" """
__slots__ = ( 'session_id', 'deaf', 'mute', 'self_mute', __slots__ = ( 'session_id', 'deaf', 'mute', 'self_mute',
'self_deaf', 'is_afk', 'voice_channel' ) 'self_deaf', 'is_afk', 'channel' )
def __init__(self, **kwargs): def __init__(self, *, data, channel=None):
self.session_id = kwargs.get('session_id') self.session_id = data.get('session_id')
self._update_voice_state(**kwargs) self._update(data, channel)
def _update_voice_state(self, **kwargs): def _update(self, data, channel):
self.self_mute = kwargs.get('self_mute', False) self.self_mute = data.get('self_mute', False)
self.self_deaf = kwargs.get('self_deaf', False) self.self_deaf = data.get('self_deaf', False)
self.is_afk = kwargs.get('suppress', False) self.is_afk = data.get('suppress', False)
self.mute = kwargs.get('mute', False) self.mute = data.get('mute', False)
self.deaf = kwargs.get('deaf', False) self.deaf = data.get('deaf', False)
self.voice_channel = kwargs.get('voice_channel') self.channel = channel
def flatten_voice_states(cls):
for attr in VoiceState.__slots__:
def getter(self, x=attr):
return getattr(self.voice, x)
setattr(cls, attr, property(getter))
return cls
def flatten_user(cls): def flatten_user(cls):
for attr, value in User.__dict__.items(): for attr, value in User.__dict__.items():
@ -107,7 +97,6 @@ def flatten_user(cls):
return cls return cls
@flatten_voice_states
@flatten_user @flatten_user
class Member: class Member:
"""Represents a Discord member to a :class:`Server`. """Represents a Discord member to a :class:`Server`.
@ -130,9 +119,6 @@ class Member:
Attributes Attributes
---------- ----------
voice: :class:`VoiceState`
The member's voice state. Properties are defined to mirror access of the attributes.
e.g. ``Member.is_afk`` is equivalent to `Member.voice.is_afk``.
roles roles
A list of :class:`Role` that the member belongs to. Note that the first element of this A list of :class:`Role` that the member belongs to. Note that the first element of this
list is always the default '@everyone' role. list is always the default '@everyone' role.
@ -150,12 +136,11 @@ class Member:
The server specific nickname of the user. The server specific nickname of the user.
""" """
__slots__ = ('roles', 'joined_at', 'status', 'game', 'server', 'nick', 'voice', '_user', '_state') __slots__ = ('roles', 'joined_at', 'status', 'game', 'server', 'nick', '_user', '_state')
def __init__(self, *, data, server, state): def __init__(self, *, data, server, state):
self._state = state self._state = state
self._user = state.try_insert_user(data['user']) self._user = state.try_insert_user(data['user'])
self.voice = VoiceState(**data)
self.joined_at = utils.parse_time(data.get('joined_at')) self.joined_at = utils.parse_time(data.get('joined_at'))
self.roles = data.get('roles', []) self.roles = data.get('roles', [])
self.status = Status.offline self.status = Status.offline
@ -176,36 +161,6 @@ class Member:
def __hash__(self): def __hash__(self):
return hash(self._user.id) return hash(self._user.id)
def _update_voice_state(self, **kwargs):
self.voice.self_mute = kwargs.get('self_mute', False)
self.voice.self_deaf = kwargs.get('self_deaf', False)
self.voice.is_afk = kwargs.get('suppress', False)
self.voice.mute = kwargs.get('mute', False)
self.voice.deaf = kwargs.get('deaf', False)
old_channel = getattr(self, 'voice_channel', None)
vc = kwargs.get('voice_channel')
if old_channel is None and vc is not None:
# we joined a channel
vc.voice_members.append(self)
elif old_channel is not None:
try:
# we either left a channel or we switched channels
old_channel.voice_members.remove(self)
except ValueError:
pass
finally:
# we switched channels
if vc is not None:
vc.voice_members.append(self)
self.voice.voice_channel = vc
def _copy(self):
ret = copy.copy(self)
ret.voice = copy.copy(self.voice)
return ret
def _update(self, data, user): def _update(self, data, user):
self._user.name = user['username'] self._user.name = user['username']
self._user.discriminator = user['discriminator'] self._user.discriminator = user['discriminator']
@ -323,3 +278,8 @@ class Member:
return Permissions.all() return Permissions.all()
return base return base
@property
def voice(self):
"""Optional[:class:`VoiceState`]: Returns the member's current voice state."""
return self.server._voice_state_for(self._user.id)

51
discord/server.py

@ -26,13 +26,15 @@ DEALINGS IN THE SOFTWARE.
from . import utils from . import utils
from .role import Role from .role import Role
from .member import Member from .member import Member, VoiceState
from .emoji import Emoji from .emoji import Emoji
from .game import Game from .game import Game
from .channel import Channel from .channel import Channel
from .enums import ServerRegion, Status, try_enum, VerificationLevel from .enums import ServerRegion, Status, try_enum, VerificationLevel
from .mixins import Hashable from .mixins import Hashable
import copy
class Server(Hashable): class Server(Hashable):
"""Represents a Discord server. """Represents a Discord server.
@ -112,12 +114,13 @@ class Server(Hashable):
'name', 'id', 'owner', 'unavailable', 'name', 'region', 'name', 'id', 'owner', 'unavailable', 'name', 'region',
'_default_role', '_default_channel', 'roles', '_member_count', '_default_role', '_default_channel', 'roles', '_member_count',
'large', 'owner_id', 'mfa_level', 'emojis', 'features', 'large', 'owner_id', 'mfa_level', 'emojis', 'features',
'verification_level', 'splash' ) 'verification_level', 'splash', '_voice_states' )
def __init__(self, *, data, state): def __init__(self, *, data, state):
self._channels = {} self._channels = {}
self.owner = None self.owner = None
self._members = {} self._members = {}
self._voice_states = {}
self._state = state self._state = state
self._from_data(data) self._from_data(data)
@ -135,6 +138,9 @@ class Server(Hashable):
def _remove_channel(self, channel): def _remove_channel(self, channel):
self._channels.pop(channel.id, None) self._channels.pop(channel.id, None)
def _voice_state_for(self, user_id):
return self._voice_states.get(user_id)
@property @property
def members(self): def members(self):
return self._members.values() return self._members.values()
@ -153,15 +159,42 @@ class Server(Hashable):
return self.name return self.name
def _update_voice_state(self, data): def _update_voice_state(self, data):
user_id = data.get('user_id') user_id = data['user_id']
channel = self.get_channel(data['channel_id'])
try:
# check if we should remove the voice state from cache
if channel is None:
after = self._voice_states.pop(user_id)
else:
after = self._voice_states[user_id]
before = copy.copy(after)
after._update(data, channel)
except KeyError:
# if we're here then we're getting added into the cache
after = VoiceState(data=data, channel=channel)
before = VoiceState(data=data, channel=None)
self._voice_states[user_id] = after
member = self.get_member(user_id) member = self.get_member(user_id)
before = None
if member is not None: if member is not None:
before = member._copy() old = before.channel
ch_id = data.get('channel_id') # update the references pointed to by the voice channels
channel = self.get_channel(ch_id) if old is None and channel is not None:
member._update_voice_state(voice_channel=channel, **data) # we joined a channel
return before, member channel.voice_members.append(member)
elif old is not None:
try:
# we either left a channel or switched channels
old.voice_members.remove(member)
except ValueError:
pass
finally:
# we switched channels
if channel is not None:
channel.voice_members.append(self)
return member, before, after
def _add_role(self, role): def _add_role(self, role):
# roles get added to the bottom (position 1, pos 0 is @everyone) # roles get added to the bottom (position 1, pos 0 is @everyone)

25
discord/state.py

@ -340,7 +340,7 @@ class ConnectionState:
member = self._make_member(server, data) member = self._make_member(server, data)
server._add_member(member) server._add_member(member)
old_member = member._copy() old_member = copy.copy(member)
member._presence_update(data=data, user=user) member._presence_update(data=data, user=user)
self.dispatch('member_update', old_member, member) self.dispatch('member_update', old_member, member)
@ -431,12 +431,14 @@ class ConnectionState:
server._member_count -= 1 server._member_count -= 1
# remove them from the voice channel member list # remove them from the voice channel member list
vc = member.voice_channel vc = server._voice_state_for(user_id)
if vc is not None: if vc:
try: voice_channel = vc.channel
vc.voice_members.remove(member) if voice_channel is not None:
except: try:
pass voice_channel.voice_members.remove(member)
except ValueError:
pass
self.dispatch('member_remove', member) self.dispatch('member_remove', member)
@ -446,7 +448,7 @@ class ConnectionState:
user_id = user['id'] user_id = user['id']
member = server.get_member(user_id) member = server.get_member(user_id)
if member is not None: if member is not None:
old_member = member._copy() old_member = copy.copy(member)
member._update(data, user) member._update(data, user)
self.dispatch('member_update', old_member, member) self.dispatch('member_update', old_member, member)
@ -620,15 +622,14 @@ class ConnectionState:
def parse_voice_state_update(self, data): def parse_voice_state_update(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
if server is not None: if server is not None:
channel = server.get_channel(data.get('channel_id'))
if data.get('user_id') == self.user.id: if data.get('user_id') == self.user.id:
voice = self._get_voice_client(server.id) voice = self._get_voice_client(server.id)
if voice is not None: if voice is not None:
voice.channel = channel voice.channel = server.get_channel(data.get('channel_id'))
before, after = server._update_voice_state(data) member, before, after = server._update_voice_state(data)
if after is not None: if after is not None:
self.dispatch('voice_state_update', before, after) self.dispatch('voice_state_update', member, before, after)
else: else:
# in here we're either at private or group calls # in here we're either at private or group calls
call = self._calls.get(data.get('channel_id'), None) call = self._calls.get(data.get('channel_id'), None)

Loading…
Cancel
Save