[Bf-committers] Python!

bf-committers@blender.org bf-committers@blender.org
Thu, 9 Jan 2003 11:56:34 +0000

On Thu, Jan 09, 2003 at 09:39:06AM +0100, Laurence Bourn wrote:
> One of the problems, I think was associated with past python
> implementtations is that they were built directly onto weak, ill thought out
> C,C++ interfaces. It becomes more and more difficult to refactor, improve
> existing C,C++ code because you have essentially exposed it's internal
> working (through the python API) to end-users.

My thoughts exactly. One interesting approach to scripting is that taken
by World Foundry (http://worldfoundry.org, or http://wf-gdk.sourceforge.net).
It supports 3 scripting languages, with others being looked at.

The way World Foundry abstracts the engine from the scripting language
is through the concept of a mailbox. It's very similar to memory-mapped
I/O. For simplicity I will use the analogy of the peek and poke commands
in BASIC. Peek reads a memory address; poke writes to a memory address.
With memory-mapped I/O, reading certain addresses that are mapped to
hardware I/O doesn't return you a memory value, but instead returns to
you the state of a certain hardware device. Similarly, writing to a memory
address which is mapped to I/O doesn't store a value in memory, but
instead passes the value on to some hardware device. We can
use this memory-mapped I/O idea to read/write engine state through a
similar interface.

The idea is that the engine exposes its state only through "mailboxes",
i.e., only through memory addresses or slots. For instance, in a script, if
you said "a = peek(1000)", you would get the value that the engine is
exposing in slot 1000. The engine intercepts this call, then returns some
state. For instance, you might have a convention that slot 1000 represents
the integer ID of the currently active object. Slot 1001 might represent
the total number of objects in the scene. And so on and so forth.

Writing to a mailbox is like writing to a hardware port. For instance,
if you say "poke(1000,10)" you could set the current object ID to be 10.
The engine intercepts the call, realizes that mailbox slot 1000 is the
object ID, then calls the internal functions to change the active object

Furthermore there are system-wide mailboxes (which reflect global
system state) and system-local mailboxes (which reflect state specific to
a particular object). For instance, system local mailbox 2000 could
represent the first element of an object's matrix. 

The engine publishes a list of constants defining which mailboxes
have which meaning so you don't need to refer to them by name.

In other words, the engine only exposes its state through numbered
addresses (which can be given names through constants). The engine
needs only implement two commands to support a new scripting language:
set_mailbox and get_mailbox. The architectural decision made at the engine
level is "what state do I need to expose?" and (trivial) "what address
do I want to use to expose this state"?

The scripting language (e.g. Python, TCL, Lua) can then declare its own
helper wrapper functions which access the engine through mailboxes, but
which provide a more convenient interface to the scripting language
programmer. For instance, to retrieve a 3x3 rotation matrix, you need
to read 9 values, and read 9 maiboxes. A python script could declare
a help function to read 9 mailbox values and pack them into a python object.
The script progrmamer doesn't need to deal with the mailboxes directly.
And importantly, the Python API is thus defined ONLY in Python - it
has nothing to do with Blender. Indeed, the Python "API" is a Python
convention of using Python structures to wrap access to mailboxes in
an easier fashion. Thus, defining new scripting API's has absolutely
nothing whatsoever to do with the engine code. A script API function is
just a wrapper to access engine mailboxes.

This makes adding new API's in one scripting language, or new scripting
languages entirely, easy. The scripting API has zero, nada, zilch to
do with engine internals. The only interface between the engine and the
scripting language is two functions: get_mailbox and set_mailbox.
Everything else is a helper function in the scripting language, in a
separate file totally unknown to the engine.

I have used this scheme with success on a commercial coin-op game
project. I had to change the scripting language mid-project due to
performance reasons. With the mailbox approach, this was no problem.

See http://worldfoundry.org for more information on mailboxes in World