Composite Design Pattern
A large part of the purpose of writing LxEngine is to experiment with producing a flexible yet simple architecture for visualization and simulation applications. One of the major corollaries to that statement is that the architecture needs to reduce the total number of concepts the coder needs to understand in order to use the system completely. The less the programmer needs to learn, the more intuitively they will understand the system from the start.
This adds several overlapping principles to the LxEngine architecture:
- Reuse commonly known and used design patterns and concepts
- Reuse design patterns and concepts already used with the system
- Eliminate unnecessary patterns and concepts from the design
The LxEngine Design
LxEngine’s architecture is still under development. The project itself is, in part, a project to define the architecture – therefore, the design is continually subject to change. However, for now…
LxEngine is based at the highest level on the Model-View-Controller pattern. The user creates an Engine Singleton. This Engine object then can load one or more Document objects (the Model). Each Document can have one or more Views attached to it. Each View in turn can have a stack of one or more Controllers applied to it to process incoming user or device interaction into application-specific events.
The architecture relies heavily upon the Composite design pattern. The Document, View, and Controller objects are effectively shell objects with minimal implementation. These objects allow Component objects to be attached to the shell object; these Components implement an interface (or subset of the interface) which allows them to respond to application events – such as changes to the Document. When a Component makes a change to the system, it does so via an event which is sent through the Engine. This event allows the Components to stay de-coupled and abstracted from one another.
One of the primary roles of the Engine object is dispatch and coordination of events. The Engine dispatches an event to all the necessary Views and/or Documents. For example, a Physics Component may be attached to the document: if an object moves, it sends a “redraw” event to the Engine. The Engine then dispatches the event to the view. The Physics Component has no specific knowledge of the rendering system in use (is it DirectX 11 based? OpenGL based? If the window not visible so this is actually a no-op?), but instead merely has the knowledge of an abstract event defined as “redraw”. This loose-coupling has advantages and disadvantages, but in a complex system designed for reuse (as well as simple scripting), the advantages outweigh the disadvantages.
The Document and its Elements
The Document object itself is based on the HTML DOM and is composed of a XML-like tree structure of named elements with optional attributes and children. These Element objects in turn can have Components attached to them to provide a meaningful implementation. For example, a <Sphere> Element in itself is just a data holder; however, a “Runtime” Component may be attached to provide a runtime representation of the object that has functions such as computing the volume, the surface area, etc. Likewise, a “Physics” Component could provide collision detection, a “Render” Component could provide necessary graphics information, etc. In other words, the system is fully dynamic as implementation is attached to each Element within the document. In turns of software reuse, the C++ object used for the “Runtime” could potentially be shared between a ray-tracer and OpenGL-based viewer, even though the “Render” component could not be; this is the purpose of the loose-coupling between components.
For scripting, only the Element interface itself needs to be exposed. A Javascript script might change find an Element in the Document with the tag “Sphere” and change the attribute “radius” to be 10. The core architecture doesn’t have any knowledge of what this change means (since it understands only Elements, not Spheres), but will forward the attribute change event onto the specific Element and its components. The registered physics and graphics Components then have an opportunity to update their own internal representations, which in turn may trigger redraws, new collisions and intersections, etc.
The HTML DOM approach is used largely because of the real-world proven success of HTML and Javascript scripting. See “the Internet” for proof of this
