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.