efro.message tidying and language updates

This commit is contained in:
Eric Froemling 2021-09-27 16:48:11 -05:00
parent 638e428883
commit f368cbf30f
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
7 changed files with 92 additions and 43 deletions

View File

@ -420,7 +420,7 @@
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/14/f1/4f2995d78fc20dd79dfb39c5d554", "assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/14/f1/4f2995d78fc20dd79dfb39c5d554",
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/57/ac/6ed0caecd25dc23688debed24c45", "assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/57/ac/6ed0caecd25dc23688debed24c45",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/32/08/38dac4a79ab2acee76a75d32a310", "assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/32/08/38dac4a79ab2acee76a75d32a310",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/1f/21/0f8b5de13f6bd5fb1e13564d7c58", "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/e4/48/6648ea4178c12d6e25ed3a53f628",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/0f/0e/7184059414320d32104463e41038", "assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/0f/0e/7184059414320d32104463e41038",
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/e2/58/c2c5964370df118c51528dc4bfa2", "assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/e2/58/c2c5964370df118c51528dc4bfa2",
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/28/96/397e5c164a595c2b6c2d3eb2d4f1", "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/28/96/397e5c164a595c2b6c2d3eb2d4f1",
@ -436,11 +436,11 @@
"assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/0a/ec/f6665a696238275c806e7a0b1d0d", "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/0a/ec/f6665a696238275c806e7a0b1d0d",
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/ff/08/0d32d1babc60fdebd39def8b51da", "assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/ff/08/0d32d1babc60fdebd39def8b51da",
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/63/f0/cc8dd75a100f7d58000a361ca160", "assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/63/f0/cc8dd75a100f7d58000a361ca160",
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/cf/2b/23acc62ab35c4763a9cfe23495dc", "assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/2d/e5/3737c6c3979cf381321c5472bea5",
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/87/e5/a10ddd73cfb7996bbd576032db6a", "assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/87/e5/a10ddd73cfb7996bbd576032db6a",
"assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/dd/97/42d117db366ad4584eb8c58d191e", "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/dd/97/42d117db366ad4584eb8c58d191e",
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/26/8d/bf9cc8db2cc71b69e789898e1093", "assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/26/8d/bf9cc8db2cc71b69e789898e1093",
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/d8/b7/9098f0cb25088d233541490e3e68", "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/2d/d5/661c050696d5a2e70e678054b9e7",
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/2e/17/fb3e7ed77fa54427b434b1791793", "assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/2e/17/fb3e7ed77fa54427b434b1791793",
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/fe/6d/751277bc6b704d4f2a54cf1a9cfa", "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/fe/6d/751277bc6b704d4f2a54cf1a9cfa",
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/82/12/57bf144e12be229a9b70da9c45cb", "assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/82/12/57bf144e12be229a9b70da9c45cb",
@ -451,8 +451,8 @@
"assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/50/9f/be006ba19be6a69a57837eb6dca0", "assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/50/9f/be006ba19be6a69a57837eb6dca0",
"assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/dd/de/c197fa9aff42e4422bc66b95ad88", "assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/dd/de/c197fa9aff42e4422bc66b95ad88",
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/65/e4/b9308f15437972209b4d3fce7abd", "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/65/e4/b9308f15437972209b4d3fce7abd",
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/c3/61/d5bcf2bcad50104b26d22d3365a4", "assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/8f/42/56f3ebcc6005f382449c1c2422fd",
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/4b/da/7e444f86c768aee70779a0f7a28f", "assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/4f/cc/285443e3e8e65a318df338bbc7f7",
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/8b/65/adfbe450da3f61677bd909fff707", "assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/8b/65/adfbe450da3f61677bd909fff707",
"assets/build/ba_data/data/maps/big_g.json": "https://files.ballistica.net/cache/ba1/47/0a/a617cc85d927b576c4e6fc1091ed", "assets/build/ba_data/data/maps/big_g.json": "https://files.ballistica.net/cache/ba1/47/0a/a617cc85d927b576c4e6fc1091ed",
"assets/build/ba_data/data/maps/bridgit.json": "https://files.ballistica.net/cache/ba1/03/4b/57ee9b42854b26f23f81bd8c58ef", "assets/build/ba_data/data/maps/bridgit.json": "https://files.ballistica.net/cache/ba1/03/4b/57ee9b42854b26f23f81bd8c58ef",

View File

@ -1600,6 +1600,7 @@
<w>outvalue</w> <w>outvalue</w>
<w>ouya</w> <w>ouya</w>
<w>overloadsigs</w> <w>overloadsigs</w>
<w>ovld</w>
<w>packagedir</w> <w>packagedir</w>
<w>packagedirs</w> <w>packagedirs</w>
<w>packagename</w> <w>packagename</w>
@ -2356,6 +2357,7 @@
<w>touchpad</w> <w>touchpad</w>
<w>tournamententry</w> <w>tournamententry</w>
<w>tournamentscores</w> <w>tournamentscores</w>
<w>tpimport</w>
<w>tpimportex</w> <w>tpimportex</w>
<w>tpimports</w> <w>tpimports</w>
<w>tplayer</w> <w>tplayer</w>

View File

@ -767,6 +767,7 @@
<w>outval</w> <w>outval</w>
<w>outvalue</w> <w>outvalue</w>
<w>ouya</w> <w>ouya</w>
<w>ovld</w>
<w>parameteriv</w> <w>parameteriv</w>
<w>passcode</w> <w>passcode</w>
<w>pausable</w> <w>pausable</w>
@ -1075,6 +1076,7 @@
<w>touchpad</w> <w>touchpad</w>
<w>toucs</w> <w>toucs</w>
<w>toutf</w> <w>toutf</w>
<w>tpimport</w>
<w>tpimportex</w> <w>tpimportex</w>
<w>tpimports</w> <w>tpimports</w>
<w>tracebacks</w> <w>tracebacks</w>

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2021-09-24 for Ballistica version 1.6.5 build 20393</em></h4> <h4><em>last updated on 2021-09-27 for Ballistica version 1.6.5 build 20393</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module, <p>This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p> which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr> <hr>

View File

@ -6,7 +6,7 @@ from __future__ import annotations
import os import os
import asyncio import asyncio
from typing import TYPE_CHECKING, overload from typing import TYPE_CHECKING, overload, Union
from dataclasses import dataclass from dataclasses import dataclass
import pytest import pytest
@ -19,7 +19,7 @@ from efro.message import (Message, Response, MessageProtocol, MessageSender,
BoundMessageReceiver) BoundMessageReceiver)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import List, Type, Any, Callable, Union, Optional, Awaitable from typing import List, Type, Any, Callable, Optional, Awaitable
@ioprepped @ioprepped

View File

@ -75,6 +75,8 @@ class EmptyResponse(Response):
# TODO: could allow handlers to deal in raw values for these # TODO: could allow handlers to deal in raw values for these
# types similar to how we allow None in place of EmptyResponse. # types similar to how we allow None in place of EmptyResponse.
# Though not sure if they are widely used enough to warrant the
# extra code complexity.
@ioprepped @ioprepped
@dataclass @dataclass
class BoolResponse(Response): class BoolResponse(Response):
@ -83,6 +85,14 @@ class BoolResponse(Response):
value: Annotated[bool, IOAttrs('v')] value: Annotated[bool, IOAttrs('v')]
@ioprepped
@dataclass
class StringResponse(Response):
"""A simple string value response."""
value: Annotated[str, IOAttrs('v')]
class MessageProtocol: class MessageProtocol:
"""Wrangles a set of message types, formats, and response types. """Wrangles a set of message types, formats, and response types.
Both endpoints must be using a compatible Protocol for communication Both endpoints must be using a compatible Protocol for communication
@ -154,7 +164,7 @@ class MessageProtocol:
_reg_if_not(ErrorResponse, -1) _reg_if_not(ErrorResponse, -1)
_reg_if_not(EmptyResponse, -2) _reg_if_not(EmptyResponse, -2)
_reg_if_not(BoolResponse, -3) # _reg_if_not(BoolResponse, -3)
# Some extra-thorough validation in debug mode. # Some extra-thorough validation in debug mode.
if __debug__: if __debug__:
@ -173,7 +183,8 @@ class MessageProtocol:
assert issubclass(cls, Response) assert issubclass(cls, Response)
if cls not in self.response_ids_by_type: if cls not in self.response_ids_by_type:
raise ValueError(f'Possible response type {cls}' raise ValueError(f'Possible response type {cls}'
f' was not included in response_types.') f' needs to be included in response_types'
f' for this protocol.')
# Make sure all registered types have unique base names. # Make sure all registered types have unique base names.
# We can take advantage of this to generate cleaner looking # We can take advantage of this to generate cleaner looking
@ -270,46 +281,78 @@ class MessageProtocol:
def _get_module_header(self, part: str) -> str: def _get_module_header(self, part: str) -> str:
"""Return common parts of generated modules.""" """Return common parts of generated modules."""
# pylint: disable=too-many-locals, too-many-branches
import textwrap
tpimports: Dict[str, List[str]] = {}
imports: Dict[str, List[str]] = {} imports: Dict[str, List[str]] = {}
single_message_type = len(self.message_ids_by_type) == 1
# Always import messages
for msgtype in list(self.message_ids_by_type) + [Message]: for msgtype in list(self.message_ids_by_type) + [Message]:
imports.setdefault(msgtype.__module__, []).append(msgtype.__name__) tpimports.setdefault(msgtype.__module__,
[]).append(msgtype.__name__)
for rsp_tp in list(self.response_ids_by_type) + [Response]: for rsp_tp in list(self.response_ids_by_type) + [Response]:
# Skip these as they don't actually show up in code. # Skip these as they don't actually show up in code.
if rsp_tp is EmptyResponse or rsp_tp is ErrorResponse: if rsp_tp is EmptyResponse or rsp_tp is ErrorResponse:
continue continue
imports.setdefault(rsp_tp.__module__, []).append(rsp_tp.__name__) if (single_message_type and part == 'sender'
importlines2 = '' and rsp_tp is not Response):
# We need to cast to the single supported response type
# in this case so need response types at runtime.
imports.setdefault(rsp_tp.__module__,
[]).append(rsp_tp.__name__)
else:
tpimports.setdefault(rsp_tp.__module__,
[]).append(rsp_tp.__name__)
import_lines = ''
tpimport_lines = ''
for module, names in sorted(imports.items()): for module, names in sorted(imports.items()):
jnames = ', '.join(names) jnames = ', '.join(names)
line = f'from {module} import {jnames}' line = f'from {module} import {jnames}'
if len(line) > 79: if len(line) > 79:
# Recreate in a wrapping-friendly form. # Recreate in a wrapping-friendly form.
line = f'from {module} import ({jnames})' line = f'from {module} import ({jnames})'
importlines2 += f' {line}\n' import_lines += f'{line}\n'
for module, names in sorted(tpimports.items()):
jnames = ', '.join(names)
line = f'from {module} import {jnames}'
if len(line) > 75: # Account for indent
# Recreate in a wrapping-friendly form.
line = f'from {module} import ({jnames})'
tpimport_lines += f'{line}\n'
if part == 'sender': if part == 'sender':
importlines1 = ( import_lines += ('from efro.message import MessageSender,'
'from efro.message import MessageSender, BoundMessageSender') ' BoundMessageSender')
tpimportex = '' tpimport_typing_extras = ''
else: else:
importlines1 = ('from efro.message import MessageReceiver,' if single_message_type:
' BoundMessageReceiver') import_lines += ('from efro.message import (MessageReceiver,'
tpimportex = ', Awaitable' ' BoundMessageReceiver, Message, Response)')
else:
import_lines += ('from efro.message import MessageReceiver,'
' BoundMessageReceiver')
tpimport_typing_extras = ', Awaitable'
ovld = ', overload' if not single_message_type else ''
tpimport_lines = textwrap.indent(tpimport_lines, ' ')
out = ('# Released under the MIT License. See LICENSE for details.\n' out = ('# Released under the MIT License. See LICENSE for details.\n'
f'#\n' f'#\n'
f'"""Auto-generated {part} module. Do not edit by hand."""\n' f'"""Auto-generated {part} module. Do not edit by hand."""\n'
f'\n' f'\n'
f'from __future__ import annotations\n' f'from __future__ import annotations\n'
f'\n' f'\n'
f'from typing import TYPE_CHECKING, overload\n' f'from typing import TYPE_CHECKING{ovld}\n'
f'\n' f'\n'
f'{importlines1}\n' f'{import_lines}\n'
f'\n' f'\n'
f'if TYPE_CHECKING:\n' f'if TYPE_CHECKING:\n'
f' from typing import Union, Any, Optional, Callable' f' from typing import Union, Any, Optional, Callable'
f'{tpimportex}\n' f'{tpimport_typing_extras}\n'
f'{importlines2}' f'{tpimport_lines}'
f'\n' f'\n'
f'\n') f'\n')
return out return out
@ -324,6 +367,8 @@ class MessageProtocol:
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
import textwrap import textwrap
msgtypes = list(self.message_ids_by_type.keys())
ppre = '_' if private else '' ppre = '_' if private else ''
out = self._get_module_header('sender') out = self._get_module_header('sender')
ccind = textwrap.indent(protocol_create_code, ' ') ccind = textwrap.indent(protocol_create_code, ' ')
@ -345,16 +390,12 @@ class MessageProtocol:
f'class {ppre}Bound{basename}(BoundMessageSender):\n' f'class {ppre}Bound{basename}(BoundMessageSender):\n'
f' """Protocol-specific bound sender."""\n') f' """Protocol-specific bound sender."""\n')
# Define handler() overloads for all registered message types.
msgtypes = [
t for t in self.message_ids_by_type if issubclass(t, Message)
]
def _filt_tp_name(rtype: Type[Response]) -> str: def _filt_tp_name(rtype: Type[Response]) -> str:
# We accept None to equal EmptyResponse so reflect that # We accept None to equal EmptyResponse so reflect that
# in the type annotation. # in the type annotation.
return 'None' if rtype is EmptyResponse else rtype.__name__ return 'None' if rtype is EmptyResponse else rtype.__name__
# Define handler() overloads for all registered message types.
if msgtypes: if msgtypes:
for async_pass in False, True: for async_pass in False, True:
if async_pass and not enable_async_sends: if async_pass and not enable_async_sends:
@ -422,6 +463,7 @@ class MessageProtocol:
desc = 'asynchronous' if is_async else 'synchronous' desc = 'asynchronous' if is_async else 'synchronous'
ppre = '_' if private else '' ppre = '_' if private else ''
msgtypes = list(self.message_ids_by_type.keys())
out = self._get_module_header('receiver') out = self._get_module_header('receiver')
ccind = textwrap.indent(protocol_create_code, ' ') ccind = textwrap.indent(protocol_create_code, ' ')
out += (f'class {ppre}{basename}(MessageReceiver):\n' out += (f'class {ppre}{basename}(MessageReceiver):\n'
@ -442,9 +484,6 @@ class MessageProtocol:
f'obj, self)\n') f'obj, self)\n')
# Define handler() overloads for all registered message types. # Define handler() overloads for all registered message types.
msgtypes = [
t for t in self.message_ids_by_type if issubclass(t, Message)
]
def _filt_tp_name(rtype: Type[Response]) -> str: def _filt_tp_name(rtype: Type[Response]) -> str:
# We accept None to equal EmptyResponse so reflect that # We accept None to equal EmptyResponse so reflect that
@ -539,7 +578,7 @@ class MessageSender:
class MyClass: class MyClass:
msg = MyMessageSender(some_protocol) msg = MyMessageSender(some_protocol)
@msg.sendmethod @msg.send_method
def send_raw_message(self, message: str) -> str: def send_raw_message(self, message: str) -> str:
# Actually send the message here. # Actually send the message here.
@ -676,10 +715,9 @@ class MessageReceiver:
The message type handled by the call is determined by its The message type handled by the call is determined by its
type annotation. type annotation.
""" """
# pylint: disable=too-many-locals
# TODO: can use types.GenericAlias in 3.9. # TODO: can use types.GenericAlias in 3.9.
from typing import _GenericAlias # type: ignore from typing import _GenericAlias # type: ignore
from typing import Union, get_type_hints, get_args from typing import get_type_hints, get_args
sig = inspect.getfullargspec(call) sig = inspect.getfullargspec(call)
@ -700,7 +738,10 @@ class MessageReceiver:
# Check annotation types to determine what message types we handle. # Check annotation types to determine what message types we handle.
# Return-type annotation can be a Union, but we probably don't # Return-type annotation can be a Union, but we probably don't
# have it available at runtime. Explicitly pull it in. # have it available at runtime. Explicitly pull it in.
anns = get_type_hints(call, localns={'Union': Union}) # UPDATE: we've updated our pylint filter to where we should
# have all annotations available.
# anns = get_type_hints(call, localns={'Union': Union})
anns = get_type_hints(call)
msgtype = anns.get('msg') msgtype = anns.get('msg')
if not isinstance(msgtype, type): if not isinstance(msgtype, type):
@ -758,11 +799,12 @@ class MessageReceiver:
if issubclass(msgtype, Response): if issubclass(msgtype, Response):
continue continue
if msgtype not in self._handlers: if msgtype not in self._handlers:
msg = (f'Protocol message {msgtype} not handled' msg = (f'Protocol message type {msgtype} is not handled'
f' by receiver.') f' by receiver type {type(self)}.')
if warn_only: if warn_only:
logging.warning(msg) logging.warning(msg)
raise TypeError(msg) else:
raise TypeError(msg)
def _decode_incoming_message(self, def _decode_incoming_message(self,
msg: str) -> Tuple[Message, Type[Message]]: msg: str) -> Tuple[Message, Type[Message]]:

View File

@ -123,17 +123,20 @@ def func_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
# Wipe out argument annotations. # Wipe out argument annotations.
# Special-case: functools.singledispatch and ba.dispatchmethod *do* # Special-case: certain function decorators *do*
# evaluate annotations at runtime so we want to leave theirs intact. # evaluate annotations at runtime so we want to leave theirs intact.
# Lets just look for a @XXX.register decorator used by both I guess. # This includes functools.singledispatch, ba.dispatchmethod, and
# efro.MessageReceiver.
# Lets just look for a @XXX.register or @XXX.handler decorators for
# now; can get more specific if we get false positives.
if node.decorators is not None: if node.decorators is not None:
for dnode in node.decorators.nodes: for dnode in node.decorators.nodes:
if (isinstance(dnode, astroid.nodes.Name) if (isinstance(dnode, astroid.nodes.Name)
and dnode.name in ('dispatchmethod', 'singledispatch')): and dnode.name in {'dispatchmethod', 'singledispatch'}):
return node # Leave annotations intact. return node # Leave annotations intact.
if (isinstance(dnode, astroid.nodes.Attribute) if (isinstance(dnode, astroid.nodes.Attribute)
and dnode.attrname == 'register'): and dnode.attrname in {'register', 'handler'}):
return node # Leave annotations intact. return node # Leave annotations intact.
node.args.annotations = [None for _ in node.args.args] node.args.annotations = [None for _ in node.args.args]