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/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/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/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",
@ -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/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/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/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/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/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",
@ -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/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/ukrainian.json": "https://files.ballistica.net/cache/ba1/c3/61/d5bcf2bcad50104b26d22d3365a4",
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/4b/da/7e444f86c768aee70779a0f7a28f",
"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/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/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",

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<!-- 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,
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>

View File

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

View File

@ -75,6 +75,8 @@ class EmptyResponse(Response):
# TODO: could allow handlers to deal in raw values for these
# 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
@dataclass
class BoolResponse(Response):
@ -83,6 +85,14 @@ class BoolResponse(Response):
value: Annotated[bool, IOAttrs('v')]
@ioprepped
@dataclass
class StringResponse(Response):
"""A simple string value response."""
value: Annotated[str, IOAttrs('v')]
class MessageProtocol:
"""Wrangles a set of message types, formats, and response types.
Both endpoints must be using a compatible Protocol for communication
@ -154,7 +164,7 @@ class MessageProtocol:
_reg_if_not(ErrorResponse, -1)
_reg_if_not(EmptyResponse, -2)
_reg_if_not(BoolResponse, -3)
# _reg_if_not(BoolResponse, -3)
# Some extra-thorough validation in debug mode.
if __debug__:
@ -173,7 +183,8 @@ class MessageProtocol:
assert issubclass(cls, Response)
if cls not in self.response_ids_by_type:
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.
# We can take advantage of this to generate cleaner looking
@ -270,46 +281,78 @@ class MessageProtocol:
def _get_module_header(self, part: str) -> str:
"""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]] = {}
single_message_type = len(self.message_ids_by_type) == 1
# Always import messages
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]:
# Skip these as they don't actually show up in code.
if rsp_tp is EmptyResponse or rsp_tp is ErrorResponse:
continue
imports.setdefault(rsp_tp.__module__, []).append(rsp_tp.__name__)
importlines2 = ''
if (single_message_type and part == 'sender'
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()):
jnames = ', '.join(names)
line = f'from {module} import {jnames}'
if len(line) > 79:
# Recreate in a wrapping-friendly form.
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':
importlines1 = (
'from efro.message import MessageSender, BoundMessageSender')
tpimportex = ''
import_lines += ('from efro.message import MessageSender,'
' BoundMessageSender')
tpimport_typing_extras = ''
else:
importlines1 = ('from efro.message import MessageReceiver,'
' BoundMessageReceiver')
tpimportex = ', Awaitable'
if single_message_type:
import_lines += ('from efro.message import (MessageReceiver,'
' 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'
f'#\n'
f'"""Auto-generated {part} module. Do not edit by hand."""\n'
f'\n'
f'from __future__ import annotations\n'
f'\n'
f'from typing import TYPE_CHECKING, overload\n'
f'from typing import TYPE_CHECKING{ovld}\n'
f'\n'
f'{importlines1}\n'
f'{import_lines}\n'
f'\n'
f'if TYPE_CHECKING:\n'
f' from typing import Union, Any, Optional, Callable'
f'{tpimportex}\n'
f'{importlines2}'
f'{tpimport_typing_extras}\n'
f'{tpimport_lines}'
f'\n'
f'\n')
return out
@ -324,6 +367,8 @@ class MessageProtocol:
# pylint: disable=too-many-locals
import textwrap
msgtypes = list(self.message_ids_by_type.keys())
ppre = '_' if private else ''
out = self._get_module_header('sender')
ccind = textwrap.indent(protocol_create_code, ' ')
@ -345,16 +390,12 @@ class MessageProtocol:
f'class {ppre}Bound{basename}(BoundMessageSender):\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:
# We accept None to equal EmptyResponse so reflect that
# in the type annotation.
return 'None' if rtype is EmptyResponse else rtype.__name__
# Define handler() overloads for all registered message types.
if msgtypes:
for async_pass in False, True:
if async_pass and not enable_async_sends:
@ -422,6 +463,7 @@ class MessageProtocol:
desc = 'asynchronous' if is_async else 'synchronous'
ppre = '_' if private else ''
msgtypes = list(self.message_ids_by_type.keys())
out = self._get_module_header('receiver')
ccind = textwrap.indent(protocol_create_code, ' ')
out += (f'class {ppre}{basename}(MessageReceiver):\n'
@ -442,9 +484,6 @@ class MessageProtocol:
f'obj, self)\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:
# We accept None to equal EmptyResponse so reflect that
@ -539,7 +578,7 @@ class MessageSender:
class MyClass:
msg = MyMessageSender(some_protocol)
@msg.sendmethod
@msg.send_method
def send_raw_message(self, message: str) -> str:
# Actually send the message here.
@ -676,10 +715,9 @@ class MessageReceiver:
The message type handled by the call is determined by its
type annotation.
"""
# pylint: disable=too-many-locals
# TODO: can use types.GenericAlias in 3.9.
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)
@ -700,7 +738,10 @@ class MessageReceiver:
# Check annotation types to determine what message types we handle.
# Return-type annotation can be a Union, but we probably don't
# 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')
if not isinstance(msgtype, type):
@ -758,11 +799,12 @@ class MessageReceiver:
if issubclass(msgtype, Response):
continue
if msgtype not in self._handlers:
msg = (f'Protocol message {msgtype} not handled'
f' by receiver.')
msg = (f'Protocol message type {msgtype} is not handled'
f' by receiver type {type(self)}.')
if warn_only:
logging.warning(msg)
raise TypeError(msg)
else:
raise TypeError(msg)
def _decode_incoming_message(self,
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.
# 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.
# 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:
for dnode in node.decorators.nodes:
if (isinstance(dnode, astroid.nodes.Name)
and dnode.name in ('dispatchmethod', 'singledispatch')):
and dnode.name in {'dispatchmethod', 'singledispatch'}):
return node # Leave annotations intact.
if (isinstance(dnode, astroid.nodes.Attribute)
and dnode.attrname == 'register'):
and dnode.attrname in {'register', 'handler'}):
return node # Leave annotations intact.
node.args.annotations = [None for _ in node.args.args]