Levelization is an important tool for large software projects, though few seem to use it. The concept is that all components in the project should be part of a well defined hierarchy. In C++, a component is defined as a pair of files, one of which is a header (ending in .hh) and the other is a code file (ending in .cc).
So what do I mean about hierarchy? Basically, for any two components A and B, if B requires A in to successfully compile, than A should not also require B. Thus, A is a "lower level" component than B. To illustrate this in avida, we can use the example of the "genome" and "organism" components. The cOrganism class requires the cGenome class to be compiled, so we need make sure that the reverse is not true. For those of you who are familiar with graph theory, a project being levelized simply means that the project's components and their dependencies form a directed, acyclic graph (DAG).
This is not always as easy as it sounds. For example, when I decided to levelize the Avida source code, my biggest challenge was with the pair of classes cOrganism and cPopulation. The population has a collection of organisms inside of it, so obviously it needs to know about them. Unfortunately, the organism also needs to interact with the population, for example to let it know when it is ready to have its child be born. To solve this problem, I created the cPopulationInterface class. This class, is at a lover level than either population or organism, so they both have access to it. The object of class cPopulation (or of class cTestCPU, for when we need to test an organism) will give the population interface pointers to the functions that should be executed under specific conditions, and then the organism will use those function pointers as needed. Don't worry too much about this now; this example is merely intended to illustrate that levelization is not always easy, but its also not impossible.
The term "levelization" refers to the levels in the hierarchy. All of those components that don't have any dependencies are on level 0. Those that only depend on components in level zero are themselves at level one, and so on.
Level | Component | Dependencies |
0 | main/config | [none] |
---|---|---|
0 | cpu/cpu_stats | [none] |
0 | cpu/cpu_stack | [none] |
0 | main/inst | [none] |
0 | cpu/label | [none] |
0 | main/mutations | [none] |
0 | main/pop_interface | [none] |
0 | main/reaction | [none] |
0 | main/reaction_result | [none] |
0 | main/resource | [none] |
0 | main/resource_count | [none] |
0 | main/tasks | [none] |
1 | main/genome | inst |
1 | main/inst_lib | inst |
1 | main/stats | config |
2 | main/environment | reaction, resource, tasks, inst_lib, mutations, reaction_result |
2 | cpu/cpu_memory | genome |
2 | main/genome_util | genome |
2 | cpu/hardware_base | inst, inst_lib |
2 | main/inst_util | inst_lib, genome |
3 | main/phenotype | environment, config |
3 | cpu/head | inst, inst_lib, hardware_base, cpu_memory, label |
4 | main/organism | mutations, pop_interface, cpu_stats, phenotype, config, inst_lib, inst_util, genome, genome_util, hardware_base |
5 | main/population_cell | mutations, organism |
5 | cpu/test_cpu | config, hardware_base, inst_lib, inst_util, organism, phenotype, pop_interface, resource_count, tasks |
6 | main/fitness_matrix | stats, organism, test_cpu, config, inst_lib |
6 | main/genotype | genome, config, genome_util, organism, phenotype, stats, test_cpu |
6 | cpu/hardware_cpu | cpu_memory, cpu_stack, label, head, config, inst_lib, genome_util, organism, phenotype, test_cpu |
7 | cpu/hardware_factory | hardware_base, hardware_cpu |
7 | cpu/hardware_util | inst_lib, inst_util, hardware_base, hardware_cpu |
7 | main/species | genome, stats, genotype, genome_util, test_cpu |
7 | cpu/test_util | genome, genotype, inst_util, organism, phenotype, stats, hardware_base, test_cpu |
8 | main/genebank | species, genotype, config, stats, test_util |
8 | main/landscape | genome, test_cpu, cpu_memory, stats, inst_lib, organism, phenotype, test_util |
9 | main/analyze | genome, config, species, fitness_matrix, inst_lib, inst_util, landscape, phenotype, genome_util, test_cpu, hardware_util, test_util, environment |
10 | main/population | resource_count, inst_lib, pop_interface, mutations, config, genebank, genome_util, genotype, hardware_base, hardware_factory, hardware_util, inst_util, organism, phenotype, population_cell, species, stats, tasks |
11 | main/analyze_util | config, genebank, genome, genome_util, genotype, inst_lib, inst_util, landscape, organism, phenotype, population, population_cell, species, stats, test_cpu, test_util |
11 | main/callback_util | avida, genotype, organism, population, population_cell, pop_interface, hardware_base, hardware_factory, test_cpu |
12 | main/avida | stats, environment genotype, genebank, analyze, config, species, genome_util, test_cpu, callback_util, population, {events}, {viewers} |