A Pipeline is a fancy name for an asynchronous send/receive model, where the receive side uses marker interfaces to determine what to call on the received object.
To encourage indirect coupling, you specify the names of target components via a route, so the call-site determines which other components "see" the pipeline GO on "the other end".
AGE has these pipelines:
- Install: for loading data and connecting to GOs.
- Uninstall: for reversing the effect of Install.
- Event: for inter-GO communication.
The goals of the Pipelines:
- Asynchronous design acknowledges the task of resource acquisition and offloads that work to a different thread.
- Easy to target a GO at the call site. A call to install/uninstall/event() does it all!
- Require minimum information "in hand"; just the GO to operate on, and the Installer interface.
- Standard way for everything to be initialized and enter/exit the system.
- Uniform "top-level context" (threading, locking, interfaces) to perform these actions.
Marker Interfaces
These indicate which services the GO requires.
RequireResourceLoader
Indicates requirement for asynchronous resource loading.
RequireTimer
Indicates requirement for establishing multiple timers.
TimerCallback
Indicates the GO itself is a timer to be registered.
RequireLocatable
Indicates requirement for registering multiple name/GO mappings in the Locator.
Install Pipeline
The Install pipeline is asynchronous; it is started by calling the install() method, and passing the GO to install. The GO should be minimally initialized at this point, because the Pipeline will take over the actual "work" of initializing.
Game objects (hereafter GO) opt-in for system services by implementing the marker interface of each required service. These interfaces follow the naming pattern RequireXXX where XXX represents the service.
The first stop on the Install Pipeline is a dedicated Resource Loader thread for the time-consuming work of initializing a GO from resources, e.g. texture bitmaps, object models, etc.
Load Request
The LoadObject message contains the information used by the threads in the Install Pipeline:
- the GO to target.
- a reply-to TaskChannel to notify when complete.
- a LoadedCallback to execute in that context when it receives this message. The Game Cycle uses a LoadedCallback that's outlined below.
- an optional list of bind targets, called a route, to have their LoadedCallback invoked.
Loaded Callback
This is called for every GO that completes the install pipeline. It is called on the Game Cycle's thread, so thread-safety is necessary if interacting with the UI (or other) thread of the application.
This is a top-level context with the update lock held.
Default Behavior
The install pipeline proceeds as follows (the thread executing is in parentheses):
- (Caller) Call the install() method, which sends a message to the Resource Loader thread and returns. the reply-to target is the Game Cycle, and the LoadedCallback is the remainder of the steps from step 3. Do not access the GO after this point, until objectLoaded() is called (step 4).
- (Resource Loader) If RequireResourceLoader is implemented, invokes the GO.load() method, passing ResourceLoader and Resources interfaces. If not implemented, no action is taken; this is done to maintain the order of install calls with their callbacks. When complete, the load request is passed to the reply-to target (Game Cycle).
- (Game Cycle) Obtain the update lock for the remainder of the steps.
- (Game Cycle) call the objectLoaded() method, passing the GO and error status. This is a good place to trigger additional installs that depend on a GO being completely installed before certain methods can be called successfully.
- (Game Cycle) if GO.locatable flag is set, register with the Locator service.
- (Game Cycle) if RequireLocatable is implemented, register mappings with Locator.
- (Game Cycle) if RequireTimer or TimerCallback are implemented, register with TimerService. The RequireTimer takes priority here, so a GO implementing both interfaces must register itself in RequireTimer.
- (Game Cycle) if the install request contains a route, obtain each GO from the Locator and pass installed GO to its LoadedCallback (if implemented).
Uninstall Pipeline
The uninstall pipeline reverses the operations of the install pipeline, but does not execute anything in the Resource Loader thread.
This is a top-level context with the update lock held.
Unloaded Callback
The dual of the Loaded callback is the Unloaded callback. This is meant to undo whatever the effect of Loaded was.
Event Pipeline
This pipeline is for making asynchronous requests for additional code to execute. This makes it convenient to schedule something to happen after the current method completes, and keeps the model in a consistent state, by not mutating it at that time.
The GameObject class is reused here to represent an event.
To participate in the Event pipeline, a target GO must be in the Locator and implement the EventCallback interface. The Event pipeline does not check for any other marker interfaces.
This is a top-level context with the update lock held.
Event Request
This is analogous to the LoadObject request. The most-important part of this is the route.
Event Callback
This callback gets the GO that was sent as the event. This is an excellent place to put game logic.
Default Behavior
This is how the Game Cycle thread handles Event Requests:
- (Game Cycle) Obtain the update lock for the remainder of the steps.
- (Game Cycle) call the objectEvent() method, passing the GO. This is a good place to trigger additional installs/uninstalls/events that are unrouted or you want to process it before any route targets do.
- (Game Cycle) if the event request contains a route, obtain each GO from the Locator and pass event GO to its EventCallback (if implemented).