aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2016-10-09 05:37:37 -0400
committerRapptz <[email protected]>2017-01-03 09:51:47 -0500
commit31229a53e902e7cdc6f5f8d6e55e333cbc1fb0e6 (patch)
tree14612b14bfe19c436b13fee6077b91900b2d30c3
parentBegin working on the rewrite. (diff)
downloaddiscord.py-31229a53e902e7cdc6f5f8d6e55e333cbc1fb0e6.tar.xz
discord.py-31229a53e902e7cdc6f5f8d6e55e333cbc1fb0e6.zip
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.
-rw-r--r--discord/calls.py3
-rw-r--r--discord/member.py80
-rw-r--r--discord/server.py51
-rw-r--r--discord/state.py25
4 files changed, 76 insertions, 83 deletions
diff --git a/discord/calls.py b/discord/calls.py
index 40df55ac..6a221e50 100644
--- a/discord/calls.py
+++ b/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):
diff --git a/discord/member.py b/discord/member.py
index 4d0f86b9..08d2600a 100644
--- a/discord/member.py
+++ b/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)
diff --git a/discord/server.py b/discord/server.py
index 414d3230..fb572fc5 100644
--- a/discord/server.py
+++ b/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)
diff --git a/discord/state.py b/discord/state.py
index 1af26db8..ff28c496 100644
--- a/discord/state.py
+++ b/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)