Description
Runtime attribution is a way of deferring specification of constant values from compile-time to run-time. For example we may want to design a system that can cater for any number of cars, but may not know how many until runtime.
Compile Time Constants
In the case where values are known at compile time we can simply specify an algebraic constant (Eg. MAX_NUM_CARS) (see Macro Definitions). These definitions can then be defined in an Included Header File or the project header file ProjectHeader.hpp.
In the above example we could use the following macro;
#define MAX_NUM_CARS 3
Runtime Constants
With very few exceptions however, CDL attributes can be assigned to variables that are populated at runtime prior to circuit creation/initialization. In particular object/connection dimensions are often initialized from values held in configuration files.
In this case we can still use an algebraic constant, but could define a global variable that is initialized at run-time. The variable can then be initialized before the main CLIP Process is started. To this end, the main CLIP Process function Startup(), breaks the start sequence into a number of steps (see Processes), the first of which is a Pre-Start step, which allows some processing to be scheduled before circuitry is created and initialized.
Thus to initialize the run-time constants, simply add code to the <ProcName>Process::OnPrestart() function.
Eg.
Header File - MyRunTimeConstants.hpp
#define INI_FILE "./app.ini"
extern Uns gMAX_NUM_CARS;
Source File - MyProc1Process.cpp
#include <MyRunTimeConstants.hpp>
static Uns gMAX_NUM_CARS = 0;
bool MyProc1Process::OnPrestart()
{
FILE* fp = fopen( INI_FILE, "r" );
fscanf( fp, "%d", &gMAX_NUM_CARS );
fclose( fp );
return TRUE;
}
It is generally considered "good practice" to use a naming convention that distinguishes compile-time constants from run-time constants. For example compile-time constants should be in UPPER_CASE_WORDS_SEPARATED_BY_UNDERSCORES, whilst run-time constants should be defined with a prefix letter (ie g for global variable) gRUN_TIME_CONSTANT.
Care should be taken when using run-time constants as neither the Translator or Code editors know when the value is to be initialized, and will thus allow you to use the constants in contexts where they are invalid (for example when declaring a fixed size array). In this latter case, the problem would only become apparent when the generated application is compiled, and would produce errors such as;
gui.cpp(40) : error C2057: expected constant expression
gui.cpp(40) : error C2466: cannot allocate an array of constant size 0
gui.cpp(40) : error C2133: unknown size
Run-time constants can be used in State, Workspace and Records but we need to use the variable sized versions (see State and Workspace Objects and Record Objects). Alternatively, a constant maximum can be defined and this specified in the structure declarations. The run-time constant can then be used to limit the range at run-time.
Eg.
#define INI_FILE
"./app.ini"
#define MAX_NUM_CARS 10
#define MIN_NUM_CARS 3
#define DEF_NUM_CARS MAX_NUM_CARS
extern Uns gNUM_CARS;
struct FuelStr
{
Float level[MAX_NUM_CARS];
};
static Uns gNUM_CARS = DEF_NUM_CARS;
bool MyProc1Process::OnPrestart()
{
FILE* fp = fopen( INI_FILE, "r" );
if ( fp )
{
Uns numCars = DEF_NUM_CARS;
fscanf( fp, "%d", &numCars );
fclose( fp );
if ( numCars > MAX_NUM_CARS )
numCars = MAX_NUM_CARS;
else if ( numCars < MIN_NUM_CARS )
numCars = MIN_NUM_CARS;
gNUM_CARS = numCars;
fclose( fp );
}
return TRUE;
}
void InitFuel( FuelStr& Fuel )
{
for ( Uns car = 0; car < gNUM_CARS; ++car )
Fuel.level[car] = 0.5f;
}
In particular many of the CLIP Process attributes can benefit from run-time allocation.
Eg.
Auto Heap Size
gHEAP_SIZE
Master Heap Size
gHEAP_SIZE
Slave Heap Size
gHEAP_SIZE
Min Num Slaves
gMIN_SLAVES
Registrar Address
gREGISTRAR
Initial Worker Count
gINITIAL_WORKERS
#define
INI_FILE
"./app.ini"
#define MAX_HEAP_SIZE MB(900)
#define MIN_HEAP_SIZE MB(100)
#define DEF_HEAP_SIZE MB(250)
extern Uns gHEAP_SIZE;
extern Uns gMIN_SLAVES;
extern Char gREGISTRAR[80];
void ReadKeyword( FILE* Fp, Char* Key, Uns* Val );
void ReadKeyword( FILE* Fp, Char* Key, Char* Val );
static Uns
gHEAP_SIZE = DEF_HEAP_SIZE;
static Uns gMIN_SLAVES = 0;
static Uns gINITIAL_WORKERS = 0;
static Char gREGISTRAR[80] = "localhost";
bool MyProc1Process::OnPrestart()
{
FILE* fp = fopen( INI_FILE, "r" );
if ( fp )
{
if ( ClpAmRootMaster() )
{
ReadKeyword( fp, "MASTER_MEM",
&gHEAP_SIZE );
ReadKeyword( fp, "MIN_SLAVES",
&gMIN_SLAVES );
}
else if ( ClpAmSubMaster() )
{
ReadKeyword( fp,
"SUBMASTER_MEM", &gHEAP_SIZE );
ReadKeyword( fp, "MIN_SLAVES",
&gMIN_SLAVES );
ReadKeyword( fp, "REGISTRAR",
gREGISTRAR );
}
else if ( ClpAmSlave() )
{
ReadKeyword( fp, "SLAVE_MEM",
&gHEAP_SIZE );
ReadKeyword( fp, "REGISTRAR",
gREGISTRAR );
}
else if ( ClpAmAutomous() )
{
ReadKeyword( fp, "AUTO_MEM",
&gHEAP_SIZE );
}
ReadKeyword( fp, "INITIAL_WORKERS",
&gINITIAL_WORKERS );
if ( gHEAP_SIZE > MAX_HEAP_SIZE )
gHEAP_SIZE = MAX_HEAP_SIZE;
else if ( gHEAP_SIZE < MIN_HEAP_SIZE )
gHEAP_SIZE = MIN_HEAP_SIZE;
fclose( fp );
}
else
gINITIAL_WORKERS = ClpNumProcessors();
return TRUE;
}
$JAO$ - Brought from elsewhere...
I'm not sure whether this belongs in the Blueprint Toolset section, or the Blueprint Development section. There's no reference to the the tool. It might be worth replacing this with something that identifies the menu that allows the pre-start function to be entered, but what we do with it seems to me like a development issue.
Overview
Runtime attributes are a way of deferring specification of constant values from compile-time to run-time. For example we may want to design a system that can cater for any number of cars, but may not know how many until runtime. We can do this by using a runtime constant (also see Run-Time Attribution) instead of a numeric value or defined constant.
Eg. Store traffic[gNUM_CARS].
Run time constants are normally prefixed with a 'g' (global constant). These values need to be declared as extern in the ProjectHeader.hpp and initialized somewhere (usually in the Process::OnPrestart() function).
Eg.
#define MIN_NUM_CARS 1
#define MAX_NUM_CARS 100
#define DEF_NUM_CARS 10
extern int gNUN_CARS;
NB. It is good practice to define MAX, MIN and DEF values, so that the input value can be checked for validity, and an appropriate value used instead.
int gNUM_CARS = DEF_NUM_CARS;
Uns OnPrestart()
{
...
if ( tobe_read_from_file )
{
if ( 0 == fscanf( fp, "%d", &gNUM_CARS )
)
gNUM_CARS = DEF_NUM_CARS;
else if ( gNUM_CARS > MAX_NUM_CARS )
gNUM_CARS = MAX_NUM_CARS;
else if ( gNUM_CARS < MIN_NUM_CARS )
gNUM_CARS = MIN_NUM_CARS;
}
...
}