| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1811cae commit ea5fada
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + messages.hooks module | ||
| 2 | + ====================== | ||
| 3 | + | ||
| 4 | + .. automodule:: messages.hooks | ||
| 5 | + :members: | ||
| 6 | + :undoc-members: | ||
| 7 | + :show-inheritance: | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + messages.impl module | ||
| 2 | + ===================== | ||
| 3 | + | ||
| 4 | + .. automodule:: messages.impl | ||
| 5 | + :members: | ||
| 6 | + :undoc-members: | ||
| 7 | + :show-inheritance: | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,263 @@ | |||
| 1 | + # ../messages/hooks.py | ||
| 2 | + | ||
| 3 | + """Provides user message hooking functionality.""" | ||
| 4 | + | ||
| 5 | + # TODO: | ||
| 6 | + # - Implement more user messages | ||
| 7 | + # - Use these message implementations for the UserMessageCreator subclasses. | ||
| 8 | + | ||
| 9 | + # ============================================================================= | ||
| 10 | + # >> IMPORTS | ||
| 11 | + # ============================================================================= | ||
| 12 | + # Python | ||
| 13 | + # Collections | ||
| 14 | + from collections import defaultdict | ||
| 15 | + | ||
| 16 | + # Source.Python | ||
| 17 | + # Core | ||
| 18 | + from core import AutoUnload | ||
| 19 | + # Engines | ||
| 20 | + from engines.server import engine_server | ||
| 21 | + # Filters | ||
| 22 | + from filters.recipients import BaseRecipientFilter | ||
| 23 | + from filters.recipients import RecipientFilter | ||
| 24 | + # Bitbuffers | ||
| 25 | + from bitbuffers import BitBufferWrite | ||
| 26 | + from bitbuffers import BitBufferRead | ||
| 27 | + # Listeners | ||
| 28 | + from listeners import ListenerManager | ||
| 29 | + # Memory | ||
| 30 | + from memory import make_object | ||
| 31 | + from memory import get_virtual_function | ||
| 32 | + from memory.hooks import PreHook | ||
| 33 | + from memory.hooks import PostHook | ||
| 34 | + # Messages | ||
| 35 | + from messages import UserMessage | ||
| 36 | + from messages import get_message_index | ||
| 37 | + from messages import get_message_name | ||
| 38 | + from messages.impl import get_user_message_impl | ||
| 39 | + | ||
| 40 | + if UserMessage.is_protobuf(): | ||
| 41 | + from _messages import ProtobufMessage | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + # ============================================================================= | ||
| 45 | + # >> ALL DECLARATION | ||
| 46 | + # ============================================================================= | ||
| 47 | + __all__ = ( | ||
| 48 | + 'HookUserMessageBase', | ||
| 49 | + 'HookBitBufferUserMessage', | ||
| 50 | + 'HookProtobufUserMessage', | ||
| 51 | + 'HookUserMessage', | ||
| 52 | + ) | ||
| 53 | + | ||
| 54 | + | ||
| 55 | + # ============================================================================= | ||
| 56 | + # >> GLOBAL VARIABLES | ||
| 57 | + # ============================================================================= | ||
| 58 | + _user_message_data = None | ||
| 59 | + _recipients = RecipientFilter() | ||
| 60 | + | ||
| 61 | + | ||
| 62 | + # ============================================================================= | ||
| 63 | + # >> CLASSES | ||
| 64 | + # ============================================================================= | ||
| 65 | + class HookUserMessageBase(AutoUnload): | ||
| 66 | + """Base decorator for user message hooks.""" | ||
| 67 | + | ||
| 68 | + def __init__(self, user_message): | ||
| 69 | + """Create a new user message hook. | ||
| 70 | + | ||
| 71 | + :param int/str user_message: | ||
| 72 | + The user message index or name to hook. | ||
| 73 | + :raise TypeError: | ||
| 74 | + Raised if ``user_message`` is not and int or str. | ||
| 75 | + :raise ValueError: | ||
| 76 | + Raised if the user message does not exist. | ||
| 77 | + """ | ||
| 78 | + if isinstance(user_message, int): | ||
| 79 | + index = user_message | ||
| 80 | + elif isinstance(user_message, str): | ||
| 81 | + index = get_message_index(user_message) | ||
| 82 | + else: | ||
| 83 | + raise TypeError( | ||
| 84 | + 'Invalid type for "user_message". int or str required.') | ||
| 85 | + | ||
| 86 | + self.message_index = index | ||
| 87 | + self.message_name = get_message_name(index) | ||
| 88 | + self.callback = None | ||
| 89 | + | ||
| 90 | + # Verify that it's a valid index | ||
| 91 | + if self.message_name is None: | ||
| 92 | + raise ValueError(f'Invalid user message: {user_message}') | ||
| 93 | + | ||
| 94 | + def __call__(self, callback): | ||
| 95 | + """Finalize the hook registration by registering the callback. | ||
| 96 | + | ||
| 97 | + :param object callback: | ||
| 98 | + A callable object that will be called when a user message is | ||
| 99 | + created. | ||
| 100 | + :return: | ||
| 101 | + The callback that has been passed. | ||
| 102 | + """ | ||
| 103 | + if not callable(callback): | ||
| 104 | + raise ValueError('Callback must be callable.') | ||
| 105 | + | ||
| 106 | + self.callback = callback | ||
| 107 | + self.hooks[self.message_index].register_listener(callback) | ||
| 108 | + return self.callback | ||
| 109 | + | ||
| 110 | + def _unload_instance(self): | ||
| 111 | + """Unregister the user message hook.""" | ||
| 112 | + if self.callback is None: | ||
| 113 | + return | ||
| 114 | + | ||
| 115 | + self.hooks[self.message_index].unregister_listener(self.callback) | ||
| 116 | + | ||
| 117 | + @property | ||
| 118 | + def hooks(self): | ||
| 119 | + """Return all hooks for a user message. | ||
| 120 | + | ||
| 121 | + :rtype: ListenerManager | ||
| 122 | + """ | ||
| 123 | + raise NotImplementedError('Must be implemented by a subclass.') | ||
| 124 | + | ||
| 125 | + | ||
| 126 | + class HookBitBufferUserMessage(HookUserMessageBase): | ||
| 127 | + """Decorator to register a raw user message hook for bitbuffer messages.""" | ||
| 128 | + | ||
| 129 | + hooks = defaultdict(ListenerManager) | ||
| 130 | + | ||
| 131 | + | ||
| 132 | + class HookProtobufUserMessage(HookUserMessageBase): | ||
| 133 | + """Decorator to register a raw user message hook for protobuf messages.""" | ||
| 134 | + | ||
| 135 | + hooks = defaultdict(ListenerManager) | ||
| 136 | + | ||
| 137 | + | ||
| 138 | + class HookUserMessage(HookUserMessageBase): | ||
| 139 | + """Decorator to register a convenient user message hook.""" | ||
| 140 | + | ||
| 141 | + hooks = defaultdict(ListenerManager) | ||
| 142 | + | ||
| 143 | + def __init__(self, user_message): | ||
| 144 | + """Create a new user message hook. | ||
| 145 | + | ||
| 146 | + :raise NotImplementedError: | ||
| 147 | + Raised if the user message has not been implemented yet in | ||
| 148 | + Source.Python. | ||
| 149 | + | ||
| 150 | + .. seealso:: :meth:`HookUserMessageBase.__init__` | ||
| 151 | + """ | ||
| 152 | + super().__init__(user_message) | ||
| 153 | + | ||
| 154 | + # Verify that the user message is supported/implemented. This will | ||
| 155 | + # raise a NotImplementedError if it isn't. | ||
| 156 | + self.impl = get_user_message_impl(self.message_index) | ||
| 157 | + | ||
| 158 | + | ||
| 159 | + # ============================================================================= | ||
| 160 | + # >> HOOKS | ||
| 161 | + # ============================================================================= | ||
| 162 | + if UserMessage.is_protobuf(): | ||
| 163 | + @PreHook(get_virtual_function(engine_server, 'SendUserMessage')) | ||
| 164 | + def _pre_send_user_message(args): | ||
| 165 | + message_index = args[2] | ||
| 166 | + | ||
| 167 | + user_message_hooks = HookUserMessage.hooks[message_index] | ||
| 168 | + protobuf_user_message_hooks = HookProtobufUserMessage.hooks[message_index] | ||
| 169 | + | ||
| 170 | + # No need to do anything behind this if no listener is registered | ||
| 171 | + if not user_message_hooks and not protobuf_user_message_hooks: | ||
| 172 | + return | ||
| 173 | + | ||
| 174 | + # Replace original recipients filter | ||
| 175 | + tmp_recipients = make_object(BaseRecipientFilter, args[1]) | ||
| 176 | + _recipients.update(*tuple(tmp_recipients), clear=True) | ||
| 177 | + args[1] = _recipients | ||
| 178 | + | ||
| 179 | + buffer = make_object(ProtobufMessage, args[3]) | ||
| 180 | + | ||
| 181 | + protobuf_user_message_hooks.notify(_recipients, buffer) | ||
| 182 | + | ||
| 183 | + # No need to do anything behind this if no listener is registered | ||
| 184 | + if not user_message_hooks: | ||
| 185 | + return | ||
| 186 | + | ||
| 187 | + try: | ||
| 188 | + impl = get_user_message_impl(message_index) | ||
| 189 | + except NotImplementedError: | ||
| 190 | + return | ||
| 191 | + | ||
| 192 | + data = impl.read(buffer) | ||
| 193 | + user_message_hooks.notify(_recipients, data) | ||
| 194 | + | ||
| 195 | + # Update buffer if data has been changed | ||
| 196 | + if data.has_been_changed(): | ||
| 197 | + buffer.clear() | ||
| 198 | + impl.write(buffer, data) | ||
| 199 | + | ||
| 200 | + else: | ||
| 201 | + @PreHook(get_virtual_function(engine_server, 'UserMessageBegin')) | ||
| 202 | + def _pre_user_message_begin(args): | ||
| 203 | + # Replace original recipients filter | ||
| 204 | + tmp_recipients = make_object(BaseRecipientFilter, args[1]) | ||
| 205 | + _recipients.update(*tuple(tmp_recipients), clear=True) | ||
| 206 | + args[1] = _recipients | ||
| 207 | + | ||
| 208 | + @PostHook(get_virtual_function(engine_server, 'UserMessageBegin')) | ||
| 209 | + def _post_user_message_begin(args, return_value): | ||
| 210 | + global _user_message_data | ||
| 211 | + _user_message_data = (args[2], return_value) | ||
| 212 | + | ||
| 213 | + @PreHook(get_virtual_function(engine_server, 'MessageEnd')) | ||
| 214 | + def _pre_message_end(args): | ||
| 215 | + # This happens when we initialize our hooks, while a user message is | ||
| 216 | + # currently being created | ||
| 217 | + if _user_message_data is None: | ||
| 218 | + return | ||
| 219 | + | ||
| 220 | + message_index, buffer_write_ptr = _user_message_data | ||
| 221 | + | ||
| 222 | + # Retrieve the ListenerManager instances | ||
| 223 | + user_message_hooks = HookUserMessage.hooks[message_index] | ||
| 224 | + bitbuffer_user_message_hooks = HookBitBufferUserMessage.hooks[message_index] | ||
| 225 | + | ||
| 226 | + # No need to do anything behind this if no listener is registered | ||
| 227 | + if not user_message_hooks and not bitbuffer_user_message_hooks: | ||
| 228 | + return | ||
| 229 | + | ||
| 230 | + buffer_write = make_object(BitBufferWrite, buffer_write_ptr) | ||
| 231 | + buffer_read = BitBufferRead(buffer_write, False) | ||
| 232 | + | ||
| 233 | + org_current_bit = buffer_write.current_bit | ||
| 234 | + | ||
| 235 | + # For bitbuffers we need to make sure every callback starts reading and | ||
| 236 | + # writing from the very first bit. | ||
| 237 | + for callback in bitbuffer_user_message_hooks: | ||
| 238 | + buffer_read.seek_to_bit(0) | ||
| 239 | + buffer_write.seek_to_bit(0) | ||
| 240 | + callback(_recipients, buffer_read, buffer_write) | ||
| 241 | + | ||
| 242 | + # If none of the above callbacks wrote to the buffer, we need to restore | ||
| 243 | + # the current_bit to the original value. | ||
| 244 | + if buffer_write.current_bit == 0: | ||
| 245 | + buffer_write.seek_to_bit(org_current_bit) | ||
| 246 | + | ||
| 247 | + # No need to do anything behind this if no listener is registered | ||
| 248 | + if not user_message_hooks: | ||
| 249 | + return | ||
| 250 | + | ||
| 251 | + try: | ||
| 252 | + impl = get_user_message_impl(message_index) | ||
| 253 | + except NotImplementedError: | ||
| 254 | + return | ||
| 255 | + | ||
| 256 | + buffer_read.seek_to_bit(0) | ||
| 257 | + data = impl.read(buffer_read) | ||
| 258 | + user_message_hooks.notify(_recipients, data) | ||
| 259 | + | ||
| 260 | + # Update buffer if data has been changed | ||
| 261 | + if data.has_been_changed(): | ||
| 262 | + buffer_write.seek_to_bit(0) | ||
| 263 | + impl.write(buffer_write, data) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments