Eval Modifier (metaclass)

‘’EvalModifier’’ servers can be created using appropriate metaclasses, supporting a rich set of functionality. The most common form, however, is an eval modifier associated with an item type with channels. The item type and channels are defined by the Channels (metaclass) metaclasses and are found automatically. In this sample the eval modifier reads all the channels on the item and writes to an output channel on the same item.

Client Class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class MyEvalModifier : public CLxEvalModifier
{
    public:
        void bind (
                CLxUser_Item    &item,
                unsigned         ident)    LXx_OVERRIDE
        {
                mod_add_chan (item, "outputChannelName", LXfECHAN_WRITE);
        }

        void eval ()                       LXx_OVERRIDE
        {
                MyChannels  my;

                mod_read_attr (&my);
                double res = my.cv_float * my.cv_int;
                mod_cust_write (0, res);
        }
};

static CLxMeta_EvalModifier<MyEvalModifier>  mod_meta ("myItemModifierServerName");

The bind() method is called to define the input and output channels. All the channels in the Channels metaclass marked for read are added automatically, so the only channels that needs to be custom added is the output channel using ‘’mod_add_chan()’’. This is custom channel zero.

The eval() method is called to read inputs and compute outputs. First mod_read_attr() is called to read the channels defined as inputs into the MyChannels struct which was associated with the Channels metaclass. The next line computes the result value. The final line writes the value to custom channel zero.

The eval modifier metaclass is added as a sibling of the channels and package. This allows the modifier to use the channels and associate itself with the item type.

1
2
3
4
5
6
7
 root
  |
  +--- channels
  |
  +--- package (item type)
  |
  +--- eval modifier
1
2
3
        root_meta.add (&chan_meta);
        root_meta.add (&pkg_meta);
        root_meta.add (&mod_meta);

Item Inclusion

By default the eval modifier will create one modifier node for each item of the automatically detected type. If for some reason the automatic detection is wrong, the item type can be set manually:

1
        mod_meta.set_itemtype ("itemTypeName");

Sometimes there will need to be a modifier node for different combinations of item types, or multiple nodes for a single item. This can be accomplished by implementing the include_item() method. The overridden function will be called for all items in the scene, and it can decide to allocate a node for any of them by calling ‘’mod_add_node()’’.

1
2
3
4
5
6
7
8
class MyEvalModifier :
[...]
        void include_item(
                CLxUser_Item    &item)     LXx_OVERRIDE
        {
                if (<item-needs-modifier-node>)
                        mod_add_node ();
        }

If an eval modifier wants to allocate multiple nodes for a single item, they each need to be given an identifier in the form of an unsigned int. That will be passed back during binding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MyEvalModifier :
[...]
        void include_item(
                CLxUser_Item    &item)     LXx_OVERRIDE
        {
                if (<item-needs-multiple-nodes>)
                {
                        mod_add_node (1);
                        mod_add_node (2);
                }
        }

This behavior needs to be enabled on the metaclass by asking for all items.

1
        mod_meta.get_all_items ();

Binding Channels

The method for binding custom channels was shown in the first example. It should be noted that any number of custom channels can be bound from any other items in the scene. The number of bindings can vary based on graph links or any other type of scene variable state. Although the Evaluation interface is available from ‘’mod_eval()’’, channels should not be bound using those methods but only with ‘’mod_add_chan()’’. The reason for this is that a client object may be asked to bind channels just for the purpose of testing against an existing node. The channels are not actually added to the evaluation in that case, but are just used to see if the modifier has changed.

Handling Changes

There are two types of common changes needed for modifier nodes: adding and removing nodes, and updating the bindings of nodes. Both of these are most typically triggered by adding and removing items, and by changes to graph links. These can be tracked and the modifiers invalidated by adding these dependencies to the metaclass. Item types and graphs are added one by one.

1
2
3
4
        mod_meta.add_dependent_type ("depType1Name");
        mod_meta.add_dependent_type ("depType2Name");
        mod_meta.add_dependent_graph ("depGraph1Name");
        mod_meta.add_dependent_graph ("depGraph2Name");

It’s also possible for other types of events to invalidate modifiers. If that’s the case there will need to be a listener to watch for those events and to trigger invalidation manually. The invalidation function can take an item or scene.

1
        mod_meta.invalidate (scene);

The eval modifier metaclass will automatically handle the case of items being added and removed, and of changes to the bindings of existing modifiers. If there is any other reason that a modifier should be invalid the client can implement the CLxEvalModifier::change_test() function and return false in those cases.

Object Evaluation

The eval modifier metaclass provides a system for alternate forms of evaluation. One common modifier pattern is a modifier that reads the channels of an item and writes an object encapsulating those settings to an OBJREF channel. For example this is how falloffs work – the falloff object is allocated in the modifier, the input channels are read into it, and the COM object is stored to the common falloff channel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyModifier :
                public CLxEvalModifier,
                public CLxObjectEvaluation
{
    public:
        CLxSpawner<MyFalloff> spawn;
        MyModifier () : spawn ("my.falloff.spawner") {}

                void
        alloc_obj (
                CLxEvalModifier         &com,
                ILxUnknownID            &obj)           LXx_OVERRIDE
        {
                MyFalloff               *fall;

                fall = spawn.Alloc (obj);
                com.mod_read_attr ((MyChannels *) fall);
        }
};

static CLxMeta_EvalModifier<CModifier>          mod_meta (SRVNAME_MODIFIER);
static CLxMeta_ObjectEvaluation<CModifier>      eval_meta (LXsICHAN_FALLOFF_FALLOFF);

Our modifier class inherits from both CLxEvalModifier and ‘’CLxObjectEvaluation’’. We’ll use a Spawner to allocate our falloff object. (Creating new metaclasses is a process, so any object type that hasn’t been converted can still use the older idiom.) The alloc_obj() method allocates the falloff, which will be returned indirectly through the obj argument. We then use the CLxEvalModifier – passed as an argument – to read the defined channel values into the destination struct. In this case our MyFallff class inherits from ‘’MyChannels’’, so casting gets us a valid pointer.

(Falloffs usually need to read their own world matrix. Because this isn’t one of the channels defined by the Channels metaclas, it has to be added in a bind() method and read as a custom channel. This was omitted for clarity.)

There are two metaclasses declared, one for the eval modifier and one for the evaluation pattern. The object evaluation metaclass takes the channel name as argument to the constructor. The evaluation is placed under the modifier in the metaclass tree.

1
2
3
4
5
 root
  |
  +--- eval modifier
        |
        +--- evaluation
1
        mod_meta.add (&eval_meta);

Cache Evaluation

Another alternate form of evaluation involves caching the results of a previous evaluation to speed up reevaluating the same output. The client declares a class inheriting from CLxObject to serve as the cache, and creates a method for allocating one. This is then passed to the cache_eval() method along with a flag indicating if the cache has been evaluated before.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MyModifier :
                public CLxEvalModifier,
                public CLxCacheEvaluation
{
    public:
        class cCache : public CLxObject
        {
                [...]
        };

                CLxObject *
        cache_alloc ()
        {
                return new cCache;
        ]

                void
        cache_eval (
                CLxEvalModifier         &com,
                CLxObject               *cache,
                bool                     exists)
        {
                if (exists)
                        <process-cache-from-previous-eval>
                else
                        <init-cache-for-first-eval>
        }
};

This evaluation pattern is also placed under the eval modifier in the metaclass tree, just like previous evaluation patterns.