Example implementation
- Last UpdatedApr 24, 2025
- 7 minute read
The following code shows an example plugin implementation that manipulates a pipeline-scope value in the initialization function and then writes to an extended profile field for each milepost defined in the profile and if_profile arrays.
extern "C" OAS_EXPORT void initializeHydraulicProfile(pipelineslot plslot)
{
/*
* If anything needs to be initialized before the hydraulic profile is
calculated
* it can be done during this function.
*
* In this example there is a custom field on the pipeline record that increments
* each time initialization is called.
*
* The function is passed a slot number which refers to a write locked pipeline record, so
* there is no need to further lock any tables or records. Instead the table and record
* objects are attached to the underlying database items.
*/
RealTimeTable rtTable;
RealTimeRecord rtRecord;
rtTable.Bind("pipeline");
rtRecord.BindTable(rtTable);
if (!rtTable.AttachInsteadOfLock(REQ_SAFE_READ))
{
cpu_debug(CPU_DEBUG_ERROR, "initializeHydraulicProfile: Unable to attach
to pipeline table");
return;
}
if (!rtRecord.BindRecordNumber(plslot))
{
cpu_debug(CPU_DEBUG_ERROR, "initializeHydraulicProfile: Unable to bind to pipeline record %d", (int)plslot);
rtTable.DetachInsteadOfUnLock();
return;
}
if (!rtRecord.AttachInsteadOfLock(REQ_SAFE_WRITE))
{
cpu_debug(CPU_DEBUG_ERROR, "initializeHydraulicProfile: Unable to attach to pipeline record %d", (int)plslot);
rtTable.DetachInsteadOfUnLock();
return;
}
TFlexString name = rtRecord.ReadString("name");
char* customFieldName = "ext_customValue";
double customValue = rtRecord.ReadDouble(customFieldName);
cpu_debug(CPU_DEBUG_ALWAYS, "initializeHydraulicProfile: pipeline %s has %s %f", name.Get(), customFieldName, customValue);
// if the custom value reaches 100, reset
if (customValue == 100)
{
customValue = 0;
}
else if (customValue > 100)
{
// An error is about to be thrown so detach from the record and table first
rtRecord.DetachInsteadOfUnLock();
rtTable.DetachInsteadOfUnLock();
/*
* The code that calls this plug in function will handle any exceptions thrown.
*
* To provoke this exception the ext_customValue field could be set to
* over 100 via an external application e.g. using iSQL.
*
* The exception should be caught and an error logged.
*/
TFlexString errorMessage;
errorMessage << "initializeHydraulicProfile: pipeline: " << name << " - custom value can't be above 100. Current value " << customValue;
throw std::exception(errorMessage.Get());
}
else
{
// increment the value
customValue++;
}
rtRecord.WriteDouble(customFieldName, customValue);
cpu_debug(CPU_DEBUG_ALWAYS, "initializeHydraulicProfile: wrote new value of %s for pipeline %s: %f", customFieldName, name.Get(), customValue);
rtRecord.DetachInsteadOfUnLock();
rtTable.DetachInsteadOfUnLock();
}
extern "C" OAS_EXPORT void extendHydraulicProfile(pipelineslot plslot, pipe_segmentslt segmentslot)
{
/*
* This function receives a slot number for a write locked pipeline
* record and a slot number for a write locked pipeline segment
*
* In a particular cycle this function will be called once for
* each segment in a particular pipeline.
*
* This example works out the array element order of the mileposts
* and then writes a value to each custom profile point in milepost
* order. The value written out starts with the value of 'ext_customValue'
* that was set in the initialize function and then increments the value
* for each milepost in this segment.
*
* The example also demonstrates the two different arrays of profile points
* that exist. There are static profile points in the profile array but
* also dynamically created batch interface profile points in the if_profile
* array. The profile point extensions therefore also have to exist in two
* arrays: profile_extensions and if_profile_extensions. See the developer
* guide for more information on how to extend the schema.
*/
RealTimeTable rtTable;
RealTimeRecord rtRecord;
rtTable.Bind("pipeline");
rtRecord.BindTable(rtTable);
if (!rtTable.AttachInsteadOfLock(REQ_SAFE_READ))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach pipeline table");
return;
}
if (!rtRecord.BindRecordNumber(plslot))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to bind to pipeline record %d", (int)plslot);
rtTable.DetachInsteadOfUnLock();
return;
}
if (!rtRecord.AttachInsteadOfLock(REQ_SAFE_WRITE))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach to pipeline record %d", (int)plslot);
rtTable.DetachInsteadOfUnLock();
return;
}
TFlexString pipeName = rtRecord.ReadString("name");
char* customFieldName = "ext_customValue";
double customValue = rtRecord.ReadDouble(customFieldName);
cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: pipeline %s has %s %f", pipeName.Get(), customFieldName, customValue);
rtRecord.DetachInsteadOfUnLock();
rtTable.DetachInsteadOfUnLock();
if (customValue > 100)
{
/*
* The code that calls this plug-in function will handle any exceptions thrown.
*
* To provoke this exception the ext_customValue field could be set to over 100
* via an external application e.g. using iSQL.
*
* The exception should be caught and an error logged.
*/
TFlexString errorMessage;
errorMessage << "extendHydraulicProfile: pipeline: " << pipeName << " - custom value can't be above 100. Current value " << customValue;
throw std::exception(errorMessage.Get());
}
/*
* Attach to the pipe segment record so we can read and write from it.
*/
RealTimeTable pipeSegmentTable;
RealTimeRecord pipeSegmentRecord;
pipeSegmentTable.Bind("pipe_segment");
pipeSegmentRecord.BindTable(pipeSegmentTable);
if (!pipeSegmentTable.AttachInsteadOfLock(REQ_SAFE_READ))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach to the pipe_segment table");
return;
}
if (!pipeSegmentRecord.BindRecordNumber(segmentslot))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to bind pipe_segment record %d", (int)segmentslot);
pipeSegmentTable.DetachInsteadOfUnLock();
return;
}
if (!pipeSegmentRecord.AttachInsteadOfLock(REQ_SAFE_WRITE))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to attach pipe_segment record %d", (int)segmentslot);
pipeSegmentTable.DetachInsteadOfUnLock();
return;
}
// Get the segment name for logging purposes
TFlexString segmentName = pipeSegmentRecord.ReadString("name");
/*
* Existing profile points will be in a 40 element array called profile and
* in another 40 element array called if_profile and can be accessed using
* field names like the following:
*
* profile[0].temp
* profile[12].pres
* if_profile[2].ppnt.temp
* if_profile[7].ppnt.pres
*
* Extended profile points are expected to be in two different 40 element
* arrays called profile_extensions and if_profile_extensions and can be
* accessed using field names like the following:
*
* profile_extensions[0].ext_fld
* profile_extensions[12].my_custom_field
* if_profile_extensions[1].ext_fld
* if_profile_extensions[3].my_custom_field
*
* It is expected that the values of the custom fields at a given element
* in profile_extensions will be the values for the milepost at the same
* element in profile. Similarly it is expected that the values of custom
* fields at a given element in if_profile_extensions will be the values
* for the milepost at the same element in if_profile
*
* So if milepost 1 is element 0 in the profile array, then the extended
* field relating to milepost 1 should also be at element 0 in
* profile_extensions.
*
* The example will write a value to an extended field which is one greater
* than the previous milepost.
*
* In order to do this it will need to find out the element for each milepost
* and then write the elements in the correct order.
*
* As batch interface profile points can be at mileposts interspersed between
* static profile points and the get_dns_prof_pnt function returns profile points
* of either type in milepost order, the example code has to detect which type of
* profile point is the current one and write to the correct extension array.
*/
const char* profile_prefix = "profile[";
const char* if_profile_prefix = "if_profile[";
const char* array_postfix = "].";
const char* if_array_postfix = "].ppnt.";
const char* extended_profile_prefix = "profile_extensions[";
const char* extended_if_profile_prefix = "if_profile_extensions[";
// store in a map the array element of each milepost so it can be looked up by milepost
std::map<double, size_t> mapOfMilepostToArrayElement;
for (int i = 0; i < 40; i++)
{
TFlexString milepostFieldName = profile_prefix;
milepostFieldName << i << array_postfix << "milepost";
double milepost = -1;
if (!pipeSegmentRecord.ReadDouble(milepostFieldName, milepost))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to read field %s on pipe_segment %s in pipe %s", milepostFieldName.Get(),segmentName.Get(), pipeName.Get());
}
if (milepost < 0)
{
// no profile point defined at this element
continue;
}
cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s mapping milepost %f to index %d", segmentName.Get(), milepost, i);
mapOfMilepostToArrayElement.insert(std::make_pair(milepost, i));
}
// work out the mapping between active mileposts in the if_profile array and array elements
std::map<double, size_t> mapOfBatchInterfaceMilepostToArrayElement;
for (int i = 0; i < 40; i++)
{
// Read the 'used' field to see whether the if_profile array element is active
TFlexString usedFieldName = if_profile_prefix;
usedFieldName << i << if_array_postfix << "used";
oas_boolean_t used = OAS_FALSE;
if (!pipeSegmentRecord.ReadBool(usedFieldName, used))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to read field %s on pipe_segment %s in pipe %s", usedFieldName.Get(), segmentName.Get(), pipeName.Get());
}
if (!used)
{
// this entry in the array is no longer relevant and it can be ignored
continue;
}
TFlexString ifMilepostFieldName = if_profile_prefix;
ifMilepostFieldName << i << if_array_postfix << "milepost";
double if_milepost = -1;
if (!pipeSegmentRecord.ReadDouble(ifMilepostFieldName, if_milepost))
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Unable to read field %s on pipe_segment %s in pipe %s", ifMilepostFieldName.Get(), segmentName.Get(), pipeName.Get());
}
cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s mapping batch interface milepost %f to index %d", segmentName.Get(), if_milepost, i);
mapOfBatchInterfaceMilepostToArrayElement.insert(std::make_pair(if_milepost, i));
}
/*
* This is the value that will be written out for each milepost.
* Initialize to the custom value that was read from the pipeline record.
*/
double ext_fld_value = customValue;
/*
* Iterate through the profile points using the iterator that accounts for
* flow direction i.e. get_dns_prof_pnt
*
* To do this, a different form of the segment record is required which
* can be obtained using bt_get_rec
*/
pipe_segment* pPipeSegment = static_cast<pipe_segment*>(bt_get_rec(DATABASE_PIPE_SEGMENT, segmentslot));
profile_point* pProfPnt = get_upmost_prof_pnt(pPipeSegment);
while (pProfPnt != NULL)
{
// use the map from above to get the array index to write to the extended field
size_t index = MAXULONGLONG; // Initialize to max value to indicate not found
TFlexString extFieldName;
auto iter = mapOfMilepostToArrayElement.find(pProfPnt->milepost);
if (iter == mapOfMilepostToArrayElement.end())
{
auto if_iter = mapOfBatchInterfaceMilepostToArrayElement.find(pProfPnt->milepost);
if (if_iter != mapOfBatchInterfaceMilepostToArrayElement.end())
{
index = if_iter->second;
cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s getting index %d for batch interface milepost %f", segmentName.Get(), index, pProfPnt->milepost);
// now construct the field name to write to
extFieldName = extended_if_profile_prefix;
}
else
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: For segment %s milepost %f is not a profile point or batch interface profile point", segmentName.Get(), pProfPnt->milepost);
}
}
else
{
index = iter->second;
cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: For segment %s getting index %d for milepost %f", segmentName.Get(), index, pProfPnt->milepost);
// now construct the field name to write to
extFieldName = extended_profile_prefix;
}
if (index < 40)
{
extFieldName << index << array_postfix << "ext_fld";
// write the value
if (pipeSegmentRecord.WriteDouble(extFieldName, ext_fld_value))
{
cpu_debug(CPU_DEBUG_ALWAYS, "extendHydraulicProfile: Successfully wrote %f as new value for %s on pipe segment %s", ext_fld_value, extFieldName.Get(), segmentName.Get());
}
else
{
cpu_debug(CPU_DEBUG_ERROR, "extendHydraulicProfile: Failed to write %f as new value for %s on pipe segment %s", ext_fld_value, extFieldName.Get(), segmentName.Get());
}
// increment the value so the next milepost has a different value
ext_fld_value++;
}
pProfPnt = get_dns_prof_pnt(pPipeSegment, pProfPnt->milepost, OAS_TRUE);
}
pipeSegmentRecord.DetachInsteadOfUnLock();
pipeSegmentTable.DetachInsteadOfUnLock();
}