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:
self._voice_states.pop(user_id, None)
else:
data['voice_channel'] = self.channel
self._voice_states[user_id] = VoiceState(**data)
self._voice_states[user_id] = VoiceState(data=data, channel=self.channel)
@property
def connected(self):

80
discord/member.py

@ -31,9 +31,6 @@ from . import utils
from .enums import Status, ChannelType, try_enum
from .colour import Colour
import copy
import inspect
class VoiceState:
"""Represents a Discord user's voice state.
@ -49,32 +46,25 @@ class VoiceState:
Indicates if the user is currently deafened by their own accord.
is_afk: bool
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
is not currently in a voice channel.
"""
__slots__ = ( 'session_id', 'deaf', 'mute', 'self_mute',
'self_deaf', 'is_afk', 'voice_channel' )
def __init__(self, **kwargs):
self.session_id = kwargs.get('session_id')
self._update_voice_state(**kwargs)
def _update_voice_state(self, **kwargs):
self.self_mute = kwargs.get('self_mute', False)
self.self_deaf = kwargs.get('self_deaf', False)
self.is_afk = kwargs.get('suppress', False)
self.mute = kwargs.get('mute', False)
self.deaf = kwargs.get('deaf', False)
self.voice_channel = kwargs.get('voice_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
'self_deaf', 'is_afk', 'channel' )
def __init__(self, *, data, channel=None):
self.session_id = data.get('session_id')
self._update(data, channel)
def _update(self, data, channel):
self.self_mute = data.get('self_mute', False)
self.self_deaf = data.get('self_deaf', False)
self.is_afk = data.get('suppress', False)
self.mute = data.get('mute', False)
self.deaf = data.get('deaf', False)
self.channel = channel
def flatten_user(cls):
for attr, value in User.__dict__.items():
@ -107,7 +97,6 @@ def flatten_user(cls):
return cls
@flatten_voice_states
@flatten_user
class Member:
"""Represents a Discord member to a :class:`Server`.
@ -130,9 +119,6 @@ class Member:
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
A list of :class:`Role` that the member belongs to. Note that the first element of this
list is always the default '@everyone' role.
@ -150,12 +136,11 @@ class Member:
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):
self._state = state
self._user = state.try_insert_user(data['user'])
self.voice = VoiceState(**data)
self.joined_at = utils.parse_time(data.get('joined_at'))
self.roles = data.get('roles', [])
self.status = Status.offline
@ -176,36 +161,6 @@ class Member:
def __hash__(self):
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):
self._user.name = user['username']
self._user.discriminator = user['discriminator']
@ -323,3 +278,8 @@ class Member:
return Permissions.all()
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 .role import Role
from .member import Member
from .member import Member, VoiceState
from .emoji import Emoji
from .game import Game
from .channel import Channel
from .enums import ServerRegion, Status, try_enum, VerificationLevel
from .mixins import Hashable
import copy
class Server(Hashable):
"""Represents a Discord server.
@ -112,12 +114,13 @@ class Server(Hashable):
'name', 'id', 'owner', 'unavailable', 'name', 'region',
'_default_role', '_default_channel', 'roles', '_member_count',
'large', 'owner_id', 'mfa_level', 'emojis', 'features',
'verification_level', 'splash' )
'verification_level', 'splash', '_voice_states' )
def __init__(self, *, data, state):
self._channels = {}
self.owner = None
self._members = {}
self._voice_states = {}
self._state = state
self._from_data(data)
@ -135,6 +138,9 @@ class Server(Hashable):
def _remove_channel(self, channel):
self._channels.pop(channel.id, None)
def _voice_state_for(self, user_id):
return self._voice_states.get(user_id)
@property
def members(self):
return self._members.values()
@ -153,15 +159,42 @@ class Server(Hashable):
return self.name
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)
before = None
if member is not None:
before = member._copy()
ch_id = data.get('channel_id')
channel = self.get_channel(ch_id)
member._update_voice_state(voice_channel=channel, **data)
return before, member
old = before.channel
# update the references pointed to by the voice channels
if old is None and channel is not None:
# we joined a channel
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):
# 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)
server._add_member(member)
old_member = member._copy()
old_member = copy.copy(member)
member._presence_update(data=data, user=user)
self.dispatch('member_update', old_member, member)
@ -431,12 +431,14 @@ class ConnectionState:
server._member_count -= 1
# remove them from the voice channel member list
vc = member.voice_channel
if vc is not None:
try:
vc.voice_members.remove(member)
except:
pass
vc = server._voice_state_for(user_id)
if vc:
voice_channel = vc.channel
if voice_channel is not None:
try:
voice_channel.voice_members.remove(member)
except ValueError:
pass
self.dispatch('member_remove', member)
@ -446,7 +448,7 @@ class ConnectionState:
user_id = user['id']
member = server.get_member(user_id)
if member is not None:
old_member = member._copy()
old_member = copy.copy(member)
member._update(data, user)
self.dispatch('member_update', old_member, member)
@ -620,15 +622,14 @@ class ConnectionState:
def parse_voice_state_update(self, data):
server = self._get_server(data.get('guild_id'))
if server is not None:
channel = server.get_channel(data.get('channel_id'))
if data.get('user_id') == self.user.id:
voice = self._get_voice_client(server.id)
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:
self.dispatch('voice_state_update', before, after)
self.dispatch('voice_state_update', member, before, after)
else:
# in here we're either at private or group calls
call = self._calls.get(data.get('channel_id'), None)

Loading…
Cancel
Save