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: | ||||
|             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): | ||||
|   | ||||
| @@ -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' ) | ||||
|                   'self_deaf', 'is_afk', 'channel' ) | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         self.session_id = kwargs.get('session_id') | ||||
|         self._update_voice_state(**kwargs) | ||||
|     def __init__(self, *, data, channel=None): | ||||
|         self.session_id = data.get('session_id') | ||||
|         self._update(data, channel) | ||||
|  | ||||
|     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 | ||||
|     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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user