Filter
A filter is a "top-level" entity in SmartCGMS configuration. It may manage other entity types, depending on its purpose. A single filter is configured as a single link in SmartCGMS configuration.
Descriptor
Every filter is described by its descriptor (scgms::TFilter_Descriptor
):
struct TFilter_Descriptor {
const GUID id;
const NFilter_Flags flags;
const wchar_t *description;
const size_t parameters_count;
const NParameter_Type* parameter_type;
const wchar_t** ui_parameter_name;
const wchar_t** config_parameter_name;
const wchar_t** ui_parameter_tooltip;
};
id
is an unique GUID assigned to this filter. The filter is created using this identifier.flags
represents a filter flags; this field is currently unused.description
is a string representing a name of the filter; it must not benullptr
and must contain a valid zero-terminated stringparameters_count
represents a count of parameters (and a size of following 4 arrays)parameter_type
is an array of sizeparameters_count
, containing parameter types fromscgms::NParameter_Type
enumerator (see below)ui_parameter_name
is an array of sizeparameters_count
, containing the human-readable parameter names for each parameterconfig_parameter_name
is an array of sizeparameters_count
, containing keys used for each parameter in configuration file- such a key should consist solely of alphanumeric characters, dash and underscore
- using this key, the filter is able to read the configuration parameter from the container passed to
Configure
method (see below)
ui_parameter_tooltip
is an array of sizeparameters_count
, containing extended (human-readable) descriptions of parameters
A scgms::NParameter_Type
enumerator contains a set of pre-defined constants, which are used to represent either a data-type of given parameter,
and also a parameter semantic. Currently, the enumerator holds the following values:
ptNull
- "empty" parameter, not interpreted, UI may choose to ignore it; currently the gpredict3-desktop frontend uses this value to create a visual separatorptWChar_Array
- string parameter (internally represented as a vector of characters to allow interoperability)ptInt64_Array
- an array of integersptDouble
- a double-precision floating point valueptRatTime
- same asptDouble
, but interpreted as a time; the UI may display a date/time picker controlptInt64
- a single 64-bit (long) integerptBool
- a boolean (true/false) value; internally represented as a single byteptSignal_Model_Id
- a GUID of a signal model; the UI may display a combobox with known signal modelsptDiscrete_Model_Id
- a GUID of discrete model; the UI may display a combobox with known discrete modelsptMetric_Id
- a GUID of a metric; the UI may display a combobox with known metricsptSolver_Id
- a GUID of a solver; the UI may display a combobox with known solversptModel_Produced_Signal_Id
- a GUID of a signal produced by a model (discrete or signal); the UI may display a combobox with known signals - if the configuration contains a model selector, the combobox may contain only selected model-produced signalsptSignal_Id
- a GUID of a signal; the UI may display a combobox with known signals (not necessarily produced by any known model)ptDouble_Array
- an array of double-precision floating point valuesptSubject_Id
- same asptInt64
, but interpreted as a subject ID; this ID may come from a database
A shared object exporting a filter must:
- export a
do_get_filter_descriptors
function- this function returns a continuous array of filter descriptors identified by its first and one-after-last element
- create this filter, when requested by
do_create_filter
call
An example of do_get_filter_descriptors
may be as follows:
const std::array filter_descriptors = { { descriptor_1, descriptor_2 } };
HRESULT IfaceCalling do_get_filter_descriptors(scgms::TFilter_Descriptor **begin, scgms::TFilter_Descriptor **end) {
*begin = filter_descriptors.data();
*end = filter_descriptors.data() + filter_descriptors.size();
return S_OK;
}
An example of do_create_filter
may be as follows:
HRESULT IfaceCalling do_create_filter(const GUID *id, scgms::IFilter *output, scgms::IFilter **filter) {
if (*id == descriptor_1.id)
return Manufacture_Object<CMy_Filter>(filter, output);
return E_NOTIMPL;
}
In this example, Manufacture_Object
is a SDK function which creates an instance of IReferenced
object and initializes its reference counter. The function is called with
GUID of desired filter, output filter pointer and target memory, in which the pointer to newly created entity should be stored. Note the output
filter pointer gets passed here - the filter
itself is responsible for passing the device events to next filters in chain.
Typically, the filter chain is configured through the SDK class CFilter_Executor
(and its RAII reference counted wrapper, SFilter_Executor
). Thus, the filter factory function
gets automatically called with the succeeding filter. The end of the chain should contain a terminal filter, which should properly deallocate device events passed through the filter chain. The SDK
executor includes the default terminal filter, which drops the reference count of device event in Execute
method call, returning S_OK
, indicating, that the event passed through
the whole chain without an error.
Interface
Every filter entity must implement the scgms::IFilter
interface. The interface is defined as an abstract C++ class as follows:
class IFilter : public virtual refcnt::IReferenced {
public:
virtual HRESULT IfaceCalling Configure(IFilter_Configuration* configuration, refcnt::wstr_list *error_description) = 0;
virtual HRESULT IfaceCalling Execute(scgms::IDevice_Event *event) = 0;
};
The Configure
method is called prior the full operation mode. This method passes the configuration parameters to the filter and configures it to operational state.
The return value may indicate a success (S_OK
), success with warning (S_FALSE
) or a fatal error (any other non-success code). Yielding a faulty result code
leads to configuration failure and fails the whole process of configuring the filter chain.
Once in operational state, the outer code (often wrapped in a filter executor or similar code) may choose to call the Execute
method. This invokes the actual filter's control loop.
The device event passed to this call is passed with a move semantic - the device event is now owned by this filter. The Execute
method must either pass the event to the next filter, or
call Release
to properly deallocate the device event memory.
One may choose to implement this interface on his own. However, SmartCGMS SDK provides the developer with base class CBase_Filter
, implementing the repeating parts of code and wrapping
all pointers into its reference-counted RAII wrappers. This class implements the interface and defines protected pure virtual methods Do_Configure
and Do_Execute
. An example of
a filter implemented using the wrapper is included at the bottom of this page.
Operation
The filter has basically three states, in which it may operate:
- Created - the filter just got instantiated and waits for the
Configure
call. - Operational - the filter is configured and is able to process device events in
Execute
method. TheExecute
method is called exclusively in this state. - Terminated - the filter received the
Shut_Down
device event code, deallocated all its resources and waits for its deallocation by outer code. Any furtherConfigure
andExecute
call on this filter instance is invalid.

The filter may be partially reset to its initial state by sending Warm_Reset
device code through the chain. By using this code, the filter may "remember" useful information from its previous runs,
but is should reset to its initial state. An example may include parameter optimalization - such a filter may remember newly obtained parameters, but should discard all data that the parameters are based on.
Events passing
Device events are processed synchronously in the filter chain. This means, that when the code calls Execute
on any filter in the chain, the call does not return until the last device event reaches
the last filter. In other words, the call to the Execute
method is recursive. When using the standard way of working with filter chains (via scgms
library), the execution is guarded
by a recursive mutex. Thus, only a single device event may pass through the filter chain at one time.
If the filter creates a new thread, the Execute
calls (or Send
calls in case of using the SDK) are all synchronized. The developer should bear in mind, that this synchronization
may occur in order to avoid deadlocks.
Example implementation
An example filter implemented using SmartCGMS SDK:
// the following SDK header files are required
#include <iface/DeviceIface.h>
#include <iface/FilterIface.h>
#include <rtl/FilterLib.h>
class CExample_Filter : public scgms::CBase_Filter {
private:
double mMy_Var = std::numeric_limits::quiet_NaN();
protected:
virtual HRESULT Do_Configure(scgms::SFilter_Configuration configuration, refcnt::Swstr_list& error_description) override final {
// read Example_Config_Var (defined in descriptor, potentially contained in config.ini file)
// the rsExample_Config_Var constant is defined in an example descriptor block below
// if it is not found in configuration, use default value of 3.0
mMy_Var = configuration.Read_Double(rsExample_Config_Var, 3.0);
// maybe do some validation; do not forget to indicate an error, so the configuration process fails
if (mMy_Var < 0.0) {
error_description.push(L"Invalid Example_Config_Var value! Use positive values");
return E_INVALIDARG;
}
// everything is configured correctly
return S_OK;
}
virtual HRESULT Do_Execute(scgms::UDevice_Event event) override final {
if (event.device_code() == scgms::NDevice_Event_Code::Shut_Down) {
// close all files...
// terminate all threads...
// deallocate all memory...
}
// ...
// pass the event further
// this is not mandatory - if you do not wish to propagate events to next filters, you may just return S_OK, the UDevice_Event wrapper deallocates the device event automatically
// and the call will unroll properly; most situations, however, requires you to pass the events to the next filter in chain:
return mOutput.Send(event);
}
public:
CExample_Filter(scgms::IFilter *output);
virtual ~CExample_Filter();
};
The implementation above demonstrates the base code for a filter. We will use this filter as an example in the following code.
Every filter should have its own GUID and filter descriptor. The general recommendation in SmartCGMS code is to use namespaces to contain a specific entity info and descriptions. An example of header definition follows:
namespace example_filter {
constexpr GUID filter_id = { 0x904410ca, 0xb0aa, 0x4fb1, { 0x8f, 0x76, 0x74, 0x68, 0x80, 0x13, 0x82, 0xab } }; // { 904410CA-B0AA-4FB1-8F76-7468801382AB }
extern const wchar_t* rsExample_Config_Var;
}
This code may be shared between the filter code and descriptor code, and thus may be placed in a reachable header file.
The descriptor block example follows:
// we will need all of the following includes
#include <iface/DeviceIface.h> // TFilter_Descriptor
#include <iface/FilterIface.h> // filter flags
#include <rtl/FilterLib.h> // filter parameters
#include <rtl/manufactory.h> // Manufacture_Object
#include <utils/descriptor_utils.h> // do_get_descriptors
namespace example_filter {
constexpr size_t param_count = 1;
const scgms::NParameter_Type param_type[param_count] = {
scgms::NParameter_Type::ptDouble
};
const wchar_t* ui_param_name[param_count] = {
L"Some multiplier (example)"
};
const wchar_t* rsExample_Config_Var = L"Example_Config_Var";
const wchar_t* config_param_name[param_count] = {
rsExample_Config_Var
};
const wchar_t* ui_param_tooltips[param_count] = {
L"This is just an example of some parameter going to the filter in configure method"
};
const wchar_t* filter_name = L"My example filter";
const scgms::TFilter_Descriptor descriptor = {
filter_id,
scgms::NFilter_Flags::None,
filter_name,
param_count,
param_type,
ui_param_name,
config_param_name,
ui_param_tooltips
};
}
Now, we have to export the descriptor and the filter itself in the do_get_filter_descriptors
and do_create_filter
functions. An example of these functions follows:
// we often summarize known descriptors in an array or vector (continuous memory container)
const std::array<scgms::TFilter_Descriptor, 1> filter_descriptors = { { example_filter::descriptor } };
extern "C" HRESULT IfaceCalling do_get_filter_descriptors(scgms::TFilter_Descriptor **begin, scgms::TFilter_Descriptor **end) {
// do_get_descriptors is SDK function
return do_get_descriptors(filter_descriptors, begin, end);
}
extern "C" HRESULT IfaceCalling do_create_filter(const GUID *id, scgms::IFilter *output, scgms::IFilter **filter) {
// is this our filter? If yes, instantiate it!
if (*id == example_filter::descriptor.id) {
return Manufacture_Object<CExample_Filter>(filter, output);
}
// we do not know how to instantiate such filter
return E_NOTIMPL;
}