A template is a crude implementation of a reusable code block, similar in purpose to C++ templates but much more limited. Were FreeWPC written in C++, regular templates would have been used here.
Templates end in .ct (for C template) and are always kept in the drivers/ directory. A template file is instantiated via commands in the machine description. The script ctemp converts a template to one or more .c or .h files, which are then compiled normally. Thus, templates can contain RTTs or event handlers just like any other file.
A template contains ordinary C code, plus special template directives. These always begin with two at-signs (@@). The list of valid directives, in the order that they are normally used, is listed below:
@@class
@@parameter
name@@file
@@
Within the C code, you use single-at (@) references to substitute the values of template variables into the text. For example,
@@class xyz int @class_variable
would be translated into the following plain C code:
int xyz_variable
Notice that template variable names cannot contain underscores.
You define your own template variables with the @@parameter directive. Some variables are predefined by the code generator:
@class
@self
@instance
Variable substitutions can occur just about anywhere. For example, you can use a variable as the argument to the @@file directive. In fact, this is generally how you would use it. Consider the following:
@@class widget @@file @class.h @@file @self.h @@file @class.c @@file @self.c
Here, the template named widget
is defined. It generates four
different source files: two based on the name of the class (widget.c
and widget.h), and two based on the name of each instance.
If there were 2 widgets instantiated by the machine, say a left widget and
a right widget, then we would have left_widget.c, left_widget.h,
right_widget.c, and right_widget.h.
The use case for templates is to define reusable device drivers. From game to game, there is commonality on how certain devices should be treated, although the parameters are different.
Typically, a device requires a number of different functions to control it. Most devices need a realtime function if hardware accesses must be frequent. A periodic function can handle less critical actions. APIs are defined for the rest of the logic to access it. All of this can be put into a single template, then instantiated by each machine with values for the machine-specific parameters.
Currently there are device drivers for all of the following:
spsol.ct
)
These are switch-activated coils, like slingshots, jet bumpers, or
kickbacks. When the switch activates, the associated coil is fired.
The driver allows you to enable/disable the automatic action, as well
as defining the length of the coil pulse. The switch is also briefly
ignored afterwards to prevent rapid-fire action.
spinner.ct
)
The driver is useful because it handles the fact that spinner switches
activate very fast. It polls the spinner switch at a fast rate and
keeps a count of the number of activations, which are then processing
by a slower callback at normal task time.
duty.ct
)
This is typically for a diverter that needs to be held on for a long period of time, like TZ's left ramp. Enabling the coil begins by applying a high power pulse, then dropping to a lower power duty cycle (typically 25% but it is configurable.) It is emulating what the Fliptronic flippers do with a power and hold coil, except that there is only one physical coil.
bivar.ct
)
Used by the Twilight Zone clock and the World Cup Soccer ball. For when there are two inputs to the motor to control direction.
drop.ct
)
A generic, single drop target driver. Handles software knockdown when
capable, and retry functions.