There are several things to understand about the dynamic objects in Hank. Visual objects are generally derived from the PerceivableObject class and non-visual objects (things that cannot be seen in the simulation) are derived from the HankObject class. This, however, is only the first step and prepares the object to be used by the various components within the simulation framework.
After you rename the template and add your code to Hank, you will need to edit the Makefile.am file in the src/libhankobj directory by simply adding your source file (.C) to the list of files that form the libhankobj.a library.
At this point, your object should compile into the libhankobj.a library.
static int partsize = 3;
static PartStruct partlist[] = {{ "SimpleEntity", SimpleEntity::create },
{ "Vehicle", Vehicle::create },
{ "TrafficLight", TrafficLight::create }};
Do you notice the pattern here? Basically, this is just a simple
array that is used to initialize the ObjectFactory when Hank is
started. The ObjectFactory is used by the SDL and other parts of Hank
to create objects by referring to their string identifier. There will
be more discussion of why this is the case later.
So, for example, say you just created a MechanicalBull object for Hank and you want to use it in Hank. You would then augment the file src/libhankobj/ObjectFactory.C and the partlist structure as follows:
static int partsize = 4; // increment this number
static PartStruct partlist[] = {{ "SimpleEntity", SimpleEntity::create },
{ "Vehicle", Vehicle::create },
{ "TrafficLight", TrafficLight::create },
{ "MechanicalBull", MechanicalBull::create }};
At this point, your object can be used in Hank. It can be created from the SDL (Scenario Description Language), or it can be created by directly calling the ObjectFactory::create(...) functions. For an example of how internal Hank code creates objects (and what the SDL eventually calls), refer to the function edf_create_object in the file src/libhankdb/edf_interface.C.
There are other reasons for the inherent complexity in object creation in Hank. For instance, why do we use the ObjectFactory and why is it that the object constructors cannot just use whatever parameters we may deem necessary for the object. I'll try to explain these reasons here.
The SDL (Scenario Description Language) Interpreter is the other reason why we have the ObjectFactory and why we use the strange Hank::ParameterBase* structures to pass parameters. Since the SDL is an interpreter it must match strings to the correct objects in the simulation. Hence, the existance of the ObjectFactory hash table. The ObjectFactory binds a string to a specific creation function that can be called when it is time to instance the object. Without this mapping, the SDL would have no means to instance objects in the simulation unless we hard-coded the mapping into the interpreter itself. This seems unreasonable since we eventually expect many object to be added to Hank. Ideally, when we create a new object type, we shouldn't have to recompile the libhankobj.a library, but rather, we should be able to dynamically load and link the new object at runtime. The ObjectFactory was designed with this in mind and could eventually be used to support dynamic loading of objects created outside of Hank.
The SDL is also the reason why we use the list of Hank::ParameterBase*. Just as the SDL requires a mapping from an object name to a means to instance it, the SDL also needs to understand how parameters should be passed to the object constructors. Because there is no protocol dictating a specific set of parameters that all objects must take, the SDL has no way to understand how parameters get translated to the objects themselves. For instance, if every Hank object took four doubles as their constructor parameters, the implementation in the SDL would be simple. However, this is not the case. For example, a Vehicle may need it's initial position, an aggressiveness parameter, and an initial velocity whereas a Pedestrian may need more or less information. The problem lies in how the SDL should interpret this information.
I made the decision that the SDL should not be responsible for intrepreting how various parameters get passed to different objects. Instead, it is up to the object itself to interpret the parameters it is passed. This information is conveyed through a property name/value pair which provides a string identifier for the property (i.e.aggressiveness) and a templated value (i.e.4.321). This is implemented in the following classes:
Hank::ParameterBase
template <class T> Hank::Parameter : public Hank::ParameterBase
The class Hank::ParameterBase is the base class and allows us to refer to these parameters in a very abstract and polymorphic manner. The class Hank::Parameter is the templated class that lets you set whatever value you want your property to have, whether the parameter should be a list, your own structure, or a double.
The SDL understands this parameter system and packages your SDL parameters into the correct structures.
create MechanicalBull( aggressiveness=3.23, meanness="high" );
then the SDL will package the correct parameters for you. You will have one parameter with the name/value pair ("aggressiveness", 3.23) and the other parameter will be ("meanness", "high").
If you create your object within the Hank code itself (e.g.from another Hcsm), the you will have to package the parameters yourself and pass the ParameterBase* list to the ObjectFactory creation functions.
#include <list>
#include "Hank/db/edf_interface.H"
std::list<Hank::ParameterBase*> mechbull_params;
// set the first parameter, aggressiveness
mechbull_params.push_back( new Hank::Parameter<double>( "aggressiveness", 3.23 ) );
// set the second parameter, meanness
mechbull_params.push_back( new Hank::Parameter<std::string>( "meanness", "high" ) );
// have the system create the bull
edf_create_object( "MechanicalBull", mechbull_params );
That is the easiest way to currently create an object from within the Hank source itself.
I suggest that you interpret parameters passed to your object in the object's initialize member function. The reason for this is that the parameters may not be stored in the object when the constructor is called. The initialization function wfill be called before anything else happens to the object.
For this example, I'll parse the parameters passed to the MechanicalBull in one of the previous questions.
void MechanicalBull::initialize( void )
{
// ****************************************************
// Examine the property list and find the properties needed for
// initialization of this object. Refer to the FAQ on object
// creation for a detailed explanation of parameters as used in
// object initialization.
// ****************************************************
const Hank::ParameterBase* ptr;
//
// Obtain the "aggressiveness" property
//
double aggr;
ptr = getProperty( "aggressiveness" );
if (ptr) {
// Re-cast the abstract hank parameter into the specific type
// that holds the aggressiveness value which is a double.
const Hank::Parameter<double> *aggr_param = dynamic_cast<const Hank::Parameter<double>* >(ptr);
// Obtain the aggressiveness in double form.
aggr = aggr_param->value();
}
else
aggr = 1.0;
//
// Obtain the "meanness" property
//
std::string mean;
ptr = getProperty( "meanness" );
if (ptr) {
// Re-cast the abstract hank parameter into the specific type
// that holds the meanness value which is a string.
const Hank::Parameter<std::string> *mean_param = dynamic_cast<const Hank::Parameter<std::string>* >(ptr);
// Obtain the meanness in string form.
mean = mean_param->value();
}
else
mean = "normal";
}
For a more complicated example in which the location list parameter is interpreted, see the Vehicle.C source code.