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/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",
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cd/cc/837483543f1d6b184f64b5e6a950",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/e6/d372324c7c08b5b300490fa5594e",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/0e/11/3dda0974b64f51be4961628f572c",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cc/76/3f6356dd599091f5955ce349593b",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e1/23/3fe78cdef456a99140837ede7e49",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1c/bd/4c73637ee172630ee00145032ce7",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/7a/a7/342bea2a6ec2f94d5de7bb52a59f",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/aa/24/5426cb7e6ca01b9e5d67ad3217b0",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/cb/73/469fe3eb016f1e9c7f5c7811b182",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7c/fb/7e0880c1ab90b0484cdedc41c8ae",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f3/f2/097e861cf6ca981b18191e4c43b5",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/6b/7c/8407f4a7326b8b19f3e187d3ffcb"
"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/4a/0e/d344541dc2ae2a6eaf1024c99f7c",
"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/74/00/78f77d5ab9cc74a2f624f3a7253b",
"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/4f/b3/b149964180b661161e5f86109d99",
"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/d8/55/9edbc1eea06cf354fe37b50d68d2",
"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/71/73/b8d329287a0a46cc8ed2ef08a7d9",
"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/c3/5d/eb6c20729c0572e717c907482205"
}

View File

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

View File

@ -34,7 +34,7 @@ class AppDelegate:
Category: App Classes
"""
def create_default_game_config_ui(
def create_default_game_settings_ui(
self, gameclass: Type[ba.GameActivity],
sessionclass: Type[ba.Session], config: Optional[Dict[str, Any]],
completion_call: Callable[[Optional[Dict[str, Any]]],
@ -47,4 +47,4 @@ class AppDelegate:
del gameclass, sessionclass, config, completion_call # unused
from ba import _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
assert delegate is not None
delegate.create_default_game_config_ui(cls, sessionclass, settings,
completion_call)
delegate.create_default_game_settings_ui(cls, sessionclass, settings,
completion_call)
@classmethod
def get_score_info(cls) -> ba.ScoreInfo:

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<!-- 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,
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>
@ -1079,8 +1079,8 @@ manually.</p>
<h3>Methods:</h3>
<dl>
<dt><h4><a name="method_ba_AppDelegate__create_default_game_config_ui">create_default_game_config_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>
<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_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>

View File

@ -22,7 +22,7 @@
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
import pytest
@ -30,11 +30,12 @@ import pytest
from efro.dataclasses import dataclass_assign, dataclass_validate
if TYPE_CHECKING:
from typing import Optional
from typing import Optional, List
def test_assign() -> None:
"""Testing various assignments."""
# pylint: disable=too-many-statements
@dataclass
class _TestClass:
@ -46,6 +47,10 @@ def test_assign() -> None:
osval: Optional[str] = None
obval: Optional[bool] = 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()
@ -66,24 +71,39 @@ def test_assign() -> None:
dataclass_assign(tclass, {'nonexistent': 'foo'})
# Correct types.
dataclass_assign(tclass, {
'ival': 1,
'sval': 'foo',
'bval': True,
'fval': 2.0,
})
dataclass_assign(tclass, {
'oival': None,
'osval': None,
'obval': None,
'ofval': None,
})
dataclass_assign(tclass, {
'oival': 1,
'osval': 'foo',
'obval': True,
'ofval': 2.0,
})
dataclass_assign(
tclass, {
'ival': 1,
'sval': 'foo',
'bval': True,
'fval': 2.0,
'lsval': ['foo'],
'lival': [10],
'lbval': [False],
'lfval': [1.0]
})
dataclass_assign(
tclass, {
'oival': None,
'osval': None,
'obval': None,
'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.
with pytest.raises(TypeError):
@ -107,6 +127,21 @@ def test_assign() -> None:
with pytest.raises(TypeError):
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)
with pytest.raises(TypeError):
dataclass_assign(tclass, {'ival': True})
@ -120,6 +155,9 @@ def test_assign() -> None:
with pytest.raises(TypeError):
dataclass_assign(tclass, {'ofval': 1})
with pytest.raises(TypeError):
dataclass_assign(tclass, {'lfval': [1]})
def test_validate() -> None:
"""Testing validation."""

View File

@ -22,7 +22,7 @@
from __future__ import annotations
from enum import Enum
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
if TYPE_CHECKING:
@ -45,6 +45,14 @@ class ServerConfig:
# be enabled unless you are hosting on a LAN with no internet connection.
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
# servers on one machine.
# 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
if TYPE_CHECKING:
from typing import List, Sequence, Optional
from typing import List, Sequence, Optional, Any
# Python pip packages we require for this project.
@ -612,7 +612,11 @@ def _get_server_config_template_yaml(projroot: str) -> str:
assert vname.endswith(':')
vname = vname[:-1]
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.
if vname == 'playlist_code':
@ -621,7 +625,13 @@ def _get_server_config_template_yaml(projroot: str) -> str:
elif vname == 'stats_url':
vval = ('https://mystatssite.com/'
'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:
# Convert comments referring to python bools to yaml bools.
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
# to exactly match one of the tuple values to consider the assignment valid.
_SIMPLE_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = {
'bool': (bool, ),
'str': (str, ),
'int': (int, ),
'str': (str, ),
'bool': (bool, ),
'float': (float, ),
'Optional[int]': (int, type(None)),
'Optional[str]': (str, type(None)),
@ -40,6 +40,13 @@ _SIMPLE_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = {
'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:
"""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
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):
raise TypeError(f'Passed instance {instance} is not a dataclass.')
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' those dataclasses are unsupported here.')
reqtypes = _SIMPLE_ASSIGN_TYPES.get(fieldtype)
if reqtypes is not None:
# pylint: disable=unidiomatic-typecheck
if not any(type(value) is t for t in reqtypes):
if fieldtype in _SIMPLE_ASSIGN_TYPES:
reqtypes = _SIMPLE_ASSIGN_TYPES[fieldtype]
valuetype = type(value)
if not any(valuetype is t for t in reqtypes):
if len(reqtypes) == 1:
expected = reqtypes[0].__name__
else:
@ -93,23 +116,25 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
expected = f'Union[{names}]'
raise TypeError(f'Invalid value type for "{key}";'
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:
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:
"""Run lint-checking on all code deemed lint-able."""
# pylint: disable=too-many-locals
import tempfile
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import cpu_count
from efrotools import get_config
from efro.terminal import Clr
from efro.error import CleanError
os.chdir(projroot)
filenames = get_code_filenames(projroot)
if any(' ' in name for name in filenames):
raise Exception('found space in path; unexpected')
for fpath in filenames:
if ' ' in fpath:
raise Exception(f'Found space in path {fpath}; unexpected.')
# Check the config for a list of ones to ignore.
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'
f' {len(dirtyfiles)} file(s)...{Clr.RST}')
def lint_file(filename: str) -> None:
result = subprocess.call(['cpplint', '--root=src', filename])
if result != 0:
raise Exception(f'Linting failed for {filename}')
# We want to do a few custom modifications to the cpplint module...
try:
import cpplint as cpplintmodule
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:
# Converting this to a list will propagate any errors.
list(executor.map(lint_file, dirtyfiles))
# Extra headers we consider as valid C system headers.
c_headers = [
'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:
cache.mark_clean(filenames)