-
Notifications
You must be signed in to change notification settings - Fork 135
Knowledge Nuggets
This page contains snippets of useful knowledge and tips related to Ballistica development. Feel free to add your own here or upvote existing ones by incrementing the 👍 count next to them (just once per tip please). I will use upvote counts to help organize the tips by usefulness.
When using Mypy to perform static type-checking on Python code, one of the most basic and important things to be able to do is determine the static type of a variable as Mypy sees it.
Say we have the following code:
value = some_mysterious_function()How do we know what type Mypy thinks 'value' is? We could guess this ourself by looking up the definition of some_mysterious_function(), but it is often better to be sure and just ask Mypy directly. We can do that by adding the following special fake function call to our code which is understood by Mypy:
value = some_mysterious_function()
reveal_type(value)Now we can run make mypy in a terminal from the project root, and we should see output such as this:
Running Mypy (incremental)...
path/to/this/python_script.py:45: note: Revealed type is 'builtins.int'
Mypy: fail.
make: *** [mypy] Error 255
Ok, Mypy says 'value' is an int. Good to know! So if we were to add a statement such as value += 'foo', then Mypy would kindly inform us that ints and strings cannot be added. Without type checking, an error such as that might go unnoticed until the next time the code is run, and who knows when that might be. Hooray for static type checking!
Be sure to remove the reveal_type() line when you are done with it, otherwise the code will error at runtime due to that not being an actual Python function.
👍0
With the Ballistica project it is now easy to switch between debug and release builds of the game. (make prefab-debug vs make prefab-release). Debug builds perform much more error checking than release builds; this makes them very useful during development, but it also means they can be significantly slower or less efficient than release builds.
You can take advantage of having two build types by adding extra safety checks that run only in debug builds. There are a few ways to do this.
The first is the Python's 'assert' statement. These will be evaluated in debug builds and completely stripped out of release builds, so think of them as 'free'. They are a great way to make sure things are as you expect them to be.
my_thingie = make_a_thingie()
assert my_thingie is not None. # <-- Throws an AssertionError if not True.As a nice side-effect, these statements are also quite useful for Mypy type-checking.
val = eval('123') # <-- Dynamically evaluate a string to create an object (in this case an int).
reveal_type(val) # <-- Mypy will tell us val is 'Any' here because eval() can return anything.
assert isinstance(val, int). # <-- We expect val to be an int; let's make sure that's the case.
reveal_type(val). # <-- Because of the assert above, Mypy will tell us val is an int here.Another way to generate debug-only code is with Python's special __debug__ variable. Code such as this will run in debug builds of the game but will be completely stripped out of release builds, again making it 'free'.
if __debug__:
for node in all_my_nodes:
do_some_expensive_sanity_checks(node)With any code such as this, it is extremely important that no actual logic be affected as a side-effect of these expressions since they will not be run at all in release builds.
👍0
Ballistica's main Makefile is set up to list info about its available targets when you simply type make or make help.
However, it can also be handy to set up your shell to allow autocompleting target names. If you are using zsh (the default on Mac as of 10.15 Catalina), you can add this to your .zshrc file to enable it:
autoload -U compinit
compinit
Once you restart your shell, you should be able to type something like make prefab- and hit tab to see a list of all matching targets.
ericf@MacBook-Fro ballistica % make prefab-
prefab-debug prefab-mac-release
prefab-debug-build prefab-mac-release-build
prefab-linux-debug prefab-release
prefab-linux-debug-build prefab-release-build
prefab-linux-release prefab-windows-debug
prefab-linux-release-build prefab-windows-debug-build
prefab-mac-debug prefab-windows-release
prefab-mac-debug-build prefab-windows-release-build
The same functionality should be possible with other shells such as bash, but I will leave that as an exercise for the user (feel free to expand this tip).
👍0
TLDR: In some specific cases you may need to blow away .pyc files if you are editing .py files.
When Python imports a module, it attempts to first create an intermediate 'compiled' .pyc file from the raw .py script file. This compiled version can then be reused to import the module more efficiently the next time it is used. In old 2.x versions of Python these .pyc files were created alongside the .py files, and in modern 3.x versions they are placed in a __pycache__ directory alongside the .py files.
Traditionally, Python either recreates a .pyc file or ignores it whenever the timestamp on the corresponding .py file differs from it. This generally does the right thing as long as Python has write access to the .pyc file, but in cases such as game distributions this is not always the case and can lead to inefficiencies if these timestamps differ in any way after the install, leading to inefficiencies and repeated failed attempts to recreate .pyc files. See PEP 552 for more details.
Ballistica now avoids these inefficiencies by creating optimized 'unchecked hash' .pyc files as part of its build process. 'Release' builds of the game will look for .opt-1.pyc files for any loaded module, and if one is found, it will always be used, even if the corresponding .py file is subsequently changed. This means these .opt-1.pyc files must be explicitly updated instead of relying on Python to do so.
You don't need to worry about this if you are using Ballistica Makefile targets such as make prefab-release; .opt-1.pyc files will be automatically regenerated as part of these builds. But if you are hacking on scripts directly in an installed copy of the game, be aware that you will need to blow away the script's existing .opt-1.pyc file or your edits will never be seen by the game. (though you should only need to do this once; the new .opt-1.pyc file recreated by the game should be timestamp based)
Note that this only applies to the release build of the game. Debug builds still use plain old timestamp-based .pyc files and so this does not apply to them.
👍0
ballistica.net • discord • blog • support