View from 20 klicks
Imaginary is going to be a simulation engine. Simulation engine is used here to refer to any software library which is capable of modelling (not necessarily in a manner which mirrors reality) interactions between a number of systems (not necessarily systems taken from reality). Some examples of such systems are physical interaction between different objects, chemical change in a material due to the introduction or removal of energy, and alterations in the propagation of electromagnetic radiation by intermediate transmission substrates.
Behaviors implemented by a single system must be specified completely and in detail in order to achieve somewhat predictable outputs for a given set of inputs. Likewise, interaction between different systems needs to be specified somehow. Explicitly defining the interactions of each system with every other system requires N!
such definitions, where N
represents the total number of systems. As N
increases, N!
increases prohibitively more quickly, preventing a large number of systems from being supported.
An important aspect of a successful implementation of a simulation engine is avoidance of this combinatorial explosion.
Currently, it would be most accurate to call Imaginary part of a research project for exploring possible ways to implement a simulation engine. It is the 5th or 6th (or perhaps 7th or 8th, depending on how you count) generation implementation by the team of developers involved in the project.
What it can do now is support multiuser interaction in a single persistent world, accept and parse a limited number of commands for interacting with the world, and transmit a limited amount of information to each user when an observable part of the world changes somehow.
Containing Framework
In its current incarnation, Imaginary is a Mantissa offering. Mantissa offerings are beyond the scope of this post, but you can read a bit about them on the Divmod Wiki or in previous blog entries of mine. If you are interested in Imaginary, then you can probably skip that for now. The salient points are simply that:
Imaginary ignores signup because it’s taken care of.
Imaginary uses Axiom for persistence because that’s what Mantissa encourages.
Imaginary uses Twisted for networking.
Taking Action
User interaction is close to the primary purpose of Imaginary. The results of the simulation need to be exposed, and changes to the state of the simulation world must be enactable by users. The current user input system for Imaginary is a simple text based interface exposed over SSH. Text input is parsed into higher level structures known as actions. Actions define their own parsing rules and are associated with code for inspecting or manipulating the game world.
Examples of actions are opening or closing a container, picking up or dropping an object, speaking, moving around, or examining some aspect of the world. When an action has completed, observers are notified using the event system. Sometimes, the implementation of an action is responsible for emitting these events. Other times, the game system which the action has interacted with will emit them. However, actions are not always successful. An object may be too heavy to be taken or a container may already already be in the state into which opening or closing it would have put it. These are some of the simpler cases which Imaginary can handle: the implementation of the action handles exceptions from the game system it is manipulating and emits the appropriate event (for example, instead of broadcasting that Alice has lifted a boulder above her shoulders, it would broadcast that Alice struggles and strains but is unable to budge the boulder).
Certain actions can result in more complex interactions. For example, movement between two locations involves two events being broadcast: one to the observers at the location being vacated, one to the observers at the location being occupied. To deal failures with this kind of action, other projects have often adopted the “look before you leap” approach: the action implementation can perform checks to determine if the actions is going to fail; if it is, it can take care to broadcast only a failure event to the location the user attempted to leave (not broadcasting any event to the destination location, since no observable change has occurred there). The drawback to this solution is that it requires the movement action to be completely aware of every interaction which might prevent the movement from succeeding.
Instead, Imaginary’s action system uses a transactional event broadcasting mechanism. When the movement action begins, the departure event is broadcast at the location being vacated. Next, the action invokes the actual code in the movement system (or “containment system”). If the movement is not actually possible, this will raise an exception, preventing further action code from running and causing the entire action to be aborted. Events broadcast as part of an aborted action are not sent to observers. If the containment system allows the movement, the action continues to execute, resulting in an arrival event being broadcast to the destination. Once the action has finished running without aborting, all of the events it emitted are delivered to observers.
The Observable World
I’ve mentioned events above quite a bit. Events are the basic unit of information which moves around an Imaginary world. When someone walks into a room, if you can see it happen, it is because you received an event that described the change in the state of the world. If you can’t see it happen, the event has failed to reach you for some reason. If you’re in a dark room, for example, events which convey visual information may be unavailable to you.
Blindfolds, colored glass boxes, parabolic dishes and other world features which modify the propagation of an event are implemented by giving objects an opportunity to wrap a proxy around an event as it propagates. When an event is finally observed, it may have several layers of proxying wrapped around it which change what the observer actually perceives. A red glass box may make red items inside it invisible and make green items appear black. The red and green balls rolling around inside the box are unaware of the visible light red/yellow/blue color system, though. This leaves the implementation of colored balls simple and unfettered by all of the complications which may arise between the balls themselves and other actors in the world which observe or manipulate them.
Graph Traversal
Many of the concepts discussed above rely on what should probably be considered the core of Imaginary: a set of APIs which allow the object graph representing the simulation world to be queried and traversed in a structured way. All physical objects in the simulation world are represented by an instance of a class named Thing
. Thing
has a couple important methods: links
and findProviders
.
links
defines the relationships a Thing
has with other Thing
s in the world. Everything is linked to itself and its location. Boxes, for example, are also linked to their contents. A room with a door is linked to the room into which the door leads. The implementation of links is extensible using a feature of Axiom called powerups. This is reminiscent of the Componentized idea from previous iterations of the research project.
findProviders
implements a breadth-first, depth- and interface-limited search of a Thing
’s links
. It also applies proxies as it traverses the object graph. This method supports both the action system and the event system in a major way. The input parser uses it to resolve string names into concrete object references. The event broadcast system uses it to find observers who should receive events.
Get Involved
You can read more in the Reality archives: http://twistedmatrix.com/pipermail/reality/
You can check out the code:svn co svn+ssh://divmod.org/svn/Divmod/trunk Divmod
You can join us on IRC in#imagination
onirc.freenode.net