Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Class Members | File Members | Related Pages

Phoenix Framework Guide

Table of Contents

  1. Introduction

  2. Concepts
  3. Framework Classes
  4. System overview
  5. fw_todo
  6. Acknowledgements

Introduction

Phoenix provides two fundamental pieces of functionality to which you'll continually be exposed. This is the "framework" code that offers simple tools for common coding tasks, and core functionality necessary to get your code working with other objects in the simulator.

Concepts

This section describes how several components work at a conceptual level. The following section (Framework Classes) details the actual equivalents implemented in the code and how to use them.

Named Interfaces

A "named interface" is simply an interface (see What is an interface? and the subsequent sections) that has a name and identifier and can be looked up using a directory system.

The named interface has three attributes. It has (unsurprisingly) a text name, an integer-valued identifier, and a type constant. The name is actually optional -- it might be "" (the null string), in which case you can only look up the object in the directory by its identifier. In general, you should only use the name fleetingly to find the object you're looking for, and then use the identifier for any other operations -- especially network operations. If a name is given, then the identifier and the name form a one-to-one relationship: there is exactly one name for a given identifier, and one identifier for a given name. Since these "identify" a unique instance of an object, it makes sense that each object has only one name/identifier, and each name/identifier has only one object associated with it. The type attribute is a value that contains the type constant for the named interface that the object of which the object is an instance.

In addition to providing directory service, the named interface system also allows us to create instances of "named interfaces" at runtime. If the server asks a client to create a new "F15" object, then the client needs some platform-independent way to determine what an "F15" object is and how to create one. In this case, F15 is a named interface. That is, in some header there is an F15 interface class that will ultimately derive from the NamedInterface base class (so that it can use the directory service). The module that is in charge of the F15 will see to it that the F15 class's type constant is registered with the named interface directory. Then, we can request that the directory create an instance of the "F15" class by passing it the type constant for that class. The directory will then call the module that is responsible for the F15 type, which will return a new F15 object and add the F15 to the directory so that other objects can look it up. Note that the module might provide different implementations of the F15 object to us (in the extreme, every call might return an F15 with a different underlying implementation). However, these are all indistinguishable -- they are all instances of the F15 class and hence have the same type attribute (namely, their type attribute is just the type constant of the F15 class). So, the type attribute of a named interface refers to the most specific interface class of the object -- it does not (in fact, cannot) refer to the (more specific) implementation class of the object!

There are three general classes of objects that you'll create. The first are entities and value-types (see Entities and Values). Entities will usually be named interfaces (i.e., they'll derive from the NamedInterface class so that they can get all of the features described here). Entities are the objects that actually "do something" in our world, and will need to interact with each other. All of the entities that have been constructed as named interfaces will be available in the directory. i.e., you can access the directory to look up an entity by its name or identifier, and then interact with the entity to get your job done. Value-types should never be named interfaces since they have no sense of identity (which contradicts the purpose of this system).

A third class of objects is "descriptions". Descriptions are pieces of shared data that contain descriptive information about a kind of object in the simulator. For instance, a BuildingGeometry object could contain a 3D model of a building. Obviously, we don't want to store the building geometry separately for each building entity that uses it -- so we create a "description" of the building in question that can be shared between all of the objects that want to use that model. Descriptions are values because they are interchangeable -- they do represent a particular object. Meanwhile, we will generally refer to them by pointer, and avoid copying them because they are specifically intended to allow sharing of resources.

Listeners

Anyone who has used Java is probably familiar with listeners. Listeners are "observers" to an event. For instance, an input system might define an event "onMouseClick" that occurs when the user clicks the mouse. To receive notifications of these events a client can provide a "listener" to the input system. The input system then calls back the listener's onMouseClick() method whenever the user clicks the mouse (and maybe passes some parameters with additional information about the event).

From a design standpoint, listeners can be seen as a way of defining an interface to a client. As implementors of an interface, we can provide the client with ways to acquire information from us on a "request" basis -- i.e., the client reads an attribute from our interface when it chooses. If we want to go the other way (that is, "push" information to the client), then our implementation needs to call a method in the client's interface to deliver the necessary information. Of course, not all of our clients will have the same interface. So we provide a listener class that defines the interface that we need and then invoke methods on instances of the listener class. The client then implements the listener interface that we've provided, which generally will result in a call back into the client object to perform the necessary processing. In this situation, the listener is an interface and the client's object is the implementation. So listeners are just a way of adapting the interface of a client object to the needs of our own system.

Smart pointers

Phoenix provides "smart pointers" that are used to manage memory. These pointers are actually C++ templates that, as a result of operator overloading, behave like pointers. Our smart pointers are reference-counting pointers. They keep track of how many times a particular object is referenced and when the number of references drops to 0, the object is automatically deleted. In some ways, this is much easier than raw pointers. If we allocate some memory and store the pointer as a smart pointer, then our memory will automatically be freed if an exception causes our smart pointer to be destroyed or if we just let the pointer go out of scope (which would normally result in a memory leak had we not freed it).

Unfortunately, one caveat with these types of pointers is circular referencing. If A has a pointer to B and B has a pointer to A then each object will have a reference count of 1. If we release all other pointers to these objects then we will have no way to access them, and yet the objects will not be deleted because each still has 1 reference. The solution to this is not to create circular references with smart pointers. If you must have such a relationship, then you should use raw pointers or a combination of smart/raw pointers.

A helpful tool in writing your code is to draw a bunch of boxes representing instances of your objects in a particular situation. Draw solid lines with arrows pointing to objects to represent smart-pointers to those objects (also called "strong" pointers), and draw dashed or dotted lines with arrows to represent raw pointers (also "weak" pointers). Obviously, you should avoid a situation in which your picture has a "cycle" of solid arrows. Also, if the order in which objects are deleted is important to you, then you should consider what happens as references to objects are deleted. For instance, if you have a long chain of references (e.g., A points to B which points to C, which points to D, etc.) Then when the last reference to A is destroyed, A's reference to B will be destroyed. This may result in the destruction of B, which might result in the destruction of C, etc. But note that if C's reference to D is destroyed, then A, B, and C will not be affected (unless there is a circular reference). In short: objects get destructed in the order implied by the direction of references. i.e., objects at the "tails" of the arrows are destructed before the objects at the "heads" of the arrows.

Some may find this a little hard to use at first -- it's somewhat different from the manual new/delete semantics that most are taught when they first learn C++. The strangest part is that you don't have direct control over when your object is deleted unless you are the only one that holds pointers to the object. If you hold all of the references to an object, then you simply set them all to 0 or otherwise cause the references to be deleted. This will result in the destruction of the object because no other references will exist at that point. However, if other objects might have references to your object, then the object will not be deleted until those other references are released. While this seems unusual at first, it's actually not much different than raw pointers. If you hold all of the raw pointers to a particular object, then you may call "delete" on one of the pointers to destroy the object and then simply throw away all of your raw pointers. If there are other objects with raw pointers to your object then you have a problem: you will need to be sure that all of the other clients know that the object is being deleted. Otherwise, a client might try to access the pointer the the deleted object, causing an error. In short, you still need to know who has references to your object, and you still can't delete the object until those references are no longer in use.

In general: It is always important to understand who is referring to your object, whether they're using smart or raw pointers, and how they will behave in the event that your object needs to be deleted.

A real-life example that will be important for everyone to understand is the named interface directory. This directory stores smart-pointers to all of the objects that it is keeping track of. Thus, for as long as your object is tracked by the directory, it will not be deleted. Even if you release all of your references to the object, your object will still exist in the directory. If you really want your object to be deleted, you'll have to remove it from the directory first.

Lock holders

Locks are all over the place in threaded code. They're notorious for creating bugs because it's easy to forget to release a lock, resulting in a deadlock later. Because Phoenix uses exceptions, it's also easy to have an exception thrown from your code which causes the "unlock" step later in your code to be skipped. To prevent this, Phoenix provides a lock-holder object that will acquire a lock for you and hold it until the object is destructed. This will automatically release the lock when the lock-holder goes out of scope (and thus, also when an exception is thrown). Use these as much as possible -- it's easy to make a mistake if you try to handle exceptions, etc. manually.

Value types

See Entities and Values for a description of "value types". One thing that we frequently want to do is define a type that represents a simple quantity like an index, a count, or a physical quantity like weight or volume. One way to do this is to use a "typedef". e.g.,
typedef double Kilograms;
...
void mass(Kilograms mass);
Unfortunately, this is only a syntactic improvement, and does not protect the type from accidental conversions:
typedef double Slugs;
void mass(Kilograms mass);
...
void foo(void) {
  Slugs m = 1.0;
  mass(m);
}

If we choose, we can provide "type isolation" that prevents such conversions by defining these types as classes (which cannot be converted between by the compiler). One way to do this is to write a separate class for each type:

class Slugs {
public:
      Slugs operator+(Slugs) const;
      Slugs operator-(Slugs) const;
      Slugs operator/(Slugs) const;
      Slugs operator*(Slugs) const;
      ...
private:
        double mSlugs;
};

Using operator overloading, this type can be manipulated just like a double. Perhaps better, we can restrict our operators to prevent undesired behavior. For instance, a "SerialNumber" value might not allow operator< and operator> to be used, but only operator==. Also, if we similarly defined a Kilograms class as with Slugs above, the foo() method above will trigger a compile error -- because Slugs cannot be converted to Kilogram (unless one of the classes provided a conversion -- in which case we could actually convert the units automatically).

You needn't write this code yourself every time. Phoenix provides template classes that will wrap primitive types in this manner for you. See ValueType.

Framework Classes

The following section details the practical use of the classes in Phoenix that implement the ideas described above.

NamedInterface

All named interfaces derive from the NamedInterface class. This class includes three read-only attributes: type(), name(), and identifier() which are just the three attributes described in Named Interfaces.

When you create an entity or description class, it should derive either directly from this class or from another class that derives ultimately from NamedInterface. As a result, all named interfaces will provide the standard type(), name() and identifier() attributes. (This is one nice property of "attribute only" interfaces -- the inherited interface merges cleanly with your own interface, which is also composed of attributes). One consequence of this is that named interface subclasses shouldn't use type, name or identifier as names for their own attributes.

If your named interface class only defines a base class (i.e., it is an interface to which other objects will conform, but you cannot instantiate it directly), then deriving from NamedInterface (directly or otherwise) is all that's necessary. Subclasses of your class will handle the rest.

If your named interface can be instantiated (i.e., a client can request new instances of your interface from the directory) then you should add two additional things to your class. These are const static member variables which define the type constant and, optionally, the type name of the interface type. By convention, these should be named INTERFACE_TYPE and INTERFACE_TYPE_NAME with types NamedInterface::Type and String respectively:

// PhxMyNamedInterface.h
class MyNamedInterface : public NamedInterface {
public:
        NamedInterface::Type INTERFACE_TYPE;
        String               INTERFACE_TYPE_NAME;
};
// PhxMyNamedInterface.cpp
MyNamedInterface::Type MyNamedInterface::INTERFACE_TYPE_NAME(100);
String MyNamedInterface::INTERFACE_TYPE_NAME("MyNamedInterface");

The type constant will be assigned by a centralized authority to make sure that they're unique (although complaints from the code will likely show up if you try one that isn't unique). The text name should be the fully qualified name of the class, excluding the Phx:: namespace prefix. So if the class is in the top-level Phx namespace, the text name is just the class name. Otherwise, it will be the names of the enclosing namespaces/classes, separated by ::, followed finally by the class name (e.g., MyNameSpace::MyClass::MyNamedInterface).

NamedInterface::Manager

As explained earlier, we track instances of NamedInterface in a directory that allows us to look up objects by identifier or by name and also to instantiate new objects by type. The directory system is exposed to you in the form of the NamedInterface::Manager interface. This class (which is an accessory to NamedInterface) defines the interface for looking up objects, creating new objects, and also defining new named-interface types. These methods are pretty simple to understand -- the API reference paged for NamedInterface::Manager is the best place to read. In addition, the example in Window as a NamedInterface may also be helpful.

Note that the Manager class itself cannot be instantiated -- there is no publicly accessible implementation. In reality, the manager is partly integrated with the the core application code, but you will only ever see the directory service through the interface described by NamedInterface::Manager. You will usually be provided with a pointer to the interface manager when your module loads. After this point, you will not be able to access the manager -- so you'll want to store the pointer if you want to access the directory at runtime.

Ptr

The Ptr class is a template whose template parameter T is the type of object being pointed to. Thus, a Ptr<T> object will behave like a T*. For the most part, these pointers can be manipulated just like raw pointers. The * and -> operators work as usual. The raw pointer wrapped by the smart pointer class can be accessed via the ptr() attribute.

The class also wraps the C++ casting operators. The following code demonstrates:

Ptr<T> tPtr = new T();
Ptr<Y> yPtr = Ptr<Y>(dynamic_cast<Y*>(tPtr.ptr())); // long way
Ptr<Y> yPtr2 = tPtr.dynamicCast<Y>(); // short way
The class includes staticCast() as well like dynamicCast().

The purpose of the smart pointer class is to wrap a raw pointer and keep track of how many references exist to that memory. For instance, if I create an Aircraft and there are three smart-pointers pointing the Aircraft, then its reference count is 3. If the reference count ever drops from 1 to 0 (i.e., the last smart pointer pointing to it was destroyed) then the object, by default, is freed automatically. An example of how this works:

Ptr<MyObject>  smartPtr(new MyObject());
Ptr<MyObject>  smartPtr2 = smartPtr;
smartPtr->foo();
smartPtr = 0; // destroyed a reference to the object
smartPtr2 = 0; // destroyed another reference to the object;  no references left.
// generally, the object would be freed by this point, automatically

Our smart pointer class (Ptr) is what's called an "intrusive" pointer. Unlike the Boost pointer class which stores the reference count in a hidden structure, this class embeds the reference counting code in the referenced object itself. This interface is:

class MyObject {
    void newReference(void) { /* new pointer to this was created */ }
    void deleteReference(void) { /* a pointer to this was destroyed */ }
};

Note that this interface says nothing about the reference count itself, or what happens when references are created or deleted. The example given previously simply calls newReference() (twice - once when smartPtr is created and again when smartPtr2 is created), and deleteReference() (twice - once when smartPtr is set to 0, and again when smartPtr2 is set to 0). The typical use of these methods is to increment and decrement a reference count. When that count goes from 1 to 0, we delete the object. But this doesn't mean this must be the case -- we can implement this interface however we'd like to perform special processing when all of the references are destroyed, etc.

Most people will never write this code. To support the standard case, we provide two off-the-shelf implementations of this interface called PtrInterface and LockedPtrInterface. They are identical except that LockedPtrInterface adds locking to protect the reference counting process in a threaded program (and hence, this is the one that most objects will use). PtrInterface simply implements the scheme described above: when a new reference is created it increments its reference count; when a reference is deleted it decrements its count. When the count goes to 0, the object is deleted. The actual code to use this interface is:

class MyObject : public PtrInterface<MyObject> {
};

This is sufficient to embed the standard reference counting code into MyObject. If MyObject derives from a base class that derives from PtrInterface, then that is sufficient (you don't want multiple PtrInterface base classes). The template parameter of PtrInterface is to guarantee that MyObject gets a PtrInterface base class (which is just mechanical goop and not conceptually important in our class hierarchy) that is a different type than the PtrInterface underlying other classes.

PtrInterface (and LockedPtrInterface) provides two additional methods:

references() returns the current reference count. For LockedPtrInterface this is unreliable, since the reference count may have changed from the time the method was called and the time you take any action based on the value it returns.

onZeroReferences() is a virtual function that can be overridden by the base class. This is called when the reference count reaches 0. Its default implementation does "delete this;" which deletes the object. You could override it to do other things instead or in addition. For instance, if the object is always going to be statically allocated (i.e., not allocated via "new") then you could override this with an empty function so that the object is not deleted when all references are destroyed (since this would be bad for a statically allocated object).

On this same note, you should NOT use smart-pointers on ANY statically allocated object unless something has been done to make this safe (as described above). The following is bad:

MyObject object;
Ptr<MyObject> ptr(&object);
// When ptr is destroyed, delete &object is executed.
// But object is statically allocated!

For the same reason, you should not construct a smart pointer from a raw pointer that was passed to your code by a client unless you know for certain that the raw pointer is already being used with smart-pointers elsewhere. It may be the case that the raw pointer is a pointer to static storage, or is not referenced by other smart-pointers. In either case, creating a smart pointer will yield incorrect behavior as in the following examples:

void foo(MyObject* myObj) {
   Ptr myObjPtr(myObj); // BAD:  don't know where myObj came from.
   // It might be created in any of the ways listed below, and we shouldn't assume
   // the caller always does it the same way.
}

...
MyObject* obj1 = new MyObject();
foo(obj1); // obj1 will be deleted before returning!
// That is probably not what we intended.

MyObject obj2;
foo(obj2); // obj2 will be deleted before returning!
// This is wrong because obj2 is statically allocated.

Ptr obj3(new MyObject());
foo(obj3); // This is okay.  We hold a reference to obj3, so it won't be deleted.
// foo() only works in this case -- which is an unsafe assumption most of the time.

A handy use of smart-pointers is to assure proper memory cleanup after returning from a function, or when exceptions may be thrown.

void foo(void) {
     MyObject* myObjectPtr = new MyObject();
     Ptr<MyObject> myObjectPtrSmart(new MyObject());
     // do something
}

When the function returns, myObjectPtr is orphaned and its memory is leaked. myObjectPtrSmart is destructed, the reference count of its object goes to 0, and the memory is freed correctly. Also, if the code in "do something" throws an unhandled exception, the stack cleanup will ensure that the memory pointed to by myObjectPtrSmart is freed correctly. Again, myObjectPtr will not get freed and its memory will be leaked.

LockHolder

There are several lock holder classes. For Lock objects, there is the standard LockHolder. For ReadWriteLocks there is ReadLockHolder and WriteLockHolder.

LockHolder is a simple lock holder that acquires a lock in its constructor and unlocks the lock in its destructor. This is done as follows:

Lock gLock;
void foo(void) {
LockHolder lockHolder(&gLock); // calls gLock.lock()
// do something
} // end of scope: LockHolder::~LockHolder() calls gLock.unlock()

Note that the above code is exception safe. If an exception is thrown, the LockHolder will be destructed and the lock will be released.

ReadLockHolder and WriteLockHolder work similarly. ReadLockHolder acquires the "read" privilege of a ReadWriteLock; WriteLockHolder acquires the "write" privilege. Both of them release the lock on their destruction.

Sometimes we need to pass responsibility for a lock from one lock holder to another. If we've already constructed a LockHolder that holds a particular lock, no other LockHolder can acquire the lock until the existing LockHolder is destructed (and, hence, the lock is released). But if we want to transfer control of the lock from one holder to the other without releasing the lock in between, we can do so by constructing the new lock holder as follows:

Lock gLock;
void foo(void) {
LockHolder lockHolder1(&gLock); // calls gLock.lock();
LockHolder lockHolder2(&lockHolder1); // takes control of gLock from lockHolder1

// lockHolder1 is "inert".  Its destructor does nothing.
// lockHolder2 holds gLock.  Its destructor calls gLock.unlock();
}

ValueType

For wrapping primitive types, we provide the ValueType template. To prevent accidental conversions between typedef'd types (which are just aliases and will not cause errors if you accidentally try to convert between types), we provide a wrapper that includes basic comparison operators, etc. The first template parameter of the ValueType is a class name which serves to isolate that type from others. The second parameter is the primitive type that the ValueType should behave like.

Any example of how this is used:

class MyClass {
   class BitCountClass; // declare, but never defined.
public:
   typedef ValueType<BitCountClass, uint32_t> BitCount;
};

In this example, MyClass::BitCount is an alias for ValueType<BitCountClass, uint32_t>. Because this is a type in itself (defined by the two template arguments), it cannot be converted to any other type based on different parameters. So, if we had another value type based on ValueType<BitIndexClass, uint32_t>, then it could not be converted to our BitCount type because the first template parameters are different classes. This trick provides "type isolation" between the two types, meaning that they can't accidentally be coerced into other types.

Any primitive type (more specifically, any type T for which "T t(0);" makes sense) can be used as the second template parameter. The default constructor of a ValueType<C, T> will construct a new T whose value is 0 (by calling T::T(0)).

System overview

This section provides an overview of the core system and how the components described above form the simulator.

Core

The Core object is constructed by the application's main() code. It is responsible for running the main loop. The main loop's only job is to advance the system clocks to the current simulation time. As the clocks step forward, objects update themselves to the current time, the graphics engine displays the previously computed frame, etc.

The Core itself provides one facility to outside objects. The NamedInterface::Manager that keeps track of objects is stored within the Core. Modules will generally only need to interact directly with the core when they're initialized.

Modules

The Core doesn't do much on its own. It loads other modules to perform the work of the simulator.

In Phx/Core/PhxModule.h, a base class is provided from which "Module" objects derive. A module has no interface. It can only be loaded (constructed) and unloaded (destructed). When a module is loaded, the Core calls (through the module loader) a C-style function that returns a smart-pointer to a Module object.

Modules can either be loaded from shared libraries (DLLs) or directly within the simulator (in the case of modules linked directly into the program). In either case, a module is loaded via a C-style function.

To create a module, two things are necessary. The first is to create a subclass of Module. This class will get created when your module loads, and deleted when your module unloads. If your module needs to do any setup (like register types with the named interface manager) or has any data to load, this should all be implemented in your Module class.

In addition, you need to define a function with C-style linkage. This function should have the name module_ModuleName(). Where "ModuleName" is the name of your module. If your module is loaded from a shared library, then ModuleName is the name of the shared library. e.g., if your library is PhxInput.so.0.0.0, or PhxInput.dll, then ModuleName is just PhxInput. For internal modules (those not loaded from a library), use something similar (i.e., use the name you'd use for the library if you ever created it). The prototype for this function is:

extern "C" Phx::Module* module_ModuleName(const Phx::Ptr<Phx::Description>&, Phx::Core*);
Note that if you declare your function inside the Phx namespace (or use "using namespace Phx;" in your source file), you can drop the Phx:: prefixes.

Your function should only appear in your source file. Because of the extern linkage, it can be linked against by outside code. For modules that are built in to the program, these prototypes will just be added to a file elsewhere to allow for direct calls. In the case of a shared library, these function will be located by the dynamic loader and called at runtime -- no prototype is needed in that case. In short: you do not need (or want) a publicly accessible prototype.

The function should be implemented to return a new, dynamically allocated, instance of your module. The dynamic allocate is a critical detail: you must use the 'new' operator to construct your module and return it -- DO NOT statically allocate your object. On return, the raw pointer will be converted to a smart pointer, which will do 'delete' on the pointer when it is no longer needed. Thus, statically allocating your module will result in a crash. Ideally, we'd simply return a Ptr<Module> here, but some compilers do not allow C-linkage functions to return C++ objects.

The first parameter is a pointer to a Description that can contain configuration information for your module. This will normally by parsed from an XML config file, but it can just as well be sent over the network or loaded from a binary file like other Descriptions. If you don't need any configuration information, you can just ignore this.

The second parameter passed to the function is a pointer to the core. The Core has a few methods that provide access to the simulator's named interface manager, description manager, etc. You can install your interface types and any objects you choose into these managers at startup. If you anticipate needing the managers at runtime, you should store the raw pointer to the core, or to any of the accessible components. The managers held by the Core are the last objects to be destructed, so these pointers will always be valid when your module is loaded.

Note that objects in the simulator that depend on your module (and its library!) will cease to function properly if the module or library is unloaded. Generally, your library is unloaded when your Module object is destructed. It is the client's responsibility to be sure that all objects constructed using your module are destroyed before your Module object is destroyed -- you do not need to worry about it. You may assume that your library and Module object are loaded and will not be destroyed/unloaded until all of the publicly accessible objects in the Core, etc. that use it are removed. The obvious exception, of course, is that the module must, when it is destructed, remove any objects that it created on its own -- the client obviously won't know anything about these.

This clearly puts a large burden on the client. In the case of the Core, most of the modules are loaded at startup and only unloaded when the program exits. A more intelligent system may, in the future, attempt to load inidividual modules only temporarily. For instance, a special campaign may attempt to load just a handful of libraries containing objects needed for the current campaign. In this case, the campaign would need to be certain that it had destroyed all of its objects first, then unloaded the module as the very last operation when it comes time to delete the campaign.

Tasks

Once your module is loaded, you'll probably have some types installed into the NamedInterface::Manager, and maybe a few objects already created. To actually execute anything, something needs to call your code periodically. As mentioned earlier, the only thing the Core does is advance the core clocks each frame. The Clocks are stored in the named interface manager under names defined in the Core class. You can get pointers to these clocks from the named interface manager.
#include <Phx/Core/PhxCore.h>
#include <Phx/Core/PhxClock.h>
...
Ptr<Clock> frameClock =
interfaceMgr->namedInterface(Core::WALL_CLOCK_NAME).dynamicCast<Clock>();
Once you have a pointer to the clock, you can acquire a Clock::Task subinterface for that clock. This interface allows you to install a listener that is called back by the clock when the time on that clock passes a certain point.

With your Task, you can install a listener that calls back into your code to do whatever processing you want. In order to get your listener called, you need to set its nextTime() attribute. This attribute is the next time at which your listener will be called. When the Clock for that Task reaches the time you set, your listener will be called and the nextTime attribute will be set to Clock::NEVER. If you set nextTime() to a time that is earlier than the current time, your listener will be called back as soon as possible and the nextTime attribute will be set back to NEVER. Note that setting nextTime() to 0 will always result in an immediate callback, since the current time on the clock is always positive. Also, a callback is initiated for EACH time that the nextTime() attribute becomes less than the current clock time. Hence, if you set nextTime() to zero once, it will be set back to NEVER and your listener will be called back as soon as possible. If you do this twice, your listener will be scheduled for TWO calls.

Acknowledgements

This document and the methodology presented within are based on material from the CS249 and CS349 programming courses (and the accompanying course notes) at Stanford Univ. taught by David R. Cheriton (Sep 2004 - Mar 2005).
Generated on Wed Dec 21 22:05:38 2005 for Phoenix OSFS by  doxygen 1.4.2