"Auto Cache" for C# Process nodes

Hello again,

while checking out the “new way” of writing process nodes in C# I came accross the following “issue”:
The update function of the process is constantly executed (naturally) but I can imagine in a lot of scenarios where you’ll only want to execute if the inputs have changed. One can of course write change checks but that can become quite tedious if there are many parameters. A different approach and that’s what I did in the end is wrapping the process into another patched process adding a Cache region. But this also feels kinda wrong.

image

Could the process maybe get an additional annotation like [isCached] that automagically wraps it into a “cache region”?

3 Likes

The correct way is to build the node with fragments (like many Stride nodes) for each input (property) that are either not called at all (because they are not connected) or called before update because they are ordered before update, and then only recalculate what is necessary for the change in update. Unfortunately, that isn’t trivial to write, but the new C# version has source generators which make that easier, for example: Source Generator For INotifyPropertyChanged

vvvv could ship such a source generator with the core/node factory package.

hey bjoern,

It’s a nice idea and sorrily this currently isn’t possible.
In the future, this might work just like you proposed without the need to restructure your code.
The actual way to ship the feature could be a source code generator as @tonfilm suggested or some “lifting” on application side that automatically wraps the update fragment of the process node with cache region (without you seeing it in the user patch).


Up to that point, I personally would prefer the primitive approach. This one:

One can of course write change checks but that can become quite tedious if there are many parameters.

        string input;
        ZalgoMode zalgoMode;
        bool up;
        bool middle;
        bool down;
        string cachedResult;

        public string Update(string input = "everything you knw is wrong", ZalgoMode zalgoMode = ZalgoMode.NormalZalgo, bool up = true, bool middle = true, bool down = true )
        {
            if (input == this.input &&
                zalgoMode == this.zalgoMode &&
                up == this.up &&
                middle == this.middle &&
                down == this.down)
                return cachedResult;
            this.input = input;
            this.zalgoMode = zalgoMode;
            this.up = up;
            this.middle = middle;
            this.down = down;
            ///...
            cachedResult = newText;
            return newText;
        }
    

Maybe the C# record feature could be tested here and help you with the equality checks…


Note that the way that @tonfilm proposes indeed is a good option as well, but for now, as he points out would require a lot more code; and even if the source code generator would exist it would add some complexity to the setup.

So the property-based approach with properties would somehow look like this:

        ZalgoMode zalgoMode;

        [DefaultValue(ZalgoMode.NormalZalgo)]
        public ZalgoMode ZalgoMode
        {
            get => zalgoMode;
            set
            {
                if (zalgoMode == value) 
                    return;

                zalgoMode = value;
                changed = true;
            }
        }

in Update…

            if (!changed)
                return this.cachedResult;

            changed = false;

The DefaultValue attribute on the property gets used for the default value of the input pin.

Note however that you’d still need to make sure that the fields actually get initialized correctly.
VL doesn’t call the property or field setter for you in order to initialize when the process node gets created.
So you’d need to make sure that all defaults match and set them on create.

too much information

This is of course very tedious and thus we are since a while looking into other solutions since it affects all fragmented process nodes - also patched ones - and can mislead the user. The default visualized in the pin tooltip can potentially differ from what data the node operates with. It’s currently the node designers’ responsibility to make that match.

EDIT: this shows the problem: Zalgo_.zip (144.8 KB)

Alt-right-clicking (resetting the pin) leads to executing the fragment once more with the default value of the pin before not getting called anymore. Here the system actually helps out, but it feels kind of weird as well.

So, yes we are currently looking into ways how to simplify this system, e.g. by just calling all fragments on update (if they weren’t assigned otherwise), eliminating both problems at the same time. This however would be a breaking change and so it won’t make it into the next stable release.


So all in all I’d just stick with what you have and write some more lines of code. Don’t mix C# and some VL wrapper which you need to keep in sync.
Just write 5 lines of code and check out the record feature and its value equality feature.
And some day you might get lucky and just can specify Cache in the ProcessNode attribute.

3 Likes