Niv
Niv2d ago

Getting property 'didChange' callbacks but value is identical to previous value.

This seems to happen when other properties in the same RealtimeComponent are changed. Is there any grouping mechanism at play here that causes this? There's no real 'harm' in applying the same value twice, but in some cases this causes unnecessary work to get done.
18 Replies
kapa.ai
kapa.ai2d ago
Hi @Niv. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- This behavior you're experiencing with RealtimeComponents in NormalVR is related to how Normcore handles property updates and change events. Let me explain based on the information from the knowledge sources: The reason you're seeing 'didChange' callbacks for properties that haven't actually changed is due to how Normcore groups and applies updates. According to the Room > Datastore documentation: "Normcore serializes all updates once per network frame (~20hz) and groups them into the same packet. When model updates are applied, they're applied one at a time, and a change event fires for that value. However, it's possible the other values from that packet hadn't yet been applied." This means that when multiple properties in a RealtimeComponent are changed, Normcore groups these changes together and sends them in a single packet. When applying these updates on the receiving end, it processes them one by one, firing change events for each property, even if some of those properties haven't actually changed their values. To address your specific concern about unnecessary work being done, the documentation suggests using RealtimeCallback to get an event that fires once all updates have been applied to a model. This can help you avoid processing changes multiple times for properties that haven't actually changed. Unfortunately, the knowledge sources don't provide information about a specific grouping mechanism that you can control to prevent this behavior. It seems to be an inherent part of how Normcore optimizes network updates. If this behavior is causing significant performance issues in your application, you might want to consider implementing additional checks in your callback methods to verify if the value has actually changed before performing any expensive operations. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2d ago
<@269927864265211906> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
maxweisel
maxweisel2d ago
what version of Normcore are you on?
Niv
NivOP2d ago
latest I believe 2.14
maxweisel
maxweisel2d ago
2.14.0 changed how we track changes. does the bug happen with the version right before that release? cc @NormalMark
Niv
NivOP2d ago
I can rollback and see
maxweisel
maxweisel2d ago
🙏🏻
Niv
NivOP2d ago
Seems like version 2.13.2 doesn't have that issue are we better off being on that?
maxweisel
maxweisel2d ago
It sounds like we introduced a bug then with this new change. do you know how we might be able to repro the bug on our end so we can put a quick fix out? is it only happening in specific cases? is it only during instantiation? are multiple clients modifying the model? any tips would be greatly appreciated how many properties on the model? are any other properties changing value? etc
Niv
NivOP2d ago
well this is the model in question:
public partial class GameEntityModel
{
[RealtimeProperty(1, true, true)]
private int _health;

[RealtimeProperty(2, true, true)]
private float _stunDamage;

[RealtimeProperty(3, true, true)]
private float _curStunDuration;

[RealtimeProperty(4, true, true)]
private bool _isTurbo;

[RealtimeProperty(5, true, true)]
private bool _isOverheated;

[RealtimeProperty(6, true, true)]
private int _syncTeam;

[RealtimeProperty(7, true, true)]
private int _uuid;

}
public partial class GameEntityModel
{
[RealtimeProperty(1, true, true)]
private int _health;

[RealtimeProperty(2, true, true)]
private float _stunDamage;

[RealtimeProperty(3, true, true)]
private float _curStunDuration;

[RealtimeProperty(4, true, true)]
private bool _isTurbo;

[RealtimeProperty(5, true, true)]
private bool _isOverheated;

[RealtimeProperty(6, true, true)]
private int _syncTeam;

[RealtimeProperty(7, true, true)]
private int _uuid;

}
` When we change other properties (like stunDamage and curStunDuration) it seems like uuid is getting the 'didChange' event as well but its value is the same so a repro would probably be change some of the properties in the model, and to verify that only those properties get the 'didChange' events triggered
NormalMark
NormalMark2d ago
Investigating this issue atm Hey, I'm using this test script currently:
c#
using Normal.Realtime;
using UnityEngine;

public class TestComponent : RealtimeComponent<TestComponentModel>
{
protected override void OnRealtimeModelReplaced(TestComponentModel previousModel, TestComponentModel currentModel)
{
if (previousModel != null)
{
previousModel.healthDidChange -= OnHealthDidChange;
previousModel.stunDamageDidChange -= OnStunDamageDidChange;
previousModel.curStunDurationDidChange -= OnCurStunDurationDidChange;
previousModel.isTurboDidChange -= OnIsTurboDidChange;
previousModel.isOverheatedDidChange -= OnIsOverheatedDidChange;
previousModel.syncTeamDidChange -= OnSyncTeamDidChange;
previousModel.uuidDidChange -= OnUUIDDidChange;
}

if (currentModel != null)
{
currentModel.healthDidChange += OnHealthDidChange;
currentModel.stunDamageDidChange += OnStunDamageDidChange;
currentModel.curStunDurationDidChange += OnCurStunDurationDidChange;
currentModel.isTurboDidChange += OnIsTurboDidChange;
currentModel.isOverheatedDidChange += OnIsOverheatedDidChange;
currentModel.syncTeamDidChange += OnSyncTeamDidChange;
currentModel.uuidDidChange += OnUUIDDidChange;
}
}

[ContextMenu("Test")]
private void Test()
{
// Change a few properties
model.curStunDuration += 1f;
model.stunDamage += 1f;

// Test equality check optimization
model.uuid = model.uuid;
}

private void OnHealthDidChange(TestComponentModel testComponentModel, int value)
{
Debug.LogError($"OnHealthDidChange: {value}");
}

// ... +the rest of the XDidChange methods
}
c#
using Normal.Realtime;
using UnityEngine;

public class TestComponent : RealtimeComponent<TestComponentModel>
{
protected override void OnRealtimeModelReplaced(TestComponentModel previousModel, TestComponentModel currentModel)
{
if (previousModel != null)
{
previousModel.healthDidChange -= OnHealthDidChange;
previousModel.stunDamageDidChange -= OnStunDamageDidChange;
previousModel.curStunDurationDidChange -= OnCurStunDurationDidChange;
previousModel.isTurboDidChange -= OnIsTurboDidChange;
previousModel.isOverheatedDidChange -= OnIsOverheatedDidChange;
previousModel.syncTeamDidChange -= OnSyncTeamDidChange;
previousModel.uuidDidChange -= OnUUIDDidChange;
}

if (currentModel != null)
{
currentModel.healthDidChange += OnHealthDidChange;
currentModel.stunDamageDidChange += OnStunDamageDidChange;
currentModel.curStunDurationDidChange += OnCurStunDurationDidChange;
currentModel.isTurboDidChange += OnIsTurboDidChange;
currentModel.isOverheatedDidChange += OnIsOverheatedDidChange;
currentModel.syncTeamDidChange += OnSyncTeamDidChange;
currentModel.uuidDidChange += OnUUIDDidChange;
}
}

[ContextMenu("Test")]
private void Test()
{
// Change a few properties
model.curStunDuration += 1f;
model.stunDamage += 1f;

// Test equality check optimization
model.uuid = model.uuid;
}

private void OnHealthDidChange(TestComponentModel testComponentModel, int value)
{
Debug.LogError($"OnHealthDidChange: {value}");
}

// ... +the rest of the XDidChange methods
}
I'm not able to repro that particular symptom with this script
NormalMark
NormalMark2d ago
Ex after triggering the Test() function twice I get the expected result on local and remote clients:
No description
NormalMark
NormalMark2d ago
Is there a way I can modify my test script to more closely match your situation? I could also investigate a repro project if you can put one together or the project you're working on directly
Niv
NivOP2d ago
well, a couple things we're doing differently (not sure if that's going to trigger it but worth a shot): 1. When our stun changes it usually happens every frame for a period of ~2 seconds. So try changing those properties every frame. 2. Even though we specify in the model we want 'didChange' events for every property - we only actually subscribe to the uuidDidChange event (and none of the others). Also, that model goes along with a bunch of other models in the same RealtimeView heirarchy so perhaps the bug has to do with how the datastore handles it in a certain context, I don't know. Our project is huge and a bit of a pain to get up and running, I'd rather not go through that just for this bug Oh one more thing that might be relevant, we have an unusual structure: - We have rootRealtimeView which is unowned (and has no components) - We have a childView with 2 compoenents - Another childView with 2 components and it has another child view with 5 components. The reason for this structure is that we can have split ownership for the two children. (although the bug manifests even when the same client owns both children)
NormalMark
NormalMark2d ago
I'll keep looking One guess that's worth checking out is if you have the following pattern inside a frame:
c#
var originalValue = model.uuid;

// "Dirties" the property locally and triggers a change event locally
model.uuid = originalValue + 1;

// "Dirties" the property locally and triggers a change event locally
model.uuid = originalValue;

// Even though we technically end up with the same value by the end of the frame, the change event will have been called twice locally (it's invoked in the mode.uuid auto-generated setter)
c#
var originalValue = model.uuid;

// "Dirties" the property locally and triggers a change event locally
model.uuid = originalValue + 1;

// "Dirties" the property locally and triggers a change event locally
model.uuid = originalValue;

// Even though we technically end up with the same value by the end of the frame, the change event will have been called twice locally (it's invoked in the mode.uuid auto-generated setter)
Does the redundant change event happen on the local client or on a remote client or both?
Niv
NivOP12h ago
only on remote
holofermes
holofermes6h ago
hi all! I'm having the same problem, and changing from 2.14.0 to 2.13.2 was my solution. I have just started working with this, and my super simple starting point is a model with two properties with the createDidChangeEvent set to true. Basically changing one of the two prop would fire the event for both of them.
NormalMark
NormalMark5h ago
Hey, thanks for reporting the bug. I'll test again but I haven't been able to repro yesterday. Do you have a test project I could debug by any chance? I found the bug, working on a fix 👍

Did you find this page helpful?