is there a way i can get which player originates a CustomNetworkEvent without transferring ownership? or is that unavoidable?
i'm trying to write a script that adds/removes players from a list on button press, but transferring ownership of the whole object feels a bit excessive
i recall reading somewhere in the documentation about network events passing something as an input, but i can't find that anywhere
#udon-networking
1 messages · Page 11 of 1
That would be cool but does not sound like it would be an option. The only way the system will accept a network event from an object is if the player calling the event owns it
I think this is a bit misleading or I'm misunderstanding. If you are describing SendCustomNetworkEvent it doesn't / shouldn't require ownership of anything.
The key thing here (I think) is to separate the transfer of sync'd data with an object (or obects) that need to act on the data. A little tricky to explain but you can set up "data sync" objects to transfer state. These do not need to be part of the object that uses the data. A list can "react" to the data change on another object. So (for instance) each local copy of a list should be able to update itself when a "sync'd object" that contains (for instance the player id) arrives.
Some care is needed to coordinate with late joiners as is always the case with sync'd data.
Hey guys, I'm new the Networking for this game. I was wondering if you could clarify something for me.
Using UdonSharp, I tried to make a script that teleports all players to a specific location when a single user interacts with a button. The first player would teleport, but when it would get to the second player, they would teleport to the wrong location.
I used RequestSerialization and OnDeserialization to sync these changes manually.
I ended up using SendCustomNetworkEvent instead which worked fine. But why doesn't this work?:
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using UnityEngine.UI;
using UnityEditor;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class Teleporter : UdonSharpBehaviour
{
public override void Interact()
{
base.Interact();
if (!Networking.IsOwner(gameObject)) {
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
RequestSerialization();
OnDeserialization();
}
public override void OnDeserialization()
{
Networking.LocalPlayer.TeleportTo(new Vector3(28.5f, 13f, 18f), Quaternion.Euler(0,0,0));
}
}
Again. Player 1 would teleport to this location normally, but the second player would not.
you dont need networking for this, RequestSerialzation will only work if you changed an UdonSynced variable, instead use this
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using UnityEngine.UI;
using UnityEditor;
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class Teleporter : UdonSharpBehaviour
{
new void Interact()
{
SendCustomEventDelayedFrames(nameof(_TeleportPlayer), 0);
}
public void _TeleportPlayer()
{
Networking.LocalPlayer.TeleportTo(new Vector3(28.5f, 13f, 18f), Quaternion.Euler(0,0,0));
}
}```
you might be wondering, why use "SendCustomEventDelayedFrames", teleport will only work properly when delayed by 1 frame, something something execution order
oh my bad, this is single user, you are trying to teleport everyone
Ohhhhhh. I see. So OnDeserialization is called only when UdonSynced variables are changed
and you RequestSerialization
I see
one moment im fixing it for networked
ok
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
using UnityEngine.UI;
using UnityEditor;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class Teleporter : UdonSharpBehaviour
{
new void Interact()
{
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(TeleportPlayers));
}
public void TeleportPlayers() {
SendCustomEventDelayedFrames(nameof(_TeleportPlayer), 0);
}
public void _TeleportPlayer()
{
Networking.LocalPlayer.TeleportTo(new Vector3(28.5f, 13f, 18f), Quaternion.Euler(0,0,0));
}
}```
this should teleport everyone
you can also use RequestSerialization to ensure it goes through, NetworkEvents arent as reliable
but youd need to set up logic for it, this would be easier to understand for now
Why 2 events?
the second one isnt really an event, its more like a coroutine, i explained above
teleport will only work properly when delayed 1 frame...
maybe that's why it wasn't working properly when I was using OnDeserialization
Because the 2nd player would get teleported to some random location
even if I put a specific teleport location
With the code that I provided I excluded some of the UdonSynced variables that I was changing.
So that's probably why the OnDeserialization method was running for me
but the code I provided, the 2nd player should technically not teleport at all
@vapid pagoda Thanks for the help. I think I understand this a bit better now.
Teleports are so weird for that reason!!
When am I gonna see a bug fix for “fix teleporting when called from OnDeserialization” lol
you wont, it has nothing to do with OnDeserialization on its own, but the execution order of when teleport gets called, and when the player position is allowed to be updated
;-; that’s fair
Hello ! Out of curiosity, does RequestSerialization only send variables that changed (instead of every synced variables) in Manual sync ?
(And if that's the case, do Data Dictionaries only send the tokens that changed ?)
you can't sync DataLists/DataDictionaries unless you serialize to a JSON string, and sync that string
Ah so that answers the 2nd question
i believe it syncs every variable no matter if it has changed
i do not believe this is true, there's a limit on how much data can be synchronized every serialization, it would be wildly inefficient to do that
It is in fact true, it sends all data every serialization
how come? doesn't it make more sense to only serialize and what's actually changed?
not just for like, network congestion reasons, but surely it costs yall less in bandwidth costs
I'd only think sending only what changed has more complication and prone to bug than sending as a whole.
so I wanna make a button that toogles something off and something else on that can only be done by an instance owner and idk how this stuff works so help
Add a branch in Interaction event to check whether a local player is instance owner before it runs its program.
where I put this?
May you read again?
And also, if you use network event to sync object state, you're doing it wrong.
all sounds chinese to me
Not an excuse to not learn a proper way.
still confuse
cant even find the stuff in this video like state n stuff
god im old
So you asked where to add.
I had explained "Add a branch in Interaction event". It's your "Interact" node. Isn't that clear enough?
You can skip that video for now if you want to solve your problem at hand. But state sync is not going to work correctly.
so this helps?
Yes. That's one part of it.
okk
so nothing works now
What do you have now?
the thing ive sent above
You added only the branch?
That's one part.
Means you had done a part of many parts of the whole process. So that isn't done yet.
tell me about it cuz i dont understand anything
its surprising i got that far
Add a branch in Interaction event to check whether a local player is instance owner before it runs its program.
howww xddd
Get localplayer from networking.
Get IsInstanceOwner from that player
Put the Result to Branch.
so like that?
Yes.
poggers
Networking is all about tradeoffs and picking what you care about. In vrchat's case, it prioritizes CPU speed over bytecount and bandwidth. This particular aspect is one example where that is particularly visible
Hmmmmm... so in my case, for a big UI with ~20 interactive components (buttons, sliders, input fields...) would it be better to make individual components sync their own values or sync the whole thing when one of them changes ?
At that number of elements, the data size is going to be small regardless so the overhead of putting them on separate objects would make it worse anyway
Unless of course the input fields have the potential to be very large strings... In that case yeah sync them separately
Thanks, that actually makes my life easier ^^
Well "worse" is a relative term I think. One shouldn't worry about the networking of simple values, you have to sync data between players there is no way around it. However I would hesitate to place all the sync'd variables on (for instance) one large UI panel. It is mostly a case for organization (not absolute speed).
When data is sync'd there is more going on than simply the transfer of the data values or there would be no reason to sync the values. Something on the other clients change and this can cause needless redrawing and/or other events to fire. But even if it doesn't having your UI components update their appearance when nothing has changed gets you nothing.
Is this likely to change with the release of Udon 2 (whenever it happens.. probably delayed further because of the layoffs.)
surely you'll have some CPU headroom to spare, once that happens
Concerns over bytes are (mistakenly in my view) expressed here fairly routinely. Networking isn't limited to the few bytes transferred when sync'ing a bool and/or int value. Data is transferring all the time. View the logs and you will see how much data is transferred at login, each time you join a world, move around, pick something up, etc. If people are counting bytes they must have solved every other issue at hand which I doubt 🙂
i mean i feel like udon's biggest issue is the fact it's not wasm yet
the networking is okay
(though i hate the p2p network style (yes i know there's a proton proxy server in-between), vrchat would have been much better suited with a client-server architecture, like that of roblox)
it's simpler for developers, and prevents a lot of vrchat's networking issues (ahem, if a world master crashes, the game is locked up until that person gets fully disconnected and master status is transferred)
i assume the only reason proton (and ultimately the client-ownership system) was chosen was because of costs
because it costs a LOT more to run an entire headless server of the world for each instance
when instead they can just have a proxy
Zero chance of changing with udon 2, but small chance of changing later
Eh, the concerns are warranted but sometimes they're misplaced. It's worth considering at large scale, because a 30% saving can be huge for thousands of bytes. But if your data is just a handful of variables, yeah don't bother worrying about it
I dont get why is it not working. Local player ie owner sees the result (i just print owner's name and itemtype value into TMP for now). Remote player with crossed node enabled sees owner's name but default variable value, with it disconnected "serialize" event doesnt fire at all.
Expected behaviour - crossed node is not needed, on each requestserialization im guaranteed to fire "serialize" event for everyone, late joiners fire it as well.
Sync is set to manual, ownership of the object given before enabling the object and is correct.
I even made simplified test script and it kinda works but also kinda not. Joining or pushing a button should add 1 to counter and print it along with your username for everyone. It works except for some reason joining (ie. Start event) doesnt add 1 to variable, but shows new owner's name and old value correctly for everyone. Why??
using onpreserialization doesn't work at all, it just doesnt happen for owner, no log event
Changed. Button (so, local interact event) sends NewItemType value, so NewItemType is changed only for one person who pushed the button. And it doesnt set the owner! Player2 is the one pushing the button and for him "serialize" happens, but he's not the owner of the object, what? Bottom pre/post events fire never.
Okay it sets the owner allright according to ~+8, but prints wrong owner name
I'm not sure about this entire system, but for this specific thing I can say it's because start is too early for you to receive synced data, so it will always be the default 0 at the point in time that you try to add 1 to it. You're not giving it enough time to receive the current count and then add one to it. A good way to do that might be to have a bool that tracks if you've added your number to the count. In ondeserialization, if you haven't, take ownership and add your number.
But i recieve data alright. I'd understand if it was always resetting to 1 (default local 0+1)
but it shows synced value
but id rather understand why none of other 3 images EVER fires OnDeserialization
I'm not sure what your goal with this is but if that's a synced variable I can say it's not a good idea to have everybody take ownership on any change of a synced variable. That's going to make everybody fight for it and cause havoc. Nothing will make any sense or be consistent with that there
Theres button, it's local. It sets local var NewItemType in networked object script thus firing local event. As the result of local event, person who pushed the button becomes owner of the object and tells everyone the value of synced ItemType var, causing everyone to run OnDeserialization to well, update object state with that new value. In theory.
I don't get why setting ownership is a bad idea, since person who pushed the button is also one most likely to interact with that networked object later.
Is newitemtype not a synced variable?
Oh ok that's fine
could it be about the same naming for multiple networked objects? they have unique ids/pathes tho, and im on 3.6.1
No that's fine. But maybe something else is causing it to fail to sync. Are these instantiated?
Are they ever disabled?
That could be it. Disabling network objects can break things like late sync
well since im using onenable, they are initially disabled. pool object enables them.
Why does this need to be in a pool?
pool=vrcsync pickup, but having all instantiation stuff on pickup as well (so, forced continuous sync) happened to be way to unreliable, so im trying to remake it with serialization and ability to send data now and then.
Why does the thing keeping track of how many people have visited need to be a child of a pickup? Is it on multiple pickups all doing the same thing?
uh, second screenshot was just a test
Then what's the actual use case
its pool of pickups, inside of each theres script that syncs ItemType and instantiates object
so its 100 pickups but each of them can be of any type
Ohh gotcha, yes I've done things like that too. I would not recommend having the syncing for that be on the pickups themselves, that's going to be very unstable. I'd build a custom pool manager and have it manage an array of item types centrally. You can even reuse itemtype where one of the values is just non-existent, despawned.
well i have it working with continuous. Now i went for serializtion and it just doesnt fire
ofc i can have it outside and instantiate object not to itself but to pickup but why should i, with current setup i can easily use getparent/child
Because it's a far more stable way to do networking
If the itemtype is determined by the pool, you can spawn it and then instantly set the itemtype at the same type. If the itemtype is synced separately from the pool you have to spawn a dummy object and then wait for it to receive its type which might never happen properly
i dont like single array idea cause then it will certainly cause some clashing
No more than the existing pool already does
i dont get it. I spawn an object (pool) and set itemtype to its child (manual sync on screenshots). It works locally perfectly, so it receives it's type.
Syncing is not reliable on objects which are sometimes disabled
Having them enabled at least on start and then disabling once they've initialized is at least slightly better, but having their synced data change on remote before you've ever initialized them because they were disabled at start is not good
why onpreserialization/on post dont fire? at least in sim. i added them to test script that was working and still nothing
Ok you never clarified clientsim, yeah it doesn't do onpre/onpost
I mean, i just connected them to logs. i cant see udon logs in game can i?
Okaaaay, that actually helped. I literally just enabled objects in editor (so they get disabled only as a child of the member of pool) and now it works. Network sync, late sync.
Thank you, time to test it with lots of objects to see if its actually reliable
Is this where i get to see 11kb limit or not?
Kinda, but not exactly. There are some cases where you can be capped out on output speed but that actually shows a lower number, due to reasons such as transferring many small sets of data rather than few large ones
So I'm wondering how I'm supposed to network this the "right" way in Udon:
My map is tile-based and randomly generated when the instance starts (by selecting random tiles out of a list of tile prefabs, and instantiating them) how do I sync this state to other players?
My initial idea was an UdonSynced integer array which points to the index of the prefab
var prefabIndex = gameMap[x * y];
and then var prefab = Instantiate(tiles[prefabIndex]);
But yeah, I was wondering if there was a better way?
easier to sync the random seed and just generate the same on all clients.
How do I get a seed and use it?
Never used seed-based random generation before lol
the function to seed unity's random number generator is UnityEngine.Random.InitState(int seed)
just generate a random number first and then save/sync that. then when you go to generate your tiles you call initstate first with your number
Okay, I'm lost here, wtf do I do to get my VRC Pickups to sync as well as having a random map generation system?
There'll be 3,136 plots of land, so I don't think a VRC Object Pool is a viable option, cause I'd need 3136 object instances of each individual plot type
Then again, I don't think scripts start syncing until their first Start() is called, right?
So if they're disabled until they're needed, they should have zero performance impact (renderers will be off, and my Object Sync / Pickups will not be running nor syncing until they're turned on)
Try it and see if it works ! There are also alternatives like https://github.com/MMMaellon/LightSync that can lighten the network load by only syncing what it needs/when it needs to
But like we said before, it's simpler to just base the state on the initial seed - then you can take a lot of workarounds
does a GameObject always have an owner? Does the owner get reassigned automatically if the current owner leaves the instance?
@wispy arrow uh, your plots of land are pickups or what?
@orchid locust objects with udon on it (and some others like avatar pedestals for some reason). yes. theres docs you know.
Yes, but I couldn't find where in the docs this was mentioned, if at all.
(a link to the docs would've been more helpful here)
@orchid locust if you didn’t already get it here ya goh https://creators.vrchat.com/worlds/udon/networking/
Multiplayer experiences are the heart of VRChat, so creating a world that reacts to players and synchronizes the data between them is key.
my plots have items that can be picked up
not the whole plot
wanted to ask in relation to an vrc object pool, if the items in question are manual networked items, is there a chance they wont get their synced values for a late joiner before the object pool sets them to spawned?
like will it have the object active before the networking sets its data, or is there a chance that it wont get the data before its active?
i believe you'll need to enable the object once at any point for it to initialize its networking code or something
so if you have it as apart of vrcobject pool, does it handle that?
not sure
i'm gonna be using one EXTENSIVELY for my project, so I can let you know how it goes with regards to networking things lol
thanks, i wanted to use it to optimize how im doing my dynamic items. Right now they just all exist, so i tried to optimize if they are in stand still to do nothing, but it could be a step better if they were handled by a vrcobject pool
but im debating weither or not that would actually be a help or hinderance
I mean, a pool will disable the objects when they're not in use, and you just fetch them as you need them
i think that's pretty good for performance
maybe, right now in idel they wont have any active mesh renderers, so they wouldnt eat up the gpu, but they do have post updates with a bool to prevent it from running so they are still doing an if statment a frame
so def would be a bump up, just not sure by how much XD
Yeah, if an UdonBehaviour has networking (either by Manual or Continuous sync), it WILL consume more CPU frametime than an entirely unsynced one
I know when I've used manually syncd objects with a pool, I had to account for values being set and never receiving an OnDeserialization(). Of course you'll also need Ondeserialization() or similar for when its active, so you end of having to code for both possibilities anyway.
hmm....sounds like a headache...i think ill look at it as an option if i find i need to save more cpu
funny enough 600 manual sync objects doesnt consume a lot
assuming they are idle
Cool. The other headache, in case you end up going this route, is that you'll likely need to do something in OnEnable() (when it comes out of the pool). The first call to OnEnable() happens before the call to Start(), so you can't rely on any initialization you normally put in Start() having occurred.
ah right
well then you messed up since a. every networked stuff uncluding pickups must be in the scene already b. 200 pickups (with vrc sync) is the adequate limit, better less. not saying its impossible but lotta work to make it work
yeah, seems like a bit of an oversight, no? is it really that hard to add an instantiate function that assigns a network id to an object during instantiation, and syncs it to all clients?
you have 11kB per second and for the reasons dev only know it sends it every frame despite pickup being dead. so 200 items having global transform ie vrc sync is the most you can ask for without addding layers
i mean, i'll probably end up doing a custom pickup system because of the way i need it to function, so that's probably no issue, just a bit weird how we don't have more of these seemingly obvious features
i have plain as bord synced variable with on deserialization, and it doesnt sync on join. why indeed
and i have this global day/night cycle
this should not need this much code
it's not even that much
but in any client-server program, it wouldn't need all this master bs, because the server will always be present
You likely don't need to check for owner change. And you could just rely on GetServerTime alone without using any networking or sync.
How do you propose that would work if I had 6 minute days, and 5 minute nights?
It'd be easier if they were just, the same, but yanno
Mod server time with 11 minutes. Then if the result is greater than 6 minutes, it's night, otherwise, it's day.
Ooooh, that seems pretty smart
And that's guaranteed to be the same for every single player? (Obviously, accounting for differences because of people's latency to the server)
Yes. Although latency is negligible. No one would go to compare time precision down to a second.
Yeah, and I mean as long as it's not like, minutes out of sync it's fine
Hey, so this mystical 11 kb/s max outgoing bandwidth mentioned in the VRChat docs... I seem to recall someone at some point mentioning that this is a given client's total max outgoing data, and includes the player's IK data, animator synced variables, and VOIP. I can't find documentation that confirms this, however, and I would REALLY love to know what the numbers are on how much of that data players have been actually observed to use up on their own before any udon networking is added to the pie.
I've written down that whomever told me this suggested that players already use on average 4-5 kb/s of this 11 kb/s limit without udon. Again, I can't find any documentation that confirms this so I don't know how much variance I could expect from that number, but...
... if that's true, it's wildly misleading to tell world creators that their limit is 11 kb/s if realistically it's usually half that?
My main concern is, even if I trust these numbers, what is that 4-5 kb/s based on? How much does VOIP contribute, and how much variance can be expected between a really optimized model with a simple rig and animator, vs a more complicated rig and a beefy animator that's sending data synchronization super frequently?
Knowing whether my reasonable UDON bandwidth ceiling is 11 kb/s, 7 kb/s, 5 kb/s, or even less? is a BIG deal when speccing out a networking plan for a game world...
Got my answer. Estimate is that clients in VR start at ~3.5 kb/s, plus 0.5 kb/s for voice, plus up to an additional 2 kb/s depending on how bad their parameters are thrashing.
So if I want to be extra conservative and avoid network 'suffering' I'm gonna target 4, maybe 5 kb/s absolute max sustained network traffic, from udon, per client
Definitely feel like this should be documented still.
uh avatar complexity doesnt matter (as soon as its fbt humanoid ofc, it can be less), its anyway limited to ik tracking points and 256 bits at 10hz so worst case, avi params take .32kbyte except open puppet menu but its like one-four 8bit params atm
where did you find this information?
Phasedragon
Why was 11 kb made the limit, anyway?
"Similar platforms" can go up to 100 kb/s, and yes, while their network architecture wildly differs from VRChat's, it makes sense to allow a more unbounded network throughput, since almost every home network is at least 1 mbps both ways
(that mentioned platform is Roblox, lol)
The primary reason is because the data goes through a relay/proxy server and that costs money
yeah but it's really inhibiting, especially when apparently avatars are included in that total
i could probably understand 11kb/s for the world, and a different amount for avatars, and that's merged into one larger maximum
but some games have to transfer a lot of data as a part of how they work, and that's quite difficult within the constraints of trying to be within this network limit which literally varies based on what avatar someone's wearing
cuz the more IK stuff someone has, and the more synced params, the more network traffic, which means less for the world
How and why this fails? It should always be true in client sim, it should be true for id 1 in VRC unless ownership has transferred. But, it just isnt. I removed check and everything works now, since non-owner cannot ask for serialization anyway and im my case thats all i need, but why.
Also what's reasonable time to delay Start event to be sure OnDeserialization fires first?
So if they do same stuff Start doesnt use default pre-sync values
Why don't you just put it on OnDeserialization and have a isFirstTime boolean that is flipped, so you can call an event after the first deserialization?
@tulip sphinx
cause i need to run it for master (ie before any serialization) as well
Can't you do that with preserialization?
Are you trying to mix network event callouts and variables serialization. That's always runs into race conditions for me
you lost me, and those (pre/post) dont work in sim so wont touch it.
Lemme explain, i load img from url, url is synced but if i load it on Start, it uses fallback value cause OnDeserialization fires afterward and game is already busy loading fallback. So late joiners see fallback, unless i delay loading via Start on X seconds so OnDeserialization's part of loading fires first and Start part gets igonred
Oh you have to manually run the pre and post serialization on the client Sim.
In the "run events" drop down on the gameobject
I still don't get what am i supposed to do. All i need is to run X once. After serialization, ie with proper networked variables, if there was ever one. Otherwise asap.
Oh in that case can't you start -> send custom event delayed?
Well yes
So, whats the reasonable time
Idk. I think 20 - 30 seconds and you might be good. Start happens WAY before onPlayerjoined and even before the fade from black is initiated.
huh. I set it to 5 seconds and while working, it's already suboptimal
I.e. i manage to run upstairs and see blank
What if it was right in front of player's face on load
oh if this is something that players first see you may have to do some trickery like, also have your surrounding start black and then fade out.
like that breath of the wild world. you start out in blackness and hear zeldas voice "link! wake up link!" then you fade into the environment. that gives some extra time to get things loaded.
I have a player object pool with a player state script
I also have this networked event which is called for everyone, but Owner is never equal to Networking.LocalPlayer, which is causing the tag to appear for the person who whistled (which is NOT what I want)
Where do you set the Owner variable?
It's handled by CyanPlayerObjectPool, or it's supposed to be.
What is the best way of syncing a local ui / local variables to a networked script for syncing.
Hmmm... here's the way I do it
The local player changes the UI/variable:
- detect changes on the local UI/variable (for an interactive UI component you can use its events)
- check if the local player has authority on the networked object
- if no,
SetOwner(localPlayer)if you can or refresh the UI/variable from the networked object - if yes or just set owner, update the network object with the UI/variable and
RequestSerialization()
- if no,
A remote player changed the UI/variable:
- refresh the UI/variable from the networked object in
OnDeserialization()
Local UI: https://gist.github.com/AvocadoVR/934cede6b5d7aeaeabd6b14fff656717
Start Game Code (Executes First)
public void StartGame()
{
if (!Networking.IsOwner(gameObject)) return;
Debug.Log("Game has Started!");
hasGameStarted = true;
sync = -1;
RequestSerialization();
GenRound();
SendCustomEventDelayedSeconds(nameof(SN), 5);
mainMenuUI.SetActive(false);
}
public void SN()
{
Networking.SetOwner(player, networkManager.gameObject);
networkManager.StartGameNetworking();
}
Networking Code for Local UI (Executes 2nd)
public void StartGameNetworking()
{
for (int i = 0; i < gameManager.promptAndUsers.Length; i++)
{
promptIDs.Add((int)gameManager.promptAndUsers[i].x);
}
var players = playerManager.players;
for (int i = 0; i < playerManager.players.Count; i++)
{
var _player = gameManager.players[i];
VRCPlayerApi currentPlayer = VRCPlayerApi.GetPlayerById(player.playerId);
if (!Utilities.IsValid(currentPlayer)) return;
Networking.SetOwner(currentPlayer, gameObject);
Debug.Log(players[i].Double);
int playerId = (int)players[i].Double;
_player.playerId = playerId;
_player.RequestSerialization();
EnableVoting();
playerUI.promptID = gameManager.userPrompts(player.playerId);
}
Executes Third
ShowPrompt()
playerUI is the local ui
Sooo... what's the issue you're having ?
Does everyone have the same prompt, is the prompt not initialized, or does the UI not show at all ?
I think the issue here is you SetOwner and RequestSerialization immediately in a loop
I think you'll be better off making the player data synced so every player owns their own data.
Don't change the owner of the managers (or do it safely by waiting for confirmation) but use networked events/a syncing var on the player data
How. I use a join/leave system
Create as many player data objects as there can be players and place them in an object pool. Reference the pool in the manager, and then:
- when a player joins, get a player data from the pool and initialize it with the new player
- when a player leaves, return the player data to the pool
https://creators.vrchat.com/worlds/udon/networking/network-components#vrc-object-pool
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
set them as an owner or smth
That too
i'm not really sure why this isn't working as intended. everything works object-owner-side, but remote players don't see the cars getting enabled
on the remote player's client, i see the logs and it says the correct carIndex and that carEnabled = true, but the gameobject isn't getting enabled
I see progress in your coding but you are still (in my opinion) working way too hard to accomplish routine tasks. It may seem non-intuitive but you are not really trying (or shouldn't be trying) to write something that enables a car. Write some code that absolutely, positively sync's an int and a bool. With that working what you do when the sync occurs is up to you. The sync code is guaranteed to work and you reduce the surface area when trying to figure out why this particular object doesn't enable.
Note that you call CarSpawnLoop for everyone and then check if the localPlayer is the owner. But you needn't call the method at all in that case right?
And (hopefully you take this as helpful) while you logging is better it is still way to complex. You shouldn't need to pass arbitrary strings like "aqua" and "orange". These mean something don't they? Are they different types of log statements? Consider adding other methods to your Logger. like LimeLog(), OrangeLog(), etc. if you absolutely want to indicate the color.. These simply call ColorLog and pass along the color. What do the true and false parameters indicate? Again make them available via more static methods so you don't end up sending them all the time.
i think part of my issue with this script specifically, is that i was trying to convert a (not that great) graph to U#
and, i have my logger set to accept a name, message, color, bold, and italic. that's what those extra parts mean
That may be related but that will happen all the time. I often translated from one language to another, Z80 ASM, BASIC, Pascal, C and others. You need to keep what your goal is clear regardless.
What is the value of the bold and italic when it comes to tracking what is going on in the code? What is a lime true false tell you that an aqua true false doesn't?
I wrote a color-coded logging system in JavaScript but I don't pass all the colors and stuff. The colors are used to denote the "level" so Info, Warning, Error, Trace, etc.
One just doesn't want to have their logging system get in the way of writing and reading the code that matters.
Even the colors BTW, if you insist on using them might be better off defined as enum values. You don't need strings for this.
or be goofy like me!
String interp plz
in my project, these are for debug testing, it doesnt matter in the live world
But string interp
i dont know what to tell you, to each their own
Of course everyone should write code the way they prefer but it is nice to see alternatives particularly if the options offer simpler, more robust solutions. The following is one of my calls to my Logger class.
Logger.Info($"{_instanceId}:{_nameofClass}.{nameof(Start)} Version={AppApi.AppVersion} IsQuest={AppApi.IsQuest} IsDevelopment={IsDevelopment}");
it outputs the following (minus the color strings which I've removed from the post)
[app][18:07:20.12] 27068:App.Start Version=v 0.29.1 IsQuest=False IsDevelopment=True
The color is defined by the fact that I asked for Info logging, both the time and the colorization are optional.
Plus it supports the typical logging levels plus "any" meaning output it regardless of level and Assert which will only log if the assertion is true.
The last one can be useful in say Update methods where you may only want to see a log a minute or so and not every frame.
With subclassing which I make heavy use of the instance Id lets me group the subclassed logging
i re-wrote it completely myself instead of converting from graph this time, and it worked first try
i also re-did my custom logger a bit to use a color enum instead of string, it only takes one bool for bold or not, and i only have one method (Log) because i didn't really use my other two i had
how can I make an instantiated object synced for all players in my world? I tried using networking.instantiate but it says its not exposed in udon
I've also tried regularly instantiated an object with a vrc object sync component but that isn't working for some reason
Instantiated object just can't do networking.
You have to use object pool and create object preemptively.
thanks, I'll look into that
if youre curious why it won't sync, every networked object has a network id, but these can't be generated at runtime, only from the editor
technically its possible to fake instantiated objects, but its quite advanced and takes time, especially long if you have pickups or other manipulatable objects
if youre a beginner, create an object pool, theres many cool things you can do with it still
Since my last Update of the SDK my NetworkIDUtility isnt working anymore. It wont let me Import the IDs from my PC World into my Quest world. It throws this error, for every ID it tries to import:
VRCNetworkIDUtility.<DetectConflicts>g__DoTypesMatch|28_6 (VRCNetworkIDUtility+NetworkObjectRef scene, VRCNetworkIDUtility+NetworkObjectRef loaded) (at ./Packages/com.vrchat.base/Editor/VRCSDK/Dependencies/VRChat/VRCNetworkIDUtility.cs:550)
VRCNetworkIDUtility.DetectConflicts (System.Collections.Generic.Dictionary`2[TKey,TValue] loadedRefs, System.Collections.Generic.List`1[T] conflictList) (at ./Packages/com.vrchat.base/Editor/VRCSDK/Dependencies/VRChat/VRCNetworkIDUtility.cs:520)
VRCNetworkIDUtility.OnGUI () (at ./Packages/com.vrchat.base/Editor/VRCSDK/Dependencies/VRChat/VRCNetworkIDUtility.cs:297)
UnityEditor.HostView.InvokeOnGUI (UnityEngine.Rect onGUIPosition) (at <80a8ce1980c648dca8e68f0d8aa3b930>:0)
UnityEditor.DockArea.DrawView (UnityEngine.Rect dockAreaRect) (at <80a8ce1980c648dca8e68f0d8aa3b930>:0)
UnityEditor.DockArea.OldOnGUI () (at <80a8ce1980c648dca8e68f0d8aa3b930>:0)
UnityEngine.UIElements.IMGUIContainer.DoOnGUI (UnityEngine.Event evt, UnityEngine.Matrix4x4 parentTransform, UnityEngine.Rect clippingRect, System.Boolean isComputingLayout, UnityEngine.Rect layoutSize, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <332857d8803a4878904bcf8f9581ec33>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)```
Any Ideas?
It Shows the Window (Where you can Click "Accept all" and "Ignore" those, who arent in the Project. And when Clicking Accept, it throws all the errors. And the IDUtillity Window stays Empty.
Also I don't see the IDs in the WorldDescriptor anymore.
How can I keep my World in Sync now? Does it safe the IDs somewhere so I can put it Manually there?
question, when a player leaves the game who dose the objects they had ownership fall to? is it random? dose it go to the instance master? im verry curious and dont rlly want to test it for myself. what about when a quest player desides to put their headset down and it enters that bugged state? how do you deal with that?
used to be random, now it kinda leans towards pc/low ping person, still random overall tho. bugged questies transfer ownership without leaving.
if you need it for master start button or smth, make counter that unlocks, ie if master have not started the game in like 5 min, anyone can start. for public, waiting even 5 min even if its genuine afk is a lot and in non-public shouldnt be an issue at all
It's never been random, it used to always be the person who has been in the instance the longest, and that person was the master. There was a change so that when the master leaves, it goes to someone with a more stable connection rather than the oldest person. But regardless of how master is determined, it has always been that ownership of orphaned objects goes to the master, whoever that might be
what kind of sync mode am i supposed to use if i have a getter/setter?
i have this script that uses a getter/setter, but it isnt syncing properly. the Cost and VoxGain isn't increasing for all players, only the player who clicks it
(Vox and VoxGain are both synced getter/setters too)
@timber ferry I don't see any serialization requests in there
Each time you want to push networked changes with a manual script, you have to run RequestSerialization()
i didn't think i had to if i used a getter/setter
The point of manual is all serializations become manual, afaik
I've never heard of clients receiving anything over the network without serialization used o.o
Okay, yeah, I had to look this up again, but FieldChangeCallback is not directly a neworking concept. However, the setter will get called by their networking system when the target variable is synced, aka RequestSerialization is used and the variable changed.
In other words, it doesn't do it for you.
FieldChangeCallback only will cause SyncedToggle's setter to fire when either SetProgramVariable or a network sync updates the value of syncedToggle.
From https://udonsharp.docs.vrchat.com/udonsharp/#fieldchangecallback
All supported attributes in UdonSharp
okay, so i need to Request Serialization after changing the variables then?
yes, just like any other UdonSynced variable
good to know
since i'm changing getter/setters on other scripts from that one, should i be requesting serialization on the other scripts from that one?
i added a request serialization before the UpdateInteractText in Upgrade(), but it still isn't syncing the cost or VoxGain for everyone
RequestSerialiazation only works on the object you call it on. Seems like VoxGain is on the manualClicker, so you'd need to do it there.
yeah i know, but i was wondering if i had to do manualClicker.RequestSerialization();
which, it sounds like i do
Both is possible. I prefer to keep syncing code inside their objects because I think it makes it more organized. But idk what's actually better, just my opinion.
Is there any way to get the current world owner for Udon Graph scripts
world owner, or instance owner?
Do you mean instance master? If so, yes.
It's under Networking
Does anybody know how to do a local ui/object sending data out to a script that syncs that data.
how can i sync a random number with everyone in my game?
[UdonSynced] private int _randomNumber;
private void Start()
{
if (!Networking.IsOwner(gameObject)) return;
_randomNumber = UnityEngine.Random.Range(System.Int32.MinValue, System.Int32.MaxValue);
RequestSerialization();
}
basically it means, you're ready to send UdonSynced variables over the network
Remember to set your script to Manual sync mode, and to use the OnDeserialization event!
That's not quite right.
OnDeserialization Is fired right after you've received synced variables.
This is where you want to do stuff that you need the synced variable for.
this?
also what does this do?
synced makes it smth networked, only synced values get to be sent on deserialization or on constant sync. smooth will basically smooth the value to the new one over sync latency time or smth (ie in a fraction of a second), usually usefull for gradually moving objects so its smooth on remote (every pickup also uses same interpolation), pointless or harmful for toggles etc.
so i have this system where you interact with it and it generates a random number which is displayed for everyone. if anyone who isnt the game master interacts with it however, the number previous disappears only for them. how can i fix this?
I think your missing the set owner part, after interact it should be networking-set owner getting playerapi-get local player attached to it.
alright ill try that
Hello, how does networking behave when a child object has a different owner than its parent ?
In theory, here the parent (red) could have a different owner than the 2 children (purple) but I don't know if that's allowed by Udon
...Nothing in the docs say otherwise, so guess I'll roll with it unless it creates some critical issue ¯_(ツ)_/¯
Hello, i have this: if (_CheckAllowedAdmin(Networking.LocalPlayer.displayName))
How can i change it to get the ID?
That's fine to do, gameobject ownership is independent of the scene hierarchy
By default every gameobject is owned by the master until you call SetOwner on it
Networking.LocalPlayer.playerId
Keep in mind it's only the ID of the player in the instance, not a globally unique ID
If they leave and rejoin the ID will change
displayName would still be the most reliable way to check for specific player permissions
How do I make API calls to external URLs in Udon?
Or just connect to them in general.
String loading is the main way right now https://creators.vrchat.com/worlds/udon/string-loading/
String Loading allows you to download text files from the internet and use them in your VRChat world. You can either use the DownloadString script included in the SDK, or you can make your own script using the new VRCStringDownloader.LoadUrl function.
You can only load in data not send any back
I see. I guess this is the only way to get binary data from URLs too, right?
Is there any way to send data?
Realistically: No - Theoretically: Yes, some people have built custom webservices to get data out of VR-Chat but the transmission rates are rather pathetic like 12bps
Well there goes my idea. Still good to know. Thank you.
Of course no problem
Hello, I have an issue where Networking.IsInstanceOwner is false in the Start() method even when I just created the instance
It's not present in the docs, so is it because that property is not available in U# ?
It works in the editor, dosn't work in "Build & Test" (Apparently that's expected https://creators.vrchat.com/worlds/udon/networking/network-components#networking-properties) but it should work in a regular VRC instance
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
After trying other things (deferring the initialization, comparing local player to Networking.InstanceOwner) it just seems plain broken.
Will try with a minimum repro script
Okay, seems like it's my script specifically - it works fine on an empty project
Still very strange, I hope it's not some kind of very hidden edge case
Sorry about that, but I really can't figure out what's wrong - everything in this script looks like it should work, and it does work in the editor but not ingame.
- first image is in editor, switch can be interacted and displays the correct text
- second image is ingame (I created the instance), switch is disabled and shows the text for non-instance owners
Aaaaand seems like it's the world itself that breaks it ???
The same test script that worked fine in my test world broke when putting it in this world
Seems like it only happens for public instances. Worked correctly in a Friends+ instance.
Canny time: https://feedback.vrchat.com/udon/p/networkingisinstanceowner-always-false-for-world-creator-in-public-instance-they
hello, im having a problem i havent been able to fix after like hours of testing and trying things, its meant to be part of a more complicated shop script but i cant even get this part working right. Basically there are multiple buttons that can affect the off and on state of a object, one script toggles on the bool in the script directly which then gets a specific object from a game object array and turns it on, and the other script targets the boolean on the main script to also turn it on, but it isnt working right. When one player presses the 2nd script (the one using setprogramvariable) it doesnt toggle the object for everyone, just the one who pressed it. But oddly as long as the first script (the one that changes the bool directly) is used once the 2nd runs networked just fine? im very confused
No, that's correct.
Public sessions and Group sessions have no instance owner.
It's a bit annoying. You probably want to use instance master, not instance owner.
You could also try manually keeping track of the first instance master, who would in reality probably be the instance owner (but not always, e.g. if someone else runs through the portal to this instance before the person that created the portal).
The reason the canny is invalid is because this is the documented deliberate behaviour.
There was another Canny for a very similar issue...
https://feedback.vrchat.com/bug-reports/p/networkingisinstanceowner-true-for-all-users
Not sure if it might be related or not, haven't really tested it for a while. Audio Link actually uses Networking.LocalPlayer.isInstanceOwner instead since that one seems to work
https://github.com/llealloo/audiolink/blob/master/Packages/com.llealloo.audiolink/Runtime/Scripts/AudioLink.cs#L371
Thanks for both of your answers !
...I'll try making that switch a bit more flexible by locking the owner to the last person who activated it. Will be a bit more complex, but worth it because it'll be less arbitrary.
And yeah about it being "documented deliberate behavior", I've looked in both the Networking and Networking Components pages of the documentation and there's nothing about this. It's actually the opposite, I thought it was more reliable (for my use) than the instance master as written here: https://creators.vrchat.com/worlds/udon/networking/#the-instance-master
Multiplayer experiences are the heart of VRChat, so creating a world that reacts to players and synchronizes the data between them is key.
Would be worth it to add a line about it in the description of IsInstanceOwner and InstanceOwner: https://creators.vrchat.com/worlds/udon/networking/network-components#networking-properties
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
They originally had a table in the docs explaining IsInstanceOwner's behaviour in different instance types but seems it was overwritten in a recent merge
https://github.com/vrchat-community/creator-docs/pull/156/files
Invite, Invite+, Friends, Friends+ - true for the instance creator, otherwise false.
Group, Group+, Group Public, Public - Always false.
ok so im trying to make an udon graph that can toggle an array of objects (for certain things i need multiple objects to be toggled on or off and can not use a parent object and just toggle a single thing) and in doing so i am struggling to make it sync with late joiners. If anyone here knows how to fix what i have please let me know how to fix it. Its probably something stupid because ive been working on this for too long.
thats one hell of a graph🫡. first, theres no sync for people who already in the instance k found it, second, why it iterates through an array to call network event (thus calling it multiple times despite each of them doing the same (or opposite) thing), third, you should use serialization and not network events.
What is your goal again, just toggle x objects on or off (ie just two states A/B) globally for everyone by anyone? Or each object can be toggled individually? Or not everyone can toggle them?
Theres few types of toggles including synced and master that come with u#, click add component - type toggle, select fitting one, populate an array. That would be enough for you if it is just A/B toggle. Otherwise ye, explain how you see it.
The late join sync logic is pretty weird:
When the late joiner loads in, they fire off a network event to everyone in the instance multiple times, once per object in the array, and then everyone receives all of those events and iterates through the object array for each event that was fired
You should use a synced variable to store the toggled state and update the object states using that variable from the OnDeserialization event
And in your interact event take ownership, set the toggled state variable and request serialization to sync the change to everyone
Late joiner syncing will be handled automatically with that setup
for starters im going to clarify i am very new to udon graph if you couldnt tell already. (to be clear i know unity decently well and blender and what not but i havent bothered learning more about doing stuff like this until recently) and i was sort of trying to combine what i saw in this video https://youtu.be/O3VeBzV9HgI?si=2MGbjhSEd0gsm4-a and add an array.
As for what my goal is i am wanting to create a button that when pressed will toggle all objects under the array (specifically setting them to the opposite state of their current state. For certain use cases i would like some objects to turn off and some objects to turn on via a single button press) and of course syncing that and making sure its synced for people who join after the fact.
I was also using this as a chance to try and learn some of this stuff but if there is a thing built into the sdk then i seem to have wasted a lot of time.
So this took me longer than it should have.
Time to toggle objects again, but this time, for everyone! You'll see that syncing is a little bit more complicated than it was in SDK2, so to set everything up we have to tell every player to toggle that object, but also catch up anyone who joins in after the button was pressed!
All assets are availab...
well ye, use existing u# synced toggle. i have this as an example of "proper" nrtwork but it needs fixing for an array #world-development message
@pseudo mortar also best tutorial ever #udon-networking message
got it, and ill definitly check out that tutorial
Trying to figure out why the second script isnt toggling the object for everyone except the one who pressed the button (not networked) until the first script is ran once, can anyone help?
request serialization works only if youre the owner, so calling for it from another script is questionable.
you want to set variable + run custom event, then in part 1 do event that sets owner+serialization
Ahhhh i think i get where i messed up noww, ill try that when i get home
Hello ! Two questions at once, sorry about that:
- Do you know if OnPlayerLeft is emitted on the player that's leaving before it actually leaves, or is it only emitted on the remaining players in the instance ?
- When the owner of an object leaves the instance, does it transfer ownership to the master and (if yes) does it do it before or after OnPlayerLeft is emitted ?
(After yesterday I'm trying to make my "editing lock" independant from the instance owner and instead allow any player to use the lock - but I'd like to reset the lock when the current owner of the lock leaves)
IT WORKS MY SUFFERING IS OVER thank youuuu 
I think i just assumed setting owner on the 2nd part would carry over to the first part oh man a week of pain is over finallyyyy
Found the answer to my questions after some trial and error - seems like OnPlayerLeft is not emitted on the leaving player (that... makes sense, it would be weird), and the ownership of the object gets transferred to the instance master when its owner leaves, before OnPlayerLeft
That changed recently now its not always the instance owner
Oh, instance owner and not master ?
Had issues with the instance owner in public instances before, but the way I coded it now it shouldn't be an issue
Thanks for the info though
actually as mods explained me here, they changed that master is not always the "oldest" person in the world now, it can skip questies/other regions and give every abandoned object to someone else, but one single person still. anyway using whole "instance master" is not a good idea, if you need someone to blame just check ownership of some object that never transfers ownership otherwise. and instance owner is another diferent thing that, ye doesnt even always exist and has nothing to do with ownership of objects.
do game objects that act as interact triggers need to be toggled on for everyone in order for its script to sync with everyone?
You can only send or receive networking on an object if it's enabled, so kinda yes. However that doesn't require everybody to have it enabled and it also has nothing to do with interact triggers
well i just have a simple toggle on a button that I only wanted a single person to be able to see and use (The Game Master for a game) but it doesnt work for anyone else unless the interact button is on for them, so im confused
yeah that looks perfect, that'll sync if it's enabled
if you don't want everyone to have it enabled because the gamemaster's panel is disabled, then you'll have to route the networking through to some other object and separate the UI from the networking
ohhhhh i didnt think of that thank you!
is there any good explanation how to network/synchronise animations?
Re: OnDeserialization(DeserializationResult) I would like to confirm my understanding of the sendTime and receiveTime properties. I'm trying to implement a simple "tie window expired" for the action since late joiners will get the sync'd values. If they were not present when it was sync'd I don't want them to take any action.
owner: OnDeserialization sent=63.42865 received=63.53965 diff=0.1110001 action=True
other: OnDeserialization sent=60.34761 received=60.47361 diff=0.1259995 action=True
Given these 2 log items it appears that the sent and received are based upon a player's perspective (it says as much in the docs). And in this case the "other" player would perform the action since less than say .5 seconds had elapsed. The owner values are slightly higher because (i assume) they entered the world first.
There is something about late joiners (not seemingly easy for me to test during development) where they can have a negative value for sent. Again I assume that it is computing a value based upon the player and the negative sent value tells us "you were not in the world" at the time.
Subtracting sent from received would in such a case yield a large positive value (I believe). While this wouldn't cause a problem if the cutoff is a second or two it could yield a false positive if the period was longer. So I'm checking for a negative "sent" and simply flagging it as not applicable.
Does all this seem to make sense**?** 🙂
Oh just had a thought... if the send time is negative and that indicates that the player was not in the room I may not have to check anything else. The number of seconds (or fraction of course) between sent and received would typically be very short for anyone in the world. I can't even figure out what could cause it to be greater than some cut-off value.
Implementation depends on what purpose is for the animation. There's a few different ways, but sadly I haven't found any all in one page you can go to that'll explain it.
For simple looping animations, you can go to the vrcprefabs database and find there's an animation sync script that will sync up the animation based on universal timecode of the players system clock. Pretty genius and uses 0 networking, but can differ slightly based on the system if clocks aren't set correctly.
The other way is to build flags and logic into OnDeserialization to trigger animations around the same time for everyone. You can also do that with SendCustomNetworkEvent, but events only fire once so not late join friendly.
The only other method I can think of is one I am just starting to try and work with, but using a simple master system that periodically samples the current playback time of an animation and adjust every other players animator to that if they deviate too far. You would also have to add the time it takes for the remote to deserialize and receive the new value. This would be done with OnDeserialization as well
No but I can give some advice
If your looking for "realtively synced animations" you can use a simple network script to change a boolean or an enum to trigger the animation being enabled/disabled on the object
If your looking for "the animation happens at the precise same time for everyone", you will need a delay for the person triggering the animation, roughly 1/3-1/4th a second, then use server time to correlate all of the clients to trigger at the same time
I heavily recommend the former rrather than the latter
"all" for network events means "all but me", you need to also add normal SendCustomEvent. also thats not how you sync doors.
two doors on one animator setup, replace d1 event with Interact for one door
additionally, synced bools/onchange nodes do both for the client, for everyone else, and for late joiners. Ideal for such situations imo
question, do player ID's change if someone leaves?
or what can cause player ID ints to change?
"playerId" is a bad name in my opinion.
Basically its an incrementing per-session ID.
That means that the first player to join the world gets the ID 1, and then the second player gets 2, etc. even if the first player leaves and comes back they will just get the next increment and not the one they originally had.
Its not "bound" to a user/player, but it is unique, meaning that at no point will there be two players with the same ID in one instance.
Yeah I've been using it as short term networking "I found this player" type stuff. Not any long form persistence.
But yeah a playerId will not change during a session
@lone zealot yeah I'm looking through my code and it's just not adding up.
Is there particular cases where a call for an udon custom event (non-networked) just...doesn't execute?
Im seeing some oddities at the moment, so question
Assuming this here is on the "Udonbehaviour 1"
Would this execute "Event 2" on the Owner of "Udonbehaviour 1" or "Udonbehaviour 2" ?
This should be on n1, right? Because the Isowner node checks for the owner of the script the node is on
yes
@hoary frost This would execute Event 2 on Udonbehavior2 locally for whoever the Owner is for Uddonbehavior 1.
If the public variable of Udonbehavior2 isn't pointed to anything, it will call the event, but nothing will happen. (it wont throw an udon exception)
You might need to provide the instance of the current gameobject to IsOwner, try using the Const This node
nah, empty stuff defaults to this already
Hello, i have a problem in my world currently with users with clients being able to mass teleport users.
is there a way to fix my udon script so that clients wont be able to do that ?
this has been a thing for 2+ years so if it's not fixed yet there must be a good reason
is there a way to stop this from happing ?
i'm not a dev i know nothing about it other than it's happened to me several times. i just world hop
Do you make use of TeleportTo method in udon and if so, is it networked?
Yes i did use the TeleportTo Method & yes its networked
Well, there might be the issue, people with cheat clients can call networked udon methods
You can add an _ front of the method name to prevent networking if your use case allows for it
thanks so much for letting me know abt that 😭🙏🙏
So I have this where is you click a button only one row of avatars show while there others are off. when you go to click a different button for a different row the previous turns off and so on. This works for the three rows that I have but if I click the same button again it will turn on all that are hidden which is bad cause of lag. Is there anything I can do to fix it?
Okay so, message from my programmer
"There is recursion between onDeserialization() and ApplyToggle() due to all the buttons being connected to one another. Is there a fix to get bypass calling RequestSerialization() or to somehow detect whether RequestSerialization() is being called from a specific spot? If not than is there an alternate way to code a music system?"
I have no idea what any of that means, but we don't know how to fix this problem and wanted to see if anyone knows a way
Are your buttons calling Interact in an OnEnable/Disable event or something?
The only place RequestSerialization is being called is through Interact
When that happens and OnDeserialization is fired there's nothing else in that code path that calls RequestSerialization again unless there's some other component on one of those gameobjects being turned on or off that is triggering Interact or a RequestSerialization again
RequestSerialization does not directly call OnDeserialization, so it's not possible that those are causing recursion, at least not directly. What can happen though, is that by enabling/disabling your button objects, you are somehow calling their OnDeserialization method with the wrong states.
I don't have enough information to know what the problem is, but here are some tips for networking stuff:
- don't ever disable networked objects, it's just easier to reason about when you receive data and when you don't. I'm pretty sure OnDeserialization won't get triggered when the object is disabled, right?
- isolate toggling into their own objects, so instead of doing go.SetActive on the objects of another button, you call button.ToggleObjects or something
We'll try these out when she's next avaliable. Thank you both!
I have no idea what any of that means, but we don't know how to fix this problem and wanted to see if anyone knows a way When your code becomes too complex to follow it is an indication that you are doing something wrong. This particular example has many potential problems. Notice that your public book named global is red? This is a reserved word and should not be used as a property name. It might work but why risk it? What is "global" about it? Your arrays named turnOff and turnOn seem to contain objects that both turn on and off depending upon the state of State. There must be another name you can use that indicates what the collections are. There is only 1 thing different in the branch in Interact and that is a call to RequestSerialization, I wouldn't duplicate the other stuff. Importantly however is why would you "sometimes" not sync a UdonSynced property? Doesn't this just sound like a bad idea? Simplify your code until you recognize what it is doing at an instant.
You know it's going to be a good day when you wake up and the first thing you see is a post from tom saying he has no clue what anything means 😂
I wake up most days not knowing how anything works 🙂
Hey @frozen igloo sorry for the ping but I think you have some experience with this.. do you have any tips / examples / templates of a basic flow for syncing npc enemy state machines? I don't have trouble making local ai state machines but I've never practiced with syncing to a master, and any logic accounting for master change and ownership transfer.
instead of using unarynegation for the other two rows, you can just set their values to a const negative
this seems needlessly complicated for what its trying to do im ngl
but yeah as Puppet said, you shouldnt ever disable objects that have networking stuff in them, otherwise they might not recieve data when you need them to.
Thats why if I need to disable something, i make sure it doesnt have any logic inside of it itself, but on something related to it that i can easily link it to
i do wanna help you here, but i dont think i understand exactly what your programmer wants to do?
And yes there are definitely different ways to code a music system, although enabling/disabling gameobjects does work
Afaik there is no way to independently detect if requestserialization is called from a specific part of the script, but if thats what you want to do, you could use a logic gate there with a customevent or something, but again needlessly complicated when there are simpler ways
Sorry was just confused what to swap out but i figured it out. my bad
is it possible to get the type of the instance by code (group public, public, friends+, ...)?
Does anyone know why another player colliding with a player in a VRCStation causes the seated player to exit the station? And if that other player is close enough then they can't re-enter the station either, it teleports the player to the station but doesn't actually seat them. This issue happens when a rigidbody is attached to my object.
no
I'm queuing up data to send using OnPreSerialization. Could another player take ownership after RequestSerialization but before OnPreSerialization is called? Do I need to reject ownership requests here?
Someone can take ownership up until OnPreSerialization so you'd have to handle ownership changes, after that it's about to serialize data so RequestSerialization calls shouldn't apply again until after serialization has completed and the next network update happens:
Take Ownership > RequestSerialization > Time passes until next network update (someone else could take ownership up until this point) > OnPreSerialization > Data is serialized and broadcast to other clients > OnPostSerialization > Time passes until next network update
I'd be surprised if ownership transfers could happen during the serialization phase
Think of the case where you have a button that 2 players could press and it doesn't sync the button state until 10 seconds later, during that 10 second interval both players could have pressed the button multiple times and transferred ownership back and forth numerous times before any data is synced
Ok, that really helps! It just felt wrong calling SetOwner and editing the synced variables in different places. I think I'm getting it now
https://gist.github.com/AvocadoVR/34fa4fa086ab6b79b1a44a46a9fa46e7
I'm having two issues.
- I have a Object Pool to spawn Player Data and I save the sibling index with the players id. It keeps returning null.
- My pool dosen't return the player data object after they left.
If I remember correctly "PlayerManager" is a reserved name by Unity and you shouldnt call your class that.
Might or might not be related to your issue though.
It works still.
I would still avoid it just in case. But again I might be misremembering or Unity fixed it. Just a heads up.
If you put it into its own namespace then I guess yeah it should be fine.
Thats what I was about to say
whats goin on here consol isnt giving me much to work with
map can upload but cannot enter it in game
where can I find the network id
ah nvm found where it was
okay I got you
open this menu and then hit regenerate scenes ID
I think that's the solution
yee that worked, not sure what casused it though
What are the values that are being provided for DeserializationResult under OnDeserialization when it's triggered after player join? If I take the difference between result.receiveTime - result.sendTime when OnDeserialization is triggered for player join, I get a rather large value in the order of 10s of seconds (like ~60s). Are these values undefined for player join?
I'd be happy to chat a bit about this and we can come up with a reusable formula. The docs suggest that SendTime can be a negative number.
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
I'm mostly interested in a formula that can be used to "expire" the message so late joiners sync the value but do not run the processes associated.
Hello can someone explain the networking to me i just want to spawn an object which everyone can see it right now its just local the object hast the component vrcsynced and its on continues also i set the owner to the person which press the button which spawns the object what am i doing wrong?
i'm working with c# not with the graph
public class UITestButton: UdonSharpBehaviour
{
[SerializeField] private GameObject spawnObject;
[SerializeField] private Vector3 spawnPoint;
[SerializeField] private Transform parent;
public void Spawn()
{
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(SpawnObject));
}
public void SpawnObject()
{
GameObject go = Instantiate(spawnObject, spawnPoint, Quaternion.identity);
Networking.SetOwner(Networking.LocalPlayer, go);
}
}
Hey @tulip sphinx, i know you helped me out a bit ago and i was wondering if you could help me again. So using the video you linked previously on how to do proper udon networking i attempted to make an udon graph that toggles an array of objects like i had previously been trying to do. This time it works for the most part. The only issue is that for each button in the world it must be clicked twice before it starts working. Is there any way to fix this? (to be clear after one person has clicked said button twice everyone else only needs to click it once and it doesnt matter who presses the button from what i can tell) When someone clicks the button at first nothing will happen then on the second click objects will be toggled. Sorry if im bugging you at all
@pseudo mortar if objects are enabled initially, set default value for synced variable to true
got it, what do i do if its toggling multiple objects back and forth though such as a button to turn off one thing and turn on another to put it simply.
with some of the objects being toggled being off by default and some being on by default
check state of first one against synced variable. if equals, do nothing. if not equal, for array: get object state - negation - set state
got it, thankyou
@tulip sphinx sorry to bothering you but can you maybe help me out too?
Instanciated object can't be network. You have to use VRCObjectPool to pre-populate objects and spawn from it.
thanks for the hint i will look into it
public void Spawn()
{
GameObject ob = pool.TryToSpawn();
if (ob == null) return;
ob.transform.SetPositionAndRotation(spawnPosition, spawnDirection);
}
can u give me maybe an example or explain why i still cant see it when someone else is pressing the button and over the pool the object spawns?
maybe the problem is on the gameobject itself i dont know vrc object sync component is on it do i need something else?
A pool has to be owned in order to call spawn.
public void Awake()
{
pool = GetComponent<VRCObjectPool>();
SetOwner(Networking.InstanceOwner);
}
or do i need to change it all the time when the button is pressed?
public void SetOwner(VRCPlayerApi player)
{
Networking.SetOwner(player, gameObject);
}
public void Spawn()
{
GameObject ob = pool.TryToSpawn();
if (ob == null) return;
ob.transform.SetPositionAndRotation(spawnPosition, spawnDirection);
Networking.SetOwner(Networking.LocalPlayer, ob);
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
wait do i need to do this here something like that?
You have to own the pool before calling spawn.
im really sorry but i am not 100% sure what nodes you are saying i would need to add here. Again really sorry but could you possibly explain which nodes i should add?
What can to fail serialization. I have a local ui sending data to a synced script.
@pseudo mortar uuuh, along with setstate you have getstate. so if getstate of first item in array is not equal to synced bool you: get - negate - set state of every object im array
i believe i know how to do most of what your saying via getting the bool value of the current item then using unary negation to do the opposite value but im unsure what you mean by getting it to see if its not eqal to the synced bool.
With the current screenshot being what i have rn in an attempt to follow what your saying it produces the affect seen in the following two videos where if the object is off when someone joins it syncs but when someone joins and the object has been toggled back to be on then it desyncs. I imagine the part of your explanation im not understanding will solve this but i thought i should include it just for reference.
said videos
if getstate of objects[0] is equal to synced bool, do nothing. otherwise do your stuff
"apply" gets called on deserialization (and locally)
sorry for wasting your time and being stupid, but what would i actually add to check if its equal to the synced bool?
ok so the thing in my image doesnt work exactly so where did i go wrong? and where would i put the equality node in?
uuuuuh. event - branch (items - get item 0 - get active - equals synced bool) - false - loop
somehow i dont see synced bool in your stuff anymore. but it was there
forgot to add it back, is this correct?
uh, false
true would work but only if object 0 is initially opposite of synced variable
wait noo
you get active self and then compare to synced bool
and the result send to branch
ok so just to be clear change only the things before the branch node nothing after? everything after the branch node is setup correctly?
yes, looks fine
got it, and by compare you mean use the equality node and put the values of get active self and my state bool into it then the bool value of equality into the branch node??
like this?
probly ye, i honestly dont remember how they called
ye looks fine
once, agsin, branch - false
or youd get double click agsin
one second let me test
ok so i tested and it of course toggles the object back and forth and i only need to click the button once but it doesnt sync for some reason.
like it wont sync for late joiners and it wont sync for those in world
ah oops
ok well ignoring that last thing i said there is a small thing where if one object is by default off you have to click it twice and then once you click it the second time the object thats off will be toggled on and the object that is on will just stay on and then from there itll toggle both of them. But if all objects are on by default it acts fine. Im assuming this is because its only looking at the first object in the array with the get gameobject array node that is before the branch node. Would adding a for loop fix this @tulip sphinx?
i just realized that wouldnt exactly work, so what should i do exactly?
oh i see
in loop
object(index) - getstate - unary negation - (same object) set state
so replace "state" variable with state of thay exact object in loop
When scripting in continuous-sync mode, what changes with how and when values arrive, and when network functions are called? Since continuous sync interpolates values, I presume the value of a synced variable can change even beyond frames where OnDeserialization is called... so how can you efficiently tell when or when not to perform logic or change the state of something? Basically determining when the script should 'sleep.'
Also, does network activity on a continuous-synced script stop when the synced variable isn't changing? Or do continuous-synced objects spam the network at all times?
... I don't really WANT to use continuous sync, but the alternative is some very complicated shenanigans juggling VRCPickup/VRCObjectSync stuff and it'd be nice to not have to do that.
even when their value isn't changing??
God. How do worlds with more than like 3 synced pickups not have network suffering at all times???
Something doesn't check out
my tests show that 200 is more or less the limit, i ended up with 150+anything other than vrc sync ie transform values is on manual sync
ok @tulip sphinx so it works almost perfectly but the one issue is that if the first object in the array (object 0) is off by default you have to click it twice. But if the first object in the array is on by default it works just fine no issues
then make synced State var public and set it true of false manually for each instance
or just make sure that first object in array is always enabled initially
got it, thankyou for all the help.
or if you want to do it properly then add another bool var (not synced, default is true) and check against it, not state of 1st object. and after loop set that variable state to negated as well.
i might do that, well see. For now im just happy i finally got this working
well funny enough it turns out i do need to do it. I have little capture point things throughout the world (similar to what youd see in the star wars battlefront games) and i have them set up as buttons so to say so when you click them theyll toggle things around to change it from one side to the other. Well i also have little admin buttons which do the exact same thing from the spawn area so that way you dont have to go around the map to toggle them if needed. So in order to prevent the double click thing happening on those specifically i need to do what you were saying
essentially i have 2 buttons in different places in the map that do the same thing
for this i am slightly confused as to where you are suggesting i put the other bool variable
for reference the regular bool variable (state) is set to be synced and to be defaulted at true
and the second bool (OtherState) i just added is not synced and its value is also by default set to true.
So in the image below i have it set up how i think your saying it should be but now i am unsure what else you are saying to do. Sorry for bugging you again hopefully this will be the last thing.
nvm got it working
When you call Networking.SetOwner(), how does - or doesn't - this interact with network serialization? Does it...
- automatically flag the object as network-dirty, like RequestSerialization() does?
- send its own smaller, independent serialization event over the network immediately/at some point to resolve the ownership change?
- not do anything at all, and a manual RequestSerialization() call is needed for ownership to try and change?
kind of a 4) I think. What it does behind the scenes I don't know for certain but it establishes the player as the authority. They can set values and upon calling RequestSerialization the values it set will be transmitted. If the owner wasn't set it wouldn't transmit them as the current owner is the authority. It absolutely doesn't do 2 as the values should be identical and if they aren't they are about to be changed in any case.
The nitty gritty here is important because players can enter race conditions where they fight over authority, and especially in my case, I need to execute logic (or stop logic) based on authority.
I know, also, that there are some ways you can 'refuse' an ownership transfer
but that takes time
If you locally set yourself as owner, you're the owner until someone tells you "No, actually, you couldn't do that"
But in those instances... what happens in the interrim, and after?
I don't believe there can be a race condition as the server handles the processing. It most likely has something in place. You can only be refused ownership if you "ask" rather than set it.
The server doesn't handle anything
There is effectively no server.
Just a relay for messages between players.
AFAIK, the proton servers handle like VRChat authentication and message repeating and that's it. There is no networking 'authority' happening up there, right?...
Well not "server" per se then. But the acting "server" being the entity responsible for completing the operation. And of course when a player leaves a bunch of sync calls go out establishing who is the owner now.
I don't know the details of course.
Reading into detail on https://creators.vrchat.com/worlds/udon/networking/network-components/#onownershiprequest
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
Okay, so. OnOwnershipRequest runs both on the requester and the owner... :S
That may be helpful but it complicates things a lot I feel.
It's triggered, I presume, by the requester calling SetOwner()
There are no alternatives, no "RequestSetOwner" for example
God. I swear, I feel like I had a conversation with someone about this like a year ago and I've lost it entirely. There is a specific order of operations here with SetOwner(), OnOwnershipRequest(), and OnOwnershipTransferred() that I don't recall...
For live music events, where the video source is a live stream like VRCDN... how important is it to code udon to make sure that everyone is "together" and watching the same moment?
Wouldn't it be impossible to be behind or ahead of a live stream?
use pro tv3, they got auto sync.
While you use RTSTP link, it's sub-zero latency so delay should be minimum.
In terms of VRCDN, it would be impossible to be behind or ahead of a live stream since VRCDN doesn't have that data. There's no need to "sync to master" or anything like that with a livestream. Having a local resync button (stop / play) is handy for those who do end up becoming far behind due to issues on their side (connection drops for a sec so there's a buffer, PC struggling to keep up, etc)
Wow that's so helpful 🥺 straight from the source 🌸 thanks Jazzy
No worries, happy to help!
Would you recommend this for just raw music as well?
Like, im thinking about different buttons putting in links into a video player out of bounds, playing music for everyone in order to save space on audio files
Keep in mind that doesn't work on Quest. You need to use differnet links for Quest and PC (assuming the world is Quest compatible).
A resync button is also still useful as AVPro has a habit of drifting sometimes with the audio and video going out of sync from each other. Lower end PCs do it much more often.
ProTV3 is one of the more fully featured players, so unless you're trying to run light, it's a good idea.
Thanks 🌸 I'll keep that in mind
Is resync something specific I request within udon, from an object with an AVPro player on it? Or is it just toggling play/pause?
Looking forward to getting into udon and seeing what I can push/receive from the player
I'm not sure how ProTV does it now that you mention it. Fixing A/V desync would just require an pause/play toggle though to fix, which is what that bit would do
The resync in ProTV likely reads the current position from the video owner, pauses, changes position then unpauses.
ah, but it wouldnt work on quest right? as i saw above your message
and i am trying to run it for both quest and pc in a performant manner, its for diverse background music in a game world
It works fine for Quest, I use it in my cross-compatible worlds.
This desync can mostly be resolved by ensuring that your stream has a audio sample rate of 48k
ah okay, then what doesnt work specifically?
Codec, packaging and transport support are different between PC and Quest (and even between PC to PC in the case of codecs). Anyway, Quest doesn't support RTSP so you need to use the VRCDN HLS stream instead
alright, i have no idea what that means but i guess ill figure it out bahahah
Why use this video player for my described purpose instead of the standart unity vrc player?
You can use the standard one but you'll need to build all the functionality yourself, e.g. reloading, resyncing, handling owner sync, etc (oh, and different video URLs for PC and Quest).
The Unity VRC player is essentially what everything else is built on top of, extending the functionality.
ah okay makes sense, all that matters to me is sub three seconds loading times for new songs, and late joiner sync, and for it to generate next to no frame drops, so the default vrc player is good enough right?
Will you be playing Live Streams exclusively, or just normal videos?
exclusively music, i would discard the video part of it if i could
This is only to save space on music audio files
AVPro supports audio-only but not sure VRC supports requesting audio-only from YouTube if you're pulling from there. If you're hosting the content yourself then you can load pure audio files and it'll work, it's the Unity player that doesn't support audio-only files.
Ive just been placing the video player out of bounds for a singular test and it seemed to work. Would looking for an audio only player be a significant improvement over that "technique"?
yeah im pulling from youtube
Video decoding seems to add a notable overhead for some reason, so I'd say yes if you're going for optimising, also lower network bandwidth usage.
Ah, in that case won't matter as you can't do audio only from Youtube, VRC doesn't handle passing arguments to yt-dlp to do it.
(it CAN, but it's not implemented, as I know Resonite does it)
alright, thanks!
Then im looking at AVPro since this is what you recommended, to disable screen encoding would i just, disable the screen component?
That would disable the rendering aspect, but it would still be loading and decoding the video.
ah right, how would you disable rendering then, is there a documentation page for it
I am jumping in the middle, but what I do is create a video with a static image and the audio I want. That is very easy to decode. You can use ffmpeg to mux the audio with the static image for the video.
You just hide the gameobject or disable the meshrenderer.
Yeah, that's what I've historically done. It still has notable overhead compared to not doing that though (e.g. using it for world ambience).
Interesting. I was able to play audio-only files via AVPro in Altspace, but haven't tried that in VRC. Does that work here?
oh okay, even easier then, thank youu
Yes when using avpro specifically.
VRCDN does not support HLS, use MPEG-TS for Quest. We mention this on the VRCDN control panel
I'm clearly misremembering the formats as I remember there being 4 or so. Let me check what I've pointed it at..
VRCDN outputs are:
- RTMP
- RTSP
- MPEG-TS
- FLV (and FLV/WS)
- FMP4
The only real ones to worry about are RTSP for low latency PCVR streaming, and MPEG-TS for higher latency Quest and PCVR support
Yeah, I was thinking of MPEG-TS over HTTPS, it's been a while and HLS came up a bunch recently due to the yt-dlp changes for other things.
Yep yep, no worries 😄 We appreciate your support!
how would i add a custom vsarable to the player object like health?
You've have a custom networked object that every player has (Perhaps via an Object pool there are probably a few prefabs for that), then having a sync'd variable for health
As long as the player whose object it is controls it, it should prevent odd issues like crossfire or desync, I recommend sending damage to that player via some sort of damage management object (So perhaps 2 objects per player)
how do i network instantiation via udon sharp
if you want to instance an object that has networked stuff on it, you can't.
im trying to spawn a drink prefab on an event
it sounds like you'd be better of using an object pool
ill check it out
thx
public VRCObjectPool ObjectPool; is the correct variable for setting an object pool right?
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
public class recipeye : UdonSharpBehaviour
{
//ignore these variables there controlled by a seperate script
public int milkCount;
public int caffeineCount;
public int syrupCount;
public int coffeeBeansCount;
[Header("Recipe")]
public int recipeMilkCount;
public int recipeCaffeineCount;
public int recipeSyrupCount;
public int recipeCoffeeBeansCount;
public VRC.SDK3.Components.VRCObjectPool ObjectPool;
public GameObject Drink;
public Transform SpawnLocation;
private void OnTriggerEnter(Collider other)
{
MakeCoffee();
}
public void MakeCoffeeYe()
{
MakeCoffee();
}
public void MakeCoffee()
{
// Check if all required ingredients match the recipe exactly
bool isMilkCorrect = milkCount == recipeMilkCount;
bool isCaffeineCorrect = caffeineCount == recipeCaffeineCount;
bool isSyrupCorrect = syrupCount == recipeSyrupCount;
bool isCoffeeBeansCorrect = coffeeBeansCount == recipeCoffeeBeansCount;
// Verify all conditions
if (isMilkCorrect && isCaffeineCorrect && isSyrupCorrect && isCoffeeBeansCorrect)
{
InstantiateDrink();
}
}
private void InstantiateDrink()
{
// Instantiate the drink object and set its position
milkCount = 0;
caffeineCount = 0;
syrupCount = 0;
coffeeBeansCount = 0;
GameObject drinkInstance = VRCInstantiate(Drink);
drinkInstance.transform.position = SpawnLocation.position;
// Optionally, initialize the drink with recipe details here
// For example, you can set the quantities or any other relevant data
}
}
``` i really need help networking this
you cannot instantiate networked objects (unless you create your own syncing solution for that)
every networked objects has an id assigned to it, which cant be assigned at runtime, only in the editor
the idea of an object pool is to have a lot of objects already stored and hidden, and then shown when you need them
https://creators.vrchat.com/worlds/udon/networking/network-components/#vrc-object-pool
https://udonsharp.docs.vrchat.com/vrchat-api/#vrcobjectpool
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
alr
this really sucks since if we were able to use photon which i dont think vrc uses photon we could have just done photonnetwork.instantiate();
Man. I am struggling a lot with networked object ownership.
I am writing a control scheme for a vehicle that includes a number of interactable objects. These objects need to be owned by the same player at the same time, and so long as a player is actively operating the vehicle with them, none of those controls - which are all separate objects and scripts - should be 'stealable' via other players.
This is not an easy task. Ownership race conditions are so easy to produce, and the order of operations in which network events and ownership changes take place is so opaque I cannot figure out how to safeguard the system against lockups. I'm on like week 2 of just trying to make a two-handed wheel and throttle not softlock if two people grab it at the same time.
The following seems to be possible and it's REALLY painful to resolve:
- Player A grabs a control, proclaims ownership of it, and sets various Udon Synced variables on it.
- Player A holds this ownership long enough to successfully serialize an outgoing message with those synced variables in it.
- Player B manages to locally take ownership of the same control during this timeframe, having not received notice that Player A already owns it.
- Player B changing the ownership of the control, itself, does not inherently serialize the synced variables.
- Player A gets their ownership revoked, but because Player B taking over ownership itself doesn't send a variable serialization, Player A still sees the synced variables as they set them, during the window of time where they had ownership.
- Player B took ownership before receiving Player A's variable serialization, and so they seem to have disregarded Player A's message. The two players now are in different universes.
To point 6: That should not be happening. Both, ownership and variables, should resolve automatically when two clients interact with the object at the same time. I assume you are doing more than just setting ownership and request to serialize a new value? Maybe it could be related to you having multiple objects, but I feel like they would take ownership at the same time anyways, at least if you set the owner on the same tick. Alternatively, you could just take the main object and check if the owner is the same before you take any network actions. Oh, and last thing, do you disable some of those objects? In the past at least, I always noticed that some values are not synced properly, so I just keep network objects enabled as much as I can.
You could use OnOwnershipTransferred to verify if localplayer is still the owner and if not, kick them out of the vehicle, that may solve the race condition
This should be ok code for a button to set ownership of another object to the local player right?
I know playerapi holds a lot of data in it, but it seems like that's the type of info setowner wants
Yep
As you are pointing out it is almost assuredly due to having multiple objects. This is BTW how objects in the real world interoperate and it is only worse due to sync'ing times in the virtual space. There is nothing in the design that suggests that the sync object needs to exist within a game object and/or be attached to a single game object.
The default ownership philosophy seems to be to allow things to get out of sync, but eventually let one of them prevail so eventually sync them. Since player B doesn't modify the sync'd variables in your example, you may have run across a situation where that doesn't work. You might try adding a variable to modify to force a sync, if you can rely on "eventually".
Another thing to look at is the more complex ownership transfer system that provides for rejecting an ownership transfer: https://creators.vrchat.com/worlds/udon/networking/#requesting-ownership-advanced. That's not a panacea either, though. If you look at the diagram, there is a period of time "Orange" thinks he owns the object before receiving the rejection from "Blue". Also, there is no positive confirmation to "Orange" that he was allowed to take ownership, so he never really know if the transfer was successful. He needs to be able to "unroll" the state if he receives a rejection after his request.
Multiplayer experiences are the heart of VRChat, so creating a world that reacts to players and synchronizes the data between them is key.
I've alluded before to the system I developed and now use exclusively and I won't go back at this point. My game objects don't sync at all and are all SyncMode.None. They all have a "sync" object which can support multiple game objects when needed. This means that local and remote objects are all syncable, almost by default. I recommend that the UdonSync properties be placed on such a sync object and that other game objects just reference them.
If we use a simple example of a sphere and a UI control toggle. Neither of them are syncable but both reference a "sync object" that contains the state is takes care of the networking without my having to think about it. The toggle sets the sync object, the sync object tells the local sphere (and remote instances) that the value changed.
Hey that's similar to what I've done for my synced UIs ! It's inspired by what we do at work (which is itself inspired by how React works).
Basically the UI elements are SyncMode.None but they update a parent object which is SyncMode.Manual and stores the state of the whole panel in a DataDictionary. If the local player has authorization to update the object, it will update the state from the UI elements and - detecting that change - the parent object will update its synced variables and call RequestSerialization(). But if the local player doesn't have authorization, the UI element will be refreshed from the state
(but when you have hundreds of synced vars for different objects I wouldn't recommend keeping them on a single object, especially if some of them update more frequently than others)
Anyway a small thing I have for my UI that could interest you @frank fjord is that the panels use a "lock source", an empty game object + script with a single Locked() method that says if the local player can modify that UI. I can give the same reference to multiple panels so that they all lock simultaneously, for example if a player takes ownership of one of them :)
Practical example - in my world I have a lever that locks the 3 generator panels for everyone except for the person that activated it, and it's that way until that person unlocks it or leaves. The lever activates the lock source, and the panels read from it when someone tries to change a value (in practice it also shows a "Locked" popup)
It is good to have a system rather than a collection of inconsistent, ad-hoc, one-up solutions. I tend to consider the game object I call an "agent" to be the definer of the state of something. The sync object is a communication convention and they contain a single property. If a sphere can toggle between visible (or not) and a button can toggle it then the SphereAgent is the authority. The sync object gets its initial state from the SphereAgent which knows if it is visible or not at start up. The button (let's say it is a modal button) checks the state of the sync object to see if it should appear pressed or not.
This is the problem I'm running into: the requesting player goes on their merry way assuming they successfully obtained ownership, and when ownership is rejected later, or taken back - and to be clear, this is DESPITE having safeguards against it, this is happening because of a race condition - stuff gets out of sync and/or locked up.
I'm curious about this implementation, but I must be missing something because it's not clear to me how this actually helps resolve the problem of ownership race conditions. It's not obviously clicking for me what the huge benefit of decoupling the synced variables off to some other object actually is.
I do have methodologies for checking to make sure that it's legal to take ownership in the first place. In my case, if a player is currently holding any of the piloting controls (and other players KNOW that they're being held), other players do not have the ability to take over those controls.
The problem is what happens when other players DON'T yet know that the controls are being held, because data hasn't yet been received.
On a further note, I'm even MORE afraid of using this ownership request flow because it introduces a window in time where the requesting player could get an ACTUAL DATA SERIALIZATION while waiting for an ownership request reply, BEFORE that ownership request reply arrives. This would be super bad and make it even harder to "undo" the actions the requesting player tried to take in the interrim (and resolve synchronization between them and the original owner)
Without that method, when a player requests ownership, a message is sent to the other players (not sure when) saying the requestor is the owner and, locally, you are considered the owner. There is no facility for rejecting the request. That's likely what you are running into -- if two players request at the same time (or close to it), they both think they are the owner and continue on. Things "eventually" resolve themselves are ownership is transferred at the server and a serialization request is sent to everyone. It's not a synchronized system at all.
@stone badger I'm thinking the same thing as Luzak. I don't see how that resolves the issue. I may be missing something...
The first step (in my process) is to be organized and this helps me keep everything organized. How things interact is well-known and if something turns out to be missing I add it to the "system" and the system got smarter. I don't add more random code. I'm curious as to why you are encountering race conditions. Perhaps others are or have but I don't recall reading about it.
Let me try to lay out what I'm actually doing
Are you encountering race conditions? Routinely?
They are there whenever people have the chance to request ownership at nearly the same time. I try to adjust for it when it happens.
It shows up mostly in my "world editor", where I allow anyone to edit any object.
How does it manifest itself? I have player selectable buttons (for instance) I don't code for 2 people pressing them at the same time. So using the "sphere" example, would it toggle on and then off or blow up and boot everyone out?
I have a piloting control deck with 3 grabbable controls: a wheel, throttle, and altitude control.
Players start by grabbing a local, non-synced pickup. This pickup script informs its "manager" script that it was picked up or dropped.
The manager script querries a script that manages the whole control deck, and asks if it's legal to take control, based on if the player does or doesn't already own all the control objects, and if any of the control objects are known to be grabbed by another player. If it's legal, the control deck returns "true" to the pickup manager.
The pickup manager becomes owned by the grabbing player, and synchronizes a one-time message with the player's position and rotation offset from their hand bone to the grab point, what hand it is, etc, and replicates that to other players.
Other players, receiving this message, can now locally - without continuous network activity - simulate the movement of the grabbed controls by just applying the sent offsets to the owning player's hand bone.
Eventually, the grabbing player drops the control, and sends out another network message saying the pickup was dropped.
In your example, it would affect a single object, the sphere. If two people would press at about the same time, it likely wouldn't be noticed. Assuming it was off, (1) if the race condition occurred it would be turned on so everyone who pressed the button would say "it worked", and (2) if the race condition didn't occur, people would see it go on then off, then have two people saying "I clicked the button" so the toggle would be expected.
Currently, I'm using my own sanity check ("Are any of the other controls grabbed by a player that isn't you?") to locally reject ownership transfer. In a race condition, the player grabbing "first" loses when the second player - thinking that it's safe to take ownership - steals it away in that split second gap.
Alternatively, I could implement VRChat's "request ownership" logic, but that isn't infallible either. All it does is require more function calls, and changes it so that the player grabbing "second" loses.
Remember, it "eventually" resolves itself with one player's view overwriting all others. That's how it eventually synchronizes. But if the action isn't a simple state change, things don't work.
In either case, a player thinks they grabbed a control, and later finds out they didn't, and needs to resolve the state of the universe such that everything works and is in sync again, and this is not trivial. It's especially not trivial when I am deliberately not sending serializations frequently. It's the whole reason I made this system to begin with, is so that I don't need to spam the network with updates.
I'm also trying to keep the controls decoupled. I don't want to send info about all of the grabbable points on the control system, every time a single one of them is grabbed or released.
So what happens if I try to grab the wheel, but you try to grab the throttle? First I take ownership of everything - but I'm only sending a serialization about the wheel - and then you inform me that no, actually, YOU'VE taken ownership of everything - but only send me a serialization about the throttle.
That's very similar to my world editor issue. Unfortunately, I haven't found a great solution. In my case, I was able to mitigate it somewhat by making the controls local and synchronizing the effect, if that makes sense. The effect in my case was changing the transform (including scale).
Right. But synchronizing the effect is not a 'smooth' operation
I actually am synchronizing the effect elsewhere, in either case: the piloting of the ship
but the visible movement of the controls I want to be smooth and not update at 5hz (and not update out of sync with remote players' hands)
Yeah, I wrote a smoother too. Deserialization changes a "target" transform, and locally each player LERPs the transform to the new target.
Got it. And if it was incrementing a value it would increment correctly or sometimes one increment wouldn't occur?
That's right.
The smoother may now be ideal in your case, though, @frank fjord , since it sounds like you have avatars in a vehicle. Their position wouldn't necessarily be kept with the vehicle.
Sounds like a good project for the community to attempt to build.
The vehicle is irrelevant. The game features a single shared vehicle that all players inhabit, and it does not 'truly' move (the world moves around it)
There are two different things happening. The broken system is the system that smoothly displays the rotation and movement of the grabbable controls themselves, and coordinates when and where they can be grabbed by other players.
The second part is how the controls actually pilot the ship (or the world around the ship), and this I haven't even gotten to implementing yet 'cuz the controls keep breaking in different ways lmfao
I spent two weeks in an excel sheet simulating out different extrapolation and correction equations and now I'm being blocked by a steering wheel lmfao
... but I'm gonna have to solve this eventually. Every single interactive element in this game world is going to be one of these diegetic, grabbable controls. These need to work perfectly.
This is also why I'm not just using a dumb VRCObjectSync on a pickup. It's not performant or smooth enough
It may likely be I'm not doing anything in the world that is negatively affected by an occasional dropped update. I have a flight system and they consist of several parts that interact. All players see the same effects and only the person holding it can affect a change. If they drop it, pick up by someone else is possible and it cleans up if the player leaves while holding one.
Aye, this is effectively what I am trying to do. It's just... failing, hahah.
They really seem to have designed everything assuming people would use VRCObjectSync. That doesn't work in my case either.
I'd naively believe that a similar system could assign the control or the deck to whomever grabbed it. Isn't it theirs tuntil they let it go?
That's what's going on, yes. If I grab any of the controls, that control asks for ownership permission from the entire control deck. The control deck checks to make sure that no other player has ownership AND is grabbing any one of the controls. If it's clear, the control deck and all of the controls get assigned to the grabbing player, and that player SHOULD be the ONLY player able to manipulate any of the controls until such a time as they let go of all of them
... but of course there is this race condition
where two players both try to grab a control at the same time
I use VRCObjectSync for the physical properties but not for what color the stick (and the nib) are when it is being used. A player can hold one in each hand and they are similarly sync'd.
And to be fair, the ownership transfer working like this is what allows people to request ownership then immediately start using it. If they made it synchronous, we'd have to set up callbacks for when owner transfer was successfully processed and/or rejected.
Like. I understand the basic premise. If you think you can take ownership, you do; if later you turn out to be wrong, you roll back.
Just, the order of operations of when network messages of different types are arriving, how things do or don't get synced...
A bit of an example stretch but it is sort of like spawning another flight stick when there are no more available. If I had one stick it would work like your control system.
..... and the fact that there are multiple interconnected objects that all need ownership synced...
is making the real implementation of this tricky and fraught with bugs
I think that might be where the difference lies. The parts of my flight stick are integrated. The "stick" communicates locally with the other parts, the sync values are centralized.
Yeah. Like I said, the control deck is complicated 'cuz there are multiple controls, and each control wants to send as little data as possible - just one update to say it's been grabbed (and what its offsets are), and one update to say it's been dropped - I don't want to send data for ALL the controls on the deck, every time a single one is grabbed or released.
In another example I have a few service-based controls. They can be used by anyone but need to be serialized and due to the limits of how often file loads can occur I've set it up so the UI disables when the first player is processing it and it only enables again when the service has returned and the time between calls has been met.
Multiple small syncable objects should work fine. I can send individual messages.
My BaseSync class adds support for expired messages (that's always been an issue for me) as we are stuck using sync'd values and in many cases don't want late joiners to act on them.
Okay, this is stupid. I'm baffled. What call does OnOwnershipRequest() actually return TO...?
Like, there is no Networking.RequestOwnership() function
and SetOwner() does not magically start returning a value
Is there no dev-accessible output for OnOwnershipRequest()? No way to make the return result of OnOwnershipRequest() actually actionable...?
You looked at this section yet ? https://creators.vrchat.com/worlds/udon/networking/#requesting-ownership-advanced
Multiplayer experiences are the heart of VRChat, so creating a world that reacts to players and synchronizes the data between them is key.
OnOnwershipRequest is supposed to be overridden so you can control how ownership is transferred.
There's a graph on the page I linked that explains the order of calls, but basically OnOnwershipRequest will be called first on the player requesting ownership then on the current owner, and in both cases returning true allows ownership transfer and returning false stops it
Yeah I see that. But the value the function returns doesn't go anywhere accessible to scripting
Yes, but you could set a variable from inside the method and use it later (like use it in OnOwnershipTransferred)
Basically, OnOwnershipRequest always returns true unless you override it, and if you do there's nothing stopping you from storing the return value in a separate variable before returning it
You are right, the true/false return value isn't available to scripts. The result just impacts what OnOwnershipTransferred() messages are sent to others. And unfortunately, a "true" message response in the final call is never sent to the requester, so there is no way the requester can know if it is actually a successful transfer. It has to just assume it was successful and adjust if it later finds out it wasn't.
I also ran some tests last night and learned, rather frustratingly, that when ownership is transferred, a serialization is NOT sent
I had a system of two pickups: when either one is grabbed, they grant ownership of both pickups to the grabber, as a method of locking out other players from intervening while the whole system is in use.
However, there is this small window of time where neither player has been told that the other player has tried to grab one of the two pickups
Player A grabs pickup A and sets a value on pickup A; player B grabs pickup B and sets a value on pickup B. Player A learns late that they lost the ownership race, but doesn't actually get a serialization informing them that the value on pickup A needs to get reverted... and they don't own the object anymore, so they can't do it themselves.
By the way. Does anyone recall off-hand...
- what the 'overhead' size of a serialization is? I seem to recall it's like 32 or 64 bytes.
- is that overhead per object being serialized, or per serialization event, total?
- what is the size of an ownership acquisition? since - as above - we've established that ownership transfer does not go through the same chain of events and doesn't automatically run OnPreSerialization or OnDeserialization, I'm not sure it's possible to easily scout out how much data it takes to take ownership of something on its own...
- ... and if this ownership acquisition, if it happens at the same time as an actual manual serialization, costs the same, or more, data to do...
Ownership is not serialization, it is just which client is responsible for updating the serializable data.
OnOwnershipTransfered will tell you when ownership of an item has changed
You have built a race condition, solving it will not be trivial. You will need to detect when the race has occurred and have rules for how to solve the race. Networking.GetServerTimeInMilliseconds may be somewhat useful in resolving the dispute.
With the latency of VRC networking it will not be a small amount of time this race can occur, I have seen roundtrip delays on the order of 1000ms before. People with poor internet and on an instance continents away from the instance vrc server. (EU Client in a US-West instance)
Changing ownership requires SOME amount of data to be sent. Even if it's not the same 'kind' of data serialization as calling RequestSerialization(), at the end of the day it costs some amount of data to tell another client that you're the owner of an object now
is it possible to network Instantiate or do you have to use pools? I thought someone said there was a way to set a network id in game
Network ids can only be set in editor, i recommend using object pools but its possible to create network instantiating if youre advanced enough
i have a very fast moving object i need to sync almost exactly in order for my game to look normal. anyone know how to update the location every few frames? ive tried the object sync and its too slow
how can you expect to sync smth exactly when theres 200-700ms ping on average. unless it doesnt react to players. overall you probably make all simulation local and just send changes caused by players with deserialization, it should be faster than 5hz sync. but if you want to make a two player table tennis, no.
Thank you, Based on the way my game works it would be much better to clone objects instead of having them connected to a pool. Is there some documentation to set a network of of an object, I couldent find one.
no afaik, only few crazy people that i know of have attempted it (me included), it depends what objects you want to use, if youre dealing with just simple objects, it may not be too hard, but something like pickups will be a nightmare
you will need to store an array of objects, how you sync them, depends on your implementation, personally, im using several udonsynced ulong array with lots of bit shifting to store and retrieved all my information, which is then serialized as late joiners appear
something more simple would be to have an array with vectors and quaternions and constantly serializing them, similar to vrc object sync, you wouldnt need to worry too much about late joiner syncing in that case but bandwidth would be a lot heavier
you will probably also need an array of gameobjects, each including some base script that can access the object spawners and all required information, and derive into new scripts as needed
heres some old footage from about 9 month ago of my first implementation:
https://cdn.discordapp.com/attachments/1033878997857218582/1171052443392548945/2023-11-06_12-38-47.mp4
ive already advanced the system much more, but this is probably the best video to demonstrate to you visually
also if you want to use ulong, you would only have 64 bits of data available, which may not be enough too
Thank you for all your help. I’ll try to figure somthing out
Does anybody have code for a hud like quickdraw or price guess where its a little delayed and lock in a position but rotates.
One way to achieve this would be to calculate the difference in position and the difference in rotation from the current HUD transform to its target point, whatever you want that target point to be. Multiply that difference by some percentage of your choice between 0% and 100% - the higher, the snappier; the lower, the more languid and delayed - and then add the result to the current position and rotation.
Since the target position and rotation are based on the player's current position and rotation (their head IK, likely), make sure to do this in PostLateUpdate.
Hm.
So, I know that normally, when you request serialization on an object, ALL of its variables get serialized, whether they've changed or not.
I also know that in some cases, if you're careful, you can use UdonSync byte arrays to represent the data you want to serialize, and dynamically control when it gets sent, since byte arrays only use as much bandwidth in a given serialization as their current size requires.
I seem to recall there is some advanced 'gotcha' about doing this, though, specifically in regards to what happens with serialization if a byte array's current size is 0. I don't recall what the problem is though?
Synced arrays can be empty but shouldn't be null
And they have some extra bandwidth overhead
Oh, do they? I'll have to test this, I could have sworn last time I dicked around with this that byte arrays were exactly the same size as the number of bytes in the array...
Nah there's some extra header metadata sent to handle things like the length of the array
gotcha.
I think one of my mini-tasks for today is gonna be getting a better grasp of how big serialization overhead is (nominally) anyways, so I'll just tack this onto my list of things to benchmark.
Someone posted a talk video a while back with more in depth info on the networking implementation
If you have any idea where that is, I'd love a link to that.
Link to the slides can be found here: https://docs.google.com/presentation/d/1fiEKP0a46O5CrgkehH5gNK6kFvgQtGIa4MIG_UcFmAg/edit?usp=sharing
At around 18:52, you may notice a section missing for ~2 seconds. All I was saying there is that if people tried to make recursive functions, they might not have had them work properly, and that the attribut...
It has a lot of great info, the whole video is worth watching
oh christ, the serialization header for an array is FIFTY TWO BYTES?
pain...
With barely 4 to 5 kb/s to work with (once factoring out the bandwidth players already consume from their 11 kb/s allotment for their own IK, VoIP, and animators), it's a wonder you can synchronize anything sometimes.
How would I calculate the difference between a Vector3 and Quaternion?
If you're asking how to calculate the difference between two Vector3s, AND how to calculate the difference between two Quaternions: I can tell you offhand that Vector3s can be subtracted from one another very easily. I don't remember precisely how to do quaternion math, but I'm sure Google would be happy to help you with that ❤️ (I'm sure the mathf library has some kind of 'rotate from/to' function)
If you're asking how to calculate the difference between, specifically, a Vector3 FROM a Quaternion, or vice-a-versa, you don't, and wouldn't want to.
Then what am I calculating between? You said calculate the difference in position & rotation.
"calculate the difference in position and the difference in rotation from the current HUD transform to its target point,"
Your hud has a certain position, and a certain rotation, last frame
you want to move it to a new position, and a new rotation, this frame
there is both a difference between the last position and new position, and a second different difference between the last rotation and the new rotation
These are two separate differences: a positional difference, and a rotational difference.
So I am caculating the diffrience between the previous and new pos and same for rotation
... yes 🙂
There may be a faster way to do this, actually.
There is a function called LERP, or "linear interpolation."
It takes three parameters: an initial value, a 'target' value, and a value 't' which represents a percentage (0 to 1.0)
If you pass LERP the values 2, 4, and 0.5, it will return the value that is 50% "between" 2 and 4 (which, in this example, would be 3)
This is just a faster way to do the thing I already explained.
For position, you would set your HUD's new position to the result of a Vector3 LERP from last frame's position to your desired target point, and for 't' you would give it that percentage from 0 to 1 to control how quickly it will try and move to the target point. The closer to 1, the faster it moves.
For rotation, you would ALMOST do the same thing, except you cannot use a simple LERP on a quaternion rotation. There may be a similar interpolation function that works for quaternions: perhaps SLERP (spherical linear interpolation), but I don't recall for sure if that works
Anyhow, this technically isn't a networking question, so if you have other questions about moving elements around on a single person's game, you may wanna ask them in #udon-general
Hey, what is OnDeserialization's result.isFromStorage boolean? This isn't documented at all as far as I can tell...?
Maybe something related to world persistence? I don't remember it being there before
They do have a couple of references to persistence in the SDK like the VRCPlayerObject and VRCDisablePersistence components so it's probably just stuff they've been testing internally
Okay so I started doing some serialization bandwidth benchmarking and boy I am... thoroughly confused.
I am spamming manual serializations on an object with 1 synced byte variable in it.
I'm running this loop by requesting a new serialization at the end of each OnPostSerialization.
Somehow, serialization events are running 12-13 times a second, which is almost 3 times faster than I thought was normal. Aren't manual serialization events limited to five times a second?
Additionally, this 1-byte object has a serialization size of 14 bytes (12 overhead, 1 byte header, 1 byte data), and I've verified this both with Debug 6 and OnPostSerialization result. If this is running 12-13 times a second - which is weird already - this should be an outgoing ~175 bytes per second. But Debug 6 is telling me that it's estimating anywhere from 500 to 600 bytes per second for that object.
What in the heck is going on here...?
Additional confusion:
I'm testing a similar object that does the same thing except that it's syncing 4 bytes instead of 1 byte.
The size of the object - and its reported outgoing serialization size - is 20 bytes, which would suggest that each byte is actually 2 bytes in size.
In HappyRobot's presentation they suggest VRChat tries to 'optimize' synced variable headers. They mention this doesn't work very well, as the variables are not grouped together by type, but seemingly some other way. This should not matter if literally the only variables you're syncing are all the same type, though. I would anticipate, therefore, the size of the serialization to be 12 (object) + 1 (byte header) + 4*1 (4 bytes) = 17
Gosh I sure wish there were consistent explanations for literally anything going on in the networking layer :S
So the only conclusions I can draw from this are:
-
The size of variables isn't what is documented even in the best-case scenario where you only have 1 type of variable and headers 'can' be shared/compressed
-
Manual serialization can occur 2-3 times faster than advertised. And then, even factoring in this reported faster rate:
A) 2 to 4 TIMES more bytes are being serialized than is being reported by debug view or serialization result bytes out, and/or
B) serialization events are happening as much as 30+ TIMES faster than reported even by the debug view or by literally keeping track of serialization events in script
...
Nothing makes sense 🙂
Yeah the docs aren't all that helpful with the lack of specific details
All they say is that the send rate is influenced by the amount of data you're syncing
DO they actually say that?
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
I only recall ever reading anything like that in the context of 1: continuous serialization or 2: network suffering
Oh, so it does.
Each manually-synced object is rate limited as a factor of the data size. The more it sends, the more its send rate is limited.
Very vague
Yeah.
Unfortunately, even with that in mind, there is still this weird inconsistency where if you multiply the size of the object you're serializing (or its result.bytesOut, which are the same) by the reported send rate, you get something that is 2-4 times smaller than how many bytes per second the debugger is reporting using
14 bytes 12 times a second is like 168 bytes/s
which is definitely not even within margin of error to 530 bytes/s
Who knows what other kinds of data they could be sending as part of the serialization, could be including variable names or hashes of names to map variable values to, maybe even ownership state data
Weird that some of that 'overhead' data is included in serialization result bytes out, but some of it 'isn't'
I just ran another test just to make sure I'm not insane. I'm now manually calling RequestSerialization once a second for this 1 byte object. Debug View 6 confirms 1hz send rate. Still 14 byte object. Actual BPS ~44, still off by about 3x
Also measuring weird inconsistencies in my outgoing data rate. With no replicated objects, on desktop I'm measuring a steady 1.3 kb/s out. Replicating this 1-byte object spamming, the debug view says it's contributing about 550-560 bytes/s right now, but my overall outgoing data has shot up to 2.15 kb/s.
That's ANOTHER 300 b/s that's just mysteriously there and not accounted for anywhere?
None of this adds up lmfao.
This upsets my sensibilities so much, I have no idea how to make good use of literally any of this information or any of these tools if I don't trust them to make any sense.
I just pack everything I need into a single synced ulong array and don't pay any attention to the data rates, but my use cases haven't gone beyond 5 ulongs
According to that talk you linked, apparently the 'cost' of an array that doesn't even have anything in it yet is 52 bytes >_>;;;
oh, ulongs
Still, that'd be what... 40 bytes?
Anyone. TL;DR from what I can tell:
-
The docs say you've got 11kb/s. In reality you probably get half that, or less, after factoring out a user's VoIP, IK, and animators. for worst-case scenario.
-
The docs say a byte is 1 byte, short is 2 bytes, etc. In reality, they cost twice as much if you trust object size in the debug view 6, or reading serialization result bytes out.
-
If you actually look at the bytes per second, this 2x cost multiplier goes up to approximately 6.5x per variable. There's no explanation as to where this extra data is coming from.
-
If you look at your total overall outgoing kb/s, this multiplier goes up to nearly 10x per variable. There's no explanation as to where this extra data is coming from.
I'd love to be proven wrong on any of this :S
When serializing meta-data is sometimes added to the actual payload. This is called overhead.
Another factor is padding. Padding is sometimes added to align bytes in memory, which is usually done for performance reasons or because a specific protocol requires it.
Both of these increase the amount of data that needs to be sent in seemingly arbitrary ways. The exact serialization mechanism in VRChat is not public unfortunately.
I expected there to be some of this, but the sheer AMOUNT being added is just ludicrous. And... yeah, the fact that there's no real explanation for it in the docs.
Just really frustrating to be told "here's about how much data you can send" and have that be misleading by a factor of 20x (between the variable padding/meta-data thing, and the 11kb/s being eaten into by avatar and VoIP)
A quick note and I'll stop BUT you do know that VRC didn't write the library right? They are subject to the way the Photon works. Nobody like overhead or unnecessary traffic but that is the reality we live in. If you could post any software developers code I would be happy to review it and suggest ways to reduce it by a few byes and increase the speed by a few milliseconds. 🙂
My frustrating here is not me saying "Lol VRC sucks" and more me just being exasperated by feeling lulled into a false sense of security by documentation, or ommissions from documentation
I just want to feel like I can say "How do I do X?" go read up the information on how to do X on The Source Of Truth That Was Created To Help Make People Do X, and then find success. I'm investing a lot of time into trying to plan out efficient ways to make a project work within the constraints of VRChat's platform but I keep figuring out that my model for what those constraints are, is wrong.
Anyhow. It seems like I just have to adjust my expectations as to how much bandwidth I have available at a time down to like... the equivalent of 400 bytes per second.
I share your frustration... docs tend to be the last thing that gets updated (if they get updated). Personally I would like to see more of a .Net documentation style that showed the syntax and provided one ore more self-contained examples.
I did a couple more follow-up experiments and think I have determined a few more things:
-
The size of variable headers appears to always be 1 byte. Doesn't matter of it's a byte or a quaternion, just add 1 byte for every variable you have, in addition to those variables' network size (except arrays, which are chonkier). So a byte is 1+1 bytes, an int is 4+1 bytes, a quaternion is 16+1 bytes.
-
The difference between expected serialization (rate * object network size) and the debugger's bytes/sec goes down the more variables there are on an object. My 1-byte object (14 bytes total) was over three times more expensive to serialize in actual bytes/second (expected 168bps, got ~515bps), but an 8-quaternion object (148 bytes total) was just 1.27 times more expensive in actual bytes/second (expected 1776bps, got ~2100 bps)
-
The additional mysterious bytes added between the BPS per object view, and the actual seen outgoing KB/s in the debugger, SEEMS to be fairly static. If you're serializing any manually serialized objects, you just add 200-300 bytes/s on top of whatever your total BPS is from objects. I saw no difference between the overhead of my 1-byte object, my 8-quaternion object, and both at the same time, in terms of this extra padding.
This... helps. Clearly my estimate for how much bandwidth was getting wasted was overblown
But it does mean a few things
In particular: lots of objects with very little synced info on them are... bad. Not just because of the 12 bytes of overhead objects carry, but because of how disproportionately they are affected by this extra-extra overhead/padding
working on a local fog transition in a certain area, issue is when player first joins the density is not at 0.005 but practically 0. The only way for it to fix is if the player goes in the area then back out. Any way to alter this to fix it?
Does anyone know what's really happening under the hood when SetOwner() is called on a networked object? The debugger does not show any data sent when it's called, OnPostSerialization isn't called either...
But SOMETHING is being sent over the network, because you can successfully claim ownership of something (and have other players see that with OnOwnershipChanged) without ever requesting a serialization.
Is there an awful edge-case where it's possible for an actual data serialization to arrive BEFORE an ownership change message arrives...?
awh no, that is not something I wanted to discover
So. When you receive a serialization event from another client...
OnDeserialization() runs in order of network ID NOPE
but, when OnDeserialization() runs for a given object, only the updated data for that object and objects that have already run their own OnDeserialization() event is available.
All objects with higher network IDs, that haven't run OnDeserialization() yet, will show their old, unupdated data.
on your VRC Scene Descriptor, there's an array that's called Network IDs, find your object in there and change the id
Oh, hey Delta! Been a while. Thanks, that helps 🙂
hope you're doing well
There shouldn't be any edge cases if you change owner and serialize data in the same frame. That's the recommended way and it has worked for me consistently. The data is always bundled with the owner change properly.
And yeah, I don't think you can count on multiple OnDeserializations to trigger together in a predictable order, at least in my experience. You have to use something like a timestamp to coordinate them and detect when all required data has arrived.
so for this climbing asset I am supposed to enable swimming and climbing in my collission matrix, however mine does not include such options?
you probably have to make those layers yourself. what prefab is it?
Okay I learned two new things that both suck lmao.
OnDeserialization() does run for late joiners, but it will not accurately reflect the current owner for the object it's run on.
OnDeserialization() seems to MOSTLY run in order of network ID... except when it doesn't. I tested this extensively by myself but then as soon as I tested with some other players and someone left and rejoined, I started getting inconsistent results. Network ID does NOT deterministically, 100% of the time guarantee order of OnDeserialization(). And this sucks because that means you cannot guarantee an order that data arrives in.
So. I have a non-serialized object which references two different serialized objects: A, and B. A is always necessary, B is sometimes not sent for bandwidth optimization.
Originally, I thought network ID determined order of deserialization. So I had object A's network ID higher than object B's, so that object A could tell its parent object to do things when OnDeserialization occurs, with the confidence that - if object B WAS serialized - its data would already be available to use.
Now I realize this doesn't work. Despite object A definitely still having a higher network ID than object B, sometimes object B runs its OnDeserialization event later, and thus its data is sometimes not available by the time object A runs its own OnDeserialization event.
What the heck are you supposed to do about this lmao.
If synced variables on a given object are only guaranteed to be updated by the time OnDeserialization runs, but you also can't guarantee an order that different objects will run OnDeserialization...
That makes it very hard to break out important vs less important synced variables into different objects to save bandwidth
I seem to recall there being SOME way to influence script 'execution order.' Would that have any effect on the order in which deserialization events are run...?
That would be DefaultExecutionOrder(int order), lower means it executes sooner. More information on that is available here: https://docs.unity3d.com/2022.3/Documentation/ScriptReference/DefaultExecutionOrder.html
An example of this in action would be ProTV 3, which has a default execution order of -9999 to initialize first as seen here: https://gitlab.com/techanon/protv/-/blob/release/3.0/Runtime/Core/TVManager.cs#L18
Right, I was just reading up on this again. The VRChat docs suggest that this only affects 'update' style events, but the Unity docs suggest it also affects, for example, Start()... which is not an update-style event.
I guess I'll just have to test it to see if it affects OnDeserialization order...
aye
Thanks for linking the Unity docs though. I didn't think to read those in addition to the VRChat docs, and had sorta given up on that attack vector hours ago
I think, awkwardly, I'm learning to trust the docs less and less.
I’m gonna be real, I don’t really read the VRC docs
There’s so much stuff that exists in Udon that just, isn’t documented whatsoever
... yeah. ;u;
Well. As far as I can tell - unless I'm somehow grossly misusing this feature - no, default execution order does not have any apparent effect on the order of OnDeserialization() events within a single frame.
Bummer, back to square one. Really hoped that'd just work lol
Time to find out how good or bad an idea it is to just kick the Deserialization logic down the road one frame with a Custom Event Delayed Frames... all to make absolute certain I've got all the deserialization updates I'm waiting for.
Surely nothing can go wrong here.
@frank fjord DefaultExecutionOrder is indeed only for the "internal update loop" inside of Unity https://docs.unity3d.com/Manual/ExecutionOrder.html
VRChats Networking Architecture is a little bit difficult to work with I agree. The basic idea however is relatively simple.
All synced variables in your world together form whats called your "state" and everything you derive from that state is called "view"
The view should always reflect the current state and should be fully derivable from just the current state alone.
As long as that can be guaranteed and you build your logic around that concept its fairly straight forward.
Aye. It's unfortunate that by trying to solve one limitation (severely limited network bandwidth) I ran into another one (not all sync variables being consistently available at the same time when logic needs to occur)
Yeah its not really possible currently to really optimize network bandwidth due to how VRChats networking works :/
well, you have to optimize it SOME how
Like, if you want there to be room to send all the data you NEED to send, you have to find a way to reduce or pack or craftily break up what data you're sending, and when
otherwise you just don't get to make the thing you're making x'D
Well the best you can do is seperate synced variables that dont get updated/read together into seperate behaviours.
And be really clever about removing redundancy in your state
Yup. And this was... 95% of what I was doing, except actually I did need to read and update them together >.<
I'm making a series of grabbable controls. If a player is operating any one of them, no other player should be able to operate any one of them. This necessitated some sort of shared state between the controls.
But the controls also had all this extra data that was specific to the particular control.
I really did not want to have to send all the data about all the controls every time anything happened to any one control
As a result, I wound up with a synced "flags" object that updates whenever any control is grabbed or dropped, and several "data" objects - one per control - that only need to sync when that control in particular is interacted with
But this does mean that a given control needs data from two different synced objects in generally the same period of time... and that's how I ran into this issue of not knowing which OnDeserialization event was safe to trigger logic.
Yeah unfortunately VRChat does not allow delta-serialization :/
At least the exclusive ownership part is taken care of for you, just have a manager object that the controlling player takes ownership over and reject player interactions if they don't own the manager object
You're in control of when data is synced so the flag per control is probably not needed
Since the player has exclusive ownership over all controls
OnDeserialization only gets called for the controls you requested serialization for
Except for a first time join
it only effects the update events
The real problem is when two players try to take control at the same time
It's trivial to go "hey is someone else holding and owning this right now? If so, don't grab." But what if both parties don't know that the control is grabbed yet? This race condition was causing my controls to bug out and lock up in all sorts of ways. All of the shit I've been talking about, testing, iterating and fixing has been specifically to make sure this race condition resolves gracefully.
Seems so, but also maybe Start is technically classified as an update event? Maybe. That's why I was confused: the Unity docs suggest Start is also affected.
no, start isn't affected by the execution order
i think in unity it would be(not sure about that)
but it isn't in udon
Someone ought to tell the fellow working on that TVManager that was linked earlier in this discussion ;P They certainly think it does.
I know from experience that it doesn't :(
a painful time that was
I believe you >.<
Can somebody explain how the networking ID system works to me?
Generally speaking, all gameobjects that have a networking script attached to them (so a script that is doing manual or continuous synchronization) are assigned a networking ID on build. These IDs are used for clients to identify what 'thing' in the world is being talked about when network information arrives.
If I tell you "that ball changed position" you have zero clue what I mean by "that ball" unless we both have some way to agree what "that ball" is. That is what the network ID is for.
This means that if you somehow get yourself into a state where different clients have different network IDs for their object, your networking won't work. The three big examples of this are:
-
Two different versions of the same world (for example, you just pushed an update)
-
Two different platforms of the same world (PC vs Quest); you'd need to carefully vet your networked object IDs, or use the network ID utility to export and match up those IDs, otherwise if you have significant differences between the two platforms of your world you may wind up with different IDs for your objects
-
Instantiating new instances of a networked object. Don't do this. When two clients instantiate new instances of an object, they have no way to agree on a network ID for that new object, and thus any networking code on those objects will fail. Instead, make a pool of inactive objects before you build and recycle from that pool. This way, all those objects will have network IDs generated on build, and all players will agree.
Alright, thanks! That helps a lot
if OnPostSerialization result.success returns false, will another serialization attempt automatically occur next time it can? Or are you SOL and have to request another serialization manually?
Afaik you'll need to RequestSerialization(); again.
Well. I guess I expected this but, still a bummer. First draft on my calculus-wielding flight predictive sync system is... pretty lame.
It's just not that smooth, all things considered.
Now I have to somehow figure out how to profile and log this sufficiently to figure out what its weak point(s) actually is
So. My implementation of manual serialization, extrapolative prediction of the airship's flight path, and smoothing between last simulation and new prediction...
... is not great. Failing grade. It """works""" but it's quite jittery. The smoothing between corrections does not sell it as a nice, continuous motion at all.
Embarrassingly, simply replacing my manual serialization logic with a continuous smoothed serialization of just the position and rotation variables (see: NOT doing VRCObjectSync) and then setting position and rotation from those values every frame... was a lot smoother. Not perfect, it definitely comes across like a baby's-first-synchronized-object sorta deal and I'm not proud of that. But it beats out my more complicated code by a lot... and the serialization bandwidth of both implementations is about the same: 300 BPS.
VRCObjectSync was an abhorrent failure. Worst implementation of the three, 0/10 would not recommend.
...
What a bummer. While it's cool, I guess, that there is a significantly "simpler" implementation that does what I'm doing, but better... it's also not really "great." And I can't improve "just continuously sync the variables" because all that logic is happening under the hood.
Also, because it's not VRCObjectSync (or my original implementation), I don't get any local physics simulation at all. I effectively am treating this physics object like a kinematic object and moving it frame by frame.
Predicting the future is hard, trust me, I've tried xD
There's absolutely no shame in struggling with this.
However, there is always a way to do it better.
You can absolutely do client-side physics with corrective forces.
Sacchan's aircraft sync uses a PID-like system for smoothing with some extrapolation and ping correction.
But no client-side physics
since it would be too expensive with many vehicles
Since I'm assuming you only have one airship in this case, doing client-side physics is 100% doable from an optimization pov
It """works""" but it's quite jittery.
What does your smoothing code look like?
So... there is one PLAYER aircraft. All players share that one aircraft. There will also be some number of AI-controlled aircraft that a player will have to simulate and replicate to other players, but 1) I don't expect the number of LIVE ai-controlled ships to be very high (definitely no more than 10, probably even fewer) at a given time, and 2) the accuracy and smoothing on those can be a little more lax than the player-operated ship, since there are no players onboard to be given motion sickness :S
I've never thought of the idea of doing corrective forces. That's an interesting idea, though I've got zero clue how I would calculate the right corrective force to apply at any given time. Hmmm...
My smoothing code runs a positional LERP and a rotational quaternion SLERP from the previous (still simulating) rigidbody from the last known state, and the new, just-projected-forward-in-time rigidbody that's now active as well. However, the T value being passed to these interpolation functions is being transformed based off an S-curve (evaluating an animation curve)
So T is not changing linearly, but increasing from 0 to 1 with easing on both sides.
PID controller
Not heard of this 🤔
A proportional–integral–derivative controller (PID controller or three-term controller) is a control loop mechanism employing feedback that is widely used in industrial control systems and a variety of other applications requiring continuously modulated control. A PID controller continuously calculates an error value
e
...
Yees
There are many good YouTube videos on this topic
Don't worry, there's example of Unity C# code for PIDs scattered on the internet too.
The tl;dr is that you can feed it a target value and the current value. The PID process will try to get the current value to reach the target value by tuning the P, I, and D parameters
But often from an automation / control systems perspective
holy smokes I should have encoded the video I'm uploading
While my video does talk about smoothing instead of PID, do you have suggestions on that @cold laurel ?
Other than the PID, you could sacrifice a bit of latency to smooth the interpolated result like this video below. I do have to speed up/slow down the smoothing based on the deviation from the interpolated result since there is non-deterministic drift from having the rigidbody not being kinematic, and has the effect of dead reckoning when there is no expected result when the interpolation reaches 1.
The top one is the owner, the bottom is the observer. That jittering blue mesh is OnDeserialization result.
(fixed) oh neat I forgot to edit out the second half of the video
"... and has the effect of dead reckoning when there is no expected result when the interpolation reaches 1."
Curious what you mean by this, can you elaborate?
This looks really good. Whatever interpolation is being used - at least at this speed and framerate - doesn't produce any noticeable jerks or rubber-banding. Impressive.
so when there is no network received, the rigidbody being non-kinematic continues in the direction of the last interpolated step
Is this unideal?
depends how well the dead reckoning is setup. In this case there is no perceived local simulation where the car would still have gravity and inputs from the last network snapshot https://youtu.be/72GuLeEErTo?t=10
I feel like it would be okay for aircraft with local collision added
in some games, they just make the seemingly disconnected player disappear if too much time has passed since the last snapshot
here's an unideal case where the temporarily disconnected player regains connection and clips into a player https://youtu.be/jNkjz1lBjQ0
oh I see.
To clarify then, this is your work? And this implements a wholly smoothing-based approach, without a PID controller?
yes
imprecise actually. I was hoping for Udon 2 to go towards a more deterministic local simulation. I let this one drift away 1 meter from the expected position before speeding up the smoothing. Or just snapping it if the car teleports a minimum distance
I'd be surprised if Udon2 made anything more 'deterministic,' the physics system is just PhysX for Unity and PhysX has - to my knowledge - always been non-deterministic
I don't think Udon or Udon2 would even be able to touch any of that. Perhaps I'm misunderstanding what you mean by "a more deterministic local simulation?"
I am at the mercy of physx for the rigidbody, but everything else is in Udon at the moment. E.g. the tire forces and downforces. So I would resimulate the vehicle for non-owners in Udon 2
Incidentally, I just meant to ask "how are you implementing the smoothing," not "how precise is your smoothing" ;P
Even running 1 vehicle's physics for the owner is at the great cost of CPU since I wanted ISO 8855:2011 for the vehicle physics. The clients just get the position, rotation, and velocities
I am currently using a hermite spline with acceleration for the interpolation. And the extra correcting and smoothing is currently some arbitrary in-house stuff that looks at the deviation of the vehicle compared to the expected interpolated path
Full hermine spline? Or a simplification of it?
I'm also not sure what you mean by a spline "with" acceleration. I also presume you are doing interpolation for all three of the replicated values?
And you said you had to change the length of the interpolation based on the magnitude of the initial error, yeah?
I'm not sure if it's full or a simplification. I do plug in the linear and rotational velocity
I have last looked at that part of the code in october last year
Gotcha. I think I recall someone implementing a 'simplification' of the hermite spline that effectively looked like a lerp between two lerps, where each of the two lerps represented some 'control point' calculated in a way I don't recall. The full hermite spline involves a lot of straight algebra and I don't think is TRULY replicable 1:1 by a couple lerp functions...
My lunch break has ended but I will be peeking in here
oh.. I am not using lerp functions
Thanks for the thoughts. I'm gonna dig into PID controllers a bit, since my last attempt to use dampening/smoothing clearly did not end well. And, lacking a good example of an alternative smoothing system I can deconstruct, I should probably try something else to begin with x'D
one helpful resource for PID tuning that I recently used was this one https://www.thorlabs.com/newgrouppage9.cfm?objectgroup_id=9013
Thorlabs specializes in the building blocks for laser and fiber optic systems. From optomechanical components to telecom test instrumentation, Thorlabs' extensive manufacturing capabilities allow us to ship high quality, well priced components and devices for next-day delivery. Optomechanics, optics, opto-electronics, laser diodes, fiber optics...
I can confirm that KitKat HAS indeed passed out before I suggested smoothing. So they could be responding later too
Hm. I guess one thing I need to determine is... what actually in the system is being taken over by the PID controller?
My airship has three piloting controls: the wheel for yaw, the torch for altitude, and the throttle for speed. Each control describes a 'force target,' which the engines then spool up or down to at a fixed rate (so if I throttle to max, the engine may take up to 2 seconds to go from 0 force to 100% force).
These forces then act on the airship rigidbody, producing an angular velocity for the yaw, a "forward" velocity (in the airship's forward direction, on the Z/X plane), and a vertical velocity that is obviously independent of the yaw.
These velocities then produce a rotation and position in space.
If I want the airship to 'simulate' locally on remote clients when there is a gap in network activity, my initial intuition says I need all of this data to continue the simulation. But then where exactly does a PID controller even come in? Does the effect of the PID controller replace some of these variables? Act on top of some? Not others?
I suspect... what's actually going on is I have to store two rigidbodies still
one of them being my local simulation, the other being the current simulation from network data.
When I get a network update, the invisible network rigidbody inherits the network data - maybe plus a forward prediction, I'm not sure yet, that has seemed pretty unstable so far - and then continues simulating in real time until another network update occurs.
Then... perhaps I'm applying forces to the local simulation rigidbody in proportion to the positional and rotational error, between it and the network simulation above?
The target is always moving, technically; network updates arrive every so often but during the in-between frames, that data needs to be doing something, going somewhere
I skimmed the code and I think this function is what they were talking about https://github.com/Sacchan-VRC/SaccFlightAndVehicles/blob/master/Scripts/SaccAirVehicle/SAV_Extensions/SAV_SyncScript.cs#L355
God on the one hand it's so cool I can just read this and pick it apart—
On the other hand it is SO hard to read and pick this apart, with none of the context or headspace it was written in
Guh. The one thing that keeps tripping me up with forward prediction is that I need an estimation of acceleration and acceleration rate of change to do the calculus
the estimates are just... really bad without that
Unfortunately, unless I'm missing something clever, I only have two options to do that:
- keep a 2-frame buffer, EVERY frame, where I calculate acceleration since last frame (and stash the acceleration before that too). Then, serialize 2 extra floats and 2 extra Vector3s every time I serialize...
... or 2) Receive the serialization without that data on the remote client, do nothing for more frames than I'd like (it's more than 2 because rigidbodies take a sec to 'catch up' with changes you make to them/their gameobject transforms) while I locally simulate and catch my own acceleration estimates, then use those
I'd LOVE to just do more calculus, calculate it from my forces I've already received in the serialization
but
Drag :S
I do not know how to integrate drag.
Well. My first few hours working with PID controllers have not been particularly promising.
They seem to work... mostly. But they have a really bad tendency to enter death spirals... quite literally. Certain tunings almost immediately result in an ever-increasing orbital oscillation around the target that cannot be recovered from, and even when I adjust the tunings to something that LOOKS promising, flying my target around for less than a minute usually results in spontaneously entering a death spiral at some point anyways.
I also almost feel like I need smoothing or lurch-discarding on top of this, too. It's very easy to get the occasional misfired prediction that momentarily launches the follower a few feet because the PID tuning is actually really GOOD; it too quickly accelerates into bad data, and I wind up with rubber-banding anyways.
Maybe I should stop working on smoothing for a bit and spend some time trying to figure out how to sanitize the data I'm getting in the first place. The smoothing is definitely busted on its own but not getting particularly clean updates is not helping this matter at all... :S
... well. After sleep, that is. I'm so tired.
Hermite spline with acceleration? So you're taking snapshots of the acceleration and inferring the velocity from that?
Or do you mean that you take snapshots with position and velocity and interpolate with a hermite spline?
Cubic Bezier and cubic Hermite are pretty much the same thing but with a different representation. You can calculate a Bezier with lerps, and can thus also calculate a Hermite spline the same way. To convert Hermite tangents to Bezier control points you just need to divide the magnitude of the Hermite tangents by 3. To get the Bezier control points for the other side just flip the Hermite tangents.
The more derivatives you try to account for, the harder your life will be.
why are splines? well my god I have good news for you, here's why splines!
if you like my work, please consider supporting me 💖
https://www.patreon.com/acegikmo
This project grew much larger in scope than I had originally intended, and burnout made it impossible for me to do more with it. It was already getting incredibly unwieldy, so I apolog...
The above video on splines is fantastic.
Freya Holmér makes some amazing stuff
this should be a simple code for a collider when a player enter the room the number of players should increase and if someome leave the number should decrease
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using UnityEngine.UI;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class OnCollisionUpdate : UdonSharpBehaviour
{
[UdonSynced] int playerNum = 0;
public Text Number;
public override void OnPlayerTriggerEnter(VRCPlayerApi player)
{
Debug.Log("Entering Happened");
Networking.SetOwner(Networking.LocalPlayer, gameObject);
playerNum++;
Debug.Log(playerNum);
Number.text = playerNum.ToString();
RequestSerialization();
}
public override void OnPlayerTriggerExit(VRCPlayerApi player)
{
Debug.Log("Exteting Happneed");
Networking.SetOwner(Networking.LocalPlayer, gameObject);
if(playerNum <= 0)
{
playerNum = 0;
}
else
{
playerNum--;
}
Debug.Log(playerNum);
Number.text = playerNum.ToString();
RequestSerialization();
}
public override void OnDeserialization()
{
Debug.Log("The Deserialization");
Number.text = playerNum.ToString();
}
}
but what happened that I found when I try to request serialization it's not running for the local player so I made it happen localy
but then I found a negative number so I added the IF statment.
but now there's something I don'ts understand about the master of the room or the network owner that making if there's tow people some times it saying 3 
when you RequestSerialization for the local player, you also need to include the OnDeserialization on the next line
the local player doesn't call OnDeserialization, you have to call the function manually
Ah yeah. I've watched this video. Might need to again. And again.
I'm just not sure how else to do forward prediction with any sort of accuracy otherwise. Have people found a more clever way to do this that somehow "tolerates" lower fidelity extrapolations?
Looking a little at Sacc's code it appears they are doing a simple linear "forward" extrapolation but I can't for the life of me suss out how they're determining the magnitude of that extrapolation.
Dense code is hard to read sometimes.
Sacc is only accounting for velocity. Not Acceleration.
This is the sensible thing to do.
Even in a small window of time (say 200-300ms), acceleration - and even acceleration rate-of-change - can have a surprisingly large effect on predicting final position. As can rotational velocity/acceleration/accel-rate-of-change, if your linear acceleration is always directed in your forward-direction...
I would have liked to assume it wouldn't but when I mathed out the frame-by-frame physics it made a difference on the order of tens of centimeters.
How fast are these air ships??
Right now I'm testing with a nominal top speed of ~20 meters per second
But honestly the speed doesn't (shouldn't??) matter because the error is always going to be a ratio to the speed, and that ratio determines the "noticeability" of error to the player's eye.
Both Sacc's airplanes and Techa's cars are way faster than this and are fine with not accounting for acceleration.
Right
So. What else is happening? This is what I mean by systems tolerating larger errors/less precise predictions
Latency
... also I don't think Techa's cars are doing forward prediction at all
right?
Maybe they are.
Afaik @restive cliff isn't currently doing any extrapolation for the cars.
