I'll give a couple of examples of what happens when you add a new node or change an attribute in a node.
Setting an attribute:
myWorld = World()
myWorld.rootNode.logic = coolUpdateFunction
-> myNode.dirtyAttributeSignal(myNode, "logic", coolUpdateFunction)
-> myWorld._dirtyAttribute(myNode, "logic", coolUpdateFunction)
-> attribute name was "logic", so if we're not already storing this node, store it so we can update it when myWorld.update() is called
-> for view in myWorld.views: view._dirtyAttribute(...)
As you can see, setting an attribute results in quite a fuss inside the engine, but as I said in the last entry, setting an attribute is rare compared to using an attribute (Node.__getattr__ is not replaced so it's fast), so this makes sense. If I want to optimize this later, I guess I could implement a registry in the World class, telling which workers are interested in which attributes. At the moment, this is far from being the bottleneck.
Adding a new node to the tree:
myNode = Node()
myNode.render = myGeometry.createPrimitive().render
myNode.collisionGeometry = myGeometry.createCollisionGeometry()
_newNode methods of the various workers will then inspect the node for attributes they are interested in (see the example in last post). That's really all there is to signals in scene graphs. All the rest is the various workers keepin track of interesting nodes and updating them as needed.
I'm also using Signals for event passing in my engine: I don't have a centralized event manager (anymore), and this seems to be a much more elegant solution, and didn't really add any dependencies that weren't there already. I'm littering signals around in various places where I find them useful. Last I added was stepSignal to World, which is called for each step; step size is always constant, and there might be zero to many steps per frame depending on FPS.
About transforms then. Since they are so common an operator, all functions for working with Node transforms are in an external module (they could have been in the Node itself since they are so commonly needed, but I decided I want to keep the Node class clean). You get the world transform of a node by calling transform.getWorldPosition/Rotation/Scaling/Matrix(). They will check if there's a cached world transform available, and calculate one if there isn't. When the node's "position", "rotation" or "scaling" attributes are set, World._dirtyAttribute() in turn calls transform._invalidateWorldTransform(), which just clears the cached world transforms from the Node. An example is probably in order:
myNode.position = Vector(1, 2, 3)
# World transform cache is invalidated, so the following call will calculate it
# World transform is now cached, so the following call will use the one from cache
There are two transform utility functions at the moment in addition to world transform functions: lookAt() and getVelocity(). lookAt() is just a wrapper for quaternion.lookAt(), and gets a node whose rotation is to be calculated, and a target node to look at. getVelocity() calculates the velocity of a node based on time since last update and the previous position (which it stores itself).
More questions are welcome as always. :)
Btw. I have a reeeally basic heightmap working for my game, but it doesn't even have normals calculated (Spineless doesn't calculate normals automatically yet), so no screenshots for now. :)