HaRrY
HaRrY3mo ago

Trying to update a value reliably

I have a realtime property for example fillpercentage property. Now every client can update that property (increment/decrement) but the issue is that there is a slight chance that they might override each other values instead of adding on top of eachother. For example the default value is 0 -> then client 1 did currentVal + 10 and client 2 also did currentVal + 10 but if they both did it on the same time then currentFillPercentage will be 10 instead of 20 right? I have following ways to solve it but each solution have some pros and cons. Please let me know if there is a solution that is robust in everyway. I am not using Normcore private or headless builds and likely wont use it anytime soon. Using RealtimeDictionary or RealtimeArrays: Pros: We can record the last few added values. For example client 1 can add 5, client 2 can add 10 and client 3 can add 15 and all 3 events would be recieved on all clients and only 1 client (e.g. master client) can process them 1 by 1. Cons:. It might take sometime to sync that and its not a good experience also if the master client took off his headset for like few seconds then the game would stop updating the fillPercentage values. You might say to apply the local change first but then it opens issues of having outdated/false values if you have such a bad ping Here is what I would likely want: I want it to be so reliable that I dont miss any users value changes, I also want it to be sequential like these delta updates changes should happen sequentially, I would like it to be instant if possible and last but not least I want to detect reliably that who was the first client that made the fillPercentage = 0 so that I can add that users score etc. I can use StringKeyDictionary for the scoring part but reliably calculating all the fillPercentages delta changes and making sure its not relying on only 1 client because we are not using headless build is a challenge.
15 Replies
kapa.ai
kapa.ai3mo ago
Hi @Harris Saleem. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the information provided in the knowledge sources, it seems like you're dealing with a common issue in real-time multiplayer games, which is handling simultaneous updates from multiple clients. In Normcore, updates to RealtimeModel objects appear to take effect immediately for the local client. This is because, 99% of the time, the server accepts the update and applies it. However, it is possible that the server will reject an update due to either ownership or Normcore Private plug-ins. When this happens, the server sends back the rejected update or a modified version of the state in the update. The datastore then rolls the data back or replaces it with the version transformed by the server. Change events then fire again to reflect the new values. Normcore is also able to detect simultaneous updates from multiple clients and respond appropriately. If Client A makes a change and then receives an update from Client B that occurred before Client A made the change locally, Normcore will continue to reflect Client A's value locally as it knows that the value is more recent. If Client A's value is rejected by the server, the datastore will roll back to Client B's value and will fire a change event to notify that the value was updated. source (https://normcore.io/documentation/room/datastore#instant-updates) You can use the **RealtimeProperty] attribute to mark whether the property should be synced reliably or unreliably. If you plan to change a property very often, you should use an unreliable property. Unreliable updates are not resent if they’re dropped in transit because it’s expected that another update is following shortly after. Reliable properties are good for things that you update once and that should be resent if the packet is dropped in transit. This is great for state updates, such as whether your game has started or not. When you change the value, Normcore ensures this value is received by all clients and applied to the datastore in order. [source (https://normcore.io/documentation/realtime/synchronizing-custom-data#reliable--unreliable) You can also use the RealtimeDictionary or RealtimeSet collections. RealtimeDictionary is a special model type that represents a dictionary of models. This collection can be modified by multiple clients simultaneously without introducing datastore conflicts. All updates are applied instantly and are rolled back if rejected by the server (due to ownership). Adding or removing items sends the minimal amount of information to the server in order to perform the update on all clients. The whole collection is not sent every time. source (https://normcore.io/documentation/reference/classes/Normal.Realtime.Serialization.RealtimeDictionary%601) RealtimeSet is an unordered collection of RealtimeModels. Models can be added and removed at runtime by any clients, simultaneously, and without conflict. source** (https://normcore.io/documentation/room/collections#non-transactional-collections) In your case, you might want to consider using a combination of these features to achieve your desired functionality. For example, you could use a RealtimeDictionary to store the fillPercentage values for each client, and then use a reliable RealtimeProperty to store the total fillPercentage. This way, each client can update their own fillPercentage independently, and the total fillPercentage can be updated reliably based on the individual client values. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3mo ago
<@250518782341021697> kapa.ai is still learning and improving, please let us know how it did by reacting below
maxweisel
maxweisel3mo ago
RealtimeSet of updates is the way to go, or you'll want to have each player update their own model's int value and you get the total value by adding them all together.
HaRrY
HaRrY3mo ago
Isnt RealtimeSet is same as RealtimeArray but just unordered? If so it will have the same issue. I will simplify my question: RealtimeProperty currentValue = 100 Client 1 at currentValue = 100 did: +50 = updated currentValue = 150 Client 2 at similar room time did : -20 = updated currentValue = 80 Expected Value = 100 + 50 - 20 = 130 Actual Value = 80 because it updated last. Isnt this a very basic need or am I overthinking this? Is there a normcore way that will increment / decrement values according to delta changes on server so that its consistent? Or like you said should I use each players values that they did and then add them together is that the only best solution?
maxweisel
maxweisel3mo ago
You can add a time property to order the RealtimeSet, but the easier option is just an instance of a model with the int property for each user. Then you add up the ints for all players so if each player has their own model and the int value reads 100, 50, -20. you'd add them up to get 130
Mechabit
Mechabit3mo ago
the idea with the set is that each player adds their change amount to the set and not alter the total amount directly, that way every change is accounted for regardless of how many people update at the same time.
HaRrY
HaRrY3mo ago
yes that needs to be done regardless and I am doing it but the problem is that on those change amounts, someone needs to be responsible to update the actual amount right if actual amount is also normcore property? If I say client 1, 2, 3, or anyone is responsible then I get that issue where if that particualar client is afk by taking of their headset then the values wont update for a bit. I dont want to give a specific client this role until I have a headless build etc
Mechabit
Mechabit3mo ago
just add up all the amounts instead of syncing the total you only need to do it once per model added event each client does the adding locally
HaRrY
HaRrY3mo ago
yeah but if this is something to do with player currentHealth, and these delta changes are damage inputs from all other players and if a new player joins later and they want to check the existing health of other players do they have to iterate through all the RealtimeSet of every player to calculate the health 😛 There must be a nicer way without having to rely on a single client or a headless
Mechabit
Mechabit3mo ago
yeah pretty much each player would be responsible for updating their own health by adding damage sums you can sync health, if someone takes off their headset then they'll update their health when they put it back on the damage sums will still be in the set when they do
HaRrY
HaRrY3mo ago
yeah but then if that player has lag/bad ping then everyone can keep sending them damage updates but they wont update their health which means bad experience. Also like you said if they took off headset they cannot die until they put their headset back on which is again not good in a vr shooter. I have tried all of these approaches plus more there are always caveats and bad experiences, I know this can be solved with headless builds but I was hoping there is a way to do this without that
Mechabit
Mechabit3mo ago
in that case you use prediction and you lower the other player's health locally until you get the confirmation most fps games use prediction with lots of ping you'll have a bad experience either way you can be pretty sure what the enemy health is by adding up the damage sums in the set
HaRrY
HaRrY3mo ago
yeah but if each client is using their own prediction and only local player is going to confirm then all clients would have a different snapshot of that players health, they all would think they killed that player, and when the player comes back then yeah overwhelmed it will get sync but if they dont come back then kills wont be awarded which again is not good. This damage and health is just one example but there are other needs as well to have a consistent reliable up-to-date value instead of overriding.
Mechabit
Mechabit3mo ago
using sets for adding sums is the way without a dedicated server
HaRrY
HaRrY3mo ago
yeah I am using that approach already 🙂 thanks much appreciated Just for reference if anyone else wants to know how I am making sure value updates instead of override without having a headless build is as follows: Using a combination of RealtimeSet and StringKeyDict * Each player adds their value update in RealtimeSet and updates a local value * When a threshold happens (e.g. in case of fill percentage 100% or health becoming 0 etc.) I add that as a StringKeyDictionary and check if transaction is successful or not - so if 2 players tries to add the same key it gives one of them false. * Once that key is added successfully I store roomtime at that state and ignore any delta updates from other clients if they are before that time. * I delete the key and clear RealtimeSet according to my game needs This way I am not relying on any single client to do updates and its pretty much reliable and handles most of the edge cases. Will try to post a gist if possible. 🙂 suggestions are highly welcome if anyone know that this can be improved further