Skip to content

Commit

Permalink
Start filtering events before unmarshalling (#636)
Browse files Browse the repository at this point in the history
* Switch to iterating over mro during listener/waiter registration
(rather than during event dispatch)

* Add in a system to avoid unmarshalling data for events which aren't being used
* Add settings property to cache interface to allow for introspection
* Also add "ME" resource to cache config

* Specialise guild create and update handlers to avoid unmarshalling data which isn't being cached when no listeners are registered for the event
* For this to work gateway guild definition handling had to be refactored to switch to explicitly specifying which mappings it should include when calling it

* Logic fixes around event checks

* Register listeners by subclasses not parents (mro)
(For this the subclasses need to be cached on the Event classes)

* Add voodoo on new event cls callback to Event class
* This is meant to be a mock way to handle the edge case of new subclassing Event types being added after the event manage has been initialised which might be unorthodox but probably has some wack use case

* Switch over to mro based approach

* Switch over to mro based approach

* Cache whether a consumer can be dispatched or not

* Slight logic cleanup

* Prefer internal granularity on guild create and update methods
* rename event_manager_base.as_listener to "filtered" and remove from on_guild_create and update

* Also clear the dispatches for cache when waiters are depleted

* Only deserialize guild object on guild create and update if necessary

* Add check to shard payload dispatch and refactor consumer check logic

* Internal refactors and naming scheme changes
* Plus fix CacheImpl.update_me not copying the stored member entry before returning it

* Add internal _FilteredMethod proto to event manager base
* Move filtering to _handle_dispatch

* Add internal _FilteredMethod proto to event manager base
* Move filtering to _handle_dispatch

* Add trace logging calls to on_guild_create and on_guild_update

* Small logic fix + add code/logic comments and docs
* As an artifact of this addition, on_guild_integrations_update acn raise NotImplementedError now since it should always be skipped

* Some test fixes

* cache_components shouldn't ever be undefined if event_types isn't

* Try the builder pattern for GatewayGuildDefinition

* Switch GatewayGuildDefinition to using getter style methods for delaying deserialization

* test fixes and additions

* bug fixes + tests

* Post-rebase fixes

* Have EventManagerBase take components rather than the cache settings

* remove _dispatches_for_cache  + add in missing filtered decorator calls

* Post-rebase fix

* post-rebase fixes

* Change i forgot to commit

* formatting fixes

* Mypy and flake8 fixes
  • Loading branch information
FasterSpeeding committed Mar 25, 2022
1 parent 21e9780 commit 7492ee3
Show file tree
Hide file tree
Showing 15 changed files with 1,684 additions and 461 deletions.
6 changes: 6 additions & 0 deletions hikari/api/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

if typing.TYPE_CHECKING:
from hikari import channels
from hikari import config
from hikari import emojis
from hikari import guilds
from hikari import invites
Expand Down Expand Up @@ -88,6 +89,11 @@ class Cache(abc.ABC):

__slots__: typing.Sequence[str] = ()

@property
@abc.abstractmethod
def settings(self) -> config.CacheSettings:
"""Get the configured settings for this cache."""

@abc.abstractmethod
def get_dm_channel_id(
self, user: snowflakes.SnowflakeishOr[users.PartialUser], /
Expand Down
76 changes: 38 additions & 38 deletions hikari/api/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@
import abc
import typing

import attr

from hikari import undefined
from hikari.internal import attr_extensions

if typing.TYPE_CHECKING:
from hikari import applications as application_models
Expand Down Expand Up @@ -59,53 +56,56 @@
from hikari.internal import data_binding


@attr_extensions.with_copy
@attr.define(weakref_slot=False)
class GatewayGuildDefinition:
"""A structure for handling entities within guild create and update events."""

guild: guild_models.GatewayGuild = attr.field()
"""Object of the guild the definition is for."""

channels: typing.Optional[typing.Mapping[snowflakes.Snowflake, channel_models.GuildChannel]] = attr.field()
"""Mapping of channel IDs to the channels that belong to the guild.
class GatewayGuildDefinition(abc.ABC):
"""Structure for handling entities within guild create and update events.
Will be `builtins.None` when returned by guild update gateway events rather
than create.
!!! warning
The methods on this class may raise `builtins.LookupError` if called
when the relevant resource isn't available in the inner payload.
"""

members: typing.Optional[typing.Mapping[snowflakes.Snowflake, guild_models.Member]] = attr.field()
"""Mapping of user IDs to the members that belong to the guild.
__slots__: typing.Sequence[str] = ()

Will be `builtins.None` when returned by guild update gateway events rather
than create.
@property
@abc.abstractmethod
def id(self) -> snowflakes.Snowflake:
"""ID of the guild the definition is for."""

!!! note
This may be a partial mapping of members in the guild.
"""
@abc.abstractmethod
def channels(self) -> typing.Mapping[snowflakes.Snowflake, channel_models.GuildChannel]:
"""Get a mapping of channel IDs to the channels that belong to the guild."""

presences: typing.Optional[typing.Mapping[snowflakes.Snowflake, presence_models.MemberPresence]] = attr.field()
"""Mapping of user IDs to the presences that are active in the guild.
@abc.abstractmethod
def emojis(self) -> typing.Mapping[snowflakes.Snowflake, emoji_models.KnownCustomEmoji]:
"""Get a mapping of emoji IDs to the emojis that belong to the guild."""

Will be `builtins.None` when returned by guild update gateway events rather
than create.
@abc.abstractmethod
def guild(self) -> guild_models.GatewayGuild:
"""Get the object of the guild this definition is for."""

!!! note
This may be a partial mapping of presences active in the guild.
"""
@abc.abstractmethod
def members(self) -> typing.Mapping[snowflakes.Snowflake, guild_models.Member]:
"""Get a mapping of user IDs to the members that belong to the guild.
roles: typing.Mapping[snowflakes.Snowflake, guild_models.Role] = attr.field()
"""Mapping of role IDs to the roles that belong to the guild."""
!!! note
This may be a partial mapping of members in the guild.
"""

@abc.abstractmethod
def presences(self) -> typing.Mapping[snowflakes.Snowflake, presence_models.MemberPresence]:
"""Get a mapping of user IDs to the presences that are active in the guild.
emojis: typing.Mapping[snowflakes.Snowflake, emoji_models.KnownCustomEmoji] = attr.field()
"""Mapping of emoji IDs to the emojis that belong to the guild."""
!!! note
This may be a partial mapping of presences active in the guild.
"""

voice_states: typing.Optional[typing.Mapping[snowflakes.Snowflake, voice_models.VoiceState]] = attr.field()
"""Mapping of user IDs to the voice states that are active in the guild.
@abc.abstractmethod
def roles(self) -> typing.Mapping[snowflakes.Snowflake, guild_models.Role]:
"""Get a mapping of role IDs to the roles that belong to the guild."""

!!! note
This may be a partial mapping of voice states active in the guild.
"""
@abc.abstractmethod
def voice_states(self) -> typing.Mapping[snowflakes.Snowflake, voice_models.VoiceState]:
"""Get a mapping of user IDs to the voice states that are active in the guild."""


class EntityFactory(abc.ABC):
Expand Down
22 changes: 22 additions & 0 deletions hikari/events/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ class Event(abc.ABC):

__slots__: typing.Sequence[str] = ()

__dispatches: typing.ClassVar[typing.Tuple[typing.Type[Event], ...]]

def __init_subclass__(cls) -> None:
super().__init_subclass__()
# hasattr doesn't work with private variables in this case so we use a try except.
# We need to set Event's __dispatches when the first subclass is made as Event cannot
# be included in a tuple literal on itself due to not existing yet.
try:
Event.__dispatches
except AttributeError:
Event.__dispatches = (Event,)

mro = cls.mro()
# We don't have to explicitly include Event here as issubclass(Event, Event) returns True.
# Non-event classes should be ignored.
cls.__dispatches = tuple(cls for cls in mro if issubclass(cls, Event))

@property
@abc.abstractmethod
def app(self) -> traits.RESTAware:
Expand All @@ -68,6 +85,11 @@ def app(self) -> traits.RESTAware:
The REST-aware app trait.
"""

@classmethod
def dispatches(cls) -> typing.Sequence[typing.Type[Event]]:
"""Sequence of the event classes this event is dispatched as."""
return cls.__dispatches


def get_required_intents_for(event_type: typing.Type[Event]) -> typing.Collection[intents.Intents]:
"""Retrieve the intents that are required to listen to an event type.
Expand Down
4 changes: 3 additions & 1 deletion hikari/impl/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,9 @@ def __init__(
self._event_factory = event_factory_impl.EventFactoryImpl(self)

# Event handling
self._event_manager = event_manager_impl.EventManagerImpl(self._event_factory, self._intents, cache=self._cache)
self._event_manager = event_manager_impl.EventManagerImpl(
self._entity_factory, self._event_factory, self._intents, cache=self._cache
)

# Voice subsystem
self._voice = voice_impl.VoiceComponentImpl(self)
Expand Down
10 changes: 7 additions & 3 deletions hikari/impl/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,15 +769,19 @@ def get_me(self) -> typing.Optional[users.OwnUser]:
return copy.copy(self._me)

def set_me(self, user: users.OwnUser, /) -> None:
self._me = copy.copy(user)
if self._is_cache_enabled_for(config.CacheComponents.ME):
_LOGGER.debug("setting my user to %s", user)
self._me = copy.copy(user)

def update_me(
self, user: users.OwnUser, /
) -> typing.Tuple[typing.Optional[users.OwnUser], typing.Optional[users.OwnUser]]:
_LOGGER.debug("setting my user to %s", user)
if not self._is_cache_enabled_for(config.CacheComponents.ME):
return None, None

cached_user = self.get_me()
self.set_me(user)
return cached_user, self._me
return cached_user, self.get_me()

def _build_member(
self,
Expand Down
Loading

0 comments on commit 7492ee3

Please sign in to comment.