Establishing Timing Dependencies
Timing issues are a major nuisance when you are developing your data files. They are often hard to spot when you first introduce them, which means that you can't always assume that the problem arose with the last few changes that you made. Timing issues typically entail a tedious diagnostic process that takes the fun out of creating new data files. Most importantly, though, timing issues can quickly degenerate into a game of "Whack-A-Mole".
All of the different tasks within your data files are like a big chain of dominoes. They all need to be evaluated in the correct sequence to ensure that you reach the final goal of having an accurate character. However, if you move one domino, then that has ripple effects on the dominoes that were before and after it. Consequently, if you change the timing of one task to resolve a timing issue, you could find yourself simply creating a different timing issue between other tasks that have inter-dependencies with the task you moved. Squish one timing bug and up pops another.
Managing a large number of tasks and keeping all the timing dependencies clearly understood and enforced can quickly become a nightmare for an aspiring data file author. So the Kit provides a convenient mechanism that allows you to instantly detect when a timing issue has been introduced. We call the mechanism "timing dependencies" and it allows you to setup dependencies between different tasks that the compiler will verify for you automatically. This mechanism is the focus of the topics below.
In order to establish a timing dependency on another task, you need to assign that task a name. By default, each task is assigned a name by the Kit, but it's not something you'll be able to establish a dependency upon. So any task that will be referenced within a dependency must be given a name. This is achieved by assigning the "name" attribute within the XML element that defines the task.
Task names can be just about anything you want. The goal is to identify what the task is doing clearly and succinctly, but you're the judge of how best to accomplish that. For example, you could name a task something detailed like "Calculate the Final Value for Attributes" or something brief like "Attr Final". The only criteria to keep in mind when choosing a name is that you'll need to type it anywhere that has a dependency.
Establishing "before" and "after" Dependencies
Named tasks become anchor points within the overall evaluation cycle for your data files. Other tasks will reference these anchors by establishing timing dependencies with them. This is accomplished by identifying the named task and specifying the nature of the dependency.
A dependency can be either a "before" or an "after" relationship. A "before" relationship tells the compiler that this task must always be performed before the named task. An "after" relationship indicates that the task must always be performed after the named task. Since all tasks are evaluated in a sequence, there is never a possibility that two tasks can be performed at the same time. Even if you assign them the identical phase and priority, the HL engine will assign an order to them. Consequently, each timing dependency must specify either a before or after relationship.
You can assign any number of timing dependencies to a given task. So you could define an assortment of dependencies that require a task to occur after certain tasks and an assortment that require that task to occur before others.
NOTE! Tasks do not need to be named in order to specify dependencies. Only tasks that serve as anchor points and are referenced by other tasks require names. Consequently, if you have a named task, you could have five different tasks depend on that task, and none of the dependent tasks require names.
Using the Timing Report
When your data files are compiled, a timing report is generated. You can view this timing report at any time by going to the "Debug" menu and selecting the "View Timing Report" option. For most users, your web browser will be launched and the file will be viewed within it. The timing report is an XML file that contains information about all of the timing dependencies you've defined and the tasks involved. Details on the contents will be found within the Kit Reference section of the documentation.
The most important sections in the timing report are the first three, so we'll discuss them here briefly. The first section identifies the names of any tasks that are unknown. These are names that are referenced by a timing dependency and that have not been defined. For example, if you specify that a task has a "before" dependency on the task named "My Task" and you have not assigned that name to any task, the name will be listed as unknown and no dependency will be established. You should always make sure that this section of the timing report is empty.
The second section identifies any names that have been duplicated and are not valid. It is perfectly legal to use the same name on multiple tasks. However, all tasks given the same name must be scheduled to occur at the exact same phase and priority. If you assign the name "My Task" to two separate tasks and they are scheduled at different times, they will be listed here as duplicates. You should always make sure that this section of the timing report is empty.
The third section identifies actual timing errors that exist between the tasks. If you specify that TaskA must occur before TaskB, and TaskA is assigned a phase and priority that is after TaskB, a timing error is reported and will be listed in this section. Any pair of tasks that does not satisfy the timing dependency assigned between them results in a timing error.
Compiler Checking of Dependencies
Whenever your data files are compiled, all of the timing dependencies are analyzed for you automatically. If the compiler identifies any errors in the timing dependencies you've specified, a corresponding error is reported. Unlike most errors, timing issues are treated merely as warning. This means that you are free to load the data files and use them, even though they will not work as you intended. The advantage of allowing you to load the files is that you can review both the timing report and the run-time information provided via info windows in an effort to resolve the problem.
With the timing dependency checks integrated into the compilation process, a critical safeguard is provided by HL. As you develop your files, you will add new logic and discover that the timing of some tasks needs to be adjusted in order to properly incorporate that new logic. If you change the timing of a task in order resolve a timing issue, you no longer have to worry about that change rippling into a different timing issue and having it go undetected. The compiler will use the timing dependencies to automatically detect whether the change has caused a ripple effect that also needs to be resolved. And the other sections of the timing report make it possible to pretty easily sort out even a chain of dependencies that become broken.
Deciding What Dependencies to Setup
After reading through all this, you might be thinking that you'll be spending a great deal of time setting up timing dependencies between all your tasks. That's not the case at all. In general, only a relatively small percentage of the tasks you define will have critical timing dependencies. Most tasks will be completely immune to dependency issues by simply assigning them to the appropriate phases. Consequently, you'll probably only find yourself needing to name maybe 10-20 tasks, and you'll likely only need to establish a similar number of dependencies upon those tasks.
The critical tasks that should be named as anchor points are those that satisfy three criteria. First, the task should be central to a series of evaluations that must occur in a specific order. Second, the tasks involved should be closely grouped within the evaluation cycle, such as all occurring with the same phase or a small number of sequential phases. Third, some of the tasks involved should have a reasonable chance of needing to be moved in the evaluation cycle as new logic is integrated into the data files.
If particular tasks satisfy all three of these criteria, then timing dependencies are extremely important. However, if only one or two of the criteria apply, then defining timing dependencies may or may not be justified. For example, if you have tasks that must setup values appropriately within fields before other tasks utilize those fields, setting up timing dependencies may be prudent. However, if the setup tasks are always performed within an early phase and the tasks using the values are always performed in a later phase, there is no need to establish timing dependencies.