Avida Events Implementation

Events is one of the most complex sub-systems within avida, but because of this we have automated the process of implementing new events. Normally, when you want to add to a component of avida, you need to make changes in at least three places: the header file to declare new classes or methods, the code file to write the full definition of those methods, and a third location to activate whatever you just wrote. A conscientious programmer will also make a fourth change to document this new feature.

Events used to be the worst case of this. Not only were more than these four procedures required, but they had to be done in a specific format that even I would need to look up.

A few years ago Travis Collier wrote a program (in the language Perl) that would parse a single, specially-formatted file and write out the corresponding C++ code, including documentation. This program is "current/source/event/make_events.pl", in case you are interested in it (fortunately, you don't need to understand it to add events because Perl code can be a bit dense). The file that the program reads in is "current/source/event/cPopulation.events", which is what you will actually be editing.

When you type "make" to compile Avida, it will automatically look to see if any modifications have been made to cPopulation.events, and if so it will run make_events.pl in order to update all of the event-related C++ files.

The cPopulation.events file

This file defines all events currently in avida, most of which affect the main object from class cPopulation, hence the origin of the filename. There were once other event-definition files named for non-population objects that could be affected, but we collapsed it all down to this single file to avoid confusion. Ideally we'll change this file's name soon for further clarity.

The text in this file is broken up into blocks, separated by blank lines. Each block represents the full definition of a single event. Do not skip any lines when you are defining an event, or else the Perl script will think you have begun the definition of a new event. Below is the basic format for a single event. Anything in brown should be changed to be event specific, while portions in black are the same for all events:

   event_name
   :descr:
   /**
   * This is the documentation for this event.  Notice that it can be
   * multiple lines, but each line must begin with a single asterisk.
   **/
   :args:
   variable_type  variable_name  default_value
   var2_type      var2_name      var2_default
   ...            ...            ...
   varN_type      varN_name      varN_default
   :body:
   FirstThingToDo();
   ThenDoThis();
   AndAnyOtherFunctionsToBeRun();

For any event whose only function is to execute a method of the population object there really isn't much to write. All events have access to population, which is a pointer to the primary population (of class cPopulation) in the Avida run. Additionally the events have access to several static classes such as cAvidaDriver_Base, but since this isn't something you've learned about yet, you shouldn't worry about it here.

Let's step through the sections of the event definition. First we have the event_name, which can be any alpha-numeric sequence plus the underscore. This name is the name that is used from the events.cfg configuration file to specify that this is the event to be triggered.

The :descr: section is dropped as-is into the avida source code as the comments for this event. In C++, two methods for including comments are possible. The first, is a pair of slashes ('//') that make the remainder of the line a comment. The second is a slash-star ('/*') to begin a comment, and then a star-slash ('*/') to denote its end. This latter approach allows a command to go for multiple lines. To make it clear to a reader that this is all part of a single comment, good programming practice dictates that we should begin each line with a single star ('*'). In addition to being placed in the source code, this documentation is output when you run avida with the "-events" flag from the command line. In the future, it will also be available from the graphical interface.

Next, we have the :args section of the event description. Here we list the variables that we want the user to set when they include this event in their configuration. For example, if we create an event that forces a single organism to write a term paper, we need to specify the organism that will be subject to this unfortunate task. We might include a line in this section like "int cell_id". This would mean that when the user sets up this event, they must specify which cell they are targeting. We could then include a second argument "int num_pages 5" so that the user can also specify how long the term paper should be. But notice that I included a "5" in this latter example. This means that the user can include the second argument to specify the number of pages, but if they don't, 5 will be the default. Thus the event "write_term_paper 100 3" would make the organism in cell 100 write a three page term paper, while "write_term_paper 42" would make the organism in cell 42 write a five page paper. Since we did not include a default argument for cell_id, that variable must always be specified. Now, all you need to do is figure out how to best write this event, and you'll never have to write a paper again! Unfortunately, knowing them, they would probably make huge margins and pad the paper with a bunch of nop-X commands, hoping we won't notice.

Finally, we come to the :body: section, which contains the commands to be executed when this event is run. Since so much functionality is already implemented in the cPopulation class, quite often all that we need to do here is run a method of the population object. Below is such an example, using the inject command.

Example: The inject event

The section headings are all bold; the remaining code uses a color scheme similar to the one I used previously:

Comments are BROWN
Names of Methods are GREEN
Names of types (including classes) are RED
Variable names are BLUE
  inject
  :descr:
  /**
  * Injects a single organism into the population.
  *
  * Parameters:
  * filename (string)
  *   The filename of the genotype to load. If this is left empty, or the keyword
  *   "START_CREATURE" is given, than the genotype specified in the genesis
  *   file under "START_CREATURE" is used.
  * cell ID (integer) default: 0
  *   The grid-point into which the organism should be placed.
  * merit (double) default: -1
  *   The initial merit of the organism. If set to -1, this is ignored.
  * lineage label (integer) default: 0
  *   An integer that marks all descendants of this organism.
  * neutral metric (double) default: 0
  *   A double value that randomly drifts over time.
  **/
  :args:
  cString fname          "START_CREATURE"
  int     cell_id        0
  double  merit          -1
  int     lineage_label  0
  double  neutral_metric 0
  :body:
  if (fname == "START_CREATURE") fname = cConfig::GetStartCreature();
  cGenome genome =
     cInstUtil::LoadGenome(fname, population->GetEnvironment().GetInstLib());
  population->Inject(genome, cell_id, merit, lineage_label, neutral_metric);

As indicated by its description, the "inject" command will inject a single organism into the population. The user can specify a filename that contains the organism's genome (or START_CREATURE to use this value from the genesis file), the cell in which the organism should be placed, the merit to start the organism with (or -1 to auto-calculate it on loading), a lineage label to tag all of this organism's offspring, and a neutral value that will drift over each generation. Each of these configuration values is supplied with a default so that the user is not required to enter any additional information.

But what actually happens when this event is run?

First, the if-statement checks if the user has entered (or left as default) the value "START_CREATURE" as the filename. If so, it looks up the proper filename (from the cConfig class that holds the current state of all the genesis variables -- we'll talk more about it soon) and sets this variable such that it is now indicating a real file.

The next line builds an object of class cGenome using a function called LoadGenome in the utility class cInstUtil. This function needs the filename to load from, and an instruction set to convert the contents of that file into a genome. The instruction set is stored

event_name :descr: /** * This is the documentation for this event. Notice that it can be * multiple lines, but each line must begin with a single asterisk. **/ :args: variable_type variable_name default_value var2_type var2_name var2_default ... ... ... varN_type varN_name varN_default :body: FirstThingToDo(); ThenDoThis(); AndAnyOtherFunctionsToBeRun();

For any event whose only function is to execute a method of the population object there really isn't much to write. All events have access to population, which is a pointer to the primary population (of class cPopulation) in the Avida run. Additionally the events have access to several static classes such as cAvidaDriver_Base, but since this isn't something you've learned about yet, you shouldn't worry about it here.

Let's step through the sections of the event definition. First we have the event_name, which can be any alpha-numeric sequence plus the underscore. This name is the name that is used from the events.cfg configuration file to specify that this is the event to be triggered.

The :descr: section is dropped as-is into the avida source code as the comments for this event. In C++, two methods for including comments are possible. The first, is a pair of slashes ('//') that make the remainder of the line a comment. The second is a slash-star ('/*') to begin a comment, and then a star-slash ('*/') to denote its end. This latter approach allows a command to go for multiple lines. To make it clear to a reader that this is all part of a single comment, good programming practice dictates that we should begin each line with a single star ('*'). In addition to being placed in the source code, this documentation is output when you run avida with the "-events" flag from the command line. In the future, it will also be available from the graphical interface.

Next, we have the :args section of the event description. Here we list the variables that we want the user to set when they include this event in their configuration. For example, if we create an event that forces a single organism to write a term paper, we need to specify the organism that will be subject to this unfortunate task. We might include a line in this section like "int cell_id". This would mean that when the user sets up this event, they must specify which cell they are targeting. We could then include a second argument "int num_pages 5" so that the user can also specify how long the term paper should be. But notice that I included a "5" in this latter example. This means that the user can include the second argument to specify the number of pages, but if they don't, 5 will be the default. Thus the event "write_term_paper 100 3" would make the organism in cell 100 write a three page term paper, while "write_term_paper 42" would make the organism in cell 42 write a five page paper. Since we did not include a default argument for cell_id, that variable must always be specified. Now, all you need to do is figure out how to best write this event, and you'll never have to write a paper again! Unfortunately, knowing them, they would probably make huge margins and pad the paper with a bunch of nop-X commands, hoping we won't notice.

Finally, we come to the :body: section, which contains the commands to be executed when this event is run. Since so much functionality is already implemented in the cPopulation class, quite often all that we need to do here is run a method of the population object. Below is such an example, using the inject command.

Example: The inject event

The section headings are all bold; the remaining code uses a color scheme similar to the one I used previously:

Comments are BROWN
Names of Methods are GREEN
Names of types (including classes) are RED
Variable names are BLUE
  inject
  :descr:
  /**
  * Injects a single organism into the population.
  *
  * Parameters:
  * filename (string)
  *   The filename of the genotype to load. If this is left empty, or the keyword
  *   "START_CREATURE" is given, than the genotype specified in the genesis
  *   file under "START_CREATURE" is used.
  * cell ID (integer) default: 0
  *   The grid-point into which the organism should be placed.
  * merit (double) default: -1
  *   The initial merit of the organism. If set to -1, this is ignored.
  * lineage label (integer) default: 0
  *   An integer that marks all descendants of this organism.
  * neutral metric (double) default: 0
  *   A double value that randomly drifts over time.
  **/
  :args:
  cString fname          "START_CREATURE"
  int     cell_id        0
  double  merit          -1
  int     lineage_label  0
  double  neutral_metric 0
  :body:
  if (fname == "START_CREATURE") fname = cConfig::GetStartCreature();
  cGenome genome =
     cInstUtil::LoadGenome