mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-19 07:26:17 +00:00
Change internal representation of roles in Member and Emoji.
Introduce a new internal type, SnowflakeList, which has better memory footprint over a regular list or set of roles. It is suspected that there will be a 9x reduction of memory for every Emoji instance and a 48 byte saving per Member instance. However, these savings will probably only be evident on larger bots. As a consequence of this change, Member.roles is now computed lazily. Currently I am not sure if I want to do the initial sorting on the SnowflakeList for Member, as this comes with a O(n log n) cost when creating a Member for little purpose since SnowflakeList.has is not overly relied on. If CPU time becomes an issue this might change.
This commit is contained in:
parent
3d03dbc451
commit
95d8bb2e85
@ -415,9 +415,10 @@ class GuildChannel:
|
||||
|
||||
default = self.guild.default_role
|
||||
base = Permissions(default.permissions.value)
|
||||
roles = member.roles
|
||||
|
||||
# Apply guild roles that the member has.
|
||||
for role in member.roles:
|
||||
for role in roles:
|
||||
base.value |= role.permissions.value
|
||||
|
||||
# Guild-wide Administrator -> True for everything
|
||||
@ -436,7 +437,13 @@ class GuildChannel:
|
||||
except IndexError:
|
||||
remaining_overwrites = self._overwrites
|
||||
|
||||
member_role_ids = set(map(lambda r: r.id, member.roles))
|
||||
# not sure if doing member._roles.get(...) is better than the
|
||||
# set approach. While this is O(N) to re-create into a set for O(1)
|
||||
# the direct approach would just be O(log n) for searching with no
|
||||
# extra memory overhead. For now, I'll keep the set cast
|
||||
# Note that the member.roles accessor up top also creates a
|
||||
# temporary list
|
||||
member_role_ids = {r.id for r in roles}
|
||||
denies = 0
|
||||
allows = 0
|
||||
|
||||
|
@ -154,7 +154,7 @@ class Emoji(Hashable):
|
||||
self.id = int(emoji['id'])
|
||||
self.name = emoji['name']
|
||||
self.animated = emoji.get('animated', False)
|
||||
self._roles = set(emoji.get('roles', []))
|
||||
self._roles = utils.SnowflakeList(map(int, emoji.get('roles', [])))
|
||||
|
||||
def _iterator(self):
|
||||
for attr in self.__slots__:
|
||||
@ -187,7 +187,7 @@ class Emoji(Hashable):
|
||||
|
||||
@property
|
||||
def roles(self):
|
||||
"""List[:class:`Role`]: A list of roles that is allowed to use this emoji.
|
||||
"""List[:class:`Role`]: A :class:`list` of roles that is allowed to use this emoji.
|
||||
|
||||
If roles is empty, the emoji is unrestricted.
|
||||
"""
|
||||
@ -195,7 +195,7 @@ class Emoji(Hashable):
|
||||
if guild is None:
|
||||
return []
|
||||
|
||||
return [role for role in guild.roles if role.id in self._roles]
|
||||
return [role for role in guild.roles if self._roles.has(role.id)]
|
||||
|
||||
@property
|
||||
def guild(self):
|
||||
|
@ -136,10 +136,6 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
|
||||
Attributes
|
||||
----------
|
||||
roles: List[:class:`Role`]
|
||||
A :class:`list` of :class:`Role` that the member belongs to. Note that the first element of this
|
||||
list is always the default '@everyone' role. These roles are sorted by their position
|
||||
in the role hierarchy.
|
||||
joined_at: `datetime.datetime`
|
||||
A datetime object that specifies the date and time in UTC that the member joined the guild for
|
||||
the first time.
|
||||
@ -154,7 +150,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
The guild specific nickname of the user.
|
||||
"""
|
||||
|
||||
__slots__ = ('roles', 'joined_at', 'status', 'activity', 'guild', 'nick', '_user', '_state')
|
||||
__slots__ = ('_roles', 'joined_at', 'status', 'activity', 'guild', 'nick', '_user', '_state')
|
||||
|
||||
def __init__(self, *, data, guild, state):
|
||||
self._state = state
|
||||
@ -187,15 +183,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
return ch
|
||||
|
||||
def _update_roles(self, data):
|
||||
# update the roles
|
||||
self.roles = [self.guild.default_role]
|
||||
for role_id in map(int, data['roles']):
|
||||
role = self.guild.get_role(role_id)
|
||||
if role is not None:
|
||||
self.roles.append(role)
|
||||
|
||||
# sort the roles by hierarchy since they can be "randomised"
|
||||
self.roles.sort()
|
||||
self._roles = utils.SnowflakeList(map(int, data['roles']))
|
||||
|
||||
def _update(self, data, user=None):
|
||||
if user:
|
||||
@ -248,6 +236,24 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
|
||||
color = colour
|
||||
|
||||
@property
|
||||
def roles(self):
|
||||
"""A :class:`list` of :class:`Role` that the member belongs to. Note
|
||||
that the first element of this list is always the default '@everyone'
|
||||
role.
|
||||
|
||||
These roles are sorted by their position in the role hierarchy.
|
||||
"""
|
||||
result = []
|
||||
g = self.guild
|
||||
for role_id in self._roles:
|
||||
role = g.get_role(role_id)
|
||||
if role:
|
||||
result.append(role)
|
||||
result.append(g.default_role)
|
||||
result.sort()
|
||||
return result
|
||||
|
||||
@property
|
||||
def mention(self):
|
||||
"""Returns a string that mentions the member."""
|
||||
|
@ -173,7 +173,8 @@ class Role(Hashable):
|
||||
if self.is_default():
|
||||
return all_members
|
||||
|
||||
return [member for member in all_members if self in member.roles]
|
||||
role_id = self.id
|
||||
return [member for member in all_members if member._roles.has(role_id)]
|
||||
|
||||
async def _move(self, position, reason):
|
||||
if position <= 0:
|
||||
|
@ -26,13 +26,16 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from re import split as re_split
|
||||
from .errors import InvalidArgument
|
||||
import datetime
|
||||
from base64 import b64encode
|
||||
from email.utils import parsedate_to_datetime
|
||||
from inspect import isawaitable as _isawaitable
|
||||
from bisect import bisect_left
|
||||
|
||||
import datetime
|
||||
import asyncio
|
||||
import json
|
||||
import warnings, functools
|
||||
import array
|
||||
|
||||
DISCORD_EPOCH = 1420070400000
|
||||
|
||||
@ -289,3 +292,32 @@ async def sane_wait_for(futures, *, timeout, loop):
|
||||
def valid_icon_size(size):
|
||||
"""Icons must be power of 2 within [16, 1024]."""
|
||||
return ((size != 0) and not (size & (size - 1))) and size in range(16, 1025)
|
||||
|
||||
class SnowflakeList(array.array):
|
||||
"""Internal data storage class to efficiently store a list of snowflakes.
|
||||
|
||||
This should have the following characteristics:
|
||||
|
||||
- Low memory usage
|
||||
- O(n) iteration (obviously)
|
||||
- O(n log n) initial creation if data is unsorted
|
||||
- O(log n) search and indexing
|
||||
- O(n) insertion
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, data, *, is_sorted=False):
|
||||
return array.array.__new__(cls, 'Q', data if is_sorted else sorted(data))
|
||||
|
||||
def add(self, element):
|
||||
i = bisect_left(self, element)
|
||||
self.insert(i, element)
|
||||
|
||||
def get(self, element):
|
||||
i = bisect_left(self, element)
|
||||
return self[i] if i != len(self) and self[i] == element else None
|
||||
|
||||
def has(self, element):
|
||||
i = bisect_left(self, element)
|
||||
return i != len(self) and self[i] == element
|
||||
|
Loading…
x
Reference in New Issue
Block a user