Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime): Add OnBeforeValueChange callback to NetworkVariable #668

Closed
wants to merge 2 commits into from

Conversation

tonygiang
Copy link

The new OnBeforeValueChange callback will guarantee the order-of-execution of certain actions that have to be executed before a NetworkVariable's value changes. As a part of this commit, the comment for OnValueChanged will also be clearer about when it is invoked in relative to the value change.

… value changes. Clarify when its value changes for the comment of OnValueChanged.

The new OnBeforeValueChange callback will guarantee the order-of-execution of certain actions that have to be executed before a NetworkVariable's value changes. As a part of this commit, the comment for OnValueChanged will also be clearer about when it is invoked in relative to the value change.
@unity-cla-assistant
Copy link

unity-cla-assistant commented Mar 25, 2021

CLA assistant check
All committers have signed the CLA.

@mattwalsh-unity
Copy link
Contributor

@jeffreyrainy this looks like a pretty straightforward & useful addition. Wondered what your take is and wanted for your awareness for your network variable work

@jeffreyrainy
Copy link
Contributor

I like the improvement. Good stuff, thanks!

The only reservations I have are:

#426
We probably should address the double notification, if we are to play in this area.

Naming change from previous to old. Since we have many teams using this codebase, and since they may have their own modifications, we need a pretty high bar to rename stuff. Is there a strong reason to change the naming?

@tonygiang
Copy link
Author

tonygiang commented Mar 26, 2021

I made the change from previousValue to oldValue since previousValue would not make semantic sense when you execute an action before a value change. The value at that point has yet to change. The name oldValue retains semantic sense in the context of an action that executes after a value change.

All instances of variables named oldValue as far as I can find are strictly internal to this class. The change to oldValue in the delegate definition as I understand it is only reflected as IDE hints and possibly snippet-generation functionalities, and the .NET runtime doesn't enforce strict argument names for registering methods. Though I do understand the concern of naming consistency within the codebase.

@TwoTenPvP
Copy link
Contributor

@jeffreyrainy Could you drive this and get it merged or closed? Thanks

/// The callback to be invoked when the value gets changed
/// The callback to be invoked before the value gets changed
/// </summary>
public OnValueChangedDelegate OnBeforeValueChange;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an example use case for this? You should be able to achieve everything with OnValueChanged already by accessing the previousValue.

This increases the public API of NetworkVariable which means more documentation, code to maintain, just more potential issues for us. If this solves an existing issue then yeah lets merge this. If this is just adding an additional event for convenience then I would suggest we don't add this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use-case for this delegate is strictly to ensure order-of-execution. The previous value of a network variable itself may had been passed along OnValueChange, but what if there are other resources associated with this value upon it being changed? We typically would like the order-of-execution to be:

  1. Take a snapshot of values associated with the network variable and send it elsewhere - could be a logger, could be a SQL utility to compile a single Update statement.
  2. Update associated values so that they now reflect the network variable's new value.

If this had been UnityEvent, we could easily guarantee this order of execution by just swapping around serialized callbacks in the Inspector, order-of-execution would have been a trivial issue. But since NetworkVariable uses plain C# delegates, the order of execution is less clear. I'm aware that the order of execution of listeners registered to a C# delegate can be guaranteed by registering them in the same order you'd like them to be executed, but this presents a big potential for a gotcha, where seemingly unrelated changes in a codebase could change the order of registration and thus change the order of execution silently. Everything would still run but we'd be unknowingly sending false data back to our database. We will take 1000+ compilation errors over such a circumstance any day.

In non-Unity .Net apps, I use this wrapper class as a way to have a better control over order-of-execution:

using System;
using System.Linq;
using System.Collections.Generic;

public class SortableAction<TO> where TO : IComparable<TO>
{
  protected Action _action;
  // can be a byte to save memory, or can be System.DateTime or System.Version
  // for some reason
  public TO ExecutionOrder = default;

  public static SortableAction<TO> From(Action action, TO eo)
  { return new SortableAction<TO> { _action = action, ExecutionOrder = eo }; }

  public void Invoke() { _action.Invoke(); }
}

public static class SortableActionExtensions
{
  public static void InvokeInOrder<TO>(
    this IEnumerable<SortableAction<TO>> source) where TO : IComparable<TO>
  {
    var sortedActions = source.OrderBy(sa => sa.ExecutionOrder);
    foreach (var sa in sortedActions) sa.Invoke();
  }
}

But that solution is out of the question for this project. So I figured a "before change" event could at the very least guarantee some degree of control over execution order and hoped that other Unity developers find other use cases for it.

Copy link
Contributor

@LukeStampfli LukeStampfli May 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. Something like your wrapper class would indeed be a much better solution because it providers more fine grained control over execution order. But I can see that there are some situations where a BeforeValueChange can help to some extend.

@will-mearns will-mearns self-assigned this May 24, 2021
@LukeStampfli
Copy link
Contributor

We discussed this internally and decided to not add this into MLAPI for now. Reason being that this could also be achieved by a user by subscribing their own high level construct to the event which then calls two functions. We have some work planned in this area with the snapshot system we are building and will potentially change how events like OnValueChanged works in general.

@tonygiang
Copy link
Author

Reason being that this could also be achieved by a user by subscribing their own high level construct to the event which then calls two functions. We have some work planned in this area with the snapshot system we are building and will potentially change how events like OnValueChanged works in general.

This feels a bit too much like a workaround around a problem for comfort. But if the team is open to the possibility of changing how events like OnValueChanged works, I have some thoughts.

I've been thinking about a pure-C# solution to statically guarantee order-of-execution like UnityEvent, and since creating this PR, I have arrived at a slightly different answer than what I've used for non-Unity projects before. The answer I arrived at now is SortedList, which is supported in Unity's fork of Mono.

I first thought of the idea while coming across this C# proposal and coming up with a use-case for it. And while that proposal was for declaring generic alias, you can right now create something resembling a quasi-multicast delegate by subclassing SortedList like so:

public class SortedActionInt<T1, T2> : SortedList<int, Action<T1, T2>> { }

public SortedActionInt<T1, T2> OnValueChanged = new SortedActionInt<T1, T2>();

(The invocation semantic can be anything - member method, extension method or just a raw foreach loop)

This I think is the cleanest way to force pure C# delegates to guarantee order-of-execution like UnityEvent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants