Functions basically serve 3 purposes:
As a function can call another function, we will eventually end up with a whole hierarchy of functions, which is then basically a tree. As such we need to distinguish between leaf and non-leaf functions.
Leaf functions are those that have no parameters. Non-leaf functions do have one or more parameters, and each parameter in turn resolves as a further (nested) function call.
Examples of leaf functions:
Examples of non-leaf functions:
For the sake of convenience, the Universal Query System (UQS) automatically registers leaf functions for every registered item type. As such, each new item type will automatically add support for a global input parameter of exactly that type. The same can be said for functions that return a literal and grant access to the current item in a query.
Custom functions always need to be non-leaf functions (i. e. functions that do take at least one parameter). There's currently no use-case which would require it to be different. Here's an example from the UQS's StdLib for for a custom function that takes 2 parameters:
class CFunction_Vec3Add : public UQS::Client::CFunctionBase<
CFunction_Vec3Add,
Vec3,
UQS::Client::IFunctionFactory::ELeafFunctionKind::None>
{
public:
struct SParams
{
Vec3 v1;
Vec3 v2;
UQS_EXPOSE_PARAMS_BEGIN
UQS_EXPOSE_PARAM("v1", v1, "VECA", "First vector to add.");
UQS_EXPOSE_PARAM("v2", v2, "VECB", "Second vector to add.");
UQS_EXPOSE_PARAMS_END
};
public:
explicit CFunction_Vec3Add(const SCtorContext& ctorContext)
: CFunctionBase(ctorContext)
{}
Vec3 DoExecute(const SExecuteContext& executeContext, const SParams& params) const
{
return params.v1 + params.v2;
}
};
Notice that each custom function class must have a SParams struct embedded. The internal architecture of the UQS expects this to be present.
Notice the DoExecute() method, which will be called through a base class's method by applying the
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
In order for the UQS to be able to instantiate this new function later on at runtime, it needs a factory that knows about this specific function and how to create an instance of it:
UQS::Client::CFunctionFactory<CFunction_Vec3Add>::SCtorParams ctorParams;
ctorParams.szName = "std::Vec3Add";
ctorParams.guid = "5f4dd307-e32a-450c-a002-3c0e1cad490c"_uqs_guid;
ctorParams.szDescription = "Adds two Vec3s and returns the result.";
// Notice: This instance of CFunctionFactory<> must _not_ go out ouf scope for the lifetime of UQS,
// since the UQS core will keep a pointer to this particular factory!
static const UQS::Client::CFunctionFactory<CFunction_Vec3Add> functionFactory_Vec3Add(ctorParams);
To finally expose your new function factory to the UQS core, the you'd just call this static helper method once receiving the UQS::Core::EHubEvent::RegisterYourFactoriesNow
event:
UQS::Client::CFactoryRegistrationHelper::RegisterAllFactoryInstancesInHub();
This is the same process as is already done when exposing item factories to the UQS core.