C++ layer cleanup

This commit is contained in:
Eric Froemling 2020-06-01 15:24:36 -07:00
parent a2bb5b9751
commit 5d7c72c365
13 changed files with 218 additions and 78 deletions

View File

@ -4132,16 +4132,16 @@
"assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c", "assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c",
"assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb", "assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb",
"assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe", "assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe",
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cd/cc/837483543f1d6b184f64b5e6a950", "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1e/d6/ebdfb7bda48f2dec85c5df52ced9",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/e6/d372324c7c08b5b300490fa5594e", "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4a/0e/d344541dc2ae2a6eaf1024c99f7c",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/0e/11/3dda0974b64f51be4961628f572c", "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/68/c3/fe2482149437800c83fed338140e",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cc/76/3f6356dd599091f5955ce349593b", "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/74/00/78f77d5ab9cc74a2f624f3a7253b",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e1/23/3fe78cdef456a99140837ede7e49", "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ea/d1/017aaa42a7ace25a7b1f782632e5",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1c/bd/4c73637ee172630ee00145032ce7", "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4f/b3/b149964180b661161e5f86109d99",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/7a/a7/342bea2a6ec2f94d5de7bb52a59f", "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/7c/97/177b59dcf24b839ca2d932200bb8",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/aa/24/5426cb7e6ca01b9e5d67ad3217b0", "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d8/55/9edbc1eea06cf354fe37b50d68d2",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/cb/73/469fe3eb016f1e9c7f5c7811b182", "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/fc/ec/eb46555d63e5e3fd55497d1c1079",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7c/fb/7e0880c1ab90b0484cdedc41c8ae", "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/71/73/b8d329287a0a46cc8ed2ef08a7d9",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f3/f2/097e861cf6ca981b18191e4c43b5", "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/62/79/66fc05a4388c4b5a9e9a7fd68a46",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/6b/7c/8407f4a7326b8b19f3e187d3ffcb" "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c3/5d/eb6c20729c0572e717c907482205"
} }

View File

@ -262,6 +262,7 @@
<w>centeuro</w> <w>centeuro</w>
<w>centiseconds</w> <w>centiseconds</w>
<w>cfconfig</w> <w>cfconfig</w>
<w>cfenv</w>
<w>cfgdir</w> <w>cfgdir</w>
<w>cfgkey</w> <w>cfgkey</w>
<w>cfgkeys</w> <w>cfgkeys</w>
@ -280,6 +281,7 @@
<w>charname</w> <w>charname</w>
<w>charstr</w> <w>charstr</w>
<w>chatmessage</w> <w>chatmessage</w>
<w>cheadersline</w>
<w>checkarg</w> <w>checkarg</w>
<w>checkboxwidget</w> <w>checkboxwidget</w>
<w>checkenv</w> <w>checkenv</w>
@ -323,6 +325,7 @@
<w>codefilenames</w> <w>codefilenames</w>
<w>codefiles</w> <w>codefiles</w>
<w>codehash</w> <w>codehash</w>
<w>codelines</w>
<w>codeop</w> <w>codeop</w>
<w>collapsable</w> <w>collapsable</w>
<w>collidemodel</w> <w>collidemodel</w>
@ -374,6 +377,7 @@
<w>cpplintcode</w> <w>cpplintcode</w>
<w>cpplintcodefull</w> <w>cpplintcodefull</w>
<w>cpplintfull</w> <w>cpplintfull</w>
<w>cpplintmodule</w>
<w>cpuinfo</w> <w>cpuinfo</w>
<w>cpus</w> <w>cpus</w>
<w>cpython</w> <w>cpython</w>
@ -398,6 +402,7 @@
<w>cutscenes</w> <w>cutscenes</w>
<w>cval</w> <w>cval</w>
<w>cwdg</w> <w>cwdg</w>
<w>cxxabi</w>
<w>cyaml</w> <w>cyaml</w>
<w>cygwinccompiler</w> <w>cygwinccompiler</w>
<w>darwiin</w> <w>darwiin</w>
@ -750,6 +755,7 @@
<w>genmapjson</w> <w>genmapjson</w>
<w>genstartercache</w> <w>genstartercache</w>
<w>genutils</w> <w>genutils</w>
<w>getaccountid</w>
<w>getactivity</w> <w>getactivity</w>
<w>getclass</w> <w>getclass</w>
<w>getcollide</w> <w>getcollide</w>
@ -833,6 +839,7 @@
<w>hatmotion</w> <w>hatmotion</w>
<w>hattach</w> <w>hattach</w>
<w>hdpi</w> <w>hdpi</w>
<w>headercheckline</w>
<w>headerregistry</w> <w>headerregistry</w>
<w>heapqmodule</w> <w>heapqmodule</w>
<w>hehe</w> <w>hehe</w>
@ -960,6 +967,7 @@
<w>keywd</w> <w>keywd</w>
<w>keywds</w> <w>keywds</w>
<w>khronos</w> <w>khronos</w>
<w>kickable</w>
<w>kickin</w> <w>kickin</w>
<w>kickstart</w> <w>kickstart</w>
<w>killcount</w> <w>killcount</w>
@ -987,6 +995,7 @@
<w>lazybuilddir</w> <w>lazybuilddir</w>
<w>lbits</w> <w>lbits</w>
<w>lbld</w> <w>lbld</w>
<w>lbval</w>
<w>lcfg</w> <w>lcfg</w>
<w>lcolor</w> <w>lcolor</w>
<w>lcrypto</w> <w>lcrypto</w>
@ -999,8 +1008,10 @@
<w>levelmodule</w> <w>levelmodule</w>
<w>levelname</w> <w>levelname</w>
<w>lfull</w> <w>lfull</w>
<w>lfval</w>
<w>libcrypto</w> <w>libcrypto</w>
<w>libegl</w> <w>libegl</w>
<w>libgen</w>
<w>libinst</w> <w>libinst</w>
<w>liblzma</w> <w>liblzma</w>
<w>libmain</w> <w>libmain</w>
@ -1034,6 +1045,7 @@
<w>lintscriptsfast</w> <w>lintscriptsfast</w>
<w>listobj</w> <w>listobj</w>
<w>listvalidconfigs</w> <w>listvalidconfigs</w>
<w>lival</w>
<w>llzma</w> <w>llzma</w>
<w>lmerged</w> <w>lmerged</w>
<w>lmod</w> <w>lmod</w>
@ -1070,6 +1082,7 @@
<w>lstart</w> <w>lstart</w>
<w>lstr</w> <w>lstr</w>
<w>lstrs</w> <w>lstrs</w>
<w>lsval</w>
<w>ltex</w> <w>ltex</w>
<w>lzma</w> <w>lzma</w>
<w>lzmamodule</w> <w>lzmamodule</w>
@ -1208,6 +1221,7 @@
<w>ndkpath</w> <w>ndkpath</w>
<w>neededsettings</w> <w>neededsettings</w>
<w>ness</w> <w>ness</w>
<w>netlink</w>
<w>nettesting</w> <w>nettesting</w>
<w>netutils</w> <w>netutils</w>
<w>nevermind</w> <w>nevermind</w>
@ -1576,6 +1590,7 @@
<w>rsdr</w> <w>rsdr</w>
<w>rsms</w> <w>rsms</w>
<w>rstr</w> <w>rstr</w>
<w>rtnetlink</w>
<w>rtxt</w> <w>rtxt</w>
<w>runmypy</w> <w>runmypy</w>
<w>runonly</w> <w>runonly</w>
@ -1667,6 +1682,7 @@
<w>sharedctypes</w> <w>sharedctypes</w>
<w>sharedobj</w> <w>sharedobj</w>
<w>sharedobjs</w> <w>sharedobjs</w>
<w>shellapi</w>
<w>shiftdelay</w> <w>shiftdelay</w>
<w>shiftposition</w> <w>shiftposition</w>
<w>shobs</w> <w>shobs</w>
@ -1792,6 +1808,8 @@
<w>subrepos</w> <w>subrepos</w>
<w>subsel</w> <w>subsel</w>
<w>subval</w> <w>subval</w>
<w>subvalue</w>
<w>subvaluetype</w>
<w>successfull</w> <w>successfull</w>
<w>suiciding</w> <w>suiciding</w>
<w>sunau</w> <w>sunau</w>

View File

@ -34,7 +34,7 @@ class AppDelegate:
Category: App Classes Category: App Classes
""" """
def create_default_game_config_ui( def create_default_game_settings_ui(
self, gameclass: Type[ba.GameActivity], self, gameclass: Type[ba.GameActivity],
sessionclass: Type[ba.Session], config: Optional[Dict[str, Any]], sessionclass: Type[ba.Session], config: Optional[Dict[str, Any]],
completion_call: Callable[[Optional[Dict[str, Any]]], completion_call: Callable[[Optional[Dict[str, Any]]],
@ -47,4 +47,4 @@ class AppDelegate:
del gameclass, sessionclass, config, completion_call # unused del gameclass, sessionclass, config, completion_call # unused
from ba import _error from ba import _error
_error.print_error( _error.print_error(
"create_default_game_config_ui needs to be overridden") "create_default_game_settings_ui needs to be overridden")

View File

@ -103,8 +103,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
""" """
delegate = _ba.app.delegate delegate = _ba.app.delegate
assert delegate is not None assert delegate is not None
delegate.create_default_game_config_ui(cls, sessionclass, settings, delegate.create_default_game_settings_ui(cls, sessionclass, settings,
completion_call) completion_call)
@classmethod @classmethod
def get_score_info(cls) -> ba.ScoreInfo: def get_score_info(cls) -> ba.ScoreInfo:

View File

@ -32,7 +32,7 @@ if TYPE_CHECKING:
class AppDelegate(ba.AppDelegate): class AppDelegate(ba.AppDelegate):
"""Defines handlers for high level app functionality.""" """Defines handlers for high level app functionality."""
def create_default_game_config_ui( def create_default_game_settings_ui(
self, gameclass: Type[ba.GameActivity], self, gameclass: Type[ba.GameActivity],
sessionclass: Type[ba.Session], config: Optional[Dict[str, Any]], sessionclass: Type[ba.Session], config: Optional[Dict[str, Any]],
completion_call: Callable[[Optional[Dict[str, Any]]], completion_call: Callable[[Optional[Dict[str, Any]]],

View File

@ -12,7 +12,8 @@
"pytz", "pytz",
"yaml", "yaml",
"requests", "requests",
"typing_extensions" "typing_extensions",
"cpplint"
], ],
"python_paths": [ "python_paths": [
"assets/src/ba_data/python", "assets/src/ba_data/python",

View File

@ -21,6 +21,9 @@ no_implicit_reexport = False
[mypy-pylint.*] [mypy-pylint.*]
ignore_missing_imports = True ignore_missing_imports = True
[mypy-cpplint.*]
ignore_missing_imports = True
[mypy-xml.*] [mypy-xml.*]
ignore_missing_imports = True ignore_missing_imports = True

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 2020-05-31 for Ballistica version 1.5.0 build 20036</em></h4> <h4><em>last updated on 2020-06-01 for Ballistica version 1.5.0 build 20037</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>
@ -1079,8 +1079,8 @@ manually.</p>
<h3>Methods:</h3> <h3>Methods:</h3>
<dl> <dl>
<dt><h4><a name="method_ba_AppDelegate__create_default_game_config_ui">create_default_game_config_ui()</a></dt></h4><dd> <dt><h4><a name="method_ba_AppDelegate__create_default_game_settings_ui">create_default_game_settings_ui()</a></dt></h4><dd>
<p><span>create_default_game_config_ui(self, gameclass: Type[<a href="#class_ba_GameActivity">ba.GameActivity</a>], sessionclass: Type[<a href="#class_ba_Session">ba.Session</a>], config: Optional[Dict[str, Any]], completion_call: Callable[[Optional[Dict[str, Any]]], None]) -&gt; None</span></p> <p><span>create_default_game_settings_ui(self, gameclass: Type[<a href="#class_ba_GameActivity">ba.GameActivity</a>], sessionclass: Type[<a href="#class_ba_Session">ba.Session</a>], config: Optional[Dict[str, Any]], completion_call: Callable[[Optional[Dict[str, Any]]], None]) -&gt; None</span></p>
<p>Launch a UI to configure the given game config.</p> <p>Launch a UI to configure the given game config.</p>

View File

@ -22,7 +22,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pytest import pytest
@ -30,11 +30,12 @@ import pytest
from efro.dataclasses import dataclass_assign, dataclass_validate from efro.dataclasses import dataclass_assign, dataclass_validate
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional from typing import Optional, List
def test_assign() -> None: def test_assign() -> None:
"""Testing various assignments.""" """Testing various assignments."""
# pylint: disable=too-many-statements
@dataclass @dataclass
class _TestClass: class _TestClass:
@ -46,6 +47,10 @@ def test_assign() -> None:
osval: Optional[str] = None osval: Optional[str] = None
obval: Optional[bool] = None obval: Optional[bool] = None
ofval: Optional[float] = None ofval: Optional[float] = None
lsval: List[str] = field(default_factory=list)
lival: List[int] = field(default_factory=list)
lbval: List[bool] = field(default_factory=list)
lfval: List[float] = field(default_factory=list)
tclass = _TestClass() tclass = _TestClass()
@ -66,24 +71,39 @@ def test_assign() -> None:
dataclass_assign(tclass, {'nonexistent': 'foo'}) dataclass_assign(tclass, {'nonexistent': 'foo'})
# Correct types. # Correct types.
dataclass_assign(tclass, { dataclass_assign(
'ival': 1, tclass, {
'sval': 'foo', 'ival': 1,
'bval': True, 'sval': 'foo',
'fval': 2.0, 'bval': True,
}) 'fval': 2.0,
dataclass_assign(tclass, { 'lsval': ['foo'],
'oival': None, 'lival': [10],
'osval': None, 'lbval': [False],
'obval': None, 'lfval': [1.0]
'ofval': None, })
}) dataclass_assign(
dataclass_assign(tclass, { tclass, {
'oival': 1, 'oival': None,
'osval': 'foo', 'osval': None,
'obval': True, 'obval': None,
'ofval': 2.0, 'ofval': None,
}) 'lsval': [],
'lival': [],
'lbval': [],
'lfval': []
})
dataclass_assign(
tclass, {
'oival': 1,
'osval': 'foo',
'obval': True,
'ofval': 2.0,
'lsval': ['foo', 'bar', 'eep'],
'lival': [10, 11, 12],
'lbval': [False, True],
'lfval': [1.0, 2.0, 3.0]
})
# Type mismatches. # Type mismatches.
with pytest.raises(TypeError): with pytest.raises(TypeError):
@ -107,6 +127,21 @@ def test_assign() -> None:
with pytest.raises(TypeError): with pytest.raises(TypeError):
dataclass_assign(tclass, {'ofval': 'blah'}) dataclass_assign(tclass, {'ofval': 'blah'})
with pytest.raises(TypeError):
dataclass_assign(tclass, {'lsval': 'blah'})
with pytest.raises(TypeError):
dataclass_assign(tclass, {'lsval': [1]})
with pytest.raises(TypeError):
dataclass_assign(tclass, {'lbval': [None]})
with pytest.raises(TypeError):
dataclass_assign(tclass, {'lival': ['foo']})
with pytest.raises(TypeError):
dataclass_assign(tclass, {'lfval': [True]})
# More subtle ones (we currently require EXACT type matches) # More subtle ones (we currently require EXACT type matches)
with pytest.raises(TypeError): with pytest.raises(TypeError):
dataclass_assign(tclass, {'ival': True}) dataclass_assign(tclass, {'ival': True})
@ -120,6 +155,9 @@ def test_assign() -> None:
with pytest.raises(TypeError): with pytest.raises(TypeError):
dataclass_assign(tclass, {'ofval': 1}) dataclass_assign(tclass, {'ofval': 1})
with pytest.raises(TypeError):
dataclass_assign(tclass, {'lfval': [1]})
def test_validate() -> None: def test_validate() -> None:
"""Testing validation.""" """Testing validation."""

View File

@ -22,7 +22,7 @@
from __future__ import annotations from __future__ import annotations
from enum import Enum from enum import Enum
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
@ -45,6 +45,14 @@ class ServerConfig:
# be enabled unless you are hosting on a LAN with no internet connection. # be enabled unless you are hosting on a LAN with no internet connection.
authenticate_clients: bool = True authenticate_clients: bool = True
# IDs of server admins. Server admins are not kickable through the default
# kick vote system and they are able to kick players without a vote. To get
# your account id, enter 'getaccountid' in settings->advanced->enter-code.
admins: List[str] = field(default_factory=list)
# Whether the default kick-voting system is enabled.
enable_default_kick_voting: bool = True
# UDP port to host on. Change this to work around firewalls or run multiple # UDP port to host on. Change this to work around firewalls or run multiple
# servers on one machine. # servers on one machine.
# 43210 is the default and the only port that will show up in the LAN # 43210 is the default and the only port that will show up in the LAN

View File

@ -33,7 +33,7 @@ from typing import TYPE_CHECKING
from efro.terminal import Clr from efro.terminal import Clr
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import List, Sequence, Optional from typing import List, Sequence, Optional, Any
# Python pip packages we require for this project. # Python pip packages we require for this project.
@ -612,7 +612,11 @@ def _get_server_config_template_yaml(projroot: str) -> str:
assert vname.endswith(':') assert vname.endswith(':')
vname = vname[:-1] vname = vname[:-1]
assert veq == '=' assert veq == '='
vval = eval(vval_raw) # pylint: disable=eval-used vval: Any
if vval_raw == 'field(default_factory=list)':
vval = []
else:
vval = eval(vval_raw) # pylint: disable=eval-used
# Filter/override a few things. # Filter/override a few things.
if vname == 'playlist_code': if vname == 'playlist_code':
@ -621,7 +625,13 @@ def _get_server_config_template_yaml(projroot: str) -> str:
elif vname == 'stats_url': elif vname == 'stats_url':
vval = ('https://mystatssite.com/' vval = ('https://mystatssite.com/'
'showstats?player=${ACCOUNT}') 'showstats?player=${ACCOUNT}')
lines_out.append('#' + yaml.dump({vname: vval}).strip()) elif vname == 'admins':
vval = ['pb-yOuRAccOuNtIdHErE', 'pb-aNdMayBeAnotherHeRE']
lines_out += [
'#' + l for l in yaml.dump({
vname: vval
}).strip().splitlines()
]
else: else:
# Convert comments referring to python bools to yaml bools. # Convert comments referring to python bools to yaml bools.
line = line.replace('True', 'true').replace('False', 'false') line = line.replace('True', 'true').replace('False', 'false')

View File

@ -30,9 +30,9 @@ if TYPE_CHECKING:
# For fields with these string types, we require a passed value's type # For fields with these string types, we require a passed value's type
# to exactly match one of the tuple values to consider the assignment valid. # to exactly match one of the tuple values to consider the assignment valid.
_SIMPLE_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = { _SIMPLE_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = {
'bool': (bool, ),
'str': (str, ),
'int': (int, ), 'int': (int, ),
'str': (str, ),
'bool': (bool, ),
'float': (float, ), 'float': (float, ),
'Optional[int]': (int, type(None)), 'Optional[int]': (int, type(None)),
'Optional[str]': (str, type(None)), 'Optional[str]': (str, type(None)),
@ -40,6 +40,13 @@ _SIMPLE_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = {
'Optional[float]': (float, type(None)), 'Optional[float]': (float, type(None)),
} }
_LIST_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = {
'List[int]': (int, ),
'List[str]': (str, ),
'List[bool]': (bool, ),
'List[float]': (float, ),
}
def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None: def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
"""Safely assign values from a dict to a dataclass instance. """Safely assign values from a dict to a dataclass instance.
@ -60,6 +67,22 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
the increased safety checks may be worth the speed tradeoff in some the increased safety checks may be worth the speed tradeoff in some
cases. cases.
""" """
_dataclass_validate(instance, values)
for key, value in values.items():
setattr(instance, key, value)
def dataclass_validate(instance: Any) -> None:
"""Ensure values in a dataclass are correct types.
Note that this will always fail if a dataclass contains field types
not supported by this module.
"""
_dataclass_validate(instance, dataclasses.asdict(instance))
def _dataclass_validate(instance: Any, values: Dict[str, Any]) -> None:
# pylint: disable=too-many-branches
if not dataclasses.is_dataclass(instance): if not dataclasses.is_dataclass(instance):
raise TypeError(f'Passed instance {instance} is not a dataclass.') raise TypeError(f'Passed instance {instance} is not a dataclass.')
if not isinstance(values, dict): if not isinstance(values, dict):
@ -82,10 +105,10 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
f' been created without "from __future__ import annotations";' f' been created without "from __future__ import annotations";'
f' those dataclasses are unsupported here.') f' those dataclasses are unsupported here.')
reqtypes = _SIMPLE_ASSIGN_TYPES.get(fieldtype) if fieldtype in _SIMPLE_ASSIGN_TYPES:
if reqtypes is not None: reqtypes = _SIMPLE_ASSIGN_TYPES[fieldtype]
# pylint: disable=unidiomatic-typecheck valuetype = type(value)
if not any(type(value) is t for t in reqtypes): if not any(valuetype is t for t in reqtypes):
if len(reqtypes) == 1: if len(reqtypes) == 1:
expected = reqtypes[0].__name__ expected = reqtypes[0].__name__
else: else:
@ -93,23 +116,25 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
expected = f'Union[{names}]' expected = f'Union[{names}]'
raise TypeError(f'Invalid value type for "{key}";' raise TypeError(f'Invalid value type for "{key}";'
f' expected "{expected}", got' f' expected "{expected}", got'
f' "{type(value).__name__}".') f' "{valuetype.__name__}".')
elif fieldtype in _LIST_ASSIGN_TYPES:
reqtypes = _LIST_ASSIGN_TYPES[fieldtype]
if not isinstance(value, list):
raise TypeError(
f'Invalid value for "{key}";'
f' expected a list, got a "{type(value).__name__}"')
for subvalue in value:
subvaluetype = type(subvalue)
if not any(subvaluetype is t for t in reqtypes):
if len(reqtypes) == 1:
expected = reqtypes[0].__name__
else:
names = ', '.join(t.__name__ for t in reqtypes)
expected = f'Union[{names}]'
raise TypeError(f'Invalid value type for "{key}";'
f' expected list of "{expected}", found'
f' "{subvaluetype.__name__}".')
else: else:
raise TypeError(f'Field type "{fieldtype}" is unsupported here.') raise TypeError(f'Field type "{fieldtype}" is unsupported here.')
# Ok, if we made it here, the value is kosher. Do the assign.
setattr(instance, key, value)
def dataclass_validate(instance: Any) -> None:
"""Ensure values in a dataclass are correct types.
Note that this will always fail if a dataclass contains field types
not supported by this module.
"""
# We currently simply operate by grabbing dataclass values as a dict
# and passing them through dataclass_assign().
# It would be possible to write slightly more efficient custom code,
# this, but this keeps things simple and will allow us to easily
# incorporate things like value coercion later if we add that.
dataclass_assign(instance, dataclasses.asdict(instance))

View File

@ -82,15 +82,19 @@ def formatcode(projroot: Path, full: bool) -> None:
def cpplint(projroot: Path, full: bool) -> None: def cpplint(projroot: Path, full: bool) -> None:
"""Run lint-checking on all code deemed lint-able.""" """Run lint-checking on all code deemed lint-able."""
# pylint: disable=too-many-locals
import tempfile
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from multiprocessing import cpu_count from multiprocessing import cpu_count
from efrotools import get_config from efrotools import get_config
from efro.terminal import Clr from efro.terminal import Clr
from efro.error import CleanError
os.chdir(projroot) os.chdir(projroot)
filenames = get_code_filenames(projroot) filenames = get_code_filenames(projroot)
if any(' ' in name for name in filenames): for fpath in filenames:
raise Exception('found space in path; unexpected') if ' ' in fpath:
raise Exception(f'Found space in path {fpath}; unexpected.')
# Check the config for a list of ones to ignore. # Check the config for a list of ones to ignore.
code_blacklist: List[str] = get_config(projroot).get( code_blacklist: List[str] = get_config(projroot).get(
@ -114,14 +118,47 @@ def cpplint(projroot: Path, full: bool) -> None:
print(f'{Clr.BLU}CppLint checking' print(f'{Clr.BLU}CppLint checking'
f' {len(dirtyfiles)} file(s)...{Clr.RST}') f' {len(dirtyfiles)} file(s)...{Clr.RST}')
def lint_file(filename: str) -> None: # We want to do a few custom modifications to the cpplint module...
result = subprocess.call(['cpplint', '--root=src', filename]) try:
if result != 0: import cpplint as cpplintmodule
raise Exception(f'Linting failed for {filename}') except Exception:
raise CleanError('Unable to import cpplint')
with open(cpplintmodule.__file__) as infile:
codelines = infile.read().splitlines()
cheadersline = codelines.index('_C_HEADERS = frozenset([')
with ThreadPoolExecutor(max_workers=cpu_count()) as executor: # Extra headers we consider as valid C system headers.
# Converting this to a list will propagate any errors. c_headers = [
list(executor.map(lint_file, dirtyfiles)) 'malloc.h', 'tchar.h', 'jni.h', 'android/log.h', 'EGL/egl.h',
'libgen.h', 'linux/netlink.h', 'linux/rtnetlink.h', 'android/bitmap.h',
'android/log.h', 'uuid/uuid.h', 'cxxabi.h', 'direct.h', 'shellapi.h',
'rpc.h', 'io.h'
]
codelines.insert(cheadersline + 1, ''.join(f"'{h}'," for h in c_headers))
# Skip unapproved C++ headers check (it flags <mutex>, <thread>, etc.)
headercheckline = codelines.index(
" if include and include.group(1) in ('cfenv',")
codelines[headercheckline] = (
" if False and include and include.group(1) in ('cfenv',")
def lint_file(filename: str) -> None:
result = subprocess.call(['cpplint', '--root=src', filename], env=env)
if result != 0:
raise CleanError(
f'{Clr.RED}Cpplint failed for {filename}.{Clr.RST}')
with tempfile.TemporaryDirectory() as tmpdir:
# Write our replacement module, make it discoverable, then run.
with open(tmpdir + '/cpplint.py', 'w') as outfile:
outfile.write('\n'.join(codelines))
env = os.environ.copy()
env['PYTHONPATH'] = tmpdir
with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
# Converting this to a list will propagate any errors.
list(executor.map(lint_file, dirtyfiles))
if dirtyfiles: if dirtyfiles:
cache.mark_clean(filenames) cache.mark_clean(filenames)