cleaned up ba_meta scanning to return a dataclass instead of a dict

This commit is contained in:
Eric Froemling 2020-03-22 18:37:36 -07:00
parent 7366738622
commit 9bb0caa5bb
3 changed files with 76 additions and 69 deletions

View File

@ -173,10 +173,12 @@ def have_pro_options() -> bool:
# We expose pro options if the server tells us to
# (which is generally just when we own pro),
# or also if we've been grandfathered in.
# or also if we've been grandfathered in or are using ballistica-core
# builds.
return bool(
_ba.get_account_misc_read_val_2('proOptionsUnlocked', False)
or _ba.app.config.get('lc14292', 0) > 1)
or _ba.app.config.get('lc14292', 0) > 1
or 'ballistica' + 'core' == 'ballisticacore')
def show_post_purchase_message() -> None:

View File

@ -28,7 +28,7 @@ import _ba
if TYPE_CHECKING:
import ba
from ba import _lang as bs_lang
from ba import _lang, _meta
from bastd.actor import spazappearance
from typing import (Optional, Dict, Tuple, Set, Any, List, Type, Tuple,
Callable)
@ -240,16 +240,7 @@ class App:
"""
# pylint: disable=too-many-statements
test_https = False
if test_https:
# Testing https support (would be nice to get this working on
# our custom python builds; need to wrangle certificates somehow).
import urllib.request
try:
val = urllib.request.urlopen('https://example.com').read()
print("HTTPS TEST SUCCESS", len(val))
except Exception as exc:
print("HTTPS TEST FAIL:", exc)
# _test_https()
# Config.
self.config_file_healthy = False
@ -274,7 +265,7 @@ class App:
self._platform: str = env['platform']
self._subplatform: str = env['subplatform']
self._interface_type: str = env['interface_type']
self._on_tv: bool = env['on_tv'] #
self._on_tv: bool = env['on_tv']
self._vr_mode: bool = env['vr_mode']
self.protocol_version: int = env['protocol_version']
self.toolbar_test: bool = env['toolbar_test']
@ -282,7 +273,7 @@ class App:
# Misc.
self.default_language = self._get_default_language()
self.metascan: Optional[Dict[str, Any]] = None
self.metascan: Optional[_meta.ScanResults] = None
self.tips: List[str] = []
self.stress_test_reset_timer: Optional[ba.Timer] = None
self.suppress_debug_reports = False
@ -337,8 +328,8 @@ class App:
}
# Language.
self.language_target: Optional[bs_lang.AttrDict] = None
self.language_merged: Optional[bs_lang.AttrDict] = None
self.language_target: Optional[_lang.AttrDict] = None
self.language_merged: Optional[_lang.AttrDict] = None
# Achievements.
self.achievements: List[ba.Achievement] = []
@ -418,7 +409,6 @@ class App:
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from ba import _apputils
# from ba._general import Call
from ba import _appconfig
from ba import ui as bsui
from ba import _achievement
@ -443,6 +433,8 @@ class App:
self.music_player_type = _music.InternalMusicPlayer
elif _ba.env()['platform'] == 'mac' and hasattr(_ba, 'itunes_init'):
self.music_player_type = _music.MacITunesMusicPlayer
# FIXME: This should not be hard-coded.
for maptype in [
stdmaps.HockeyStadium, stdmaps.FootballStadium,
stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout,
@ -543,6 +535,7 @@ class App:
# Debugging - make note if we're using the local test server so we
# don't accidentally leave it on in a release.
# FIXME - move this to native layer.
server_addr = _ba.get_master_server_address()
if 'localhost' in server_addr:
_ba.timer(2.0,
@ -574,10 +567,8 @@ class App:
if self.subplatform != 'headless':
_ba.timer(3.0, check_special_offer, timetype=TimeType.REAL)
_meta.startscan()
# Start scanning for stuff available in our scripts.
# meta.get_game_types()
# Start scanning for things exposed via ba_meta.
_meta.start_scan()
# Auto-sign-in to a local account in a moment if we're set to.
def do_auto_sign_in() -> None:
@ -592,8 +583,6 @@ class App:
from ba._dependency import test_depset
test_depset()
# print('GAME TYPES ARE', meta.get_game_types())
# _bs.quit()
def read_config(self) -> None:
"""(internal)"""
@ -610,21 +599,21 @@ class App:
activity = _ba.get_foreground_host_activity()
if (activity is not None and activity.allow_pausing
and not _ba.have_connected_clients()):
from ba import _gameutils, _actor, _lang
# FIXME: Shouldn't be touching scene stuff here;
# should just pass the request on to the host-session.
import ba
with ba.Context(activity):
globs = ba.sharedobj('globals')
with _ba.Context(activity):
globs = _gameutils.sharedobj('globals')
if not globs.paused:
ba.playsound(ba.getsound('refWhistle'))
_ba.playsound(_ba.getsound('refWhistle'))
globs.paused = True
# FIXME: This should not be an attr on Actor.
activity.paused_text = ba.Actor(
ba.newnode(
activity.paused_text = _actor.Actor(
_ba.newnode(
'text',
attrs={
'text': ba.Lstr(resource='pausedByHostText'),
'text': _lang.Lstr(resource='pausedByHostText'),
'client_only': True,
'flatness': 1.0,
'h_align': 'center'
@ -821,3 +810,16 @@ class App:
else:
_ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
_ba.playsound(_ba.getsound('error'))
def _test_https(self) -> None:
"""Testing https support.
(would be nice to get this working on our custom python builds; need
to wrangle certificates somehow).
"""
import urllib.request
try:
val = urllib.request.urlopen('https://example.com').read()
print("HTTPS TEST SUCCESS", len(val))
except Exception as exc:
print("HTTPS TEST FAIL:", exc)

View File

@ -26,11 +26,12 @@ import os
import pathlib
import threading
from typing import TYPE_CHECKING
from dataclasses import dataclass, field
import _ba
if TYPE_CHECKING:
from typing import (Any, Dict, List, Tuple, Union, Optional, Type, Set)
from typing import Dict, List, Tuple, Union, Optional, Type, Set
import ba
# The API version of this build of the game.
@ -40,7 +41,7 @@ if TYPE_CHECKING:
CURRENT_API_VERSION = 6
def startscan() -> None:
def start_scan() -> None:
"""Begin scanning script directories for scripts containing metadata.
Should be called only once at launch."""
@ -52,23 +53,31 @@ def startscan() -> None:
thread.start()
def handle_scan_results(results: Dict[str, Any]) -> None:
@dataclass
class ScanResults:
"""Final results from a metadata scan."""
games: List[str] = field(default_factory=list)
errors: str = ''
warnings: str = ''
def handle_scan_results(results: ScanResults) -> None:
"""Called in the game thread with results of a completed scan."""
from ba import _lang
# Warnings generally only get printed locally for users' benefit
# (things like out-of-date scripts being ignored, etc.)
# Errors are more serious and will get included in the regular log
warnings = results.get('warnings', '')
errors = results.get('errors', '')
if warnings != '' or errors != '':
# warnings = results.get('warnings', '')
# errors = results.get('errors', '')
if results.warnings != '' or results.errors != '':
_ba.screenmessage(_lang.Lstr(resource='scanScriptsErrorText'),
color=(1, 0, 0))
_ba.playsound(_ba.getsound('error'))
if warnings != '':
_ba.log(warnings, to_server=False)
if errors != '':
_ba.log(errors)
if results.warnings != '':
_ba.log(results.warnings, to_server=False)
if results.errors != '':
_ba.log(results.errors)
class ScanThread(threading.Thread):
@ -85,7 +94,8 @@ class ScanThread(threading.Thread):
scan.scan()
results = scan.results
except Exception as exc:
results = {'errors': 'Scan exception: ' + str(exc)}
# results = {'errors': 'Scan exception: ' + str(exc)}
results = ScanResults(errors=f'Scan exception: {exc}')
# Push a call to the game thread to print warnings/errors
# or otherwise deal with scan results.
@ -116,11 +126,7 @@ class DirectoryScan:
'errors': errors encountered during scan; should be fully logged
"""
self.paths = [pathlib.Path(p) for p in paths]
self.results: Dict[str, Any] = {
'errors': '',
'warnings': '',
'games': []
}
self.results = ScanResults()
def _get_path_module_entries(
self, path: pathlib.Path, subpath: Union[str, pathlib.Path],
@ -137,7 +143,7 @@ class DirectoryScan:
entries = []
except Exception as exc:
# Unexpected; report this.
self.results['errors'] += str(exc) + '\n'
self.results.errors += f'{exc}\n'
entries = []
# Now identify python packages/modules out of what we found.
@ -158,9 +164,8 @@ class DirectoryScan:
self.scan_module(moduledir, subpath)
except Exception:
from ba import _error
self.results['warnings'] += ("Error scanning '" +
str(subpath) + "': " +
_error.exc_str() + '\n')
self.results.warnings += ("Error scanning '" + str(subpath) +
"': " + _error.exc_str() + '\n')
def scan_module(self, moduledir: pathlib.Path,
subpath: pathlib.Path) -> None:
@ -187,11 +192,11 @@ class DirectoryScan:
# If we find a module requiring a different api version, warn
# and ignore.
if required_api is not None and required_api != CURRENT_API_VERSION:
self.results['warnings'] += ('Warning: ' + str(subpath) +
' requires api ' + str(required_api) +
' but we are running ' +
str(CURRENT_API_VERSION) +
'; ignoring module.\n')
self.results.warnings += ('Warning: ' + str(subpath) +
' requires api ' + str(required_api) +
' but we are running ' +
str(CURRENT_API_VERSION) +
'; ignoring module.\n')
return
# Ok; can proceed with a full scan of this module.
@ -206,9 +211,8 @@ class DirectoryScan:
self.scan_module(submodule[0], submodule[1])
except Exception:
from ba import _error
self.results['warnings'] += ("Error scanning '" +
str(subpath) + "': " +
_error.exc_str() + '\n')
self.results.warnings += ("Error scanning '" + str(subpath) +
"': " + _error.exc_str() + '\n')
def _process_module_meta_tags(self, subpath: pathlib.Path,
flines: List[str],
@ -219,7 +223,7 @@ class DirectoryScan:
# the ba_meta is in the right place.
if mline[0] != 'ba_meta':
print(f'GOT "{mline[0]}"')
self.results['warnings'] += (
self.results.warnings += (
'Warning: ' + str(subpath) +
': malformed ba_meta statement on line ' +
str(lindex + 1) + '.\n')
@ -230,7 +234,7 @@ class DirectoryScan:
elif len(mline) != 3 or mline[1] != 'export':
# Currently we only support 'ba_meta export FOO';
# complain for anything else we see.
self.results['warnings'] += (
self.results.warnings += (
'Warning: ' + str(subpath) +
': unrecognized ba_meta statement on line ' +
str(lindex + 1) + '.\n')
@ -245,9 +249,9 @@ class DirectoryScan:
if export_class_name is not None:
classname = modulename + '.' + export_class_name
if exporttype == 'game':
self.results['games'].append(classname)
self.results.games.append(classname)
else:
self.results['warnings'] += (
self.results.warnings += (
'Warning: ' + str(subpath) +
': unrecognized export type "' + exporttype +
'" on line ' + str(lindex + 1) + '.\n')
@ -272,7 +276,7 @@ class DirectoryScan:
classname = cbits[0]
break # success!
if classname is None:
self.results['warnings'] += (
self.results.warnings += (
'Warning: ' + str(subpath) + ': class definition not found'
' below "ba_meta export" statement on line ' +
str(lindexorig + 1) + '.\n')
@ -296,21 +300,21 @@ class DirectoryScan:
# Ok; not successful. lets issue warnings for a few error cases.
if len(lines) > 1:
self.results['warnings'] += (
self.results.warnings += (
'Warning: ' + str(subpath) +
': multiple "# ba_meta api require <NUM>" lines found;'
' ignoring module.\n')
elif not lines and toplevel and meta_lines:
# If we're a top-level module containing meta lines but
# no valid api require, complain.
self.results['warnings'] += (
self.results.warnings += (
'Warning: ' + str(subpath) +
': no valid "# ba_meta api require <NUM>" line found;'
' ignoring module.\n')
return None
def get_scan_results() -> Dict[str, Any]:
def get_scan_results() -> ScanResults:
"""Return meta scan results; blocking if the scan is not yet complete."""
import time
app = _ba.app
@ -326,7 +330,6 @@ def get_scan_results() -> Dict[str, Any]:
time.sleep(0.05)
if time.time() - starttime > 10.0:
raise Exception('timeout waiting for meta scan to complete.')
print('RETURNING SCAN RESULTS')
return app.metascan
@ -334,7 +337,7 @@ def get_game_types() -> List[Type[ba.GameActivity]]:
"""Return available game types."""
from ba import _general
from ba import _gameactivity
gameclassnames = get_scan_results().get('games', [])
gameclassnames = get_scan_results().games
gameclasses = []
for gameclassname in gameclassnames:
try: