How To Create Plugins/Creating ProtoFlux Nodes

From Resonite Wiki
Revision as of 16:03, 17 July 2024 by Paradox19 (talk | contribs) (fix link)
This page is in a state of significant expansion or restructuring. You are welcome to assist in its construction by editing it as well.
Reason: Doesn't list tutorials for all node types and missing links.

Creating custom ProtoFlux nodes comes in two parts, the node itself and the binding. The node itself does all the calculations while the binding tells FrooxEngine how to generate the in game node[Citation needed].

There are many types of ProtoFlux nodes, and this page will cover most of them. This page will not cover writing ProtoFlux bindings, due to the much more complicated nature of those.

Getting Started

  1. Add references to
    • FrooxEngine
    • FrooxEngine.ProtoFlux
    • ProtoFlux.Core
    • ProtoFlux.Runtimes.Execution
  2. Assign a namespace to your node.
    • This namespace should contain your node's path.
    • Your namespace must start with ProtoFlux.Runtimes.Execution.Nodes
    • A good example: ProtoFlux.Runtimes.Execution.Nodes.MyPlugin.Users, for a node in the MyPlugin/Users category.
  3. Assign a node category
    • To do this, add [NodeCategory("PATH")] before your node class.
    • Some examples:
      • [NodeCategory("MyPlugin/Math/Constants")]
      • [NodeCategory("MyPlugin/Users")]
  4. Optional: Set a friendly name for your node.
    • These convert your node's class name into another name, such as how the Conditional nodes appear as ?:
    • To do this: Add in [NodeName("NAME", SV)] underneath your node category, where NAME is your node name and SV is if this custom name only shows in overview mode.
  5. Optional: Set if your node is Continuously Changing.
    • This makes it so your node is constantly evaluated, rather than whenever needed. Adding this flag means your output doesn't need to run through a Continuously Changing Relay to be updated every cycle.
    • To do this: Add in [ContinuouslyChanging] before your class definition.
    • NOTE: This is incompatible with callable nodes, as those are evaluated every time they are called.

Creating input and output variables

Private variables are immune from any of the following rules and are defined the same way as in regular C#.

All inputs and outputs must follow the following rules:

  • They must be the ProtoFlux Wrapped Equivalent of your original type. Such as ValueInput<T>, where T is your target type.
  • They must be readonly
  • The name of the variable is what will show up in game

Inputs go immediately after your node class definition and outputs are placed after the inputs.

A warning about inputs

Node inputs can be any of:

  • null
  • type?
  • type

and should be handled accordingly. Failure to handle a null correctly will result in a session crash upon the node's evaluation. Here are some examples of ways to handle null, by simply returning null.

These code snippets are only valid for ObjectFunctionNodes!
You will need to adapt them for other node types. Such as returning -1 for a ValueFunctionNode<int>

Basic "if" check:

if (MyWrappedVariable.Evaluate(context) == null)
{
    return null;
}
return MyWrappedVariable.Element;

Null conditional operators

return MyWrappedVariable?.Element;

Or you can use your own null handling, such as assigning default values if the input is null!

Node specific tutorials

Value/Object Function Nodes

These are the most simple type of node, they take a number of variables as inputs and return a singular output. Choosing whether to use a Value Function or Object Function is dependent on your output type. If you're outputting a value, you use the Value Function; if outputting an object, you use the Object Function.

If you need to output more than one value, please see Void nodes

Creating a Value/Object Function class

  1. Create a new public class which inherits ValueFunctionNode<C, T> or ObjectFunctionNode<C, T>
    • It should look like this: public class MyNode : ValueFunctionNode<C, T> or public class MyNode : ObjectFunctionNode<C, T>
  2. Add in your variables, following the guide above. You don't need to define an output for these node types, as it's implicit through the class type (T)

Adding functionality

To add functionality into your node, you need to override the Compute(context) function and return a singular value.
To get the data from an input, you should use MyInput.Evaluate(context)

An example of returning a user's username:

using FrooxEngine;
using FrooxEngine.ProtoFlux;
using ProtoFlux.Core;
using ProtoFlux.Runtimes.Execution;

namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Users;
[NodeCategory("Obsidian/Users")]
[NodeName("User Username", false)]
[ContinuouslyChanging]
public class ExampleNode : ObjectFunctionNode<FrooxEngineContext, string>
{
    public readonly ObjectInput<User> WrappedUser;

    protected override string Compute(FrooxEngineContext context)
    {
        // Unwrap our user from the input.
        User unwrapped_user = WrappedUser.Evaluate(context);

        // I assume you read the warning above, but if not:
        // WARNING! If your unwrapped variable is null, you should handle it accordingly!
        // Here we return null if the variable is null.
        // (you could also use "return unwrapped_user?.UserName;" to return null or the username safely.)

        // Failure to handle a null correctly will result in a session crash!
        if (unwrapped_user == null)
        {
            return null;
        }
        return unwrapped_user.UserName;
    }
}

When used in game (and bound), this creates a node called ExampleNode in the browser but says "User Username" as the name. It takes one input, called WrappedUser (as that's what we called it in code) and outputs the user's username!

Void Nodes

TODO

Next steps

After creating your node, you might rush to see it in game but it wont appear yet: it still needs a binding. You either need to create your own or use a binding generator. Here are some open source generators:

  • TODO

See also