Check for definitive Ownership
Am I correct that, in theory, two clients could return "true" to "isOwnedLocallySelf" in a given frame (right before the server blocks and rolls back the request for one of the two clients). What would be the most definitive way to make sure no-one else is fighting for ownership in a given frame? Check over multiple frames seems like a brute force way of doing this? I sthere a better way?
30 Replies
Hi @Mischa. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
----------------------
Yes, you are correct. In theory, two clients could return "true" to "isOwnedLocallySelf" in a given frame before the server blocks and rolls back the request for one of the two clients. This is because the
RequestOwnership()
and ClearOwnership()
methods take effect immediately on the client side, but the server might block the request due to reasons like preventOwnershipTakeover
being true
or the view's parent being owned by another client. In such cases, ownership and all changes that required ownership will be rolled back by the server.
To definitively ensure no one else is fighting for ownership in a given frame, you could use the RequestOwnership(bool sendRedundantUpdates = false)
method with the sendRedundantUpdates
parameter set to true
. This will send the ownership update even if the model is already owned by the local client. This might send redundant updates but is useful if you expect contention. However, this does not circumvent the "Prevent Ownership Takeover" lifetime flag if it applies.
Here's an example of how you can use it:
This approach ensures that the server is updated about the client's intent to own the object, even if the client already owns it. This can help in situations where multiple clients might be trying to take ownership of the same object.
However, please note that this approach might lead to increased network traffic due to redundant updates. If this becomes a concern, you might need to implement a more sophisticated system for managing ownership requests, possibly involving some form of client-side prediction or a queue system for ownership requests.
Sources:
- RequestOwnership() + ClearOwnership() (https://normcore.io/documentation/room/ownership-and-lifetime-flags#requestownership--clearownership)
- RealtimeModel Methods (https://normcore.io/documentation/reference/classes/Normal.Realtime.RealtimeModel)
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@847936237003604048> kapa.ai is still learning and improving, please let us know how it did by reacting below
StringKeyDictionary is the only data type that returns a message when the server receives it. you can use that for what you need
Oh, so you would just add a custom filed of that type that holds the ownership value to the realtime model?
if you wanted to make sure only one person owned a thing you can have them both try to insert a model and the one that succeeds can make the ownership request
Thank you for the idea!
Let me check the knowledge base for you. One moment! :books:
----------------------
To invite someone to a private room in Normcore, you need to share the room name and the App Key with them. When they use these details to connect to the room, they will join the same room server.
Here's a basic example of how to connect to a room:
In this example, replace "YourRoomName" with the name of your room and "YourAppKey" with your App Key.
Please note that all clients that connect to the same room name with the same App Key will connect to the same room server.
If you are working with Normcore Private, make sure to set the
matcherURL
parameter to your Normcore Private matcher URL.
For more information, you can refer to the Connecting to a room server (https://normcore.io/documentation/room/room#connecting-to-a-room-server) section in the Normcore documentation.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------<@1124402583155511357> kapa.ai is still learning and improving, please let us know how it did by reacting below
Are we sure that isOwnedLocallySelf is not verified by the server? I.e. will this flag be set immediately when requesting ownership or does it only get set once it gets confirmed by the server? Would be be nice to have a simpler way to test for definitive ownership other than attempting to add a model to a transactional collection.
@Max ?
it's set immediately and waits for the server to reject as we expect the majority case to be that it's allowed
there is an ownerIDChanged event you can subscribe to, but I think it will fire instantly and then fire again on roll back
StringKeyDictionary is the correct approach for atomic value changes as it won't fire a callback until the server confirms / denies
We're definitely working to create a better API for these types of transactions in the next version.
I'm hoping as a whole we can provide both an instant caching layer + server confirmation events for all properties in the datastore
Thank you for the clarification.
sure thing!
@maxweisel - circling back to this as we're running into similar questions.
This thread being a bit old - perhaps things have changed since or best practices have been developed.
Let's say we have a manager type of object that is responsible for spawning things for everyone. When the previous owner leaves - some other player should take ownership so spawning can resume. Since spawning will always get accepted by server we need to make sure only 1 client does this after he has 100% verified he is the owner and the server won't roll that back.
It seems like ownership is not sufficient in its current form because there's no way to tell locally when the server approves/denies an ownership request. Is there another best practice to achieve this?
Even with string key dictionary - if the model is unowned everyone is allowed to write to the model, so even if my process is write to dictionay, wait for approval. I might get this approval and then another client will overwrite my approved value with his value (and also get approval). It seems like however I change things around there's a potential race condition. Am I not getting something?
I use the lowest client Id method
there's never anyone with the same client id so no need to sync anything
how do you know which client IDs are actually connected?
use the avatar manager
if you're not using that you can make something similar
we're not using that, but I will look into what it is and why its helpful in this situation
seems like a very fundemental thing that should be systematically supported on the API level though
all it does is keep a list of all the avatars in the scene and then you can get the client ID from the avatar view
the avatar method is how it's recommended to track players
I understand the transactional nature of the StringKeyDictionary<> - but when the model is unowned (prev owner left) everyone can make changes. And then both/all clients need to clear it to be able to insert a key. I can definitely still envision a scenario where it breaks - like 1st client succesfully clears and inserts a key -> then requests ownership -> then 2nd client (before 1st client ownership request is approved) does the same clear + insert key.
Just seems like a lot of gymnastics where the API should be clearer/cleaner
The lowest client ID approach works. Next month we’ll be adding an API that lets you know when ownership has been confirmed by the server.
You can also use StringKeyDictionary’s atomic nature to pick the client and once they’re picked you have them request ownership.
That single thing would remove a lot of our headaches
it’s in the works
There's something about this method that still seems off to me. Like if the model is unowned, everyone can write to the dictionary but if they try to write "at the same time" (what does that even mean?) only one succeeds.
How can 2 clients ask for something "at the same time" - how close do they have to be to be considered the same time?
And if it's not unowned, then only one client can write to it in the first place. I'm just not getting how to use this atomic/transactional behavior to achieve our goals which are:
- Make sure one (and only one) client executes some code after everyone mutually agreed that client is "the one".
If I'm missing something and that feature does solve that - maybe it would be beneficial for everyone if an example / docs were written that demonstrate how to achieve that.
(like what do you do first - ask for ownership or try to write to the dictionary? when can you safely assume everyone is in agreement and that client can execute "irreversible" code, like spawning)
I'd still go with the lowest client ID method
but if you want a different method you can request ownership of a view and set prevent ownership take over and then wait 1 second, if you're still the owner that means you can spawn objects
when the owner leaves the view will become unowned and the next person can request ownership
You try to update a key in StringKeyDictionary first. It has a callback that will tell you if you succeeded only after the server confirms that you successfully inserted a model for that key. All other clients will be told they failed and can check again to see if the key is null.
once you’ve successfully inserted the model atomically, then you request ownership.
and when inserting the model, you set the destroy flags to destroy when you leave so everyone will get a notification when you get disconnected
the StringKeyDictionary insert acts as a lock / mutex for the request ownership call
One suggestion for improvement of the StringKeyDictionary to make it much more powerful.
I think the Insert() API should only succeed if the key is empty.
The way it currently works is while the model is unowned, anyone can "insert" - if 2 clients do it semi-simultaneously then one will get a fail. But after long enough time (which is fuzzy = bad) he will suddenly succeed in his "insert" (which is actually an UpdateValue call).
This makes me think the flow you described is still not guaranteed to be 100% error free.
Of course server verified ownership will be the best solution to my problem - but I believe explicit API's for Insert/InsertOrUpdate, where insert fails if the key exists will make it more powerful as atomic operations. You never really know how lagged clients are.
just FYI, the buggy scenario I can see is this:
1. model with dictionary is unowned.
2. 2 clients (A & B) try to write to the dictionary.
3. server accepts A's insert op and sends approval.
4. A receives approval and takes ownership and prevents takeover
5. A's ownership takeover is delayed because of network
6. B receives A's insert very delayed, so he issues his own insert
7. Server receives B's insert because enough time passes since A inserted (I saw this can happen)
you can get where it goes from here ...
The only way the server will approve B’s insert is if B’s insert was made after it received A’s insert fwiw
It’s not based on time, it’s based on the key version. When A inserts a value, the server increments the key version. When B inserts it includes its local key version. The server only allows the insert if the version matches, which means B was aware of A’s change before it sent anything by
And the key version is recorded the moment you call Insert() so even if the packet goes out late, it would be rejected
all this to say, if B checks the state of the connection before calling Insert, it will always show A’s change. If it doesn’t include A’s change, the Insert will fail
ok that actually makes better sense then - I need to rethink 😄
Thanks!
Apparently I was thinking about it all wrong - so thanks for clarifying.
I had a feeling I'm not understanding something ...
all good! happy to help