LiteEntitySystem icon indicating copy to clipboard operation
LiteEntitySystem copied to clipboard

SyncVar/Field Change Events

Open ltwlf opened this issue 11 months ago • 2 comments

This PR enables on-change callbacks for value-type SyncVar<> fields by allowing users to specify a callback method name via [SyncVarFlags("CallbackName")]. Whenever a SyncVar<> changes.

Additionally, custom sync fields (e.g., SyncString, SyncNetSerializable<T>) now support a ValueChanged event, letting developers subscribe to any changes.

Unfortunately, SyncVar Value Types and SyncFields must be handled differently.

Example:

using LiteEntitySystem;
using LiteEntitySystem.Internal;
using UnityEngine;

[EntityFlags(EntityFlags.UpdateOnClient)]
public class TestEntity : EntityLogic
{
    // A simple SyncVar<int> with an on-change callback
    [SyncVarFlags("OnCounterChanged")]
    private SyncVar<int> _counter;

    // A SyncVar<bool> with another callback
    [SyncVarFlags("OnToggle")]
    private SyncVar<bool> _toggle;

    // A custom sync field (e.g., SyncString) that raises a ValueChanged event
    private readonly SyncString _greeting = new();

    // Normal constructor
    public TestEntity(EntityParams entityParams) : base(entityParams) {}

    // Called when `_counter` changes
    private void OnCounterChanged(int newValue)
    {
        if (IsClient)
            Debug.Log("Counter changed to: " + newValue);
    }

    // Called when `_toggle` changes
    private void OnToggle(bool state)
    {
        if (IsClient)
            Debug.Log("Toggle state changed: " + state);
    }

    // Example usage of SyncString's event
    private void OnGreetingValueChanged(string oldValue, string newValue)
    {
        if (IsClient)
            Debug.Log($"Greeting changed from '{oldValue}' to '{newValue}'");
    }

    protected override void OnConstructed()
    {
        base.OnConstructed();

        // Subscribe to the SyncString's ValueChanged event
        _greeting.ValueChanged += OnGreetingValueChanged;

        if (IsServer)
        {
            // You can initialize values on the server
            _counter.Value = 1;
            _greeting.Value = "Hello from the server!";
        }
    }

    private float _elapsedTime;

    protected override void Update()
    {
        if (IsServer)
        {
            // Increment `_counter` and flip `_toggle` every 2 seconds
            _elapsedTime += Time.deltaTime;
            if (_elapsedTime >= 2f)
            {
                _counter.Value += 1;
                _toggle.Value = !_toggle.Value;

                // Also update the greeting
                _greeting.Value = "Server greeting " + _counter.Value;

                _elapsedTime = 0f;
            }
        }
    }
}

ltwlf avatar Jan 29 '25 10:01 ltwlf

@RevenantX would do you thinks about this? I know that you can subscribe with bind to value type changes, but I tried to make it easier for devs. For Sync Fields I coudn't find a way for change notifications in the current codebase.

ltwlf avatar Feb 02 '25 16:02 ltwlf

@ltwlf i use BindOnChange because it work for IL2CPP (and possibly for some AOT things in future) inside Unity. IL2CPP need to know all generic types that will be used in resulting code. And methods that called by reflection without direct call can be stripped from resulting executable

https://docs.unity3d.com/6000.0/Documentation/Manual/scripting-backends-il2cpp.html

RevenantX avatar Feb 02 '25 16:02 RevenantX