#udon-networking

1 messages · Page 14 of 1

finite sierra
#

@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.

stone badger
#

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.

finite sierra
#

And it would work 100% of the time. As for late joiners. Well you could just invalidate it on the local side

plush tiger
#

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?

finite sierra
#

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.

stone badger
finite sierra
#

No

stone badger
#

Anders Heilsberg would beg to differ with you...

finite sierra
#

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

stone badger
#

Again you are mistaken

finite sierra
#

Nope

stone badger
#

Then please quote these experts and veterans and geez what does that make me?

finite sierra
#

Using vars also implies you do not know the expected type

stone badger
#

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

finite sierra
stone badger
#

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...

▶ Play video
#

So is this guy a professional?

finite sierra
#

He is

stone badger
#

Ok I'm sure he appreciates it 🙂 I've gotta go now

plush tiger
#

quick test shows that no, the same number getting set does not fire the FieldChangeCallback

strange gyro
#

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

finite folio
#

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.

finite sierra
finite sierra
plush tiger
#

yeah could just throw some garbage bits into the high bits and mask them out I guess

strange gyro
#

You could use the high bits to indicate the repeated call count

finite sierra
#

im sure you can find something 😄

#

that to.

plush tiger
#

or use it as an increment

strange gyro
#

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

finite sierra
#

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

strange gyro
#

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

finite sierra
#

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

strange gyro
#

Ah that's true if the owner runs any logic from the very beginning of the event that started it all

plush tiger
#

It is single owner based. There is a master object that handles most of the primary game logic

finite sierra
#

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

plush tiger
#

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

finite sierra
#

it allows you to do events with parameters

#

and it works quite well

plush tiger
#

I believe I looked at it and it had a baked in delay if I remember

#

memory wants to say .33 seconds

finite sierra
#

eh. that was something else.

#

dont worry about it

plush tiger
#

could have been, been at least 5 months or so since I was looking for a solution

finite sierra
#

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

plush tiger
#

yeah this was probably what I was looking at

finite sierra
#

i did ask him about it and it was related to something else. cant exactly remember what

plush tiger
#

it's probably good enough, but eh

finite sierra
#

believe me it is lol

normal sonnet
#

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?

strange gyro
# normal sonnet I'm making a new project with 300-400 synced bools All of them have to be indi...

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;
        }
lapis lodge
#

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.

strange gyro
# lapis lodge I need help. I am getting major rubberbanding and wondering why that is. I tried...

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);
normal sonnet
lapis lodge
strange gyro
#

Add using VRC.SDK3.Components;

lapis lodge
#

Got ya.

lapis lodge
weak tinsel
cold laurel
weak tinsel
#

thanks

cold laurel
#

np!

finite sierra
finite sierra
#

I hope that's not true lol. Otherwise every udon world goes over the limit

cold laurel
#

Pretty much! A floppy disk is faster.

hearty vale
#

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?

cold laurel
finite sierra
#

how fast does the set owner trigger btw?

frozen igloo
finite sierra
frozen igloo
#

roughly, but it's very loosely defined

finite sierra
#

@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

frozen igloo
finite sierra
#

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

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

finite sierra
#

i realize i should prob have done that from the get go ngl lol

frozen igloo
#

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

finite sierra
#

yea no the wait. was purposely added for the master only. but i ended up forgetting that setting owneship can take longer then expected

finite sierra
#

well it fixed the problems.

normal sonnet
finite sierra
faint rock
finite sierra
faint rock
finite sierra
#

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

north thistle
#

then you can't handle intentionally handing it to the master

broken rampart
#

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

finite sierra
north thistle
finite sierra
# north thistle In the design you suggested it would presume it was the result of a player leavi...

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

north thistle
# finite sierra No. in the result of a person leaving and no one new joining. it automaticly goe...

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.

finite sierra
#

thats why i said it was related to the scenario of the previous person... if you need different things to happen. Handle it differently

north thistle
#

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.

finite sierra
#

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

frozen igloo
north thistle
frozen igloo
#

yes, and that exact message you linked is referring to handing out one unique object to each player, not just letting anybody grab anything

north thistle
#

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?

north thistle
frozen igloo
#

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

cerulean zealot
#

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.

normal sonnet
#

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

stone badger
finite sierra
finite sierra
north thistle
#

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

finite sierra
north thistle
#

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

frozen igloo
# north thistle The main "inconsistency" I can think of is that in offline testing the networkin...

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

broken rampart
#

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?

frozen igloo
sturdy hornet
#

will this be synced for late joiners?

#

also found out it doesnt even sync and idk why

broken rampart
timber ferry
# sturdy hornet will this be synced for late joiners?

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,

  1. set ownership
  2. change your synced variable
  3. request serialization after changing a synced variable
  4. apply that (now changed) synced variable to whatever you need it for
  5. OnDeserialization, apply that synced variable in the same way
sturdy hornet
#

working on it

#

thanks

sturdy hornet
#

it worked! thanks man fr

#

oh and happy birthday @timber ferry

#

its mine too

#

we were born on the same day

timber ferry
# sturdy hornet like this?

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

timber ferry
sturdy hornet
#

and fixed it

tired quail
#

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?

tired quail
#

Hmmm yea seems to be working fine ingame but not in the editor

frozen igloo
north thistle
#

Just upgrade to unity 6 and use those new multiplayer features I saw mentioned; ez clap

humble girder
weak tinsel
#

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?

vapid pagoda
weak tinsel
#

yes, saw this on canny too

#

I was expecting more people screaming at this bug, seems like not many people do RequestSerialization at Start

waxen dove
#

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

weak tinsel
#

OMG

waxen dove
#

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

stone badger
# weak tinsel I was expecting more people screaming at this bug, seems like not many people do...

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.

obtuse echo
#

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

cerulean zealot
cerulean zealot
stone badger
weak tinsel
#

Agreed, the issus is not just RequestSerialization in Start. Still checking what the problem is.

stone badger
#

That said any breaking changes should ordinarily be mentioned but it isn't a science. Why didn't anyone notice during beta?

finite sierra
stone badger
finite sierra
#

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

finite sierra
stone badger
# waxen dove I only use graph sorry

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.

stone badger
finite sierra
#

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.

stone badger
#

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.

finite sierra
#

its also pretty risky. cause it can fuck things up

waxen dove
#

alright so my issue seems to have been fixed by changing from manual to continuous on the synchronisation

stone badger
stone badger
waxen dove
stone badger
# waxen dove I'm not entirely sure how it works I got it from a tutorial awhile ago. Its purp...

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.

waxen dove
#

Ah ok makes sense, Sorry for wasting your time with this

waxen dove
#

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

broken rampart
#

are you setting the owner to whoever is requesting to use the object?

waxen dove
sturdy hornet
#

so im trying to swap a game obj mat for everyone but its not even swapping and idk why

#

also this isent syncing why?

stone badger
obsidian python
hearty vale
#

does player need to be owner of object to update continuous sync

north thistle
#

yes

cerulean zealot
#

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?

north thistle
#

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

cerulean zealot
#

cool

sturdy hornet
#

also sorry for late reply

#

i was super busy

cold laurel
#

As long as it's just an empty array it'll be fine

sturdy hornet
#

how do i make this synced for late joiners

frozen igloo
# sturdy hornet 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
mossy ruin
#

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

frozen igloo
mossy ruin
#

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

frozen igloo
#

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

mossy ruin
#

you mean on the OnDeserialization event or somthing else?

#

oh you mean when they get an instance of the object?

frozen igloo
#

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

mossy ruin
#

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!

restive cliff
#

Does anyone have benchmarks for udonsynced byte[] vs string?

finite sierra
restive cliff
#

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

finite sierra
restive cliff
finite sierra
#

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.

restive cliff
#

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

finite sierra
#

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

stone badger
# restive cliff Thank you for your input, I highly suggest the screenshots which estimate the ov...

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 🙂

latent tinsel
#

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

tulip sphinx
#

everything shoud be done through serialization and you shouldnt use network events for anything long term.

latent tinsel
#

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...

tulip sphinx
latent tinsel
#

although, this is an old prject using 2019 so idk if that's affecting it XD

tulip sphinx
#

it doesnt

stone badger
# latent tinsel huh, weird, whenever I press a button to hide an object, others in world will se...

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.

north thistle
# restive cliff Does anyone have benchmarks for udonsynced `byte[]` vs `string`?

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.

finite sierra
#

@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

obsidian cedar
#

I plan to push update today/tomorrow that does it automagically

#

for now just press it manually

finite sierra
#

ooh. so no longer do i need to set how many etc?

obsidian cedar
#

Nope

#

It uses persistence

#

Or rather

#

Player Objects

#

so automatically scales up

finite sierra
#

alright. thanks 😄 for the quick heads up

valid rock
#

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.

restive cliff
obsidian python
#

i had the same issue when my ownership transfer didn't work, and it was because i had the same ownership check like yours

valid rock
# obsidian python maybe you can try remove the ``Networking.LocalPlayer.IsOwner(this.gameObject)``...

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.

obsidian python
#

where do you call the ToggleItems method? do you have it on a UI button or something?

valid rock
obsidian python
#

does the code executes if you already the owner?

valid rock
#

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.

obsidian python
#

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

valid rock
north thistle
#

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

obsidian cedar
valid rock
north thistle
#

Did you update to the fix?

valid rock
north thistle
#

so your project has this?

valid rock
north thistle
#

working on my end

valid rock
#

Huh. Wondering why it is not working for me then.

north thistle
#

Are you using OnOwnershipReuqest in your code? That can prevent an ownership request if it returns false for either the owner or the requester

valid rock
#

No. I am not using OnOwnershipRequest.

north thistle
#

You can try putting logs into the different parts of your code to see what is and isn't being called

valid rock
#

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.

finite sierra
finite sierra
#

@obsidian cedar uh. something broke with the latest update you did. i am getting the No caller assigned again. even through it worked prio

obsidian cedar
#

xD

finite sierra
#

i rolled back to 3.23 for now 😄

obsidian cedar
#

👌 I'm gonna be trying to figure out why it's being weird 😅

#

Or might just roll-back the change awhoknows we'll see

#

But I'd love ot have auto-fix implemented.. Just one that works bettter xD

finite sierra
#

yee

#

do we know how many Bytes The VrcPlayerAPI takes as a reference? when sending over the network

rigid temple
stone badger
#

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.

finite sierra
north thistle
#

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)

finite sierra
#

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

north thistle
#

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

finite sierra
#

fixed memory i cannot gurantee since i dont know how they coded it in place. but i imagine it would be.

north thistle
#

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

finite sierra
#

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.

north thistle
#

the id for every VrcPlayerApi is fixed

#

it increments for each new player who joins and is never reused

finite sierra
#

i know. thats not the point either.

#

either way

north thistle
#

the id is a guaranteed way to get the correct reference

strange token
#

@finite sierra you can network sync a variable of VRCPlayerAPI type?? :O

north thistle
#

No you can't

strange token
#

Oof misunderstood

north thistle
#

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

strange token
north thistle
#

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

finite sierra
#

seeing how they say DataTokens can use References.

north thistle
#

Actually you probably don't even need to do an id; you could just give the player a "isFirstTimeJoining" bool

strange token
#

But that sounds really cool and really helpful as a neat little trick with player objects

north thistle
#

Actually I can't find where PlayerObjects were discussed having instance-only persistence; maybe I imagined it...?

north thistle
#

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)

stone badger
finite sierra
mossy ruin
#

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?

frozen igloo
mossy ruin
#

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

frozen igloo
#

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"

mossy ruin
#

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

frozen igloo
#

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.

mossy ruin
#

alrighty!

frozen igloo
#

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

north thistle
#

What if you disable them the frame after start?

frozen igloo
honest lance
#

what if you don't disable the objects, but just the components required for interaction [renderers, colliders, etc, but not behaviours]

frozen igloo
#

Yeah, that's fine as far as networking goes

north thistle
#

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

frozen igloo
#

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

frozen igloo
honest lance
north thistle
#

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?

frozen igloo
frozen igloo
north thistle
#

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?

frozen igloo
#

Yeah exactly

north thistle
#

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

frozen igloo
#

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

north thistle
#

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?

frozen igloo
north thistle
#

Unfortunate, but thanks for the info

mossy ruin
#

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 ^.^

frozen igloo
mossy ruin
#

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

frozen igloo
#

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

mossy ruin
#

right, but what is the way to identify when the network is done?

frozen igloo
# mossy ruin hmm...what about the OnPlayerrestored? I essentially just need to be sure when a...

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.

mossy ruin
#

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

frozen igloo
# mossy ruin like a frame or a second later kinda deal?

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

mossy ruin
#

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

frozen igloo
#

I'm not quite following what your whole structure looks like, could you elaborate?

mossy ruin
#

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

frozen igloo
#

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

mossy ruin
#

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?

frozen igloo
#

yes

sturdy hornet
#

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

red rivet
#

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 vrcAevSip

cerulean zealot
#

@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

red rivet
cerulean zealot
red rivet
#

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 vrcAevSip

sturdy hornet
#

how do I serialize for just one person?

north thistle
#

you can't

hoary frost
#

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

harsh ore
sturdy hornet
north thistle
#

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

sturdy hornet
finite sierra
#

@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

north thistle
#

Is that just a normal DataToken error?

plush tiger
#

That is a DataList subscript out of bounds exception

finite sierra
#

@obsidian cedar forgot to include the important part.

obsidian cedar
#

Its not finding the method target somehow

finite sierra
#

yea. i had to revert back to 3.1.15 for it to work again

north thistle
# finite sierra nope

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);
}
finite sierra
north thistle
#

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

plush tiger
#

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.

twilit pewter
#

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.

frozen igloo
# twilit pewter When a player leaves an instance who has networking ownership of an object, can ...

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.

twilit pewter
finite sierra
plush tiger
#

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

cedar flume
#

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

tulip sphinx
#

wha

#

you roll random locally

cedar flume
#

I want to roll randomly once, then send that to everyone to synch the animation for everyone

faint rock
#

I had an Udon user leave event fire before the user join event ever fired yesterday which was interesting.

tulip sphinx
#

@cedar flume then random part should happen somewhere before request serialization, not on deserialization.

cedar flume
#

ok, something like this?

tulip sphinx
#

no

#

you set synced variable, you use it later to apply to animators

cedar flume
#

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?

tulip sphinx
cedar flume
#

ok, so I got this now.. it does throw an exception when testing tho

cedar flume
#

not sure where its breaking tho..

#

ima look at the logs

tulip sphinx
#

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

twilit pewter
tulip sphinx
#

setprogramvariable is to interact with other behaviours

twilit pewter
#

I imagine I need to sync timestamps, but for keeping things lightweight I'm stuck wondering what could be a more elegant solution.

north thistle
#

what's the race condition?

twilit pewter
cedar flume
north thistle
#

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

cedar flume
#

although I am triggering both animators, just that the values for triggering the animations of either animator doesnt overlap..

twilit pewter
#

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.

cedar flume
#

where do I put this new block?

cedar flume
#

do i put it here?

cedar flume
#

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'

frozen igloo
cedar flume
frozen igloo
#

If you're not using onvariablechanged then no, it's not needed

cedar flume
#

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

fervent holly
#

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)

north thistle
#

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

fervent holly
#

I was worried the overhead from individual udonbehaviours would have its own issues there!

north thistle
#

Better than updating the information on every single one of those objects whenever even only one of them changes

strange gyro
#

Maybe compromise and split your scene into chunks and have objects in a single chunk be synced and persisted in its own behavour

obtuse echo
# fervent holly I'd like to tickle your guys big brains if I can! I'm working with persistence,...

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

north thistle
#

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.

obtuse echo
obsidian cedar
#

It sounds like I found some Edge case in persistence so yay lovely

finite sierra
north thistle
# obtuse echo Yes, your last paragraph is what I'm suggesting. It's just for messaging the "pl...

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.

finite sierra
#

@obsidian cedar eh UInt64[] is supported being send over the network right?

obsidian cedar
finite sierra
#

@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

finite sierra
#

@obsidian cedar DataList are not supported right?

obsidian cedar
#

Nope

obsidian cedar
finite sierra
#

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

obsidian cedar
#

12 digits

#

How many bits would that be I wonder 😅

finite sierra
#

uh. eh

obsidian cedar
#

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 ?

finite sierra
#

yea thats the supposed one i showed earlier

obsidian cedar
#

42 bits It seems like, and that one doesn't work ?

finite sierra
#

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

obsidian cedar
#

weird

#

don't see anything wrong here 😅

finite sierra
#

oo

#

heck?

obsidian cedar
#

Oh shit

finite sierra
#

this is how i receive it

#

or rather the parameters

obsidian cedar
#

Yep I got it

#

And it makes me sad

#

😅

finite sierra
#

i assume your checking it on the newest version of your thing or?

obsidian cedar
#

Ye

finite sierra
#

hmm.

#

sec i might try that one real quick

obsidian cedar
#

No need

finite sierra
#

o?

obsidian cedar
#

I see the issue, it's in code that hasnt changed for like a year

finite sierra
#

ooh

#

what kind of issue is it?

obsidian cedar
#

Integer overflow

finite sierra
#

ooh. makes sense lol.

#

that explains the wierd numbers

fervent holly
finite sierra
#

@obsidian cedar is it something your able to fix or?

obsidian cedar
#

Yee

#

I think ?

#

Weird is that it's happening at the Array version for you too Sus that one should work

finite sierra
#

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

north thistle
finite sierra
#

@obsidian cedar yup so a Int64[] or Long[] works fine. it sseems to just be the unssigned int64

obsidian cedar
#

ye

fervent holly
#

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.

obsidian cedar
#

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 ?

north thistle
#

OH YAH! It's manual sync but you have no control of when it actual calls RequestSerialization

fervent holly
#

Yep yep!

finite sierra
fervent holly
finite sierra
#

and since Long / int64 works i am gonna stick with that

obsidian cedar
#

👌 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 😅

finite sierra
#

yea for now i dont need more then 16 digits worth

#

but i can diff test it tommorw

north thistle
# fervent holly You can see how I ended up making it the way I did I think. 😄 The assumptions I...

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.

fervent holly
#

So I'm not sure PlayerObjects would work for that really, maybe too much overhead. 🤔

fervent holly
#

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.

finite sierra
#

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.

north thistle
#

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

obtuse echo
#

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.

obtuse echo
fervent holly
#

Or so I believed.

#

Either way, I think I have an idea in mind!

cold laurel
honest lance
#

arrays can be synced, if their elements can be synced

fervent holly
#

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. 😄

fervent holly
#

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.

north thistle
#

Oh I see what you mean

#

Yah that's a downside of this system

fervent holly
#

Yeahhhh, I don't really see a way to check if data is still syncing either otherwise this would be a none issue really.

north thistle
#

Still syncing?

fervent holly
#

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)

north thistle
#

sync happens entirely for each individual object

#

there's no such thing as a partial sync for an object

fervent holly
#

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!

north thistle
#

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?

fervent holly
#

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.

north thistle
#

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

fervent holly
#

I MEAN, again, not an issue if there's a way to know when it's done. 😄

north thistle
#

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

fervent holly
#

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.

north thistle
#

PlayerObjects can do everything PlayerData does, btw

#

The only limitation is that a PlayerObject can't be a static reference (yet)

strange gyro
#

There's the OnPlayerDataUpdated event

fervent holly
#

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.

strange gyro
#

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

fervent holly
#

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.

obtuse echo
fervent holly
#

Wait... "Manual sync is limited to 280,496 bytes per serialization.", isn't that more than PlayerData can hold total?

obtuse echo
fervent holly
#

PlayerData is 100KB right.

#

That's... Confusing if so.

obtuse echo
#

Apparently PlayerData is using the same underlying system as synced vars.

obtuse echo
# fervent holly PlayerData is 100KB right.

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?

fervent holly
#

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)

obtuse echo
fervent holly
#

Yep!

obtuse echo
#

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.

fervent holly
#

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.

fervent holly
obtuse echo
#

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

fervent holly
#

^^

#

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.

obtuse echo
#

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

fervent holly
#

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.

obtuse echo
fervent holly
#

I get that.

north thistle
#

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

north thistle
plush tiger
#

they increased it with the persistence update, docs do not reflect the new number

north thistle
#

In any case all these issues you're having would be solved by using a PlayerObject instead of PlayerData

tulip sphinx
#

@north thistle literally same page

#

and i think i remember 280k number while 70k is news to me

#

docs are great

north thistle
#

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?

obtuse echo
#

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)

north thistle
#

Also PlayerData is actually a pre setup PlayerObject

fervent holly
#

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.

obtuse echo
fervent holly
#

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.

stone badger
#

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?

fervent holly
#

(Many APIs do this same thing, text-to-speech APIs for example will give you results as you go, then a final on completion)

stone badger
#

I'm trying to explain how "evidence" works better than "hearsay".

fervent holly
#

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)

north thistle
# obtuse echo Yeah, I agree it should still work. I'm just curious about the scaling aspect of...

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.

stone badger
north thistle
fervent holly
#

This would be a lot more data than the flight worlds.

fervent holly
#

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!)

stone badger
#

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.

obtuse echo
fervent holly
stone badger
#

It could be a bug, it could be a coding oversite, it could be a misunderstanding.

stone badger
north thistle
plush tiger
#

PlayerData is also compressed so the amount you send may not be the same size stored

fervent holly
#

I don't have all the facts myself, I'm just spitballing.

#

Trying to see if others have found the same issue.

stone badger
#

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.

fervent holly
#

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.

stone badger
#

If it is a design flaw we won't consider alternatives?

#

Okay I tried.

fervent holly
#

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.

fervent holly
stone badger
north thistle
#

Why did you not end up going with a PlayerObject to store/sync this data?

strange gyro
#

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
    }
finite sierra
finite sierra
finite sierra
cold laurel
#

So you'd be looking at something like 16s

finite sierra
cold laurel
finite sierra
#

yeea i know. but still doesnt feel like its true.

cold laurel
#

Why don't you do some tests and disprove it then?

tulip sphinx
#

thats average, not hard cap

finite sierra
#

trying to but to do so i need valid testing and 70-80 people

cold laurel
#

no

#

you're misunderstanding

finite sierra
#

yes i do lol..

cold laurel
#

The limit is sending

#

not receiving

finite sierra
#

i am talking exclusively about sending.,

cold laurel
#

The amount of people in the instance does not matter for sending

finite sierra
#

since 80 people with FBT and eye and face tracking would consume way more then 11 kb

cold laurel
#

It does not matter what the other people are doing

finite sierra
#

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?

cold laurel
#

The limit is how much your client can send

finite sierra
#

or send.

strange gyro
#

What does a client need to send about other players to other players?

finite sierra
#

depends on the world.

finite sierra
cold laurel
#

Of course it is per client

finite sierra
#

right. which is not mentioned anywhere

#

which is why i was confused

finite sierra
#

cause i thought it was per instance.

#

like 11 kb/s sending max per instance

tulip sphinx
#

nah, its out traffic

finite sierra
#

or traffic if u will

tulip sphinx
#

youd see it in ingame debug console

finite sierra
#

yea.

cold laurel
#

👍

#

Now you can share the correct information in the future

fervent holly
# north thistle Why did you not end up going with a PlayerObject to store/sync this data?

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!

finite sierra
#

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?

fervent holly
#

@strange gyro ^ For that reason, I'll likely switch to some synced arrays for the initial world data on join.

finite sierra
#

well if its 6-11 kb per client instead. output that changes everything ngl

fervent holly
#

On join, clients will recieve synced variables from even manual sync behaviours without requestserialization being called.

#

(The latest state that was sent out)

finite sierra
north thistle
north thistle
#

I'm legit struggling to imagine why they would design it that way

finite sierra
#

but i am sure @frozen igloo could explain how vrchat maybe handling more then 11 kb of data sync on someone rejoining.

frozen igloo
#

I mean it does split it up into chunks, it takes multiple packets to send more than a few kb

fervent holly
#

Continuous sync is about the same rate if I recall? I feel like PlayerData is more similar to that than Manual. vrcAevSip

frozen igloo
#

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

frozen igloo
fervent holly
fervent holly
frozen igloo
#

Yes?

#

That's what normal manual does too

#

And definitely not what continuous does

fervent holly
#

Manual can support like 280KB per sync right?

#

According to the docs

#

PlayerData is absolutely not doing that.

frozen igloo
#

Yes, but not in a single chunk. It takes time to send that 280kb because it has to be split up

fervent holly
#

Ahhhh! I see!

frozen igloo
#

Playerdata also technically supports 280kb but not persistently

#

Well .. if it's all data that can be compressed easily it will work persistent

fervent holly
#

But OnDeserialize is only called once that's all sent right?

#

With PlayerData, you get the callback for it being updated in chunks.

north thistle
#

@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

fervent holly
north thistle
#

Was this only observed locally and/or remotely?

fervent holly
frozen igloo
#

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.

fervent holly
#

^

#

Maybe so!

#

It's just a bit... Confusing.

frozen igloo
#

I know playerdata has a bug that just triggers updated twice incorrectly, but that has nothing to do with networking

fervent holly
#

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?

frozen igloo
#

Well if it was continuous sync it wouldn't be able to do any of this at all heh

north thistle
#

The issue Curtis was running into is that they didn't know when the received data was accurate due to the multiple OnPlayerDataUpdated events

fervent holly
#

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.

frozen igloo
fervent holly
#

Hmm. x.x

frozen igloo
#

The reason they may get old state on join is because it loads the persistent record first because that happens faster

fervent holly
#

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. 😄

frozen igloo
#

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

fervent holly
frozen igloo
#

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

fervent holly
#

Ideally I wanted to avoid having a few giant synced arrays.

#

Which would ensure accuracy, but lots of bandwidth.

frozen igloo
#

You can split it up into chunks yourself across objects, but do not split it up into chunks across time

fervent holly
north thistle
#

lol that was the original suggstion; it has come full circle

frozen igloo
#

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

fervent holly
#

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.

frozen igloo
#

On join request serialization is part of the problem. That state should already be on the server before the player ever gets there

fervent holly
#

It can get pretty chunky.

frozen igloo
#

Then don't send the entire thing on one change. Split it up into multiple chunks across objects

fervent holly
#

I'm not entirely sure I understand. That'd end up being the same bandwidth usage anyway?
Just split up.

north thistle
#

Quick question: will having a ton of individual networking objects in your scene cause hardware or networking performance issues if they only update infrequently?

fervent holly
#

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.

frozen igloo
fervent holly
#

Maybe I'm misunderstanding!

frozen igloo
#

That caching is designed to reduce the total bandwidth you need. By not using it, you will be subject to much tighter restrictions

fervent holly
#

Hmmmm.

#

I'm just concerned that, in the worst case scenario...
I could have maybe 4x 4,000 item arrays, mainly of Short.

north thistle
fervent holly
#

That's a lot.

frozen igloo
fervent holly
#

Planting them, etc.

fervent holly
#

(x and z Short for positions, ushort for object type)

frozen igloo
#

Ok, and?

fervent holly
#

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.

north thistle
#

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

fervent holly
#

(Right now to avoid this I'm just changing some synced variables to specify create and remote calls)

fervent holly
#

Not just what changed.

#

Otherwise you're totally right!

#

With manual sync, the changes are basically instant.

frozen igloo
#

Have like one synced object per 10x10 section of crops

fervent holly
#

Ahhh, I get you.

#

Hmmm... I like that, but like... I guess my biggest fear was the CPU overhead from extra UdonBehaviours.

frozen igloo
#

If they're not running update it's fine

fervent holly
#

At least years ago when I looked into it, additional behaviours had a huge toll.

north thistle
#

Would having each individual crop have its own UdonBehavior be too much?

fervent holly
#

I wasn't sure if that mattered for Udon.

#

Wasn't sure if it would attempt to call it anyway even if not implemented.

fervent holly
#

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.

frozen igloo
#

Update is not dispatched if the udonbehaviour doesn't have it

fervent holly
#

Appreciate all the ideas btw!

north thistle
#

Oh when having crops waiting for timed events, would using SendCustomEventDelayedSeconds be more performant than running an Update based timer on each behavior?

frozen igloo
#

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

north thistle
#

btw I love SendCustomEventDelayedSeconds so much; especially since we don't have coroutines

stone badger
#

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.

fervent holly
#

And I totally agree that is an amazing function!

fervent holly
# stone badger Seemingly there remains no interest in a series of test classes with test method...

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.

stone badger
# fervent holly I can look into it if it comes down to that. It's hard to make an 'exact' repeat...

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. 🙂

finite sierra
north thistle
#

I'm 90% sure that by "delayed event loop" they mean a method that loops by calling itself with a SendCustomEventDelayed

frozen igloo
north thistle
#

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

fervent holly
#

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.

frozen igloo
#

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.

fervent holly
#

^^^^

#

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.

stone badger
#

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.

frozen igloo
#

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

fervent holly
#

OR, even worse, release a half-baked feature.

stone badger
#

There is no perfect in software engineering.

fervent holly
#

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. vrcAevSip

strange gyro
#

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

fervent holly
#

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. 😄

frozen igloo
#

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

fervent holly
#

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.

strange gyro
#

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

frozen igloo
#

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.

finite sierra
normal sonnet
#

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

obsidian python
#

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

frozen igloo
strange gyro
obsidian python
frozen igloo
# obsidian python it's not
  1. The function in that graph screenshot is string contains, not array contains

  2. 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.

strange gyro
#

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

obsidian python
#

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

frozen igloo
# obsidian python yeah i know, i think you just misunderstood me, okgood were asking if there's a ...

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

GitHub

Collection of array extension methods compatible with UdonSharp 1.x which adds partial feature set from List - Varneon/VUdon-ArrayExtensions

timber ferry
#

if using U#, an array contains method is very easy to make yourself too

north thistle
#

the issue is that if you make it yourself it's going to have Udon overhead

tulip sphinx
#

if string ",user1,user2,user3," contains ","+username+","🫠

frozen igloo
north thistle
# frozen igloo 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

strange gyro
#

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

finite sierra
#

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

heavy spindle
#

Update should be heavily avoided

finite sierra
#

however in Udon consider using the SendCustomEventDelayed instead and make it run it self to create a psuedo update loop

strange token
strange gyro
wooden saddle
#

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

strange token
wooden saddle
#

Any one have ideas how fix thises

strange gyro
#

Do you have a VRCObjectSync component on the pickup?

wooden saddle
#

Yes