Item type: Function item - C# calculator - Advanced

Here we will go deeper into how to use C# function items.

Calculation

The standard doc does not show the whole truth about the method the expression is executed in context of. The full definition is:

VQT Compute(VQT[] ex, VQT[][] arr, IItemContext itemContext, Func<IState> state, ICsScriptLog log)
{
	//Your expression will be placed here by the framework
}

itemContext is defined as follows:

public interface IItemContext
{
    /// <summary>
    /// Handle of Hive item
    /// </summary>
    int ItemHandle { get; }

    /// <summary>
    /// Func item expression
    /// </summary>
    string Expression { get; }

    /// <summary>
    /// Func item expression arrays
    /// </summary>
    string[] ExpressionArrays { get; }
}

state is actually a method (Func) that will give you an instance of IState. You can store values between computations on the state instance:

public interface IState
{
    bool Contains(StateKey key);
    byte GetOrAdd(StateKey key, byte defaultValue);
    sbyte GetOrAdd(StateKey key, sbyte defaultValue);
    short GetOrAdd(StateKey key, short defaultValue);
    ushort GetOrAdd(StateKey key, ushort defaultValue);
    int GetOrAdd(StateKey key, int defaultValue);
    uint GetOrAdd(StateKey key, uint defaultValue);
    long GetOrAdd(StateKey key, long defaultValue);
    ulong GetOrAdd(StateKey key, ulong defaultValue);
    float GetOrAdd(StateKey key, float defaultValue);
    double GetOrAdd(StateKey key, double defaultValue);
    decimal GetOrAdd(StateKey key, decimal defaultValue);
    string GetOrAdd(StateKey key, string defaultValue);
    IList<VQT> GetOrAdd(StateKey key, IList<VQT> defaultValue);
    VQT GetOrAdd(StateKey key, VQT defaultValue);

    void AddOrUpdate(StateKey key, byte defaultValue);
    void AddOrUpdate(StateKey key, sbyte defaultValue);
    void AddOrUpdate(StateKey key, short defaultValue);
    void AddOrUpdate(StateKey key, ushort defaultValue);
    void AddOrUpdate(StateKey key, int defaultValue);
    void AddOrUpdate(StateKey key, uint defaultValue);
    void AddOrUpdate(StateKey key, long defaultValue);
    void AddOrUpdate(StateKey key, ulong defaultValue);
    void AddOrUpdate(StateKey key, string defaultValue);
    void AddOrUpdate(StateKey key, float defaultValue);
    void AddOrUpdate(StateKey key, double defaultValue);
    void AddOrUpdate(StateKey key, decimal defaultValue);
    void AddOrUpdate(StateKey key, IList<VQT> defaultValue);
    void AddOrUpdate(StateKey key, VQT defaultValue);
    void Remove(StateKey key);
    void Clear();
}

The StateKey is a unique key for entries in the state:

public struct StateKey
{
    /// <summary>
    /// Key for storing values in an instance of IState
    /// </summary>
    /// <param name="paramId">The id of the parameter to store</param>
    /// <param name="algId">The id of the algorithm (method)</param>
    /// <param name="extItemHandles">The external item handles</param>
    public StateKey(int paramId, int algId, int[] extItemHandles)
    {
        _paramId = paramId;
        _algId = algId;
        _extItemHandles = extItemHandles.OrderBy(e => e).ToArray();
    }
    ...
}

log is a logger provided by Hive. Log entries will end up in the Hive log:

public interface ICsScriptLog
{
    bool IsDebugEnabled { get; }

    bool IsInfoEnabled { get; }

    bool IsWarnEnabled { get; }

    bool IsErrorEnabled { get; }

    bool IsFatalEnabled { get; }

    void Info(object logEntry);

    void Info(object logEntry, Exception e);

    void InfoFormat(string formatString, params object[] args);

    void Warn(object logEntry);

    void Warn(object logEntry, Exception e);

    void WarnFormat(string formatString, params object[] args);

    void Error(object logEntry);

    void Error(object logEntry, Exception e);

    void ErrorFormat(string formatString, params object[] args);

    void Fatal(object logEntry);

    void Fatal(object logEntry, Exception e);

    void FatalFormat(string formatString, params object[] args);

    void Debug(object logEntry);

    void Debug(object logEntry, Exception e);

    void DebugFormat(string formatString, params object[] args);
}

Example expression

Next, we will show how to use the state and log in an expression. In the example we are creating a low pass filter:


//VQT Compute(VQT[] ex, VQT[][] arr, IItemContext itemContext, Func<IState> state, ICsScriptLog log) <- context
//{
    //Expression start
    var timeConstant = 60.0;

    int algId = GetAlgorithmId("Acme_LowPass"); // Generate an id for the method. Use a string that is globally unique and means something in your context.
                                                // Here we are making a lowpass filter for the company 'Acme'

    var myState = state();     // Get the state from the environment.
    var filteredId = 1;        // Id of filter value (previously filtered value)
    var timestampId = 2;       // Id of the timestamp of previous external item

    var filteredValueKey = new StateKey(filteredId, algId, new[] { ex[0].ItemHandle });    // Generate unique key for filtered value
    var timestampKey = new StateKey(timestampId, algId, new[] { ex[0].ItemHandle });       // Generate unique key for previous timestamp

    if (!myState.Contains(filteredValueKey)) // If first time, add values to the state and return
    {
        myState.AddOrUpdate(filteredValueKey, ex[0].DblVal);
        myState.AddOrUpdate(timestampKey, ex[0].Timestamp);
        return ex[0];
    }

    var prevFiltered = myState.GetOrAdd(filteredValueKey, 0.0);   // Get previous filtered value
    var prevTimestamp = myState.GetOrAdd(timestampKey, 0UL);      // Get previous timestamp

    var dT = (ex[0].Timestamp - prevTimestamp) / 1e7; // -> sec

    if (dT <= 0.0)
    {
        log.Error($"dT is '{dT}', not a legal value");
        return ex[0];
    }

    double filtered;
    if (timeConstant > dT) // Prevent unstable filter
    {
        filtered = prevFiltered + dT * (ex[0].DblVal - prevFiltered) / timeConstant; // Compute new filtered value
    }
    else
    {
        log.Warn($"timeConstant > dT, unstable. Consider increasing your time constant");
        filtered = ex[0].DblVal;
    }

    myState.AddOrUpdate(filteredValueKey, filtered); // Update state with new filterd value
    myState.AddOrUpdate(timestampKey, ex[0].Timestamp); // Update state with new timestamp

    return new VQT(filtered, ex[0].Quality, ex[0].Timestamp); // Return filtered value
    //Expression end
//}

Pitfall !

If the state key is not unique, the state will be shared and the results will be a mess. I.e. If one call the same method twice in an expression for the same external item:

var lp1 = LowPassFilter(timeConstant, ex[0], state);
// Do something with lp1
var lp2 = LowPassFilter(timeConstant, ex[0], state); // <- same as lp1
// Do something with lp2

In the case abowe, do not lowpass ex[0] twice.