Skip to content
Eric Froemling edited this page Mar 27, 2020 · 226 revisions

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.

Mypy: Getting a Variable's Type

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 what type Mypy understands a variable to be.

Say we have the following code:

value = some_mysterious_function()

How do we know what type Mypy thinks 'value' is? We can 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 '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

Debug-Only Python Code

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 this statement does not evaluate to 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.

👍0

Makefile Autocompletion

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

Python Cache Files and Release Builds

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 .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

Clone this wiki locally