This commit is contained in:
Eric Froemling 2021-05-05 14:11:16 -05:00
parent c10cf5c2fe
commit 4b3ecba38d
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
19 changed files with 279 additions and 126 deletions

View File

@ -3932,26 +3932,26 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
"build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/75/4c/8525e8379d532892f3210bd7c007",
"build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f1/5d/1e6f8c35114957c82a4b3a56b6a4",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/22/e2/522fd2f2cc6fd0e387550f2cbe0f",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8b/e7/2fba831a7c3e3fb4c8b63ca0423f",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/34/bd/4c971cfc7b576c58d904a63d86a1",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8b/8b/b8f8a75b3ded113231265f61da9d",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/14/4e/bd10863753f44c7612ef697c4693",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/a0/883c84cb130780bb8bc8a2185604",
"build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e0/ee/e6b94bf4149530e412cfe27e5cb4",
"build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/97/62/0944cd3ab34d681cd7c9bfaa0b11",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/dd/a5/c73d18aba833987ff3e713bbf981",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/27/e1/de75aac52e10bebae81fc12aa030",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a6/81/ee957a5bfd1be45c0e865f8a27ba",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/03/29/abffbfc56fd981915253f1d4ed96",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/04/35/104446e5f91b9fe35fa413be02ca",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/c0/68b44a693639308981b5214f02c1",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/b5/c4/5935bc59cc237c42e8ac9be47ce5",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/35/3d/f04ebd4a7d066088595b8ed4bbc5",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/72/eb/25cf435771601aeec273a92ffb50",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/45/fb/166e932a6235613935ccf2e51d00",
"build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/7f/59/a431e0217c157c7077241df2c365",
"build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1d/6a/68e8714a86171f9a2be4a036491e",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/ed/1387a6c9ce1e0cea307f6ab095e7",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/66/f8/636eeec7e4b5b1b753820dd22c04",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3d/17/07ac1ff591b036ccbe3262fed5f5",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/60/e9/5fc4a69d21eba0e499940ebbc7fb",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/43/a0/4fb77c76baaa300b3689c67bd772",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ac/48/53ae96cef471c3c3754ff3100636",
"build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c2/df/31db000cc83b10b2202ba95919d7",
"build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/70/15/10385cf2b4c90e344ed6f5c083d1",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fd/ee/c73314b7dbb23db05e64837c11a0",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cf/09/c64df4e9c9eb2dddf2f60d735042",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/47/51/18a5f474d0a877b72389a435f3ec",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e7/6c/81eeee17d5b116b12089bc7bb0f9",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/35/11/985c2fcc38cc8c149fbc03352a0f",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5d/22/586c8cfed938a04a609a5b95945a",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/6c/e2/4dfb793c1a03adf694bde2c6f701",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/10/56/1bd7d8a57b9fdfd29a0be99c92ba",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f6/47/2398d24c31a2ebc0de61114eca55",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/52/2f/989db5f34be56b1950abe7b0485f",
"build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f4/59/16c01f646c16bb480a197aa9e6e3",
"build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/81/73/a321fe4aa721d07f2a44257967cf",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/99/99/2adf5a22923eed3f8b5667ee8220",
@ -3960,12 +3960,12 @@
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3d/3a/c747993afbf4c1ed1c5e8b0e5d5a",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/46/21/31fe300afbf7d9da766c04064919",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f8/bd/2b05dbfd98cd55cedf924b639765",
"build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5b/6d/877a5a015eb3d705795a2db7fa74",
"build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/58/a4/3d2a1e4a47c51e883ef29b5e280d",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a7/ca/91cfd7dc2ccc8c996daf9aaeb9fd",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/93/cb/de559dd0ea55e12a228ea1cd8974",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c9/e1/132123edad385ac0a977337f044c",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f2/e3/a82f88651f2ffef8c52a20da42a6",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d4/9c/95e358c6e929bcb046e40fe5ad2b",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8e/2d/81ce3feefa50c283cd7a8398ac72"
"build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/3f/9bc329e012cba8c7d3132f99ee3d",
"build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/95/e8/61d947fe80839edf500755fde7c6",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1c/52/1970d5b7d7e53145443cdab9f694",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/3f/dfecf86a39bb00bb908a32f531ee",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/23/fb/1e37cdfd0d67b396e526d7f404b4",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2b/7b/a189604b95bc0620d040b251d36b",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/df/f1/82e8e63feabc82d881453f00af5c",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/96/33/f76e9abe2e28429061bf0ff22cf0"
}

View File

@ -143,6 +143,7 @@
<w>audiobooks</w>
<w>audioop</w>
<w>autodesk</w>
<w>autodetected</w>
<w>autogenerate</w>
<w>autonoassets</w>
<w>autopoint</w>
@ -1085,6 +1086,8 @@
<w>introspectable</w>
<w>intstr</w>
<w>iobj</w>
<w>ioprep</w>
<w>ioprepped</w>
<w>ipaddress</w>
<w>ipos</w>
<w>iprof</w>
@ -1712,6 +1715,7 @@
<w>pthreads</w>
<w>ptrans</w>
<w>ptype</w>
<w>ptypename</w>
<w>publictab</w>
<w>pubsync</w>
<w>pucknode</w>

View File

@ -1,4 +1,4 @@
### 1.6.0 (20353)
### 1.6.0 (20357)
- Revamped netcode significantly. We still don't have client-prediction, but things should (hopefully) feel much lower latency now.
- Added network debug graphs accessible by hitting F8.
- Added private parties functionality (cloud hosted parties with associated codes making it easier to play with friends)
@ -21,6 +21,11 @@
- Plugins can now register to be called for pause, resume, and shutdown events in addition to launch
- Added ba.app.state holding the overall state of the app (running, paused, etc)
- renamed the efro.dataclasses module to efro.dataclassio and added significant functionality
- command-line input no longer errors on commands longer than 4k bytes.
- added show-tutorial option to the server wrapper config
- added custom-team-names option to the server wrapper config
- added custom-team-colors option to the server wrapper config
- added inline-playlist option to the server wrapper config
### 1.5.29 (20246)
- Exposed ba method/class initing in public C++ layer.

View File

@ -19,6 +19,7 @@ from ba._dualteamsession import DualTeamSession
if TYPE_CHECKING:
from typing import Optional, Dict, Any, Type
import ba
from bacommon.servermanager import ServerConfig
@ -301,6 +302,28 @@ class ServerController:
print('WARNING: launch_server_session() expects to run '
'with a signed in server account')
# If we didn't fetch a playlist but there's an inline one in the
# server-config, pull it in to the game config and use it.
if (self._config.playlist_code is None
and self._config.playlist_inline is not None):
self._playlist_name = 'ServerModePlaylist'
if sessiontype is FreeForAllSession:
ptypename = 'Free-for-All'
elif sessiontype is DualTeamSession:
ptypename = 'Team Tournament'
else:
raise RuntimeError(f'Unknown session type {sessiontype}')
# Need to add this in a transaction instead of just setting
# it directly or it will get overwritten by the master-server.
_ba.add_transaction({
'type': 'ADD_PLAYLIST',
'playlistType': ptypename,
'playlistName': self._playlist_name,
'playlist': self._config.playlist_inline
})
_ba.run_transactions()
if self._first_run:
curtimestr = time.strftime('%c')
_ba.log(

View File

@ -99,6 +99,7 @@ class Session:
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
# pylint: disable=too-many-branches
from ba._lobby import Lobby
from ba._stats import Stats
from ba._gameactivity import GameActivity
@ -172,8 +173,16 @@ class Session:
# Create static teams if we're using them.
if self.use_teams:
assert team_names is not None
assert team_colors is not None
if team_names is None:
raise RuntimeError(
'use_teams is True but team_names not provided.')
if team_colors is None:
raise RuntimeError(
'use_teams is True but team_colors not provided.')
if len(team_colors) != len(team_names):
raise RuntimeError(f'Got {len(team_names)} team_names'
f' and {len(team_colors)} team_colors;'
f' these numbers must match.')
for i, color in enumerate(team_colors):
team = SessionTeam(team_id=self._next_team_id,
name=GameActivity.get_team_display_string(

View File

@ -8,12 +8,12 @@ import os
import copy
import time
from enum import Enum
from dataclasses import dataclass, asdict
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
import ba
import _ba
from efro.dataclassio import dataclass_from_dict
from efro.dataclassio import dataclass_from_dict, dataclass_to_dict
from bacommon.net import (PrivateHostingState, PrivateHostingConfig,
PrivatePartyConnectResult)
from bastd.ui.gather import GatherTab
@ -150,6 +150,7 @@ class PrivateGatherTab(GatherTab):
return self._container
def _build_hosting_config(self) -> PrivateHostingConfig:
# pylint: disable=too-many-branches
from bastd.ui.playlist import PlaylistTypeVars
from ba.internal import filter_playlist
hcfg = PrivateHostingConfig()
@ -192,12 +193,29 @@ class PrivateGatherTab(GatherTab):
tutorial = cfg.get('Show Tutorial')
if not isinstance(tutorial, bool):
tutorial = False
tutorial = True
hcfg.tutorial = tutorial
if hcfg.session_type == 'teams':
hcfg.custom_team_names = copy.copy(cfg.get('Custom Team Names'))
hcfg.custom_team_colors = copy.copy(cfg.get('Custom Team Colors'))
ctn: Optional[List[str]] = cfg.get('Custom Team Names')
if ctn is not None:
if (isinstance(ctn, (list, tuple)) and len(ctn) == 2
and all(isinstance(x, str) for x in ctn)):
hcfg.custom_team_names = (ctn[0], ctn[1])
else:
print(f'Found invalid custom-team-names data: {ctn}')
ctc: Optional[List[List[float]]] = cfg.get('Custom Team Colors')
if ctc is not None:
if (isinstance(ctc, (list, tuple)) and len(ctc) == 2
and all(isinstance(x, (list, tuple)) for x in ctc)
and all(len(x) == 3 for x in ctc)):
hcfg.custom_team_colors = ((ctc[0][0], ctc[0][1],
ctc[0][2]),
(ctc[1][0], ctc[1][1],
ctc[1][2]))
else:
print(f'Found invalid custom-team-colors data: {ctc}')
return hcfg
@ -764,6 +782,8 @@ class PrivateGatherTab(GatherTab):
ba.playsound(ba.getsound('error'))
return
ba.playsound(ba.getsound('click01'))
# If we're not hosting, start.
if self._hostingstate.party_code is None:
@ -784,7 +804,7 @@ class PrivateGatherTab(GatherTab):
_ba.add_transaction(
{
'type': 'PRIVATE_PARTY_START',
'config': asdict(self._hostingconfig),
'config': dataclass_to_dict(self._hostingconfig),
'region_pings': ba.app.net.region_pings,
},
callback=ba.WeakCall(self._hosting_state_response))

View File

@ -372,7 +372,7 @@ class PartyWindow(ba.Window):
cfg.apply_and_commit()
self._update()
else:
print('unhandled popup type: ' + str(self._popup_type))
print(f'unhandled popup type: {self._popup_type}')
def popup_menu_closing(self, popup_window: popup.PopupWindow) -> None:
"""Called when the popup is closing."""

View File

@ -32,14 +32,19 @@ if TYPE_CHECKING:
from types import FrameType
from bacommon.servermanager import ServerCommand
VERSION_STR = '1.2'
VERSION_STR = '1.3'
# Version history:
# 1.3:
# Added show_tutorial config option
# Added team_names config option
# Added team_colors config option
# Added playlist_inline config option
# 1.2:
# Added optional --help arg
# Added --config arg for setting config path and --root for ba_root path
# Added --config arg for specifying config file and --root for ba_root path
# Added noninteractive mode and --interactive/--noninteractive args to
# explicitly specify
# explicitly enable/disable it (it is autodetected by default)
# Added explicit control for auto-restart: --no-auto-restart
# Config file is now reloaded each time server binary is restarted; no more
# need to bring down server wrapper to pick up changes
@ -413,7 +418,7 @@ class ServerManagerApp:
f'{Clr.BLD}--root [path]{Clr.RST}\n' + cls._par(
'Set the ballistica root directory. This is where the server'
' binary will read and write its caches, state files,'
' downloaded assets, etc. It needs to be a writable'
' downloaded assets to, etc. It needs to be a writable'
' directory. If not specified, the script will use the'
' \'dist/ba_root\' directory relative to itself.') + '\n'
f'{Clr.BLD}--interactive{Clr.RST}\n'
@ -648,7 +653,18 @@ class ServerManagerApp:
# ballisticacore config file; the rest we pass at runtime.
bincfg['Port'] = self._config.port
bincfg['Auto Balance Teams'] = self._config.auto_balance_teams
bincfg['Show Tutorial'] = False
bincfg['Show Tutorial'] = self._config.show_tutorial
if self._config.team_names is not None:
bincfg['Custom Team Names'] = self._config.team_names
elif 'Custom Team Names' in bincfg:
del bincfg['Custom Team Names']
if self._config.team_colors is not None:
bincfg['Custom Team Colors'] = self._config.team_colors
elif 'Custom Team Colors' in bincfg:
del bincfg['Custom Team Colors']
bincfg['Idle Exit Minutes'] = self._config.idle_exit_minutes
with open(cfgpath, 'w') as outfile:
outfile.write(json.dumps(bincfg))

View File

@ -57,6 +57,7 @@
<w>athome</w>
<w>attrobj</w>
<w>audiocache</w>
<w>autodetected</w>
<w>automagically</w>
<w>autoselect</w>
<w>availmins</w>
@ -485,6 +486,8 @@
<w>intstr</w>
<w>invote</w>
<w>iobj</w>
<w>ioprep</w>
<w>ioprepped</w>
<w>iserverget</w>
<w>iserverput</w>
<w>isinst</w>
@ -758,6 +761,7 @@
<w>pton</w>
<w>ptrs</w>
<w>ptype</w>
<w>ptypename</w>
<w>publictab</w>
<w>pulseaudio</w>
<w>punchmomentumlinear</w>

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2021-05-03 for Ballistica version 1.6.0 build 20355</em></h4>
<h4><em>last updated on 2021-05-05 for Ballistica version 1.6.0 build 20357</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

@ -21,7 +21,7 @@
namespace ballistica {
// These are set automatically via script; don't change here.
const int kAppBuildNumber = 20355;
const int kAppBuildNumber = 20358;
const char* kAppVersion = "1.6.0";
// Our standalone globals.

View File

@ -43,13 +43,15 @@ void StdInputModule::PushBeginReadCall() {
char buffer[4096];
char* val = fgets(buffer, sizeof(buffer), stdin);
if (val) {
int last_char = static_cast<int>(strlen(buffer) - 1);
pending_input_ += val;
// Clip off our last char if its a newline (just to keep things tidier).
if (last_char >= 0 && buffer[last_char] == '\n') {
buffer[last_char] = 0;
if (!pending_input_.empty()
&& pending_input_[pending_input_.size() - 1] == '\n') {
// Get rid of the last newline and ship it to the game.
pending_input_.pop_back();
g_game->PushStdinScriptCommand(pending_input_);
pending_input_.clear();
}
g_game->PushStdinScriptCommand(buffer);
} else {
// At the moment we bail on any read error.
if (feof(stdin)) {

View File

@ -12,6 +12,9 @@ class StdInputModule : public Module {
explicit StdInputModule(Thread* thread);
~StdInputModule() override;
void PushBeginReadCall();
private:
std::string pending_input_;
};
} // namespace ballistica

View File

@ -7,16 +7,17 @@ from __future__ import annotations
from enum import Enum
import datetime
from dataclasses import field, dataclass
from typing import TYPE_CHECKING
from typing import (TYPE_CHECKING, Optional, List, Set, Any, Dict, Sequence,
Union, Tuple)
import pytest
from efro.util import utc_now
from efro.dataclassio import (dataclass_validate, dataclass_from_dict,
dataclass_to_dict, prepped)
dataclass_to_dict, ioprepped)
if TYPE_CHECKING:
from typing import Optional, List, Set, Any, Dict, Sequence, Union, Tuple
pass
class _EnumTest(Enum):
@ -55,7 +56,7 @@ def test_assign() -> None:
# pylint: disable=too-many-statements
@prepped
@ioprepped
@dataclass
class _TestClass:
ival: int = 0
@ -276,7 +277,7 @@ def test_assign() -> None:
def test_coerce() -> None:
"""Test value coercion."""
@prepped
@ioprepped
@dataclass
class _TestClass:
ival: int = 0
@ -318,7 +319,7 @@ def test_prep() -> None:
# a strong use case.
with pytest.raises(TypeError):
@prepped
@ioprepped
@dataclass
class _TestClass:
ival: Sequence[int]
@ -328,31 +329,31 @@ def test_prep() -> None:
# get_type_hints() so we need to support at least that).
with pytest.raises(TypeError):
@prepped
@ioprepped
@dataclass
class _TestClass2:
ival: Union[int, str]
@prepped
@ioprepped
@dataclass
class _TestClass3:
uval: Union[int, None]
with pytest.raises(TypeError):
@prepped
@ioprepped
@dataclass
class _TestClass4:
ival: Union[int, str]
# This will get simplified down to simply int by get_type_hints so is ok.
@prepped
@ioprepped
@dataclass
class _TestClass5:
ival: Union[int]
# This will get simplified down to a valid 2 member union so is ok
@prepped
@ioprepped
@dataclass
class _TestClass6:
ival: Union[int, None, int, None]
@ -361,36 +362,36 @@ def test_prep() -> None:
# having those value types.
with pytest.raises(TypeError):
@prepped
@ioprepped
@dataclass
class _TestClass7:
dval: Dict[float, int]
@prepped
@ioprepped
@dataclass
class _TestClass8:
dval: Dict[str, int]
@prepped
@ioprepped
@dataclass
class _TestClass9:
dval: Dict[_GoodEnum, int]
@prepped
@ioprepped
@dataclass
class _TestClass10:
dval: Dict[_GoodEnum2, int]
with pytest.raises(TypeError):
@prepped
@ioprepped
@dataclass
class _TestClass11:
dval: Dict[_BadEnum1, int]
with pytest.raises(TypeError):
@prepped
@ioprepped
@dataclass
class _TestClass12:
dval: Dict[_BadEnum2, int]
@ -399,7 +400,7 @@ def test_prep() -> None:
def test_validate() -> None:
"""Testing validation."""
@prepped
@ioprepped
@dataclass
class _TestClass:
ival: int = 0
@ -434,7 +435,7 @@ def test_validate() -> None:
def test_extra_data() -> None:
"""Test handling of data that doesn't map to dataclass attrs."""
@prepped
@ioprepped
@dataclass
class _TestClass:
ival: int = 0
@ -462,7 +463,7 @@ def test_extra_data() -> None:
def test_dict() -> None:
"""Test various dict related bits."""
@prepped
@ioprepped
@dataclass
class _TestClass:
dval: dict
@ -481,7 +482,7 @@ def test_dict() -> None:
# Int dict-keys should actually be stored as strings internally
# (for json compatibility).
@prepped
@ioprepped
@dataclass
class _TestClass2:
dval: Dict[int, float]
@ -496,7 +497,7 @@ def test_dict() -> None:
assert obj2.dval[1] == 2.35
# Same with enum keys (we support enums with str and int values)
@prepped
@ioprepped
@dataclass
class _TestClass3:
dval: Dict[_GoodEnum, int]
@ -508,7 +509,7 @@ def test_dict() -> None:
obj3 = dataclass_from_dict(_TestClass3, out)
assert obj3.dval[_GoodEnum.VAL1] == 124
@prepped
@ioprepped
@dataclass
class _TestClass4:
dval: Dict[_GoodEnum2, int]

View File

@ -4,14 +4,14 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional, List, Dict, Any, Tuple
from dataclasses import dataclass
from efro import entity
from efro.dataclassio import prepped
from efro.dataclassio import ioprepped
if TYPE_CHECKING:
from typing import Optional, Any, List, Dict
pass
class ServerNodeEntry(entity.CompoundValue):
@ -33,7 +33,7 @@ class ServerNodeQueryResponse(entity.Entity):
store_default=False)
@prepped
@ioprepped
@dataclass
class PrivateHostingState:
"""Combined state of whether we're hosting, whether we can, etc."""
@ -45,7 +45,7 @@ class PrivateHostingState:
free_host_minutes_remaining: Optional[float] = None
@prepped
@ioprepped
@dataclass
class PrivateHostingConfig:
"""Config provided when hosting a private party."""
@ -53,12 +53,13 @@ class PrivateHostingConfig:
playlist_name: str = 'Unknown'
randomize: bool = False
tutorial: bool = False
custom_team_names: Optional[List[str]] = None
custom_team_colors: Optional[List[List[float]]] = None
custom_team_names: Optional[Tuple[str, str]] = None
custom_team_colors: Optional[Tuple[Tuple[float, float, float],
Tuple[float, float, float]]] = None
playlist: Optional[List[Dict[str, Any]]] = None
@prepped
@ioprepped
@dataclass
class PrivatePartyConnectResult:
"""Info about a server we get back when connecting."""

View File

@ -5,15 +5,15 @@ from __future__ import annotations
from enum import Enum
from dataclasses import field, dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, List, Optional, Tuple, Dict, Any
from efro.dataclassio import prepped
from efro.dataclassio import ioprepped
if TYPE_CHECKING:
from typing import Optional, Tuple, List
pass
@prepped
@ioprepped
@dataclass
class ServerConfig:
"""Configuration for the server manager app (<appname>_server)."""
@ -52,8 +52,7 @@ class ServerConfig:
max_party_size: int = 6
# Options here are 'ffa' (free-for-all) and 'teams'
# This value is only used if you do not supply a playlist_code (see below).
# In that case the default teams or free-for-all playlist gets used.
# This value is ignored if you supply a playlist_code (see below).
session_type: str = 'ffa'
# To host your own custom playlists, use the 'share' functionality in the
@ -62,6 +61,10 @@ class ServerConfig:
# playlist.
playlist_code: Optional[int] = None
# Alternately, you can embed playlist data here instead of using codes.
# Make sure to set session_type to the correct type for the data here.
playlist_inline: Optional[List[Dict[str, Any]]] = None
# Whether to shuffle the playlist or play its games in designated order.
playlist_shuffle: bool = True
@ -85,12 +88,12 @@ class ServerConfig:
# performance)
ffa_series_length: int = 24
# If you provide a custom stats webpage for your server, you can use
# this to provide a convenient in-game link to it in the server-browser
# beside the server name.
# If you have a custom stats webpage for your server, you can use this
# to provide a convenient in-game link to it in the server-browser
# alongside the server name.
# if ${ACCOUNT} is present in the string, it will be replaced by the
# currently-signed-in account's id. To fetch info about an account,
# your backend server can use the following url:
# your back-end server can use the following url:
# http://bombsquadgame.com/accountquery?id=ACCOUNT_ID_HERE
stats_url: Optional[str] = None
@ -110,10 +113,20 @@ class ServerConfig:
# If present, the server subprocess will shut down immediately if this
# amount of time passes with no activity from any players. The server
# manager will then spin up a fresh server subprocess if
# auto-restart is enabled (the default).
# manager will then spin up a fresh server subprocess if auto-restart is
# enabled (the default).
idle_exit_minutes: Optional[float] = None
# Should the tutorial be shown at the beginning of games?
show_tutorial: bool = False
# Team names (teams mode only).
team_names: Optional[Tuple[str, str]] = None
# Team colors (teams mode only).
team_colors: Optional[Tuple[Tuple[float, float, float],
Tuple[float, float, float]]] = None
# (internal) stress-testing mode.
stress_test_players: Optional[int] = None

View File

@ -657,8 +657,32 @@ def _get_server_config_template_yaml(projroot: str) -> str:
ignore_vars = {'stress_test_players'}
for line in lines_in:
if any(line.startswith(f'{var}:') for var in ignore_vars):
pass
elif line != '' and not line.startswith('#'):
continue
if line.startswith(' '):
# Ignore indented lines (our few multi-line special cases).
continue
if line.startswith('team_names:'):
lines_out += [
'#team_names:',
'#- Blue',
'#- Red',
]
continue
if line.startswith('team_colors:'):
lines_out += [
'#team_colors:',
'#- [0.1, 0.25, 1.0]',
'#- [1.0, 0.25, 0.2]',
]
continue
if line.startswith('playlist_inline:'):
lines_out += ['#playlist_inline: []']
continue
if line != '' and not line.startswith('#'):
vname, _vtype, veq, vval_raw = line.split()
assert vname.endswith(':')
vname = vname[:-1]

View File

@ -147,7 +147,7 @@ def dataclass_validate(obj: Any, coerce_to_float: bool = True) -> None:
_Outputter(obj, create=False, coerce_to_float=coerce_to_float).run()
def dataclass_prep(cls: Type, extra_types: Dict[str, Type] = None) -> None:
def ioprep(cls: Type) -> None:
"""Prep a dataclass type for use with this module's functionality.
Prepping ensures that all types contained in a data class as well as
@ -167,19 +167,18 @@ def dataclass_prep(cls: Type, extra_types: Dict[str, Type] = None) -> None:
conditional and thus not available at runtime, so are explicitly made
available during annotation evaluation.
"""
PrepSession(explicit=True,
extra_types=extra_types).prep_dataclass(cls, recursion_level=0)
PrepSession(explicit=True).prep_dataclass(cls, recursion_level=0)
def prepped(cls: Type[T]) -> Type[T]:
"""Class decorator to easily prep a dataclass at definition time.
def ioprepped(cls: Type[T]) -> Type[T]:
"""Class decorator for easily prepping a dataclass at definition time.
Note that in some cases it may not be possible to prep a dataclass
immediately (such as when its type annotations refer to forward-declared
types). In these cases, dataclass_prep() should be explicitly called for
the class once it is safe to do so.
"""
dataclass_prep(cls)
ioprep(cls)
return cls
@ -197,9 +196,8 @@ class PrepData:
class PrepSession:
"""Context for a prep."""
def __init__(self, explicit: bool, extra_types: Optional[Dict[str, Type]]):
def __init__(self, explicit: bool):
self.explicit = explicit
self.extra_types = extra_types
def prep_dataclass(self, cls: Type, recursion_level: int) -> PrepData:
"""Run prep on a dataclass if necessary and return its prep data."""
@ -228,27 +226,18 @@ class PrepSession:
' efro.dataclassio.dataclass_prep() or the'
' @efro.dataclassio.prepped decorator).', cls)
localns: Dict[str, Any] = {
'Optional': typing.Optional,
'Union': typing.Union,
'List': typing.List,
'Tuple': typing.Tuple,
'Sequence': typing.Sequence,
'Set': typing.Set,
'Any': typing.Any,
'Dict': typing.Dict,
}
if self.extra_types is not None:
localns.update(self.extra_types)
try:
# Use default globalns which should be the class' module,
# but provide our own locals to cover things like typing.*
# which are generally not actually present at runtime for us.
resolved_annotations = get_type_hints(cls, localns=localns)
# resolved_annotations = get_type_hints(cls, localns=localns)
resolved_annotations = get_type_hints(cls)
except Exception as exc:
raise RuntimeError(
f'Dataclass prep failed with error: {exc}.') from exc
f'dataclassio prep for {cls} failed with error: {exc}.'
f' Make sure all types used in annotations are defined'
f' at the module level or add them as part of an explicit'
f' prep call.') from exc
# Ok; we've resolved actual types for this dataclass.
# now recurse through them, verifying that we support all contained
@ -478,9 +467,8 @@ class _Outputter:
return self._process_dataclass(type(self._obj), self._obj, '')
def _process_dataclass(self, cls: Type, obj: Any, fieldpath: str) -> Any:
prep = PrepSession(explicit=False,
extra_types=None).prep_dataclass(type(obj),
recursion_level=0)
prep = PrepSession(explicit=False).prep_dataclass(type(obj),
recursion_level=0)
fields = dataclasses.fields(obj)
out: Optional[Dict[str, Any]] = {} if self._create else None
for field in fields:
@ -823,9 +811,8 @@ class _Inputter(Generic[T]):
if not isinstance(values, dict):
raise TypeError("Expected a dict for 'values' arg.")
prep = PrepSession(explicit=False,
extra_types=None).prep_dataclass(cls,
recursion_level=0)
prep = PrepSession(explicit=False).prep_dataclass(cls,
recursion_level=0)
extra_attrs = {}
@ -858,7 +845,12 @@ class _Inputter(Generic[T]):
if fieldpath else fieldname)
args[key] = self._value_from_input(cls, subfieldpath,
fieldtype, value)
out = cls(**args)
try:
out = cls(**args)
except Exception as exc:
raise RuntimeError(
f'Error instantiating class {cls} at {fieldpath}: {exc}'
) from exc
if extra_attrs:
setattr(out, EXTRA_ATTRS_ATTR, extra_attrs)
return out

View File

@ -153,10 +153,46 @@ def var_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
This accounts for deferred evaluation.
"""
# pylint: disable=too-many-branches
# pylint: disable=too-many-nested-blocks
if using_future_annotations(node):
# Future behavior:
# Annotations are never evaluated.
# Annotated assigns under functions are not evaluated.
# Class and module vars are normally not either. However we
# do evaluate if we come across an 'ioprepped' dataclass
# decorator. (the 'ioprepped' decorator explicitly evaluates
# dataclass annotations).
fnode = node
willeval = False
while fnode is not None:
if isinstance(fnode, astroid.ClassDef):
if fnode.decorators is not None:
found_ioprepped = False
for dec in fnode.decorators.nodes:
# Look for dataclassio.ioprepped.
if (isinstance(dec, astroid.nodes.Attribute)
and dec.attrname == 'ioprepped'
and isinstance(dec.expr, astroid.nodes.Name)
and dec.expr.name == 'dataclassio'):
found_ioprepped = True
break
# Look for simply 'ioprepped'.
if (isinstance(dec, astroid.nodes.Name)
and dec.name == 'ioprepped'):
found_ioprepped = True
break
if found_ioprepped:
willeval = True
break
fnode = fnode.parent
else:
# Legacy behavior:
# Annotated assigns under functions are not evaluated,