@strange gyro this does exactly what u want. and is synced between every single person. just change it up to how you need it. it uses a Array of audio clips and syncs whatever randomly choosen number. and also only the owner of the object can run it and you can always change the int to a byte instead of you know your not going to have more then 127 sounds.
#udon-networking
1 messages · Page 14 of 1
I'm being serious if you will accept it as such but if this is what you are doing I suspect it may have something to do with a 50/50 success rate on some things. I'm pretty sure you have misunderstood the uses of SendCustomNetworkEvent and UdonSynced properties. In this case those defined with FieldChangeCallback. They shouldn't both be used.
Also this will play for late joiners won't it? I doubt that is what was wanted.
Unless I miss my guess the following will replace method you posted
public void OnDelayedEventDisplayReceipt()
{
if(!Networking.LocalPlayer.IsOwner(gameObject)) return;
var isRare = (Random.Range(0.0f, 1.0f) < 0.03f);
SyncLine = isRare ? Random.Range(0, 4) : Random.Range(5, audioClips.Length);
RequestSerialization();
}
Still the issue of late joiners is present. You will need to "expire" the sync'd value so late joiners do not perform the action when set.
Nah. It was more or less a misplaced send network. I know how it works
And it would work 100% of the time. As for late joiners. Well you could just invalidate it on the local side
Say the random number generates the same value twice. Will it still fire a field change callback if the value has not changed? Or will it loose a voice line?
It will. Since it sets the value again. And it listens to the value being updated
@stone badger also never use var. It's bad practice.
You're completely mistaken... always use var if there is no overwhelming reason not to.
No
Anders Heilsberg would beg to differ with you...
And?
If you know what type it is. Which in this case is known and always will be known it's not just better to use the type. In this case a int.
Var just leads to issues of you having to read all the code to know what type is expected. And debugging it would be even worse
There is a reason why veterans and experts alike rarely if ever use var
Again you are mistaken
Nope
Then please quote these experts and veterans and geez what does that make me?
Using vars also implies you do not know the expected type
No it does not imply that at all. Again take it up with the designer of C# (who also wrote Delphi and TypeScript).
You are implying that he is neither a veteran or expert.
I don't have the time though and I suspect you don't either
happy coding
Yes it does lool.. anyone that ever has written software or done testing will know it. And barely use vars. And nor am I implying the creator of c# is not a vet cause he is lool. But that's for you to judge.
I don't know what any of that means... I keep posting examples you keep posting theory https://www.youtube.com/watch?v=Sa184usBTXk
Use code MICRO20 and get 20% off the brand new "Getting Started with Microservices Architecture" course on Dometrain: https://dometrain.com/course/getting-started-microservices-architecture/?coupon_code=MICRO20
Become a Patreon and get special perks: https://www.patreon.com/nickchapsas
Hello, everybody, I'm Nick, and in this video, I will talk...
So is this guy a professional?
He is
Ok I'm sure he appreciates it 🙂 I've gotta go now
quick test shows that no, the same number getting set does not fire the FieldChangeCallback
Arrays are even more cursed with FieldChangeCallback, they don't even consider the content in the array when you change an element, only the change in array size
Guys, please tell me how to set up the "Dropdown" UI? I need to make a postprocessing selection, I searched the Internet for a long time, I couldn't find it anywhere.
you can handle that. and ensure it changes properly. but in the case of this you wouldnt need that as the size would always be the same. and so would the elements in the array
well thats just something one would need to handle.
yeah could just throw some garbage bits into the high bits and mask them out I guess
You could use the high bits to indicate the repeated call count
or use it as an increment
Even then it won't guarantee that the variable won't be overwritten by other players who are competing for control over the object before your changes make it to other clients
Synced variables just aren't well suited to this kind of application
eh. you can completely avoid that problem. by only having one person be the owner. and have everyone else request the owner to run it. and have some extra checks to ensure it can only be run once per x amount of time. so yes its very well suited to have it synced
But how do you notify the owner of which index to send an event out for without running into the same problem that you're trying to work around
Whatever that solution is would be what you'd use to solve the original problem
u dont.
u request the owner to run it.
the owner does the random.
the requester do not need to know anything apart from telling the owner to run the Method that handles the random generating
Ah that's true if the owner runs any logic from the very beginning of the event that started it all
It is single owner based. There is a master object that handles most of the primary game logic
yep.
all you need to do Ximmer is just find a realiable way to ensure the change always apply
no matter if its the same value or not
for things that need responsiveness, like a player being actively hunted, I transfer ownership to the targeted player so they have good reaction times. If other players happen to be in the way, well they get locally damaged so it doesn't seem unfair to them. Just a in the wrong place at the wrong time moment.
If we do ever get events with parameters, I will be able to get rid of so much jank in my code
honestly https://github.com/Miner28/NetworkedEventCaller use this @plush tiger
it allows you to do events with parameters
and it works quite well
I believe I looked at it and it had a baked in delay if I remember
memory wants to say .33 seconds
could have been, been at least 5 months or so since I was looking for a solution
i am using it and i can confidently say that i can send out 100s of request going across to people with no crazy delay.
apart from the obvious delay that vrc has build in etc
and or the current RTT
even if 10 people click the same time on various things it will send it all no problem
yeah this was probably what I was looking at
i did ask him about it and it was related to something else. cant exactly remember what
it's probably good enough, but eh
believe me it is lol
I'm making a new project with 300-400 synced bools
All of them have to be individual no matter what, since each user will own a few of them
However If I'm doing udon code that works with a set of 100 booleans, is there optimization in having the master package them into an array? So that the end user doesn't have to run code that has 100 boolean vars on it?
You can pack them into an array of ulongs
private const ulong BOOL_MASK = 0x0000000000000001ul; // 1 bit per bool
private const int BOOL_ARRAY_SIZE = 7; // 7 * 64 = 448 bools that can be stored
[UdonSynced]
private ulong[] boolStates = new ulong[BOOL_ARRAY_SIZE];
private bool GetBoolState(int index)
{
var boolArrayIndex = (int)(index / 64f);
var boolElementIndex = index % 64;
var mask = BOOL_MASK << boolElementIndex;
return ((boolStates[boolArrayIndex] & mask) >> boolElementIndex) != 0ul;
}
private void SetBoolState(int index, bool state)
{
var boolArrayIndex = (int)(index / 64f);
var boolElementIndex = index % 64;
var mask = BOOL_MASK << boolElementIndex;
boolStates[boolArrayIndex] = boolStates[boolArrayIndex] & ~mask;
if(state) boolStates[boolArrayIndex] |= mask;
}
I need help. I am getting major rubberbanding and wondering why that is. I tried to look up the solution via our holy mother google, and everything that I can find as a solution, is exactly how I had it to beging with, minus the timer to determin the active and deactive state. I need help... desperatly.
Ball is to spawn at an empty object. the Object is set by the variable public GameObject ballToSpawn;
But instead of "teleporting" it rubberbands to the spawn object.
If the ball is using VRCObjectSync you should use its methods to instantly move it to a location otherwise it'll try and interpolate the position over time. Also you probably shouldn't disable the ball's game object or sync component or the sync won't happen correctly, disabling the ball's visuals might be more reliable:
// Disable ball visuals and interactive components
ballToSpawn.GetComponent<Renderer>().enabled = false;
//...
// Move ball to spawner
var objSync = ballToSpawn.GetComponent<VRCObjectSync>();
objSync.FlagDiscontinuity(); // Instantly move it without interpolating position over time
objSync.TeleportTo(transform.position);
So helpful! Thank you friend 🥺
I am getting an error that it can't find VRCObjectSync
Add using VRC.SDK3.Components;
Got ya.
Thank you hugely, as I think finally after a week of working on this, that I finally got it to work.
I have a question, according to this page, we got
"manual sync are limited to roughly 64690 bytes per serialization"
and
"Manual sync is limited to 280,496 bytes per serialization."
I am very confused, what? which one should I trust
https://creators.vrchat.com/worlds/udon/networking/network-details/
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
The serialization limit per manual sync was raised from 64690 to 280,496.
thanks
np!
and we have a limit from my understanding to send out 11264 bytes per second roughly. so even if we can do 280496 bytes of manual sync it would take roughly 25 seconds to send that out. and thats if its in one go. the more you data you try to sync the less times it will be able to sync. obviously because of the amount of data
Afaik it's more like 6kb/s #udon-networking message
I hope that's not true lol. Otherwise every udon world goes over the limit
Pretty much! A floppy disk is faster.
If OnDeserialization doesn't fire for objects with Continuous sync, then how do I initialize bools for late joiners or do things over the network when a sync'd var changes?
OnDeserialization fires for Continuous sync.
how fast does the set owner trigger btw?
it's a soft limit, you can burst above that briefly but then it will penalize and slow things down later
i see isee. so 6 kb/s is more realistic?
roughly, but it's very loosely defined
@frozen igloo would you per chance know of a realiable way to ensure ownership of a object has happened when someone joins on the client side?. currently i am doing alot of things that requires ownership first before it can process it. but i am running into issues with ownership transfer taking longer per time someone joins. and it causes the 3rd person that join to not see the data in time
Can you provide some example code?
this happens for the master only. sets the owner of an object.
and runs it delayed
this is the local player that joins. they get send from the master that they can run this. but it fails here. because i am relaying on owner of the object being set.
this only happens for the 3rd person. never for master or the second person
it worked for the 3rd person if i set a 10 second delay for when someone joins. to it happening.
@frozen igloo
so you're setting ownership and sending a network event at the same time, and sometimes they are received in the wrong order?
Then if the network event arrives before the transfer, set a flag that it's "pending" and wait for OnOwnershipTransferred to run the actual event
or remove the network event and use OnOwnershipTransferred as the kickoff
the networking event is delayed 3 seconds after someone joins. and targets everyone. and thats also what i am thinking of doing now. wait locally per person til they have ownership. which i realize is prob the best way to counteract the problem
i realize i should prob have done that from the get go ngl lol
I'd overall recommend not trying to solve race conditions with a hardcoded wait 😅
always identify what two things are actually racing, and build it so that it handles wrong orders gracefully
yea no the wait. was purposely added for the master only. but i ended up forgetting that setting owneship can take longer then expected
well it fixed the problems.
So basically if you do a delay it should be clientside? If I'm working with a UI list of names:
On interact
Set owner to local player
Don't tell local player it was successful
On ownership transferred
Set tmp text to owner name
no dont use a delay what so ever. use the OnOwnershipTransferred method and handle it there. atleast i found delay is not something that is consistent to work
Though be careful with this because the documentation says this fires every time ownership is transferred not just when your ownership request completes, which feels like it makes the process a lot more complicated.
no. you can easily counteract it and also it only fires when that object changes its ownership. otherwise it wont. and even then you can do plenty of things to not only prevent but also ensure that nothing happens.
"only fires when that object changes its ownership" - That's what I said. But you then need to code in extra exceptions to handle ownership changes that you didn't request for a purpose, e.g. if ownership changes due to someone leaving, not because it was requested to trigger an action.
you do know that if someone leaves and it changes its to the master. that doesnt matter. and you can still counteract it. all you need to add in is to check if its the master and return early. simple as that.
its one line
then you can't handle intentionally handing it to the master
are we allowed to use mods within the Play Test mode? i primarily want to do this so i can test my networking code against people who manage to be cheating with actual hacked clients so i can at least attempt to make my stuff harder to bypass/ break as i would rather find the problems myself before someone else can abuse it
eh yes you can lol. it still goes to the master. the OnTransferred runs when it is transfered to X person.
In the design you suggested it would presume it was the result of a player leaving, returning early and preventing an intentional handoff from happening
No. in the result of a person leaving and no one new joining. it automaticly goes back to the Master. aka the first one who joined a world. however if you don't want anything that you do in the OnTransferred to happen to the master then you do a simple Return statement. to stop it from running any of your logic regarding transferring ownership. However this does not mean it actually does not transfer. cause it still does.
if you still want seperate functionality for when it returns to a master you can always do a check for it to see if it returned to said master. and handle different logic for that
Ok, so imagine this scenario. You have a grabbable stick with a text UI above it. You want whoever grabs the stick to become owner and then upon becoming owner set that Text UI to display their name. Player 0 is the world master.
In your suggestion of returning early on transfer to master the following would happen:
Player 1 grabs the stick and the text ui is set to their name. It works.
Player 0 grabs the stick back but the text ui still reads Player 1's name. It doesn't work.
thats why i said it was related to the scenario of the previous person... if you need different things to happen. Handle it differently
To be clear what I said was in response to you dismissing the idea that you'd need to code in extra ways to handle cases where you receive ownership without a specific purpose (for example, from the owner of an object leaving). To be clear this is something you have to seriously consider and it is one of the possible downsides of using OnOwnershipTransferred to trigger events.
there is no Downsides. its all about situation and what you need. thats what programming is if you dont need it dont use it as simple as that. and it was still related to that everyone receives it. yes but again only the owner of a object should care. hence the amount of code you need is little to none. eitherway you can handle it without issue.
if master needs nothing to happen. Return early.
otherwise check if the LocalPlayer is the owner and if not. return
and various other ways to handle it. But it all depends on what you need
Yeah that solution wouldn't work for that situation but it's not because the code is bad, it's because what you described is not the same situation as the question which was originally asked. If you have a different situation you will have a different solution, simple as that.
The scenario I am alluding to that started this specific chain of replies: #udon-networking message
yes, and that exact message you linked is referring to handing out one unique object to each player, not just letting anybody grab anything
I'm confused. In the post, they only mentioned a networked object that gets transferred "On interact," correct? Did they make a post somewhere else clarifying that they were talking about unique objects getting handed out to players?
By "UI list of names" they might be imply that they want to create a list of names where each player adds their name to the list by interacting with it. Is that what you mean?
How about a situation when you do want the master to do something when receiving ownership of an object, but not when that object ownership is transferred due to a player leaving? Would you not need to fit into your code and design space ways to identify and separate those two scenarios, right?
yes, that's the entire point. Just write code that does the thing you want it to do?
if you want it to do something different then write different code
So asking real questions with networking:
How do you people test multi-player in a realiable meaningful way? Do you open multiple clients? Do you beg people to come join?
Like.. sometimes, when testing my stuff with others, it's hard to get someone who is knowledgeable about game dev. Then I get a whole bunch of useless (and quite frankly frusterating) suggestions.
There's an input box right next to 'build and test' that let's you launch as many clients as you want
But yeah you'll learn lots by trying it out with friends ✨ even if you don't like their input lol
Try the VRC Quick Launcher it should be available on the Creator Companion
thats not realiable. its a offline test so it wont work the same way.
either you get a bunch of people. Or you make multiple accounts and use the Vrc tool to launch em.
offline testing is good for testing base functionality, including for networking
live is for when you've got things working as far as you can tell and want to test for edge cases; especially if you combine it with other people trying it out and doing things you would have never considered
absolutely not. atleast not in my experience, i switched purely to online testing online now. since it often if not always gives different results if done offline
I've never encountered that personally. Maybe it's something they've improved over time?
The main "inconsistency" I can think of is that in offline testing the networking will always be stable and consistent, something that will not happen in live, especially if you're testing with other people all over the world.
Like offline testing could hide networking race conditions that would only be exposed in live
eh, not as much as you might think. Local testing hosts the files on your machine but the network traffic still does a round trip to the normal vrc servers. It's pretty much identical to what happens when you have two clients on the same computer in a normal instance. The only difference is that other people in a real situation may have different internet connections than you that are faster/slower, more/less stable, or more/less ping to the server
is the new Persistence data saved on the local machine or on a server like should i be worried that someone could just take a file on their machine and edit it then return to my world with things they shouldn't have?
All persistence data in live instances is stored on VRC servers. Data for local testing and development is stored on your machine
will this be synced for late joiners?
also found out it doesnt even sync and idk why
also see: https://vrc-beta-docs.netlify.app/worlds/udon/persistence/#data-storage-in-different-environments
What is Persistence?
i was about to ask about something and looking at this already answered it, thank you lmao ❤️
you’re missing a step or two, and some of it is out of order.
the first thing you have to do, is on Interact, set the local player as the owner of “this” object. then, connect your bool to unarynegation, and connect the negated value to the “Set (bool)” node. and connect the Set node to a SendCustomEvent, and RequestSerialization
then, in the OnDeserialization event, connect that to another SendCustomEvent
now create the Event Custom node, name it whatever you want, and connect it to Gameobject.SetActive. then connect your gameobjects variable to the Gameobject.SetActive, and connect your bool to it aswell
basically,
- set ownership
- change your synced variable
- request serialization after changing a synced variable
- apply that (now changed) synced variable to whatever you need it for
- OnDeserialization, apply that synced variable in the same way
like this?
it worked! thanks man fr
oh and happy birthday @timber ferry
its mine too
we were born on the same day
still a little bit off, so it’s not 100% working
in the Set Owner, just remove GObjs. that’ll set the player as the owner of the object with that udon graph
then, you don’t need the OnVarChanged node, that’s better fit for floats or ints most of the time. use a Event Custom instead, so that you can just call that event after changing the variable, and in OnDeserialization. it’s a little simpler than duplicating the whole SetActive bit
thanks and happy birthday to you too, cool that we have the same birthday
thanks broski
and fixed it
Does clientsim work correctly when simulating spawned remote players when it comes to network events?
I've made a very simple VRC Player object that should create a the object per player, and when the object is created, it checks if the local player owns it and if they do it starts ticking up a timer and syncing that to the other players.
But when i spawn a remote player im not getting any data back via OnDeserialization like i'd expect?
Hmmm yea seems to be working fine ingame but not in the editor
No, clientsim remote players don't have their own entire client behind them, which would be necessary for full simulation. But that's not really something you can do in unity.
So instead, clientsim remote players are just a bare bones representation of what you might see a remote player as. There isn't anything behind it.
Just upgrade to unity 6 and use those new multiplayer features I saw mentioned; ez clap
That would break every existing system.
Just got my world broken on this update, and found out RequestSerialization during Start does not synchronise to Late-Joiners
am I late to the party?
yes, saw this on canny too
I was expecting more people screaming at this bug, seems like not many people do RequestSerialization at Start
Pretty much all syncing things seems to be broke for my world
Including object sync. Items just aren't where they should be for late joiners
OMG
Yeah its a bit of a pain
I have these gates that get toggled in to stop people walking in and ruining things for people playing inside and now my worlds unplayable as the gate just opens when you rejoin
Thank you vrchat update very cool
Not intended as an argument but why would one RequestSerialization in Start? Start methods calls are uncontrollable. They are guaranteed to occur early one but not in any particular order and dependencies between classes cannot be controlled at all. I'm trying to think of a scenario where late joiners require syncing data with other players prior to having their environment stable. Could be a reason I can't think of one and haven't encountered the need.
I feel like the issue is more than just Start.
My world relies on Cyan's Object Pool and I have a similar issue. I don't think I or the pool does anything on Start though. Still need to look into it more.
Or maybe I'll just start replacing it with the Persistence Player Objects
This broke my world last night. I just moved requesting serialization onto an event call a few frames after start.
most people here are learning programing and don't have proper knowlege of things. Requestserialization at start worked for a long time, and many people built things around it even if it wasn't "proper." So as far as most people know, the "vrchat update broke muh wurld!"
Yeah, I'm not about "proper" as much as what sort of data one was sync'ing. I think of Awake, which we don't have access to and Start, which we do as similar to a constructor in C#. One can't call Internet and database operations in the constructor but it means the constructor could throw an exception and nobody wraps their object instantiates in try/catch blocks.
Agreed, the issus is not just RequestSerialization in Start. Still checking what the problem is.
That said any breaking changes should ordinarily be mentioned but it isn't a science. Why didn't anyone notice during beta?
that's most likely due to that Vrchat Udon Start was not in line with how Unity handles the Start. and since it does now. its most likely cause well its no longer starting in some order or anything.
I don't know if there is a reasonable way for you to post examples but I'd be interested to see them if they are UdonSharp (I don't do graph) 🙂
to note this is what they said
Udon Start order is now consistent with Unity.
Start will be called on all active UdonBehaviours in the same frame during initialization.
also if your having the issues atm try to delay it a few frames. some people have suggest that is the current workaround
I only use graph sorry
also you can control when something should start. https://docs.unity3d.com/Manual/execution-order.html
Someone may be able to assist if you post one example. There may be a pattern you use that should be avoided or at least could be done in another way. I for instance never do any "app" logic in the Start method. It is used to associate game objects and components only because I know those items exist by the time Start is called. Oh an only those on the specific game object. As I mentioned other Start methods may not have occurred so you cannot rely on anything else being ready.
I do not believe it works in Udon but nobody uses this mechanism that I am aware of.
it does
u can control what order things execute well it will be the order of whatever script your setting it to be
so if you set lets say PlayerManager to Execute before other scripts then you can gurantee it to execute first.
well apart from Udon and unity necessory things.
Good to know... but I doubt that many people use it.
Oh, perhaps I should have clicked on the link. I thought you were talking about prioritization of Start calls. The order of execution for event functions is pretty much understood.
its also pretty risky. cause it can fuck things up
I'm doing this for global synced toggles and worked perfectly fine until the update last night causing the sync issues with late joiners
alright so my issue seems to have been fixed by changing from manual to continuous on the synchronisation
Reading graph doesn't read like English for me but I'm a coder by profession. So, this appears odd to me. If you have the time an explanation of the purpose would help. It looks like you both sync a variable and call SendCustomEvent. Also what is the purpose, the late joiner would get the setting so are you toggling something for a user that other users need to be aware of?
Without knowing the details let me offer that this is a bad idea. One should avoid "well it seems to work now" type solutions. It's code based on logic and shouldn't rely on hope.
I'm not entirely sure how it works I got it from a tutorial awhile ago. Its purpose is to activate certain objects around my worlds such as a gate to stop people entering an area when others don't want them to get through if that makes sense
I suspect it was borderline not working but did more often than not under the circumstances. Toggling an object is quite straightforward and there should be a lot of examples. I don't see how this particular example is tied to Start though. Most certainly some logic may have been affected by the update, it is the nature of library/api updates. These are typically recognized as breaking changes but could escape detection if atypical logic is used in some places.
Ah ok makes sense, Sorry for wasting your time with this
It looks like request serialization is broken as a whole now. I thought Id fixed mine by changing to continuous but breaks again once whoever toggled the object (in my case) leaves the instance
are you setting the owner to whoever is requesting to use the object?
Yeah I believe so, What I'm doing is here
so im trying to swap a game obj mat for everyone but its not even swapping and idk why
also this isent syncing why?
So there is no misunderstanding you didn't waste anyone's time. Learning things is primarily why we are here.
there's multiple problems with it, you don't even use the Clicked's value on the Clicked Change event, also, if you only have 2 materials then you also don't need the SelectedMat Change and SkyMat Change events
here's a working example:
I have that problem too
does player need to be owner of object to update continuous sync
yes
Question about Arrays and networking. Do remote clients on the other end need to know the size of the array before it runs through Deserialization? Like, Do I need to construct the arrays on their end with the correct index slots before the tables can be filled?
or can I set a array with any index slots and fire it off via network?
for synching?
pretty sure you don't need to worry about all that; pretty sure whatever array a remote player has get replaced by the new array that gets synched with them
cool
how would i make this synced for late joiners?
also sorry for late reply
i was super busy
Afaik you need to ensure the array isn't null on either end.
As long as it's just an empty array it'll be fine
how do i make this synced for late joiners
- Start by making a synced bool.
- Add an onvariablechanged for that bool and inside of that event, you simply apply the change to the animator. As a result, any time the bool in the script changes it will be automatically applied to the animator as well.
- Then instead of using a network event to change the bool, you should set this script to manual sync and then do the following in the interact method:
- Set owner to the local player
- Change the bool with SendChange set to true (to be clear, this is an onvariablechanged thing, not a networking thing)
- RequestSerialization
So if you have a player object with a manual networked script that has a boolean, is that boolean ment to reflect accross all instantiated objects for the players, or are they ment to be individualized? I'm asking cuz I have a boolean that when it sets for one, it seems to set for all the same
only the initial editor state is shared. Once theyr're instantiated they're entirely independent
gotcha, i think the issue i mighta had is somehow having the state set by anyone, cuz when the request serialization was behind an isowner check it worked
If you set and sync a synced variable, others will receive that and apply it into the playerobject that they have instantiated and assigned to you on their client
you mean on the OnDeserialization event or somthing else?
oh you mean when they get an instance of the object?
I mean if you have player A, B, C then it will instantiate 3 objects. Each player gets one. And more importantly, all 3 clients instantiate all 3 playerobjects. Player A will see B and C's playerobjects and when B sends a synced variable, A will see the variable show up on the object that they have instantiated for B
it's effectively identical to a player pool where everyone has a unique object and people can see each other's objects
right right, i suppose i just need to muck around with it more and understand when things change and when they dont. Putting everything that changes a syced value behind and isowner check least solved my issue for now XD
ill just muck with it, thanks for the insight!
Does anyone have benchmarks for udonsynced byte[] vs string?
huh? its two vastly different varibles. one is an array the other is not. and also a byte is just 1 byte. while a string depends on the amount of characters. its typically 1-4 byte per char. so a 4 letter word would result in 4-16 bytes
yeah but I asked for a benchmark
"depends" is not concrete
The evening is over for me so I will get more concrete results next time I work with Udon. The current spreadsheet tool I use may have updated already, however, the version I use does not try to estimate string
stats shared by Centauri that preceeds this spreadsheet
sorry that was array, below is just string nearly a year ago
performance in what sense. and for what purpose? you wont have more or less performance doing one or the other. unless you mean in relation to how much data it uses?
I have never mentioned performance for the benchmark, though my question is open ended and a performance benchmark is also welcome. Please see the screenshots for the size estimates that I am trying to piece together
well byte[] over string any time. less data used. which means you can send more overall data. However complexity also rises in how you handle it and what you need to do to well Convert to bytes and back from bytes to other types of data.
for instance a 20 character string would result in 20-80 bytes just for that one person. while if you did it with bytes your likely online going to be using 1 or 2 bytes.
oh and the amount of data a char takes depends on the unicode.
Thank you for your input, I highly suggest the screenshots which estimate the overhead of byte[] and string. I am also aware of this, and hoping that someone has delved deeper than the surface level knowledge we are discussing right now
on average a 64 size byte array filled will take roughly 76 bytes. atleast in what i am doing it does.
i never try to sync strings as thats to much data
Ideally (but we don't seem to do things this way here) code could be written and made available to all that performed the benchmark tests. That would a) permit anyone to test the results they get and b) open the code to review (and/or updates). Raw benchmarks can be affected by a lot of conditions and code in the wild may (I would say "often does") not benefit much from the results. Loops where no loop was required, using GameObject.Find, etc. will overwhelm the types of minor benefit one will typically get from tiny optimizations. That's my opinion and I'm sticking to it 🙂
Hey, so I was working on some basic stuff and currently I have some buttons that play animations and hide some objects. It's all synced for those in world, but de-synced for late joiners, does anyone know how I could fix this? If pictures of the stuff is needed then I can send some
everything shoud be done through serialization and you shouldnt use network events for anything long term.
huh, weird, whenever I press a button to hide an object, others in world will see it hidden, but when I re join the item is shown for me but still hidden for them...
although, this is an old prject using 2019 so idk if that's affecting it XD
it doesnt
There is very little "weird stuff" going on. That something seems odd can often be attributed to not quite understanding what is happening or needs to happen to make multi-player apps work. Think of syncing as "data messaging" and it might help. You can sit in a chair in your room but we don't know that. But you can tell us by sending a message aka "syncing" an IsSitting bool value. You have transmitted the value from your space to all other participants.
Keep in mind that it is only "data" and what happens when the data arrives is completely up to the code. If you output "Cake is sitting" to a log or change the color of a material it doesn't matter. It doesn't make people "sit" it only transmits data. Your code determines what happens.
Unless I'm confused about what you mean by benchmark, shouldn't this be easy to test? Make two Manual Sync objects, one with a string and the other with a byte[] version of that string. Serialize both and use OnPostSerialization(SerializationResult) byteCount property to determine the size.
And iirc OnPostSerialization is also called on continuous sync.
@obsidian cedar uhm. the latest update from you causes things to completely break now btw. i am just getting Caller not assigned unable to send method
Find the networkmanager and press Setup
I plan to push update today/tomorrow that does it automagically
for now just press it manually
ooh. so no longer do i need to set how many etc?
Nope
It uses persistence
Or rather
Player Objects
so automatically scales up
alright. thanks 😄 for the quick heads up
Is ownership not transferring at all?
Been trying to find a fix for my array-toggle script. And this is where I got to. HOWEVER. It does not appear to be transfering ownership at all.
This is what it looked like before the update and worked perfectly fine.
hence "The evening is over for me so I will get more concrete results next time I work with Udon."
maybe you can try remove the Networking.LocalPlayer.IsOwner(this.gameObject) check from the ToggleItems method? and replace it just with a Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
i had the same issue when my ownership transfer didn't work, and it was because i had the same ownership check like yours
This was not in the original version of the script and it still did not work. The check was not in the version before the update.
public void ToggleItems()
{
Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
Placeholder = new bool[On.Length];
for (int i = 0; i < On.Length; i++)
{
Placeholder[i] = On[i].activeSelf;
}
OnGlobal = Placeholder;
Placeholder = new bool[Off.Length];
for (int i = 0; i < Off.Length; i++)
{
Placeholder[i] = Off[i].activeSelf;
}
OffGlobal = Placeholder;
Placeholder = new bool[Toggle.Length];
for (int i = 0; i < Toggle.Length; i++)
{
Placeholder[i] = Toggle[i].activeSelf;
}
ToggleGlobal = Placeholder;
for (int i = 0; i < On.Length; i++)
{
On[i].SetActive(true);
OnGlobal[i] = On[i].activeSelf;
}
for (int i = 0; i < Off.Length; i++)
{
Off[i].SetActive(false);
OffGlobal[i] = Off[i].activeSelf;
}
for (int i = 0; i < Toggle.Length; i++)
{
Toggle[i].SetActive(!Toggle[i].activeSelf);
ToggleGlobal[i] = Toggle[i].activeSelf;
}
RequestSerialization();
TextUpdate();
}
This is how that function currently looks and still does not work.
where do you call the ToggleItems method? do you have it on a UI button or something?
It is on a UI button.
Explained more coherently in this thread.#1310001305217138809
does the code executes if you already the owner?
Yes.
And it Appears to execute locally if you are not the owner but not sync with anyone else. And stops syncing with the owner afterwards.
I don't really know what could be the issue, but it's kinda hard to guess without knowing more information about your project
but if you want to sync, you would want to use the OnDeserialization method for that I think, not the OnOwnershipTransferred
The FieldChangeCallback("ToggleGlobalChanged") header causes the ToggleGlobalChanged set event to trigger when that variable is changed via RequestSerialisation. I did try OnDeserialization, but that did not work.
OnDeserialization works if you're using it correctly. But OnDeserialization is only called on remote clients; for the owner of the object, that logic needs to be put into either OnPreSerialization or OnPostSerialization
Pushing a version right now that does it automagically on every build, so even if you break your network manager somehow 😆 it will re-do it on next build 😅
As stated previously, the script worked prior to the update. But has not been working since.
Did you update to the fix?
I did. Still not working.
so your project has this?
Yep
#1310001305217138809
Also, for a bit of clarity on what I am asking.
Huh. Wondering why it is not working for me then.
Are you using OnOwnershipReuqest in your code? That can prevent an ownership request if it returns false for either the owner or the requester
No. I am not using OnOwnershipRequest.
You can try putting logs into the different parts of your code to see what is and isn't being called
So. For whatever reason. ThenChanged events were not firing whenever variables were changed right after ownership transfer despite having the [UdonSynced, FieldChangeCallback("ToggleGlobalChanged")]. In addition, the if(OnGlobal == null && OffGlobal == null && ToggleGlobal == null) was causing issues (not sure why) despite being fine prior to the update. After changing to OnDeserialisation AND removing the if statement, it is now working.
yea. it works after i did change 😄
@obsidian cedar uh. something broke with the latest update you did. i am getting the No caller assigned again. even through it worked prio
Ye I noticed some weird behaviour as well
The auto-fix seems to auto-break it
xD
i rolled back to 3.23 for now 😄
👌 I'm gonna be trying to figure out why it's being weird 😅
Or might just roll-back the change
we'll see
But I'd love ot have auto-fix implemented.. Just one that works bettter xD
yee
do we know how many Bytes The VrcPlayerAPI takes as a reference? when sending over the network
Not sure though I'm pretty sure the recommended method is to sync the player ID and then get the player API form the sync'd ID instead
And once again I will toss out the idea at we are not leveraging the tools at our disposal. Granted I'm not writing them but then I don't particularly want to know byte counts. If this was a C#, JavaScript, Node or TypeScript system there would be all sorts of code/apps that people could use to report things in real time. Spreadsheets about how things used to be or "on my old computer", and guessing, etc. are no substitute for simple tools that provide facts.
Is anyone aware of a repo that has such tooling? It could be extended every time questions like this come up.
eh. this is C#. just heavily limited C#. and also why wouldn't you want to know the byte counts? its essential for networking to reduce it as much as possible
not what i was asking for.
What are you asking for?
Also I imagine references are non networkable anyways
Also since VRChat is a 64-bit application, the references are going to be 64-bit (aka 8 bytes)
well i kinda asked a question but i realized what i asked i already knew. its 8 bytes and we can network VrcPlayerApi. its the only reference type we can do
Is that always in a fixed place in memory no matter what?
Because that's all a reference is, a pointer to a place in memory
fixed memory i cannot gurantee since i dont know how they coded it in place. but i imagine it would be.
At the very least it's not something we'd ever need when we can get the VrcPlayerApi via the id, something half the size
eh for what i am doing it kinda is. its the difference between a ever changing ID when people join a world and a fixed way to check who owns a object. by already having that known.
the id for every VrcPlayerApi is fixed
it increments for each new player who joins and is never reused
the id is a guaranteed way to get the correct reference
@finite sierra you can network sync a variable of VRCPlayerAPI type?? :O
No you can't
Oof misunderstood
At least not directly; and converting it to a byte[] likely has no guarantee to work unless a VRC dev can say otherwise
Syncing the player id, while taking a couple extra steps to get it from a VRCPlayerAPI and then convert it back into a VRCPlayerAPI, is the equivalent
Yeah but syncing the player ID wouldn’t catch them if they rejoined, if that’s important
no.
The VrcPlayerApi reference would become invalid anyways
Although if you want to know if a player previously in the instance rejoins I think you can use a PlayerObject syncing a player Id for that; iirc syched data on a PlayerObject, even if you don't have it marked for Persistence, still has instance-only persistence
https://creators.vrchat.com/worlds/udon/data-containers/data-tokens actually it wouldn't
Data Tokens store data. Each token stores one and only one variable. Data Tokens are used in Data Dictionaries and Data Lists.
seeing how they say DataTokens can use References.
Actually you probably don't even need to do an id; you could just give the player a "isFirstTimeJoining" bool
Damn, I really got a mess around with persistence because that makes a lot of sense considering when they first mentioned persistence, they said that there would be instance persistence and like world saves
But that sounds really cool and really helpful as a neat little trick with player objects
Actually I can't find where PlayerObjects were discussed having instance-only persistence; maybe I imagined it...?
I just tested it and that is not how PlayerObjects work; must have been a dream or something, lol
Instead if you wanted a feature like that and were willing to have it cut into your your persistent data limit, you could have the first person in an instance give it a random int Id and then everyone has a persisted "idOfLastInstanceJoined" variable
This would have a chance of failure, though, if two rng numbers happen to match (depending how the rng is created, this would have a max lowest chance of happening of 1 in 2,147,483,648 with an int)
Some key points are being lost in translation.
maybe? it happens now and then 😄
so with persistance, I wanted to make somthing where each player has their own set of several spawnable objects which are synced and can be manipulated by them. Is it a bad idea to have a set of several vrc_pickup objects with vrc_object sync on a VRC player object that you "spawn" by show/hiding them?
It's not a bad idea, just be aware that disabling synced objects can be unstable and you may have to wrestle with race conditions or late join support. It is possible though. One good way to handle it is to have all the objects enabled by default and then only disable once they have initialized and you know for certain they're not supposed to be active.
the objects themselves wont have any data I care about, just their position
but ya, they are enabled by default and disabled based on which should be shown
i have a synced bool array that designates which should be shown or not
Right, that's the structure I would expect. The important bit is that during initialization of late join, it's possible (and likely) for the position of the object to arrive before the bool array says that it's active. As a result the object never deserializes its position. Then the array comes along later and says that it's supposed to be active so it enables it, but then it doesn't have its position data.
That's why the solution is to keep the object active at the beginning until you know it needs to be turned off. That way, if it's supposed to be turned on, you will receive its position data correctly and then the bool array will come in after and say "yep that's supposed to be active, you can stay like that"
gotcha, i suppose my only worry since vrc object sync has them as continious objects, if a lot of people are in the world would there be network congestion if say you have a lot of objects per player this way, like say 50 of them
I don't have a definitive answer because that depends on many factors, especially how often the objects are moved. If you're only moving a few at a time it should be fine. But as far as late joining goes, it should be able to handle that because the owner does not need to re-send the latest state when a new person joins. The server will cache the latest state and send it directly to late joiners.
As long as the late joiner is actually capable of receiving that burst of state (as in, the objects are not disabled) then it should work.
alrighty!
One of the biggest foot guns in this topic is when you disable objects on start, which breaks the server-side late join support, which causes you to just blindly re-send a bunch of traffic when someone joins so that hopefully they get it eventually, which just clogs the network because that's a lot of data
What if you disable them the frame after start?
That's one way to make sure a behaviour's script is initialized but not the networking. Networking arrives later
what if you don't disable the objects, but just the components required for interaction [renderers, colliders, etc, but not behaviours]
Yeah, that's fine as far as networking goes
I had assumed if you had an object active at scene load and it's Start had a chance to run, then its networking would eventually be resolved even if the game object was disabled. I guess not
Eh, it's possible but I wouldn't rely on that
Should be a non-issue anyway if the enabled state of the object is synced by a separate system anyway. Like I said, if it's supposed to be enabled then it is allowed to just stay enabled the entire time. If it's supposed to be enabled then we don't care about receiving it's position.
This would only become a problem if you are syncing the enabled state directly on the same object or if the disabled object's synced data is relevant for other things outside of itself which are not disabled. And that's exactly why we recommend against doing that
Last time I made a prop pool like this, I took this a step further and made it so that each spawnable item is actually a "template" which gets instantiated under the synced object. So during initialization, they stay active but they're effectively empty gameobjects because they don't instantiate their templates until later
At that point, may as well actually instantiate gameobjects. so long as you can pull any udonbehaviours off of your template it will work
Ok, so let's say I have a system where a synched object that is always active has the ability to sync enable/disable an object.
This enabled/disabled object has children that have their own synced values. At scene load this object is enabled in order to do some initialization stuff and then disables itself on Start if the sync value tells it to (it defaults disabled). Will the child objects of this disabled object have that broken server-side late join support you mentioned before?
Additional question, if in the OnEnabled for these child objects the owner changes their manual sync networked values to a default and then requests serialization, is it guaranteed that those updated values will reach other players once networking has then enabling those objects as well?
That is covered by the exact same concept. Just make it default to enabled during initialization. Once you've received the sync state and it says disabled, you can disable. Or when you've received the synced state that says enable, you can just leave it enabled. As a result the children will be able to receive their syncing reliably and uninterrupted if it's intended to be enabled
As for your question about onenable, I'm not certain enough to give any guarantees but that does seem like something that should work
What about the first person in the instance? I assume they would want to do a RequestSerialization even on unchanged values and only perform the object disables in OnPostSerialization, right?
Yeah exactly
Thanks for the info; I had been assuming disabling stuff on Start was ok to do so this would have caused me a lot of headache
Can just do an isowner check in start. If that's the case and you want it off by default then set the synced bool to false and request serialization. Then in postserialization apply the bool to the object by disabling is
Oh on a final note, VRCObjectSync seems to have the ability to "sleep" a networked object which appears to turn off its networking until it starts moving again. Is behavior present in any continuous sync behavior? And if not, do we have any access to manually sleeping/awaking continuous sync behaviors?
If you put a continuous udonbehaviour on the same as objectsync then it will follow the same sleeping behavior, which is not usually desired. There is no way to manually set a continuous to sleep. I'd recommend avoiding relying on this behavior to intentionally make a continuous behavior sleep
Unfortunate, but thanks for the info
what if you disable the objects 1 frame after the on player joined event where the VRCPlayer is you?
also thanks for the help btw ^.^
There is no timing connection between OnDeserialization and OnPlayerJoined (especially your own onplayerjoined) so no, I would not recommend tying it to that
hmm...what about the OnPlayerrestored? I essentially just need to be sure when a player gets their version of the player object to set the pickups to disabled
It is always important to make sure that if you are waiting for something to happen, you actually wait for that thing to happen. It's not good to wait for a different unrelated thing and then add a hardcoded timer to it. That is the root of all race condition issues and it's one of the most significant drivers behind stuff breaking when we make some certain things faster or slower
right, but what is the way to identify when the network is done?
OnPlayerRestored will only fire after playerdata and all PlayerObjects associated with that player have received the persistent data from the server. So if you are wanting the saved data from the server, then yes that's appropriate. But be aware that it's possible for the server's data to be slightly out of date, and you may get a more up to date set of info directly from the owner just a moment later.
like a frame or a second later kinda deal?
overall the main script will never be disabled which will have the player data, But i dont want to cause network strain because the tools and spawnable objects are disabled
the tools have a script which does things, but doesnt matter unless the player is handling it. The spawnable objects arnt ment to do anything but be picked up and moved
Again those are two things that aren't really connected. If you want to wait for the actual data from the user then you should wait for ondeserialization on the object that you are caring about. If it's a lot of objects, then you should wait until they have all received ondeserialization
so i would require adding a script to them
i was trying to avoid the spawnable objects having a script and just using vrc object sync
also for initialization the main owner isnt going to receive ondeserlization, im trying to find when they would tell the objects to get disabled
I'm not quite following what your whole structure looks like, could you elaborate?
so main structure is the parent object has the player object on it, and the main script. This will also have all the values I want to save. This script also controls if the spawnable objects which are ment to be props they are fishing for are visible or not. The props have a vrc pick up and vrc object sync. There is also 2 tools, each with actual scripts which will interact with the game. I want these to be hidden once a player gets their player object, but since starting disabled is better I was trying to figure out based on what you said when they should be disabling themselves
or more spesificly when the main script will disable them
is this going to cause networking congestion this way?
its late XD i think i kinda get it. Try best to have them enabled in the project, disable them with start if you're the owner, let late joiners use ondeserialize for disabling
they should default to on and only be disabled when you know that they are supposed to be disabled. That's it. If you use any other metric to disable them, then it is making an assumption that may break if timing changes
so..if they need to be disabled by default then disable them on start if you're the owner, and late joiners disable them if a value designated by the owner says they should be disabled?
yes
so im trying to get this to sync with other players it works but only localy so i had another script set this bool
the script and button that set this bool is turned off for everyone but listed admins and i was going to use this separate script to send these changes the admin does so like toggle game objects or swapping mats
I was just searching to find how to make my NPC's do this and you seem to have a tone of experience so i will follow your pictures and see if i can get this to work too 
@red rivet I will be around to answer questions on friday or when ever i get back to discord. Im gonna beon vrchat for the rest of the night probably
Ok no problem thankyou, if you get bored check out my map, it's a story map and a work in progress, going to have a fable like system where you can choose good or evil (beta version has it) map is called Four Sisters Lakehouse. I will try to do this guide you provided above and bother you on Friday or so if i get stuck xD
@red rivet this one? https://vrchat.com/home/world/wrld_5b710946-4b5a-4903-83e1-b83665dfd9e7/info
Yes
I think most people join, look around and leave without knowing you can progress through the map. The very first version was average, i have updated it 2 months ago and improved it alot, i plan to do mocap for custom animations later now i got face and eye tracking headset. With the avatars turning to look at you feature i am trying now, few more improvements here and there and finishing the story branches i hope to make a somewhat decent map for people to enjoy. Have a lot to learn 
how do I serialize for just one person?
you can't
Is “dude” marked as synced?
You also need to make sure the owner is the one seralizing
Also, using setprogramvariable doesnt trigger onvar events as far as i know.
Id send a custom event to this script instead
Use a PlayerObject that syncs the data you want to sync + the ID of the player you want to send the data to.
Then in OnDeserialisation, check if the synced ID matches the local player's ID, then process the data.
oh opps thanks
can i then destroy the player object after?
I mean you can technically destroy a bridge after crossing it. But one would assume you'd plan to keep using that bridge
Also depending how player objects work, trying to destroy one could result in unintended behavior
It would be a box collider for verifying players with a scanner or something after there verified I could get rid of it right? Or would do anything crazy?
@obsidian cedar new bug with 3.2.0 - 3.2.5 this happens once a person rejoins and this works on version 3.1.15
Is that just a normal DataToken error?
That is a DataList subscript out of bounds exception
nope
@obsidian cedar forgot to include the important part.
yea. i had to revert back to 3.1.15 for it to work again
That also looks like a normal DataList error if you try doing anything with it before defining it
for example this would produce that error iirc:
private DataList myDataList;
void Start
{
myDataList.Add(10);
}
eh. it has nothing to do with that lol
that's what the error message you posted tells us, lol
it's probably the start order change that is confusing you
Oh I see, this isn't even your own package. That's why you gave a confusing version name
I'm just reading the function signature. The exception itself is the DataList not being assigned.
The source type is DataList, the function being called is _get_item which is the function to retrieve a subscripted item. It takes a int32 which means it's a numerical subscript, and it returns a datatoken.
Quite confidant that the line that is failing is akin to
var something = myDataList[myInt32];
But the data list is null.
When a player leaves an instance who has networking ownership of an object, can Networking.SetOwner take place within OnPlayerLeft() before another player receives NetworkOwnership by default?
I want strict control of the network owner to prevent, when possible, network ownership from being given to someone not in a specific VRCPlayerAPI[] array.
//Do not give network ownership to a player that should not have access.
public override bool OnOwnershipRequest(VRCPlayerApi requestingPlayer, VRCPlayerApi requestedOwner)
{
if (_tXLAccessControl._HasAccess(requestedOwner))
{
return true;
}
return false;
}
//If the local player is not on the access list and a new player joining is, give them the network ownership.
public override void OnPlayerJoined(VRCPlayerApi player)
{
if (Networking.IsOwner(gameObject) && !_tXLAccessControl._LocalHasAccess() && _tXLAccessControl._HasAccess(player))
{
Networking.SetOwner(player, gameObject);
}
}
//After a player leaves the local player may be given network ownership, so check if the local player has access; if not give network ownership to a player that does.
public override void OnPlayerLeft(VRCPlayerApi player)
{
if (Networking.IsOwner(gameObject) && !_tXLAccessControl._LocalHasAccess())
{
int playerCount = VRCPlayerApi.GetPlayerCount();
VRCPlayerApi[] allPlayers = new VRCPlayerApi[playerCount];
VRCPlayerApi.GetPlayers(allPlayers);
for (int i = 0; i < playerCount; i++)
{
if (_tXLAccessControl._HasAccess(allPlayers[i]))
{
Networking.SetOwner(allPlayers[i], gameObject);
return;
}
}
}
}```
Another reason I'm asking is if OnPlayerLeft happens before the leaving player loses network ownership, the last function here will never work as intended.
If I need to rewrite as if the leaving player still has network ownership, I'm curious how I could determine a new owner without creating a race condition within OnPlayerLeft.
Hm, I don't think that's strictly possible because when a player leaves, the new owner is assigned by the server. That means two things:
- it's all happening a fraction of a second before other users in the room ever know anything about the player leaving or the object transferring
- this type of transfer will bypass OnOwnershipRequest
As a result, I'd probably say that the best way is to hook OnOwnershipTransferred and if the new owner is somebody who you don't want to have it, just transfer it off to somebody else.
That should work for the functionality but I should warn you that if this is a security critical part of your systems, I don't believe it's possible to do it in this way. If that is your goal, then a better way to ensure that data only comes from authorized sources is to use playerobjects. Playerobject ownership is strictly limited so that only the person who the object is assigned to can sync data on them. This means that they should be usable for security critical purposes where normal objects cannot, even if they use OnOwnershipRequest.
That is absolutely genius and I greatly appreciate you pointing out that avenue. Native Player Owned Network objects is such a fantastic tool.
if it only was that simple 😄 its a deeper thing and more complex then that.
Oh for sure, cause can be very complex and dependent on many things. Udon can be a pain to debug as the vm doesn't give a good stack trace
hm... i dont think this is right.. like.. the plan is that the instance owner sets the random number, which is sent out to everyone, then used to set the animation to be the same for everyone..
But this may just scramble it so everyone sees something different instead
I want to roll randomly once, then send that to everyone to synch the animation for everyone
I had an Udon user leave event fire before the user join event ever fired yesterday which was interesting.
@cedar flume then random part should happen somewhere before request serialization, not on deserialization.
ok, something like this?
do i set the variable up like this? then if the owner sets a random value to this variable, it will be synched with everyone? then ondeserialization, it applies this values to the animators?
What do you mean?
ok, so I got this now.. it does throw an exception when testing tho
thanks, the video helped me understand networking better
not sure where its breaking tho..
ima look at the logs
youre not setting any synced variables still
if you need random value for each and every animator (i initially assumes like 3 animators with 8 snimations each and you need to all of them to run say, anim #5) you need to store synced int[] (or ideally byte[] since i doubt you need more than 256 animatiins and its less networking but whatever)
oh wait, you actually do use same value everywhere
you set variable in same behaviour by judt using set, grag your variable into canvas while holding Ctrl
Diving into this as a potential best practice, running into an issue where each Player may end up creating race conditions with each other and causing desyncs. Is there more to this that I'm overlooking?
setprogramvariable is to interact with other behaviours
I imagine I need to sync timestamps, but for keeping things lightweight I'm stuck wondering what could be a more elegant solution.
what's the race condition?
Players having different latency between each other.
ah..
I am trying to use one variable to set a random value of 1-17 for one animator to switch between 16 animations
Do you mean the normal gotcha that applies to any sort of networking activity? Generally speaking the way to get around that is to devise an approach where actions are fired by events that have guaranteed that all relevant networking has been complete
although I am triggering both animators, just that the values for triggering the animations of either animator doesnt overlap..
Player 1 and 2 pushes a button about the same time
Player 3 receives Player 1's button press first
Player 4 receives Player 2's button press first
I might be able to create a buffer time window locally and then assign a default winner somehow, but it needs to pick the same winner for everyone every time.
Even then, that's not foolproof.
Timestamps of the server time could help I think, but that's a lot of extra network data if I don't handle that elegantly either.
For a script that syncs a bool, as is there's 7 bits of unused data. Thinking I might be able to sync a byte to make use of the full byte that's synced and then use those bits to assign a weighted priority.
Kind of like a dice roll along the bool I intend to sync. Could use the full 8 bits of a byte dedicated to this priority roll if I'm trying to sync any other kind of variable.
where do I put this new block?
does sendChange send it automatically across the network?
do i put it here?
figured out why it was throwing exceptions (forgot the actually put the animators IN the array)..
it does successfully generate random values, but it shows an error of 'Hash # does not exist'
SendChange causes it to trigger an onvariablechanged event. It has nothing to do with networking
ah... is it generally not needed in this case then?
If you're not using onvariablechanged then no, it's not needed
ok
not sure how to fix the hash problem.. though i feel that its likely unrelated to networking and that i should take it to a different channel
I'd like to tickle your guys big brains if I can!
I'm working with persistence, players can place and remove objects. As it stands, this works by the placer tool raycasting the position to place an object, on click, a call is sent to Master who then spawns the object and saves it in persistence. (PlayerData, I'm using a grid system so just a ushort for X and Z is fine to save memory, Y can be determined on object spawn)
This works super well, it's perfectly synced across both clients, however... The update rate of persistence is very slow, especially with a lot of data changes being done quickly or at the same time.
I'm wondering if anyone has any thoughts how I can combat this? How can I make it 'feel like' an object spawns instantly/'fast enough' on remote clients yet ensure they haven't placed a 'ghost object' that never appears for the Master?
(If only we could send arguments over network)
Many thanks in advance. ^^
(Oops, this was in the udon-general before, my bad)
If you plan to serialize the data a lot you should probably put it on its own PlayerObject separate from everything else; so you don't have to constantly sync a lot of unneeded data and have that clogging your networking
I've thought about this too, but in this case we may have a hundred+ placed objects.
I was worried the overhead from individual udonbehaviours would have its own issues there!
Better than updating the information on every single one of those objects whenever even only one of them changes
Maybe compromise and split your scene into chunks and have objects in a single chunk be synced and persisted in its own behavour
Sounds like you are using the wrong tool for the job. Player Data is not meant to be used for updating everyone else about instance changes. That's what synced variables are for. You should use Player Objects to mimic sending arguments. Add synced vars to it like position and object type. And then add a synced counter to it to identify unique changes. So when a player places an item, you use their own object to sync that placement event to everyone else. Persistence saving can be done separately afterwards at a lower rate. And you'll only need to load it once at the start where you prevent the player from interacting until they get all the data.
That's what I'd have done at least
Persisted variables ARE synched variables. Having separate "saving" variables for already synched variables will only double the amount of of data that is clogging up your networking for no real reason.
The issue isn't persistence, it's too much data being synched too frequently when there are more efficient synching solutions available.
If you're using a PlayerObject as a way to create value-carrying network events, however, then yah that data wouldn't be synched in a savable fashion and you'd need to make a separate object to collect and save that data in ay a slow rate. For this approach, however, you'd need to consider how you want to handle late joiners.
Yes, your last paragraph is what I'm suggesting. It's just for messaging the "place down" event.
Then persistence can then be handled separately however you want.
Actually, late joining can be handled purely with persistence. Since you are already perdiocally saving all the location of items anyways, the new person will receive all that data when they join.
I updated again, but still not 100% certain the fix will work 😢
It sounds like I found some Edge case in persistence so yay lovely
no worries i dont need persistence atm 😄 so i am staying on 3.1.15 until its confirmed fully
Yah the two methods actual cover each other pretty well.
My only concern is the size and timing of the save. If the amount of data that needs to be saved is large enough then you either need to find a period of low networking activity or accept that there will be periods where networking will get clogged up.
Then again maybe Udon will still allow those smaller variable event calls to go through even will it is slowly getting the save data out there? If those important variable events calls aren't interrupted then it's fine.
@obsidian cedar eh UInt64[] is supported being send over the network right?
I believe so, yes. All base types should be
@obsidian cedar have u ever seen a behavior where a value in a ulong[] changes unexpectedly? to something completely different
to give u a reference this is the number that is correct.
this number is what it becomes when i send it over the network
@obsidian cedar DataList are not supported right?
Nope

Can you show the sending and receiving code ? Also checking the ulong encoder rn
uh eh. its eh long 😄 but i am correctly seeing it add and so on from the sending sight. but as soon as i send it over the network it changes values.
but it only happens whenever the ulong goes over 12 digits
uh. eh
Length: 11 Byte: 17179869184, Bits: 34
Length: 11 Byte: 34359738368, Bits: 35
Length: 11 Byte: 68719476736, Bits: 36
Length: 12 Byte: 137438953472, Bits: 37
Length: 12 Byte: 274877906944, Bits: 38
Length: 12 Byte: 549755813888, Bits: 39
So 2223242110012 is the number you have ?
yea thats the supposed one i showed earlier
42 bits It seems like, and that one doesn't work ?
yea for some reason as soon as i hit 13 digits actually. thats when it seems to just die
anything below that seems to work
Oh shit
i assume your checking it on the newest version of your thing or?
Ye
No need
o?
I see the issue, it's in code that hasnt changed for like a year
Integer overflow
Yep yep! Came to the same conclusion about wrong usecase myself!
I was under the impression the sync rate would be higher, to me it felt strange that you could access the player data of other players at all.
I like that idea actually! Synced vars + Counter to act as argument, good idea! Will have a try later, thanks.
@obsidian cedar is it something your able to fix or?
Yee
I think ?
Weird is that it's happening at the Array version for you too
that one should work
also the non Array just Ulong or Uint64. ends up saying value is either to small or large if i receive that.
gonna try the Int64 one to see if that works
PlayerData is basically a manual synched behavior so it should more or less have identical networking performance as any other manual synched behavior.
Also the reason you're able to get the PlayerData of other players is because behind the scenes everyone who joins an instance creates a copy of their PlayerData object on everyone else's machine and syncs that data.
@obsidian cedar yup so a Int64[] or Long[] works fine. it sseems to just be the unssigned int64
ye
That's what I would have thought too, but it's not. 😄
It synced in intervals, not all at once.
Lets say you make 10 sudden changes, you may get like...
3 changes, 2 second delay, 3 changes...
etc.
RequestSerialization to my knowledge would do this all at once, maybe just with 1 larger delay.
So could you open file "DataListSerializer.cs"
Scroll all the way down and replace this method with this code
public static int ReadVariableInt(this byte[] array, int startIndex, out ulong result)
{
byte b = array[startIndex];
result = 0;
if ((b & 0x80) == 0) // Single byte (7-bit value)
{
result = b;
return 1;
}
else if ((b & 0xC0) == 0x80) // Two bytes (14-bit value)
{
result = (ulong)((b & 0x3F) << 8 | array[startIndex + 1]);
return 2;
}
else if ((b & 0xE0) == 0xC0) // Three bytes (21-bit value)
{
result = (ulong)((b & 0x1F) << 16 | array[startIndex + 1] << 8 | array[startIndex + 2]);
return 3;
}
else if ((b & 0xF0) == 0xE0) // Four bytes (28-bit value)
{
result = (ulong)((b & 0x0F) << 24 | array[startIndex + 1] << 16 | array[startIndex + 2] << 8 |
array[startIndex + 3]);
return 4;
}
else if ((b & 0xF8) == 0xF0) // Five bytes (35-bit value)
{
result = (ulong)(b & 0x07) << 32 | (ulong)array[startIndex + 1] << 24 |
(ulong)array[startIndex + 2] << 16 | (ulong)array[startIndex + 3] << 8 |
array[startIndex + 4];
return 5;
}
else if ((b & 0xFC) == 0xF8) // Six bytes (42-bit value)
{
result = (ulong)(b & 0x03) << 40 | (ulong)array[startIndex + 1] << 32 |
(ulong)array[startIndex + 2] << 24 | (ulong)array[startIndex + 3] << 16 |
(ulong)array[startIndex + 4] << 8 | array[startIndex + 5];
return 6;
}
else if ((b & 0xFE) == 0xFC) // Seven bytes (49-bit value)
{
result = ((ulong)(b & 0x01) << 48 | (ulong)array[startIndex + 1] << 40 |
(ulong)array[startIndex + 2] << 32 | (ulong)array[startIndex + 3] << 24 |
(ulong)array[startIndex + 4] << 16 | (ulong)array[startIndex + 5] << 8 |
array[startIndex + 6]);
return 7;
}
else if (b == 0xFE) // Eight bytes (56-bit value)
{
result = (ulong)array[startIndex + 1] << 48 | (ulong)array[startIndex + 2] << 40 |
(ulong)array[startIndex + 3] << 32 | (ulong)array[startIndex + 4] << 24 |
(ulong)array[startIndex + 5] << 16 | (ulong)array[startIndex + 6] << 8 |
array[startIndex + 7];
return 8;
}
else if (b == 0xFF) // Nine bytes (64-bit value)
{
result = (ulong)array[startIndex + 1] << 56 | (ulong)array[startIndex + 2] << 48 |
(ulong)array[startIndex + 3] << 40 | (ulong)array[startIndex + 4] << 32 |
(ulong)array[startIndex + 5] << 24 | (ulong)array[startIndex + 6] << 16 |
(ulong)array[startIndex + 7] << 8 | array[startIndex + 8];
return 9;
}
return 0;
any try testing your UInt64[] again ?
OH YAH! It's manual sync but you have no control of when it actual calls RequestSerialization
Yep yep!
able to make that a file instead? i have a map test going here in 19 min 😄
You can see how I ended up making it the way I did I think. 😄
The assumptions I had are what you had.
and since Long / int64 works i am gonna stick with that
👌 Unsigned variations should have some byte optimizations normally 😅 but ye those don't work so if you can use signed variants I guess use those 😅
If you wanted to be really cursed, maybe you could find the correct PlayerData object in the scene and manually call RequestSerialization. I would, however, not recommend doing this as this is probably unintended behavior that could break in future updates, if it even works now.
But really, no need to do any of that as they made PlayerObjects for this exact purpose of needing to do things that PlayerData was too limited for.
Yeahhhhhhh, in my usecase, potentially over 100 objects could be placed, so I'm trying my best to have a manager script vs. individual components.
So I'm not sure PlayerObjects would work for that really, maybe too much overhead. 🤔
depends on what u need it for
I think as was said before, using some synced variables to 'send arguments' essentially could work.
So the initial object layout is synced from PlayerData.
Then after that, objects to be placed can be set through some synced var's.
just remember that we only have a small amount of actual synced vars able
like depending on what u try to sync it may not even be that many.
It might not have as much overhead as you're worrying about depending on how many/often you're synching them.
The variable event caller is probably more networking efficient but keep in mind you'll be limited to only 1 event per network 'tick.'
Also be cautious of how the persisted data save/sync might clog up your networking at points
Instead of the counter you could also do a like a rolling array (is that what it's called?) that gets cleared up periodically.
That way you can have multiple events just in case. Although just adding a delay to spam building is also a solution.
There are different ways you can do that. You can even start out with just one one synced script on a player object that saves all placed things. You can always split that data up into more scripts later to reduce networking load when needed.
Arrays can't be synced right? 🤔
Or so I believed.
Either way, I think I have an idea in mind!
ye
arrays can be synced, if their elements can be synced
Question for you all...
Is there currently a way to checking if persistence has finished syncing?
A concern I have is if PlayerData is used to catch joining players up to the current state, this data could be outdated if large changes have been made recently.
I would imagine restored still gets called when parts of the data have been synced, not just the final state.
Alsooooooo... @obtuse echo @north thistle, the final result I went with works nicely! Much appreciate your input!
The final result has a few synced variables, these almost act as arguments for network events.
If an item is placed, a network event is sent to all clients. Master will intercept this and fire a raycast to get the object place position in the grid, if it's valid, this position is set in some synced variables along with the 'syncType' (Place or remove), this change is then made on remote clients.
I have a 'syncBusy' bool, which is set as RequestSerialization is called, and set back to false on OnPostSerialization to ensure no one can make a change whilst we're still sending the last change.
TL;DR - Works well, miminal latency on remote clients, no desync besides potentially the raycast being wrong due to object sync of the placer tool being different on different clients. 😄
see usage of the word "all"
Hmmmm, I do see it, but... 🤔
It's not really clear for the issue I mentioned. I would 'assume' it syncs what is currently synced, as in 'all currently synced data'.
But if you make a large amount of changes, this data is synced in pieces over time. My concern is that it'd load the current data, not the final data.
Yeahhhh, I don't really see a way to check if data is still syncing either otherwise this would be a none issue really.
Still syncing?
As in, are we still pushing out our changes to persistence or not?
I know that the player data updated or whatever is called, but this is called for part-updates too.
Not just when the final sync has been completed.
(If you're syncing say 10 changes, maybe 3 changes are sent, but this still triggers a player data updated callback)
sync happens entirely for each individual object
there's no such thing as a partial sync for an object
I wish! It does not unfortunately. 😄
If you suddenly make say 9 changes, you'll get like 3 changes, 2 second wait, 3 changes, 2 second wait, etc.
It slowly pushes out the changes to persistence, not all in one go, even if you set it all at the same time.
(This is what caused that whole issue originally with me switching to some synced variables)
Unless I'm misunderstanding something!
Are you saying if you make 9 changes to PlayerData in a single frame, it takes multiple network ticks for all the changes to come into effect?
Yep!
It is extremely slow as well.
(Networking isn't clogged either, in my testing this happened with only this active)
I'm assuming they have a byte limit for each serialization.
Ok yah that is probably what is happening. Udon manual sync only supports up to 64.69kb per sync but PlayerData supports up to 100kb size, so they much employ some black magic behind the scenes to get around the limitation
I MEAN, again, not an issue if there's a way to know when it's done. 😄
So what I'm seeing is, PlayerData is terrible for runtime time sensitive actives
And if you have something that is runtime time sensitive you should use a PlayerObject instead
I guess so. 😄
But like... I really would like to use it on world join, to say reload a map from the data.
But if it can be out of sync, it's a bit useless.
PlayerObjects can do everything PlayerData does, btw
The only limitation is that a PlayerObject can't be a static reference (yet)
There's the OnPlayerDataUpdated event
But... It's called before all data has finished syncing.
If you change 9 items in PlayerData, this may change as like... 3, 3, 3, each with their own OnPlayerDataUpdated call. 😄
Over like 10 seconds.
I don't 'think' there's a way to know which call is the final call.
Ah I see, I'd probably serialize everything into a byte array so it's forced to be atomic
I guess that wouldn't work if you need VRCUrl though or something
In this case, I'm working on a farming world. 😄
So what's synced for each placed object is basically:
- (byte) Crop Type
- (short) PosX
- (short) PosZ
Each of these is saved to persistence like...
Crop_0_Type
Crop_0_PosX
Crop_0_PosZ
But like you said... As they're not all 1 item, it splits this up a lot.
Hmmm... I'll have to test this, but... If OnPlayerDataUpdated is called several times locally, I can use a synced variable for remote clients to know if the sync is 'complete' or not.
I feel like you should just use synced variables instead of persistence to tell late joiners the state of the world.
Yeahhhhhhh, it's just that this could be pretty large really x.x
Wait... "Manual sync is limited to 280,496 bytes per serialization.", isn't that more than PlayerData can hold total?
You could transmit the world state in chunks too I guess.
Apparently PlayerData is using the same underlying system as synced vars.
Persistence is simply meant to store personal stuff for the player. I'm actually curious, how do you intend to persist the state of the world when two people come in a world and have conflicting states?
My intentions are that the world master would have their farm (world state) be loaded.
If Master leaves, you're thrown back to a lobby, you can load the new master's farm instead.
(As we obviously can't continue to save now)
So only the master writes to persistence is what I'm guessing. Everyone else just participates.
Yep!
I think you could use persistence to load the data, but no idea about timings and stuff. I am only now getting into it myself. But it does feel like the wrong tool for the job again. Synced vars sound a lot more reliable to me, but maybe I'm biased.
I'd agree with you too.
With how the data gets split up, it's hard to know if the data is accurate right now.
If we know the final state of the data, that's not a problem anymore. Can just have the user wait a few seconds for it before they load the world state.
How big is your grid btw?
TBD. 😄
Just snapping to nearest meter, either way it's within the range of a Short.
For most cases, I'd just do one udon object with the state that gets synced. That's very reliable and easy to reason about.
Idk about 10000 synced objects though
^^
That's sort of my issue. I was concerned about scaling it.
This current setup does scale well.
If only I can ensure the data on join is correct.
You might want to write that yourself. Use a timestamp of the state they receive and then the master calculates what they missed and maybe sends it later?
Idk just a loose idea
So did a quick test... player data updated is called instantly on the player updating it.
In my case... Master may look like this:
- Spawned crop
- Player data updated
- Spawned crop
- Player data updated
etc. etc.
But remote may look like this: - Spawned crop
- Spawned crop
- Spawned crop ( < Through synced vars)
- Player data updated
It's just... Super late to the party. 😄
There's no way it should be over any limitations for splitting this data either, of what I can tell.
I suppose as it's a key-value pair, we're syncing the string key name too, which would add quite a chunk of data.
Persistence is not meant for real time stuff.
I get that.
You can use Persistence for real time stuff if you're using a PlayerObject instead
Remember, there is no meaningful distinction between persistence and synched data. Persistence is synced data that gets saved as it passed through the servers
Where do you get that number? The official docs list a limit of 64690 bytes https://creators.vrchat.com/worlds/udon/networking/network-details/
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
they increased it with the persistence update, docs do not reflect the new number
In any case all these issues you're having would be solved by using a PlayerObject instead of PlayerData
Oh good to know
@north thistle literally same page
and i think i remember 280k number while 70k is news to me
docs are great
But then why is PlayerData doing stuff in chunks instead of all at once?
On a side note, who do we contact about issues with the docs?
What if they have 10000+ cells in the grid? 🤔 Genuinely asking here because I feel that might be stretching it for player objects.
I think you kinda have to do a messaging system at that point and send parts one by one or something.
(Which would use player objects btw)
If a normal synched object can handle it then it will still probably work even if you slap the persistence tag on it (unless there are undocumented gotchas)
Also PlayerData is actually a pre setup PlayerObject
Curious on this too. 😄
If manual sync can be like 2.8x PlayerData's total limit...
Why is it splitting up PlayerData at all?
I know I know, it's not supposed to be realtime, but it's still weird.
Yeah, I agree it should still work. I'm just curious about the scaling aspect of the grids
Honestly, to solve my issue at least, I may just do some kind of checksum to ensure I'm accessing the latest player data on remote clients.
It's only for on-join sync, it doesn't need to be timely otherwise.
Again... I will suggest that we are guessing a bit too much about things. I have to believe the effect you are seeing is real. There may be reasons and there may be elegant workarounds. The problem is we are not all looking at the same code, i.e. not comparing the same results and therefore can't be certain that any particular fix will actually fix it.
Wouldn't it be handy if there was a class that a) demonstrated the issue b) people could use to see it happen and c) use it to form a reliable workaround if one exists?
Being realistic... A 'true' fix would be on VRChat's side with a 'final' bool.
That'd make it possible to check if it's interim data vs final data.
(Many APIs do this same thing, text-to-speech APIs for example will give you results as you go, then a final on completion)
I'm not sure relying on a library mod is realistic. And if you could demonstrate it conclusively more people may upvote it.
I'm trying to explain how "evidence" works better than "hearsay".
It's extremely easy to reproduce. Set a few entries to PlayerData every second or so. Imagine a user plating crops in a farming game for example, so they place - wait a short period - place again.
Likely hitting a rate limit?
(Though I would have thought persistence data within the instance is synced locally, not through cloud)
Well the bigger the size of the grid the more data you'll need to sync over the net. Either method won't change the core amount of data sync, it just changes when/how that data gets synched.
Putting it into a large amount of objects makes it way more flexible but adds a header tax per object. Storeing stuff into giant arrays removes the individual object header tax but adds an array header tax (which may or may not be just as big or bigger than the individual object tax) and is a lot less flexible.
Alright I'll give up then. Everybody isn't going to write their own different code. That's the beauty of standardization. There are worlds in VRC where you fly helicopters and jet planes. I think they sync a bunch of data.
That would be adding extra networking load (and design complexity) to add better support for something PlayerData is not designed to be good at, and which a better solution already exists (PlayerObjects)
The issue here is that having all this data within synced variables would be a big chunk when you call RequestSerialization.
Potentially, I could have it so this big chunk is only updated on a new player joining for example.
This would be a lot more data than the flight worlds.
I get that, I suppose from my perspective it just feels odd that can we're allowed to access the playerdata of other users, and yet, we have no way to check if this data is even updated. That's my issue here, not really the timely manner of it updating.
Why be able to access the data if it could be garbage data anyway?
Why have a player restored call if it may not even be the player's current data?
(Which on their end, has already called back OnPlayerDataUpdated!)
Again... it would be part of the standard demonstration class to report the size of the data and the time to sync. The reports could be posted and a reasonable solution arrived at. Or not... either way most other languages and dev environments work on the "show us code that demonstrates the issue" as a workable mechanism.
That's the thing, PlayerData is "saved through the cloud". From what people said it's using the same infrastructure as synced vars. It's also the reason why you can always access other player's PlayerData in your code.
Getting the size of the data is hard with PlayerData, as we don't know the total size being serialized really.
It's a key-value pair, so I assume we'd include the size of the key?
It could be a bug, it could be a coding oversite, it could be a misunderstanding.
Evidence is evidence why do you need to prove overhead? Report what you can see. Why is this such a novel concept in VRC?
PlayerData seems to be designed for save data that you'll want at load time but won't need accurate runtime updates for
PlayerData is also compressed so the amount you send may not be the same size stored
I'll do some further testing first!
I want to first confirm if this is a rate limit, or, something related to the size.
I don't have all the facts myself, I'm just spitballing.
Trying to see if others have found the same issue.
And that is why one posts code that others can check for obvious flaws and/or improvements and can test using their system, Internet connection, etc.
If it turns out to be what I believe is a bug, I'll of course write out some code to reproduce. 😄
But the code is literally just setting some random PlayerData.
But setting it quickly.
Okay... I've found a 'solution' to my own problem at least.
I have a synced variable, which is a random int. (UID basically)
This is saved in PlayerData too.
OnPlayerDataUpdated, I compare the one in PlayerData with the one from the synced var, if they match, the PlayerData is up to date, if not, ignore the PlayerData until it matches.
Right now, I 'believe' PlayerData is entirely synced when any key is changed, so this should always work.
This is still stupid IMO, the sending client shouldn't really call back that the Persistence data is updated when it is clearly not updated, this Canny post really:
https://feedback.vrchat.com/persistence/p/have-a-way-to-know-when-all-data-has-finished-reaching-the-servers
To be clear, I do much appreciate the advice and help! Apologies that I didn't manage to get you an example to test locally!
Never a problem. I'd like to see some sort of "code exchange". There are so many templates and coding snippets that could be offered. Posting bits in a threaded system like this doesn't work as well. There is no revision history for instance.
Why did you not end up going with a PlayerObject to store/sync this data?
A random value seems unreliable if OnPlayerDataUpdated fires property updates in chunks, couldn't it send an update for that random value before other properties have been sent?
I guess you could send a hash of the data contained in PlayerData instead that other clients can check against to see if they've received everything
I would still go down the route of grouping up data into a discretely syncable states so you don't have to care about whether the entire PlayerData state has been sent
Like if crop state changes can be broken into discrete updates you could group the properties of a single crop into one property so you only have to check for one property update per crop
Byte arrays are useful for packing that data together:
public void SetCropState(int cropIndex, byte cropType, short posX, short posZ)
{
// Update crop state
byte[] cropState = new byte[5]; // sizeof(byte) + sizeof(short) + sizeof(short)
cropState[0] = cropType; // Crop_X_Type
cropState[1] = (byte)(posX & 0xFF); // Crop_X_PosX
cropState[2] = (byte)((posX >> 8) & 0xFF); // Crop_X_PosX
cropState[3] = (byte)(posZ & 0xFF); // Crop_X_PosZ
cropState[4] = (byte)((posZ >> 8) & 0xFF); // Crop_X_PosZ
PlayerData.SetBytes($"Crop_{cropIndex}_State", cropState);
}
public void GetCropState(int cropIndex)
{
byte[] cropState = PlayerData.GetBytes($"Crop_{cropIndex}_State");
byte cropType = cropState[0]; // Crop_X_Type
short cropPosX = (short)((cropState[2] << 8) | cropState[1]); // Crop_X_PosX
short cropPosZ = (short)((cropState[4] << 8) | cropState[3]); // Crop_X_PosZ
}
Manual sync is limited to 280,496 bytes per serialization. aka 280.4 kb but we are still limited by doing 11 kb per second tops. so if you have a 100 kb worth of persistence that needs to be synced its going to take roughly 10 seconds
well considering it needs to save the data to their database yea. its going to be slow. if your talking about the player persistence
because its limited to 11 kb per second. so it has to split it up
Due to other VRChat systems consuming bandwidth It's more like 6kB/s unfortunately
So you'd be looking at something like 16s
see i know that has been said before but i dont believe it 😄 considering every world consumes more then that when it comes to udon and no issues happen either
The information comes directly from @frozen igloo on the VRChat team.
yeea i know. but still doesnt feel like its true.
Why don't you do some tests and disprove it then?
thats average, not hard cap
trying to but to do so i need valid testing and 70-80 people
yes i do lol..
i am talking exclusively about sending.,
The amount of people in the instance does not matter for sending
since 80 people with FBT and eye and face tracking would consume way more then 11 kb
It does not matter what the other people are doing
then i do want to know why the Count for how much KB/s that get send goes up per player joining a world then. even if nothing is being synced?
The limit is how much your client can send
or send.
What does a client need to send about other players to other players?
depends on the world.
if its per client that would result in a much higher output in total.
Of course it is per client
mb friends/mute etc
nah, its out traffic
or traffic if u will
youd see it in ingame debug console
yea.
My personal reason for going with this approach was to reduce CPU overhead. Didn't really want individual objects to have their own controller, would much prefer a manager script that simply loads the whole farm in one go then just a few small variables are used to make changes.
This is working pretty well at the moment! Though I am realizing switching to some synced variables to sync the initial world state on join is very much a better idea at this point, PlayerData is just too unreliable. My workaround with the UID does actually work, it's just too slow/impractical really. It was worth a shot though!
now my question still remains through. how come the Data out goes up when a new person joins? roughly 500-1000 bytes i have seen even with nothing going on?
Ahh, this would explain why PlayerData is so damn slow to sync across clients in the instance!
I would have thought the PlayerData was sent locally with essentially manual sync, as they state in the docs I think?
This would mean it'd update quickly locally, but obviously upload slowly to the backend, which would have been fine.
@strange gyro ^ For that reason, I'll likely switch to some synced arrays for the initial world data on join.
well if its 6-11 kb per client instead. output that changes everything ngl
Btw, this 500-1000 bytes on join, is this not client's receiving the initial synced variables?
On join, clients will recieve synced variables from even manual sync behaviours without requestserialization being called.
(The latest state that was sent out)
no well that was just a random number i have seen. and i dont actually have anything that syncs. i do everything manually. i have it set to NoVariableSync etc
I meant using a single PlayerObject in place of PlayerData.
that's not how manual (or continuous) sync works, lol
I'm legit struggling to imagine why they would design it that way
eh. if your trying to sync more then 11 Kb in one second it should split it up.
but i am sure @frozen igloo could explain how vrchat maybe handling more then 11 kb of data sync on someone rejoining.
I mean it does split it up into chunks, it takes multiple packets to send more than a few kb
Continuous sync is about the same rate if I recall? I feel like PlayerData is more similar to that than Manual. 
Though I suspect what you're actually asking about is why it has to send everything when only a single key changes. In that case, it's a bit more complicated but ultimately, the system doesn't support receiving diffs. You could write scripts on the client end to support receiving diffs but because the server doesn't handle it, that breaks late joiner support. However that is a feature we could add in the future. Now that persistence is out, that will be more likely so stay tuned
I'm not quite sure what you're implying, I mean playerdata is literally manual sync
I need to look into it more, I was thinking that both players data and objects would have the same limitations on large data, just that with objects you're splitting into multiple different objects so you're reducing the sync in one go.
And yet it syncs in chunks not just one big chunk!
Manual can support like 280KB per sync right?
According to the docs
PlayerData is absolutely not doing that.
Yes, but not in a single chunk. It takes time to send that 280kb because it has to be split up
Ahhhh! I see!
Playerdata also technically supports 280kb but not persistently
Well .. if it's all data that can be compressed easily it will work persistent
But OnDeserialize is only called once that's all sent right?
With PlayerData, you get the callback for it being updated in chunks.
@fervent holly just to make sure we haven't been playing a game of telephone, what you observed from PlayerData is that if you updated multiple keys on a single frame, it would take multiple network ticks for those keys to update with a OnPlayerDataUpdated every tick
I need to do more testing on it.
In my case, changes are happening often, but over multiple frames. It's likely a rate limit? But I'm not sure why this affects sharing the data across the instance.
Was this only observed locally and/or remotely?
Locally, the player data updated callback is called immediately.
Remotely, it can be called several seconds later and several times.
Yeah it sounds like you're looking at the playerdata layer and using that to make judgements about the networking layer. Those are two different things.
I know playerdata has a bug that just triggers updated twice incorrectly, but that has nothing to do with networking
If it truly works like manual sync, I would have thought it could share the data I'm trying to share with other clients pretty fast.
But due to the immense delays, it's just not practical.
I'm assuming other clients are pulling the data from the backend? Not sharing it locally across the instance?
Well if it was continuous sync it wouldn't be able to do any of this at all 
The issue Curtis was running into is that they didn't know when the received data was accurate due to the multiple OnPlayerDataUpdated events
^
There's no 'final' state, or UID, or time stamp.
To verify which data we're looking at.
On join, a player may get old data.
I mean they are sharing it across the instance, but all of that goes through the backend. Clients do not directly communicate, there's a relay server for all realtime networking traffic
Hmm. x.x
The reason they may get old state on join is because it loads the persistent record first because that happens faster
I mean, either way I can work around it, it just means I can't really use PlayerData to reload the world state on player join.
As the data is unreliable.
But... Can use synced vars instead of course!
I can use it for loading the world state locally for the master, that's the important part. 😄
ultimately you should be designing scripts that simply take the current state and apply that state to the world. That's it. If you are doing anything else more weird with it like making assumptions about this being a final state or combining multiple states together, then you're doing it wrong and that will cause problems
Additionally, if your synced state does not include enough information to make such a determination - then add it!
The synced state should fully describe the world at any one point
As it stands...
PlayerData loads the world state, any added or removed objects past this are done via some synced variables.
There's no 'absolute' world state to ensure it's accurate on all clients just yet.
And if you do it that way, it doesn't matter what the intermediate state is, late joiners just work, persistence just works, and you are way more resilient to bugs
Ideally I wanted to avoid having a few giant synced arrays.
Which would ensure accuracy, but lots of bandwidth.
You can split it up into chunks yourself across objects, but do not split it up into chunks across time

lol that was the original suggstion; it has come full circle
Splitting into chunks across time means that the owner will have to keep resending. That is the expensive part. It is far, far cheaper for the server to send the latest state to all the clients, especially late joiners. That's why you get penalized for trying to do it that way
My plan for now is to have a different object which has the synced arrays for the world state. On join, RequestSerialization is called.
This means the main manager isn't constantly sending out huge data, and the huge data is only sent when required.
Then PlayerData is only used for the master loading the world.
On join request serialization is part of the problem. That state should already be on the server before the player ever gets there
Well, sending this out every time a change is made is not really a great idea either!
It can get pretty chunky.
Then don't send the entire thing on one change. Split it up into multiple chunks across objects
I'm not entirely sure I understand. That'd end up being the same bandwidth usage anyway?
Just split up.
Quick question: will having a ton of individual networking objects in your scene cause hardware or networking performance issues if they only update infrequently?
I guess if it's split you could have parts of the array in different places so you're only sending out parts of the data.
But, I don't think this would work well for my setup.
As realistically... In a farming game, you're gonna be changing most of the world. 😄
Quite often too.
No, because the owner wouldn't need to re-send every time someone joins. Only the server does. And the server will always have better upload speed than you. But it only ever keeps track of the latest state per object
I get what you mean, but having the user wait for a sync would be better than bloating the network with every change wouldn't it?
Maybe I'm misunderstanding!
That caching is designed to reduce the total bandwidth you need. By not using it, you will be subject to much tighter restrictions
Hmmmm.
I'm just concerned that, in the worst case scenario...
I could have maybe 4x 4,000 item arrays, mainly of Short.
You'd only need to update the value on a player interact, right? Won't that limit how often values are being updated?
Which if you're serialising that with each change...
That's a lot.
Again, if your data is big enough then I'm not suggesting putting your entire state into a single object. Group things by how often they change, like "big, slow data" and "small, fast data". Chunk them up even further than that if necessary, group them by which ones are most likely to change together
I mean... Players are doing a lot. Like twice per second say removing a crop.
Planting them, etc.
I get you on that, it's just that all the data here is slow data really. The main data is all world data. Grid positions and object types at those.
(x and z Short for positions, ushort for object type)
Ok, and?
I mean, I can't really do anything to make it faster I think?
It's all just some giant arrays.
If a single crop is removed, those arrays would need to be synced again.
The data stored per crop is 1 byte + two doubles, right? That would 5 bytes. Add header and other networking stuff and you're probably transfering less than 30bytes per object, right? Twice per second would be only 60b/s
(Right now to avoid this I'm just changing some synced variables to specify create and remote calls)
The problem is that both Manual sync and PlayerData sync it all!
Not just what changed.
Otherwise you're totally right!
With manual sync, the changes are basically instant.
Not if you chunk it up into multiple objects
Have like one synced object per 10x10 section of crops
Ahhh, I get you.
Hmmm... I like that, but like... I guess my biggest fear was the CPU overhead from extra UdonBehaviours.
If they're not running update it's fine
At least years ago when I looked into it, additional behaviours had a huge toll.
Would having each individual crop have its own UdonBehavior be too much?
Right! Makes sense.
I wasn't sure if that mattered for Udon.
Wasn't sure if it would attempt to call it anyway even if not implemented.
IMO, yes.
But for a small area I think it could work.
Like 10x10
I don't know, I'll see what I can do! I'll experiment.
Update is not dispatched if the udonbehaviour doesn't have it
Appreciate all the ideas btw!
Oh when having crops waiting for timed events, would using SendCustomEventDelayedSeconds be more performant than running an Update based timer on each behavior?
If you need to do stuff on update but it's only situational and may be needed one time but not another, then yes a delayed event loop is better
btw I love SendCustomEventDelayedSeconds so much; especially since we don't have coroutines
Seemingly there remains no interest in a series of test classes with test methods that could demonstrate in absolute terms (no need to guess) what sort of sync scheme would work best. We had (a long time ago) a thread re: sorting and anyone could download, test and even improve the code. If arrays are faster or not (and by how much) could be tested and retested by anyone.
Also it seems possibly that my Sync objects could prove effective. None of my app objects sync data, they all rely on sync data classes that only contain data to sync. Multiple app objects can be informed of changes to any they care to know about.
As it stands, I'm saving the timestamp of when the crop was planted and watered to persistence, so crops can 'grow' whilst offline. 😄
And I totally agree that is an amazing function!
I can look into it if it comes down to that. It's hard to make an 'exact' repeatable test for this, but saying the data is updated 2 times per second maybe 10 times is not an 'invalid' example I think?
I suppose that's what I could use for testing. Unfortunately things work a lot differently with different players, local/published, etc. - But... The test can be at least a 'rough idea' of what's best.
Being realistic, I think @frozen igloo's approach of splitting the data into arrays for each 'area' of the world would be fine. More than likely I'll need to have colliders to define areas that can have crops planted in, so just throw it on there and it's done.
Data is split up, it's not a thousand behaviours, and it's not reliant on PlayerData outside of the initial load for the Master.
I'm not arguing but how can software and testing be non-repeatable? Repeatability is what makes it work. There can easily be tests that iterate 100 times or 1000 times. I can transfer 100 bytes, 1000 bytes or 10,000 bytes. They can be strings or integers or floats. It can log the results and even some stats to see if time of day, number of users, free memory, IP address and/or whatever impacts things. A mod to test out a new theory can be incorporated into a new class and the results (actual results) can be compared. Someone might conclude "this should be faster" only to find that it isn't in most cases. Reliability vs speed may an acceptable trade-off. If "lag" is noticed when 1000 items are updated people can confirm that is the limit they see as well. If an alternative is proposed it should be able to be seen that it now supports 1,200 (or whatever).
I don't recall us guessing which array sorting algorithm would work or which would/should be faster. Anyone could see the code, run it and get the results. Even on later versions of Udon, on a new machine, etc.
Out of curiosity if real-world tests only yield a "rough idea" what do we get out of theoretical guessing? Maybe we need more guesses to really zero in a solution. 🙂
well considering that SendCustomEventDelayedSeconds has to update how long ago its likely that its worse. then just doing a normal Update. like checking if it needs to run or not. which isnt expensive. but again like phase said if you only need it to run once dont put it in update loops
Phase already answered this to the contrary and I'll go with their answer.
I'm 90% sure that by "delayed event loop" they mean a method that loops by calling itself with a SendCustomEventDelayed
you would be correct if that timer had to be updated and checked inside of udon. But sendcustomeventdelayed is backed by scripts running outside of udon, so the cost of running those timers is significantly cheaper than an udonbehaviour receiving update and doing even a single check itself, like immediately returning
As a rule of thumb for Udon, if there is something that will let you avoid doing it manually within your script itself, it will most probably run better
By this I meant the rate of update of the variables isn't determined. It's caused by the user interacting with something in the world, which they may, or may not, decide to do. Some players will interact faster than others, some will simply sit around doing nothing.
The worst case scenaro I had in mind was maybe 2 updates per second, so this would be my test scenario.
Sure, we could test at some unrealistic rate just to determine which one is faster overall, but I was looking for a real-world test!
There's no point over-engineering it to do something it doesn't need to do in the real world. Something about how anyone can make a bridge but only an engineer can make a bridge that just barely stands. 😄
I personally don't want to spend several days or a week on 'finding the best way', if I already have a way that is robust enough for the needs of the project.
I'd rather put my resources elsewhere on the project so I'm not getting behind whilst art gets ahead!
I suppose I'm trying to be mindful on the fact that I have like an hour or two, several days a week, to work on the project. That time can't go entirely into this one issue unfortunately.
That's what I figured, which is exactly why a simple 10x10 chunking would be ideal. Because it should just work, and doesn't need to be over engineered. Tom is right in that there's a ton of room for improvement if you want to make this capable of handling even more. But a simple chunk system should generally work pretty well with not a ton of work.
^^^^
If I had infinite time and motivation, I'd absolutely look into creating tests, benchmarking, etc.
Finding the perfect foundation for it all.
But what you suggested @frozen igloo is more than enough for this project's needs. 😄
I have a good vision of what needs doing to achieve it.
Will it work? Yes.
Will the end-user notice the none-perfect solution? No.
Is it hard to maintain? No.
Yes but 20x20 chunking might be faster or more feasible. It may do no worse in 5x5 chunks. Put in the 10 x 10 chunks and if it doesn't work fast enough we can theorize more 🙂
You can paint software engineering with an "infinite time" brush but it is exactly why we engineer stuff. There is no infinite time available in commercial software development.
that said, an important part of accepting that "good enough" solution is in testing to make sure that the solution you have is realistic for the amount of crops you want. The problems arise when you have a basic solution and try to pile too much onto it. And part of that process is trying out different arbitrary numbers to see what performs best, instead of just making a guess and stopping there. If the answer is that they're all kinda the same and you can't really see the difference as a user, then fine. But if the answer is that there's a clear winner - then there you are. It doesn't need to be a whole testing framework to make sure you know which one is 5% better, just eyeball it for the big differences
'No infinite time in commercial software development' is unfortunately what I've learnt over the years. 😄
I used to spend FAR too long on things, I'd get too invested in 'doing it perfect' and sink months into something, eventually either losing the motivation or for other reasons abandoning the feature.
OR, even worse, release a half-baked feature.
There is no perfect in software engineering.
Exactly!
I want to do a bunch of real-world testing for it, to ensure it can actually handle the numbers, the issue I have realistically is that as the map isn't complete or being made by me I don't know exact numbers, so I'm working with worst-case ones right now like 5k.
5k I worked out would be 'somewhere around' the PlayerData limit for saving the world.
It's probably a lot more than we'd ever see in game.
Being realistic? I actually doubt there would be over 1,000 placement locations.
But I suppose 5k would only be 100x50 meters. Who knows. 
PlayerData being synced by default interferes with what should be a basic design - world state could have been synced through manual synced behaviours by anyone and the master who is persisting the world state would store it in their PlayerData as they receive serialization updates from the synced behaviour without generating a round of overhead from resyncing that persisted state back out to everyone
I really hope we get an unsynced PlayerData sooner rather than later
^^^^
I don't really like that you can't set PlayerData without also pushing it out.
I'd love to make a bunch of changes, then push it when needed like with manual.
Obviously with the above I have a plan in mind to workaround the issues, but like... It still feels dumb that PlayerData isn't really able to do what I want here. It's like I'm having to save the data somewhere, then have a local copy of it to send to other clients anyway, even though they can access the saved data.
It's just... Out of sync.
It feels like I'm having to store the data in 2 different ways for what should be no good reason.
But it is what it is, I don't want to start some deep philosophical topic about this again. 😄
There's a very simple reason why playerdata is not well suited for those use cases - it's not intended to store the state of the instance, it's player data.
There are many forms of persistence and per-player is just one of them. There's also instance persistence, and global persistence, and all of these have different use cases. Of course trying to fit instances into player data isn't going to work
But if the player data is supposed to be just for the player, it shouldn't be accessible from other clients really. x.x
It's a 'hybrid'.
Either way, it is what it is.
It just comes down to persistence missing an important tool in the toolbox which is a way to load and save data without syncing it to everyone
It's hard to build good designs with what's currently available
Instance persistence was actually the very first prototype of persistence that we had, but there were several reasons we pivoted away.
For one, the concept was that persistence would "just work" in a lot of cases, but that was only true if you set up your world in a specific way. Joining back on a persistent instance when nobody is there would behave similarly to how a late joiner receives the last state. Except there wouldn't be anybody behind that data, which meant that any kind of situation where you sent data onplayerjoin would not be persistent. We announced it as that and people were a bit frustrated by the idea that their worlds would have to be held to a higher standard that they don't have the tools to properly do in the first place.
Additionally, building the UX for instance persistence is way more complicated than player persistence because it's not something that can "just work" for the player. It's something that needs a whole UI to manage save states and sharing and branching and permissions and all kinds of other things.
So all that is why we pivoted away. People just wanted key-value storage. And they wanted a simple way to associate data with players. So we came up with the concept of playerobjects and then inside of that, playerdata is basically just a playerobject itself with a nice static interface. That's all we shipped for now, and we tried to keep it simple so that it could actually ship in a reasonable time and we can move on to other more important things (like networking improvements, which would inherently make these problems go away).
Even when something is "trying to be kept simple" there's many, many complicated parts behind the scenes that stand in the way of getting a feature like this out, and having a clear vision is exactly what gets those parts moving toward a release.
All that is to say, yes we know it's not perfect. Yes we know that there are more things you want. But we can only deliver a certain number of things at once, and this is what we went with for now. There will be more later. I can't promise that it will be exactly what you specifically think is the best way to solve the problem, but we do know what your problems are and we do plan on continuing to solve problems in general. Perfectionism and attempting to fix everything in a single batch is the direct enemy of continuing to solve problems and iterate.
i am guessing when you say outside udon its in regular c#?
Is this an inelegant way of doing a moderately secure whitelist? I saw some recent comments about an 'array contains' node but I wasn't able to find it
I don't think you can do that, it doesn't work in U# too, because it uses System.Linq
but looking at your script, I don't think if it works at all, but you could check the names by using a for loop
That's not linq, it's just a standard string op
Use Array > IndexOf node to find an element in an array
it's not
-
The function in that graph screenshot is string contains, not array contains
-
If you do directly use arrayvariable.contains, it will try to use linq. But if you use Array.Contains(arrayvariable it will be an exposed, standard function. That's why it's also hard to find in graph
And just in general, if you see a node in graph, it is exposed. Doesn't mean that node is correct or useful, but it is guaranteed to be exposed. If it's not exposed, it doesn't exist in graph.
There's no Array.Contains method in C# that I'm aware of, Array.IndexOf is the closest thing
Other than the Linq based stuff or other extension methods
yeah i know, i think you just misunderstood me, okgood were asking if there's a method for 'array contains', that's what I responded too, that it's not exposed to udon because of the Linq
Got it, my bad I misunderstood. I know that VUdon-ArrayExtensions has Contains but I assumed it just wrapped the normal method. Turns out it also uses indexof https://github.com/Varneon/VUdon-ArrayExtensions/blob/main/Packages/com.varneon.vudon.array-extensions/Runtime/UdonArrayExtensions.cs#L95
if using U#, an array contains method is very easy to make yourself too
the issue is that if you make it yourself it's going to have Udon overhead
if string ",user1,user2,user3," contains ","+username+","🫠
not if you use indexof and just change the output
Using the Udon benchmarking provided by VRCLibrary, I would have assumed that the simple fact of having an additional method call would harm performance. Doing my own benchmarking, however, showed that if there was a performance difference it was too small to measure. This also applied to having it as a Static method in another script.
The only way I had to measure a noticeable loss in performance was when I was calling the Contains method from another script instance in a non-static way (and even then it seemed only around ~1.05 slower).
Curious I replicated their benchmark and my results showed a slightly even greater penalty for using a separate method call.
My conclusion then is that while there is a tax, the tax is so minor that it effectively doesn't exist in practical applications. My next question is, could there be a real world scenario where having an extra function call could have a noticeable performance cost?
Actually thinking about it, I think I have my answer: it would need to be something that is extremely fast but is being called many times in a single frame
Pretty much, performance impact of a method call is going to be negligible for infrequent calls regardless of whether its a jump or extern instruction that causes it
yikes udon is actually extreamly slow. doing just 400 update calls tanks your fps lol.
takes 2.06 ms per frame to execute if you do an optimized manager
Update should be heavily avoided
in Udon maybe. but actual c# or non vrc no it at times cannot be avoided.
however in Udon consider using the SendCustomEventDelayed instead and make it run it self to create a psuedo update loop
This has always confused me. There’s less overhead to run a custom method every frame than there is to include that logic in update?
SendCustomEventDelayedXXXX gives you more control over the update frequency so you don't have to fire it every frame for low frequency updates, and you can start and stop the update loop on demand unlike Update which will run every frame regardless so long as the behaviour is active. It's also good for deferring expensive logic over several frames.
hello how make so whin player pick item using VR pickup script whin player holding item othere players se that item always drop and teleport back in to hand
that's what I was thinking, okay, thanks o/
First what othere players se and last what I see
Any one have ideas how fix thises
Do you have a VRCObjectSync component on the pickup?
Yes