November 2017 Development Update

Hello and welcome to our first official informative post in this series of Panda3D development blog posts. We’ll showcase newly added features as well as behind-the-scenes information and other changes in November, 2017.

Note that this is just an overview highlighting a few of the bigger features that were added this month. It is by no means a comprehensive list of all the features and bugfixes that were added. For a full list of changes, check out the git commit log.

What happened in the last month

Unit testing

Panda3D got initial test coverage utilizing the mature and full-featured PyTest toolset. This means that core features of the engine will always be tested for their intended functionality and bugs in those places will be caught earlier, helping to keep the engine in an overall good and stable state. Currently there is basic test coverage for NodePath, serializability of Bullet shapes, various interrogate features and some display configurations, but we plan to add more in the future.

Coroutines and async/await

The most exciting change this month is perhaps the support for coroutines and async/await. This is a new syntax introduced in Python 3.5 to simplify asynchronous programming. It can be hard to understand at first, but the possibilities are quite interesting.

Simply put, a “coroutine” is a function that can be temporarily suspended, pending an asynchronous operation, and continued at a later time. For example, if you use a coroutine to set up your scene, you can temporarily suspend it while a model is being loaded asynchronously, so that other operations can continue without blocking the rendering loop.

Asynchronous operations were already possible in Panda using callbacks, but these get messy quite quickly when dealing with multiple operations. The async/await syntax introduced in Python 3.5 makes it significantly easier to write code that involves a series of asynchronous operations.

Here is a side-by-side example of loading a scene using the new approach. (Use of classes is omitted for brevity.) Note that coroutines need to be added to the task manager to make use of them.

# With coroutines
 
async def loadScene():
    text = OnscreenText("Loading…")
 
    # Suspend the coroutine while waiting for it to load
    scene = await loader.loadModel("scene"blocking=False)
    scene.reparentTo(render)
 
    text.hide()
 
taskMgr.add(loadScene())
# With callbacks
 
def loadScene():
    global text
    text = OnscreenText("Loading…")
    loader.loadModel("scene"callback=loadSceneCallback)
 
def loadSceneCallback(scene):
    scene.reparentTo(render)
    text.hide()
 
loadScene()

The way this works is that loadModel, when passing blocking=False, will schedule the load operation to happen in another thread, and immediately return a ‘future’ object. This is a special object (called a ‘promise’ in some languages) that represents an operation that is not yet complete, but will later return a result. The ‘await’ keyword is what suspends the operation of the coroutine until the future is complete. At a later time, the task manager notices that the future is done, and will resume execution of the loadScene function.

The ‘await’ keyword isn’t only supported when loading models. Any task has been made ‘awaitable’, meaning you can suspend a task waiting for another task to complete. You can also ‘await’ various other operations that can occur in the background. At present, that includes:

  • any task or asynchronous loader request
  • messenger.future('event') (waits for the given event to be thrown)
  • interval.start() (waits for the interval to complete)
  • win.triggerCopy() (waits for the copy operation to be complete)
  • texture.prepare() and shader.prepare() (waits for the resource to be prepared)

Coroutines need to be added to the task manager, but they can also be used as event handlers. Here is an example of an asynchronous event handler that uses ‘await’ to time how long the spacebar was held, without needing to store external state:

async def on_press_space():
    # Record the time at which the space key was pressed.
    begin = time.time()
 
    # Suspend the method until the space-up event is detected.
    await messenger.future("space-up")
 
    # Now measure how much time it took.
    duration = time.time() – begin
    print("spacebar was held for {} seconds".format(duration))
 
base.accept("space", on_press_space)

This is still an experimental feature, and we’d love to get your feedback on how we can further improve support for asynchronous programming in Panda3D.

Bullet physics

With the help of the community, all Bullet Physics engine shapes supported by Panda3D have now been made serializable. This means all Bullet structures can now be written to a .bam file, paving the way for future model conversion tools to implement support for Bullet physics.

Interrogate property interfaces

It has been a long-requested feature to enable property interfaces in the Python bindings, as a way to access members instead of the traditional getters and setters. This has been developed further by refining the support for sequence and mapping properties. For example, instead of the get_tag() and set_tag() methods, NodePath now also exposes its tags through a tags property that behaves like a Python dictionary, implementing many standard dictionary methods such as update() and keys(). The same applies to properties that expose collections, providing a predictable, standard and aesthetic interface to manipulate the API.

Released games section

In this section we’ll give you an overview of released and updated game titles that have been written with Panda3D.

New game releases

The Traveling Fox

Updated games

Kitsunetsuki
YORG

2 thoughts on “November 2017 Development Update

  1. Hi Panda3D Team,
    I like this structured Dev. Update. Would be great to see that somehow regularly.
    Thank you and allof the Panda3D Team a lot.
    See you around.
    Matthias

Comments are closed.