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.
This commit is contained in:
		| @@ -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): | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user