#world-persistence

1 messages Β· Page 3 of 1

severe wing
#

Which reminds us... why are API calls made in random order if they get backed up? Wouldn't a known order by much handier?

wary summit
#

That's a complicated subject. It's not exactly random, and the exact order in which outbound networking happens while clogged is not something I'm prepared to dig into at this moment. But what I can say is that based on my observations, very small serializations (like a single bool) can get through clogging very easily, and do not sit waiting behind bigger serializations that will take a while

severe wing
#

No this was a mechanism I came up with. If I saw the method call in the log near the world loaded stuff I knew the general state of things. More importantly I knew the sync'd data had made it. I only wrote that stuff because players were being handed the same pool item sometimes. It is just a point in the start up process that I know I can hook. Like now I get an in-game logger and assign it to the player. That method hands me the player so it is far, far, (far) handier.

#

Interesting... small clogs vs big clogs

#

tell me about it πŸ™‚

wary summit
#

Will have to be another time, as I have a flight to catch in an hour ^^

subtle quest
#

good luck with the flight

severe wing
#

enjoy it as much as you can...

#

don't sit next to a weirdo

wary summit
#

ha, would be fun to sit next to you on a plane πŸ˜…

#

and talk udon stuff for a few hours

severe wing
#

it sure would... I'm a theorist if I'm anything

subtle quest
#

Also I wonder how difficult/easy it would be to make a priority sync system in networking heavy environments...

#

Something to think about

severe wing
#

It was an odd requirement but I have a sailing ship and it only relies on the transform so VRC takes care of the sync duties. Except if the master leaves at which time whomever becomes the master won't know the current settings.

subtle quest
#

My current project will involve a lot of AI actors with a physical, moving presence that have an impact on the environment. The decisions these actors make are based partly on player positions and thus are nondeterministic. Therefore my project requires and authority to decide how these actors make choices and syncs these choices with everyone else. Thus networking is required.

foggy mason
#

Noticed all of the Texas Hold Em poker table prefabs are broken now

spring cairn
foggy mason
foggy mason
# spring cairn

yup! how many people did you start the game with? with 5 people i could not even deal anyone in. both in mine and the creator's official world

foggy mason
spring cairn
foggy mason
elfin wadi
#

Anyone know if there is a way to see KB used by a save file? worried about hitting the limit

cold echo
#

Please don't tell me that persistence is going to be so buggy as to render it a hindrance.

fading totem
#

The closest you could get is probably SerializationResult's byteCount

#

But you can only access that if you're using a player object

#

and that would only tell you how much that one player object has used

#

So you'd need to keep track of all your player objects individuall and add them up

#

And this isn't really accurate either, because afaik the limit is based on the compressed byte count

#

I'm sorry if there's a better way and I've just missed it. I do not intend to spread misinformation.

elfin wadi
fading totem
elfin wadi
fading totem
elfin wadi
# fading totem Interesting! Are you able to share why?

Same world I was on about the other day with using byte[] for storage (thanks for the advice btw πŸ˜„ ). I'm now tracking stats, favs, etc for a list of songs in the database and this list is constantly growing so eventually it's gonna hit a limit

narrow tree
narrow tree
#

Oh, guess i missed it in there, tnx!

fading totem
elfin wadi
fading totem
#

πŸ‘€

elfin wadi
# fading totem πŸ‘€

in a good way or a bad way lol? I know PlayerData syncs everything but I've got a wrapper above it anyway which only checks for changes to save on processing

fading totem
#

But it's probably fineℒ️

elfin wadi
#

Yeah can always migrate to PlayerObjects like you say if it becomes an issue πŸ™‚

fading totem
#

Good point!

elfin wadi
#

PlayerData.SetBytes() was just too tempting and i didn't have to mess with anything else lol

#

for ref saves are handled every 60 seconds or after a player sets a score ( every few minutes ). So it's not gonna be constantly syncing

fading totem
elfin wadi
fading totem
fading totem
elfin wadi
elfin wadi
elfin wadi
#

Hell yeah, projects getting there πŸ’ͺ anyway don't wanna derail this channel lol

severe wing
severe wing
#

Happy Thanksgiving to those who celebrate... meanwhile to those who don't or who are waiting for the turkey... I have plenty of questions now that I've toyed with persistence. I'll start with PlayerData which I think others may find confusing as well. Please correct me where I've gone wrong and/or offer reasonable responses to problem cases.

#

PlayerData every player has such an object (that's good) and per the docs it shouldn't be accessed until after OnPlayerRestored has been called. That is easy to manage with a Boolean indicating that OnPlayerRestored as been called. Keep in mind OnPlayerRestored will be called for "every player" so you generally want to insure that you are the player who has been restored.

#

OnPlayerRestored gives us a point to obtain the designated PlayerObject. I believe we are supposed to (and I do) assign the local player as the owner. I also get a reference to the Udon class associated with that object as that is really what we deal with.

#

My use case for PlayerData is an in-game logger. A player can set a value which all current players can see (and display).

#

So imagine in OnPlayerRestored that I want to log that fact. I call _appPlayer.LogInfo and pass a string to log. Should be no issues as OnPlayerRestored has been called. Keep in mind that this is the first (master) player behavior nobody else has entered yet.

#

All that LogInfo has to do is to set a PlayerData Info object (a string in this case). so basically the following:

#
PlayerData.SetString(LogKey, message);
#

Having set it... "all players" (but only one player is present at the moment) have OnPlayerDataUpdated called.

#

The method is passed the player (permitting us to determine which player's data it is) and an array of PlayerData.Info objects. This could be empty or obviously contain info objects. There is requirement that all players have the same collection of Info objects but in my example every player will have one with the LogKey key.

#

Another sidenote... since OnPlayerDataUpdated is a public method on an in-game object we need to be sure that we only act on our instance, right? There is nothing "special" about these classes and general rules apply. This is why (above) I set the owner of the PlayerObject to the local player. I can tell it is my copy and ignore any others.

#

As a general matter I also check that the player has been restored. It should have been but there is no harm in checking and I'm not 100% certain that VRC couldn't call the method early.

#

We can walk the collection of "infos" and see the keys, check their State and ultimately get the values. Finding the LogKey I output it to a TMP element and it displays my "restored" message. So far, so good.

#

The State of that info BTW is "Added". I click a button that reports to the logger by similarly setting the same LogKey. Because the data changed OnPlayerDataUpdated is called again. I look up the value for that key and output it to the log. The State of that info is now "Changed".

#

Which leads me to questions. There is an "Unchanged" State but what sets this? If I set the info value to "OnPlayerRestored" the first time the State is Added. If I change the value to "Button Clicked" the State obviously becomes Changed. If another button sets the value to "Button Clicked" also will it be detected as a change, will it be considered Unchanged? And unchanged from what? What makes the value unchanged? The last time it was referenced in OnPlayerDataUpdated? Doesn't seem likely but let me add a test.

severe wing
#

Ok seems to be that the info will be marked Unchanged if one checks in OnPlayerDataUpdated and the value is identical. Importantly it isn't that "any new value" has been assigned but rather "not the previous value". Not a particularly big distinction but a distinction.

subtle quest
#

You shouldn't need or be able to change ownership of PlayerData, right? It's a pre setup PlayerObject and synced PlayerObjects cannot have thier ownership changed

severe wing
#

So big question. What happens if one changes an info value from within OnPlayerDataUpdated? Is it called again? I assume this could cause an endless loop.

subtle quest
#

And sounds like it'd loop, yah

severe wing
subtle quest
#

"When players own a PlayerObject, they also own all GameObjects with synced UdonBehaviours within their PlayerObject. They cannot transfer them to anyone else."

#

"You can guarantee that they will always own their own PlayerObjects."

#

Also why do you need persistence for your logger?

severe wing
#

I see the paragraph but don't see where the PlayerObject owner is set. I might log it to find out but again there is no harm in setting it and we do that for every other game object.

subtle quest
#

"In the Start and OnDeserialization events, ownership is guaranteed to be correct. "

severe wing
#

Not sure what you mean about persistence for the logger. It should just be a mechanism to share data between players. I'm trying to get rid of all the pool logic currently required.

subtle quest
#

All sycned data of PlayerData is persisted; that's THE reason you'd be using PlayerData

severe wing
#

OnPlayerRestored includes a reference to the player whose data was just restored. You can use this to make the Owner of the GameObject

#

You can use this?

subtle quest
#

That last sentence appears to be awkwardly cut off and is likely a mistake. Whoever maintains the docs should probably be pinged about it. Is it Momo?

severe wing
#

I think thinking of PlayerData as data the player has is the better definition.

subtle quest
#

Also using PlayerData also means that when someone joins a new instance they'd bring their persisted logger data from the last instance they were in

severe wing
#

In either case there is no harm in setting the owner again. Not seeing it in code might appear to be something one forgot.

subtle quest
#

Also I imagine them loading into a new instance would trigger OnPlayerDataUpdated, causing them to print out the last instance's log data to the new instance they just joined

severe wing
#

There is no persisted logger data per se. It is a message sender containing a single message that others can read.

#

I don't mind the problems (if minor) if it gets rid of an arbitrary pool of assigned objects.

#

My hope is that we don't have to imagine too much and simply understand how it works reliably.

subtle quest
#

Why not instead set up an unpersisted PlayerObject instead? Have it customized to your needs without all of the unwanted stuff that PlayerData forces you to include?

severe wing
#

What does that mean?

subtle quest
#

For a shared log create a PlayerObject that contains a synced string. Then onPost/Deseralized write that string to the log. Then whenever you want to write to the log have the player access their own copy of the PlayerObject and use your preferred method of either property or function to change the synced string value and call RequestSeralization.

severe wing
#

That single message string is easily reset to String.Empty as the world loads. It isn't like a lot of stuff is going to be carried forward from a prior instance.

#

The very first log message is essentially "I'm restored" in any case.

wispy latch
#

(No idea if that's what you want/need though)

severe wing
#

It may be of interest to note that the InstanceId of VRC Player Objects is a negative number.

subtle quest
#

Yah, PlayerObjects are the replacement for player pooled objects

severe wing
#

Well that all makes sense... let me RequestSerialization on a sync'd property and find out who can see it.

subtle quest
#

Should be everyone, even non owners of that PlayerObject

#

Also since you're not using persisted data you shouldn't need to worry about OnPlayerRestored. If trying to access non-persisted data prior to OnPlayerRestored causes issues it is most probably unintended behavior that should be reported to VRC.

sturdy veldt
#

Is there a load put on the overall udon networking limit when saving persistent data?

#

Or is it networked separately from Udon?

wary summit
severe wing
# subtle quest Should be everyone, even non owners of that PlayerObject

Alrighty then... a couple of false starts due to my overcomplicating things a bit as to ownership of a child object but... it is working now. I should tidy it up just a bit more so it isn't a generic PlayerObject but just one for in-game logging. And as you pointed out I can actually use it from OnPlayerJoined and forward which is handy as that typically is the first event I would want to log. Nice to have the pool objects gone.

magic dome
#

Trying to understand playerobjects a bit. When it says a copy is spawned for each player, is that copy synchronized? So if, say I have a PlayerObject with a collider and a variable that is used for some collision mechanic, and I have that playerObject change its position every fixed frame (to follow the player), do I have to do anything special to sync its position and the variable? (For context I am migrating from CyanPlayerObjectPool, so I am thinking in those terms.)

fading totem
# magic dome Trying to understand playerobjects a bit. When it says a copy is spawned for eac...

Yes PlayerObjects are instantiated synched objects!

have that playerObject change its position every fixed frame (to follow the player)
Afaik you should do this in LateUpdate since GetPosition only refreshes every Update?
I'm at least sure that doing it in FixedUpdate would be wrong.

do I have to do anything special to sync its position
I don't think you should sync its position. The best would be to do this locally.

twilit meteor
#

I have a little footstep script thingy, this is how I track the position

#

You assign the targetplayer and self through this

public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!player.isLocal) return;
targetPlayer = player;
self = gameObject;
loaded = true;
}

fading totem
#

⬆️

#

That's good

twilit meteor
#

I am having issues with syncing it for other ppl tho, I just realized.

fading totem
#

I would make some changes:

private bool _loaded;
private VRCPlayerApi _targetPlayer;

public override void PostLateUpdate()
{
    if (!_loaded) return;

    Vector3 position = _targetPlayer.GetPosition();
    Quaternion rotation = _targetPlayer.GetRotation();
    
    transform.SetPositionAndRotation(position, rotation);
}

public override void OnPlayerRestored(VRCPlayerApi player)
{
    if (!Utilities.IsValid(player)) return;
    if (!player.IsOwner(gameObject) return;

    _targetPlayer = player;
    _loaded = true;
}
#

This wouldn't really use any bandwidth, and it would align with where the players are on your screen.

twilit meteor
#

My scripts flawed. The position tracking is fine it just doesn't sync well.

magic dome
magic dome
fading totem
twilit meteor
#

My scripts fried anyways. The positioning doesn't sync correctly. Late joiners are synced

fading totem
magic dome
#

what am I doing wrong here?

fading totem
#

Is your script an UdonSharpBehaviour script?

magic dome
#

Yes, it derives from an UdonSharpBehaviour. Other overrides (OnDeserialization, OnPlayerJoined, etc) work fine

fading totem
magic dome
#

ok, let me try restarting Unity.

twilit meteor
#

Positions are synced, ty.
Now I just need to figure out how to play sound without it playing x sounds where x is # of players

magic dome
#

ok, restarting Unity fixed it. shakes fist

twilit meteor
#

classic

magic dome
#

The behavior for the remote player is not working in client sim :/

fading totem
magic dome
#

I feel like I'm doing something wrong here:

using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using VRC.SDK3.Components;
using VRC.SDK3.Data;
using VRC.SDKBase;
using VRC.Udon;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class VRpgPlayerObject : VRpgComponent
{
    public VRCPlayerApi Owner;
    private VRCPlayerApi _localPlayer;

    private void Start()
    {
        _localPlayer = Networking.LocalPlayer;
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "AskForUpdate");
    }

    public override void OnPlayerRestored(VRCPlayerApi player)
    {
        if (!player.isLocal) return;
        Owner = player;
        loaded = true;
    }

    private void FixedUpdate()
    {
        if (!Utilities.IsValid(Owner)) return;

        Vector3 pos = Owner.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
        transform.position = pos + Vector3.up * .75f;

        Vector3 locPos = _localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
        transform.GetChild(0).LookAt(locPos);
    }
}
#

THis works fine for the local player, but if I spawn in a remote player, the label (that's what this PlayerObject is attached to) does not track to them.
VRpgComponent derives from UdonBehaviour.

fading totem
# magic dome THis works fine for the local player, but if I spawn in a remote player, the lab...

Yeah, because you should do this instead

private bool _loaded;
private VRCPlayerApi _targetPlayer;

public override void PostLateUpdate()
{
    if (!_loaded) return;

    Vector3 position = _targetPlayer.GetPosition();
    Quaternion rotation = _targetPlayer.GetRotation();
    
    transform.SetPositionAndRotation(position, rotation);
}

public override void OnPlayerRestored(VRCPlayerApi player)
{
    if (!Utilities.IsValid(player)) return;
    if (!player.IsOwner(gameObject) return;

    _targetPlayer = player;
    _loaded = true;
}
fading totem
# fading totem Yeah, because you should do this instead ```cs private bool _loaded; private VRC...
using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using VRC.SDK3.Components;
using VRC.SDK3.Data;
using VRC.SDKBase;
using VRC.Udon;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class VRpgPlayerObject : VRpgComponent
{
    public VRCPlayerApi Owner;
    private VRCPlayerApi _localPlayer;

    private void Start()
    {
        _localPlayer = Networking.LocalPlayer;
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "AskForUpdate");
    }

    public override void OnPlayerRestored(VRCPlayerApi player)
    {
        if (!Utilities.IsValid(player)) return;
        if (!player.IsOwner(gameObject) return;
      
        Owner = player;
        loaded = true;
    }

    private void Update()
    {
        if (!loaded) return;

        Vector3 pos = Owner.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
        transform.position = pos + Vector3.up * .75f;

        Vector3 locPos = _localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
        transform.GetChild(0).LookAt(locPos);
    }
}
magic dome
magic dome
#

If I have a bunch of variables that I want to save in playerData (updated infrequently), can/should I use const to define key names?

wary summit
magic dome
wary summit
#

Every player has their own set of data. So the player argument of that function allows you to specify which player's data you want to fetch. The set functions are only able to ever set data for the local player, which is why there is no argument.

I suppose my example of wrapping with a property only applies if your goal is to only interact with the local player's data. If you have a use case where your script needs to read other player's data, then a property is not sufficient

magic dome
#

Yeah, it's more of a situation of knowing some stats for each player in an RPG setting, so I might need to get, say, another player's health levels

#

so that makes sense

magic dome
#

Can we not save Data Lists in this?

subtle quest
magic dome
#

Like, if I'm saving a byte array in PlayerData, and I want to compare a single byte to a byte in that array, the process seems convoluted then

subtle quest
#

Saving it into PlayerData is similar; just use the method detailed to convert it to json and save the resulting string in your PlayerData

magic dome
#

So I'm currently doing this... to compare a target byte to a byte[] called myListenChannels...

byte targetSpeakChannel = PlayerData.GetByte(allPlayers[i], "vrpg-speakChannel");

byte[] myListenChannels = PlayerData.GetBytes(Networking.LocalPlayer, "vrpg-listenChannels");
DataToken listenChannelsArray = new DataToken(myListenChannels);
DataList myListenChannelsList = new DataList(listenChannelsArray);

bool channelMatch = myListenChannelsList.Contains(targetSpeakChannel);
#

Like...will this work? Or is this an awful way of doing this?

#

it is 100% okay to tell me this is awful

subtle quest
#

I didn't know you could define a DataList like that. It might work though, you'd have to try it yourself.

I think this might work, though

byte targetSpeakChannel = PlayerData.GetByte(allPlayers[i], "vrpg-speakChannel");

byte[] myListenChannels = PlayerData.GetBytes(Networking.LocalPlayer, "vrpg-listenChannels");

bool channelMatch = (Array.IndexOf(myListenChannels , targetSpeakChannel) != -1);
magic dome
#

I didn't know you could define a DataList like that

I've never done it this way, but it's not throwing me any weird errors

subtle quest
#

Also if performance is a concern, and this indeed works, it might run faster because it's using built in C# instead of Udon to iterate through the array

magic dome
#

that's my secret: performance is always a concern πŸ˜›

light laurel
#

^ anyone know the answer to these questions?

#

rather, could someone tell me the answers

subtle quest
#

Well for that last question I'd advise looking up defensive programming

wary summit
dense owl
#

To avoid loops, I usually just use a normal variable on the action itself
and update PlayerData's corresponding variable to be equal to the normal one at the end of the event chain.
So I'm sure it's not interferring with the action itself, even if it writes it's its own custom event.
( And no writing any PlayerData during OnPlayerRestored or OnPlayerDataChanged as a rule of thumb )

But again I'm a graph user, there's probably better ways to approach it in U#.
That set of rules just looked the safest to me in that aspect.

subtle quest
#

Oh yah, true. Had a stack overflow causing infinite loop the other day when I had two event calling UI elements that would modify the other when their value changed. In my case I ended up adding a bool that would ignore additional event calls while performing this operation.

silent echo
#

There is a SetWithoutNotify function for most ui elements that will update the ui without firing events

snow quest
#
 if (!player.isLocal) return;

does this mean a player can end up loading data from another player? Is this to prevent bugs or an intentional feature?

magic dome
#

As mentioned before, the issue is that the slider is calling the method that updates the data. Instead of setting the value, use the SetValueWithoutNotify option, which will bypass the component's onValueChanged. So for example:

float XKey = PlayerData.GetFloat(player, PosX_Key);
        if (!Mathf.Approximately(previousValueX, XKey)) {
            previousValueX = XKey;
            hudAnimator.SetFloat("posX", XKey);
-            sliders[0].value = XKey;
+            sliders[0].SetValueWithoutNotify(XKey);

        }
light laurel
#

ah okay, i will try that

velvet barn
snow quest
#

there is no way to prevent players loading other players data?

velvet barn
#

By design they will always receive the data. You as the world creator decide what to do with it in Udon. If you use that line to discard it, for nearly all intents and purposes you can consider it not loaded.

#

Are your concerns academic, performance, or security?

snow quest
#

academic, security. Just exploring how it works and what you can do with it. Thought about giving the player some kind of key

#

make information intentionally available for other and/or selected other players

quick imp
#

So Im at the point where I need to figure out how I actually save player data, I have persistent objects set up but https://creators.vrchat.com/worlds/udon/persistence/player-data/ doesnt really tell you anything about actually setting up the saved data variables, do I create a game object and put a component on it to make player data or?

PlayerData is a key-value database for storing persistent data about players, such as their score in a game or their preferences in a world.

fading totem
quick imp
#

So Ive figured out that player objects are only good for temporary local storage, but I cant find how to access and adjust persistent player data

#

At first I thought it was just a gameobject that youd put into the scene and put a component on it for player data but I dont really see anything

#

The documentation isnt that good IMO :L

fading totem
fading totem
#

This means you can access it without a reference to it from any script.

fading totem
quick imp
#

Ive seen set and get, but where do I go to declare all of the saved variables that i want for the Playerdata?

fading totem
quick imp
#

Ah.... yes but is there not a place in which I initially set up the key

fading totem
#

There is not.

quick imp
#

Oh?

fading totem
#

The key is "set up" whenever you first call Set...(); with a new key

quick imp
#

Whahhhh! that sounds a bit messy! How would I know if there isnt some rogue keys set up through development that end up being cut in the end :L

fading totem
#

You can't really :)

#

And there's unfortunately currently no way to remove keys after they've been set, so you should carefully consider if the data you're saving is really going to be useful for the lifetime of your world

quick imp
#

So the saved player data is essentially created at runtime then...

fading totem
#

Pretty much

quick imp
#

Thats....

#

Not goated lol

#

But ok

#

Thanks

fading totem
#

Also also, every time you write to PlayerData, even if you just changed one thing, the whole thing will be synced.
And if you had a lot saved in there, you'll clog the network for a while.

quick imp
fading totem
#

That's a good idea

#

I mostly recommend using PlayerObjects

quick imp
#

Ah... Im just using graphs, I never really got into programming

fading totem
#

No worries! That's okay too. It is a shame that graph is missing out on these nice to haves though.

quick imp
#

Ive never even heard of singletons sooooo :L

#

I mean Im making do Ive already made a comprehensive minecraft style inventory system with guns and items

#

health, death systems and whatnot

fading totem
#

That's very impressive! You can certainly do a lot with graph despite its limitations.

quick imp
#

Oh right yeah one more thing, how do I initiate a playerdata save? or is it automatically executed every time you get or set a playerdata variable?

quick imp
quick imp
#

Thisl get unoptimised fast if people dont do this carefully :3

#

VRChat is already slogging at 10fps on my I9 as it is :L

fading totem
#

Also, you should never write to PlayerData before the local player has been restored. You can know this when the OnPlayerRestored event runs with the VRCPlayerApi that it passes being the local player.

quick imp
#

Uhhhhhhhhh

#

So I wait 3 seconds and then click my boots together, got it

fading totem
#

nooooooo

#

:(

quick imp
#

So restoring is the loading of data?

fading totem
#

That's when VRChat tells us it's "safe" to begin writing to the save data

#

because it's done fetching whatever save data you might've had on the server

quick imp
#

So Il just make a boolean that enables on restore

#

so itl fail a branch if it tries to save too early

fading totem
#

Pretty much

fading totem
quick imp
#

ye

#

Tried setting up a simple counter, running into exception errors though :L I figured this would work, but doesnt seem to :L

#

maybe a local player on the end of getint? :L

fading totem
quick imp
#

I did, same result

fading totem
#

Also, you have to branch on if the player varaible here was the local player

fading totem
quick imp
#

Not sure how Im supposed to use VRCPlayerApi player

#

is that a bool?

fading totem
#

no

#

it's a VRCPlayerApi

quick imp
#

Not sure about the context on which to use it then :L I also tried with networking>getlocalplayer but still nope :3

fading totem
#

Which I highly doubt is what you want.

quick imp
#

Yeah no lol

#

Ok I got it working I think

#

Yep, is there a way I can see how much space that took to save?

#

Ah 165 bytes

#

A bit more than I thought it would use but whatever

fading totem
#

It do be like that

quick imp
#

Well thanks for the assistance @fading totem

fading totem
#

Np!

light laurel
#

(the slider script is just there for context)

#

i also have the issue of the Toggled_Key being disabled by default, but i want it to be enabled by default. but i don't know where i would tell it to be true by default instead of false

wispy latch
#

So I made a "global" leaderboard using persistence.
It seems to work fine at first, however, it soon exposed quiet a few people who were clearly cheating...
I assume they did it through clients and calling methods/changing values? I'm not sure how exactly those tools work but I imagine they have quiet a bit of control?

#

What I got curious about though: Can they change their persistent data somehow?

light laurel
#

i would assume that worlds are supposed to be the only thing that can change playerdata

#

any other way of changing it besides clearing it is probably either an exploit or client

wispy latch
# errant token How did you make it "global"?

I let people infect each other with their top most known scores. It's like a virus, if someone visits a public even once, they will have it and will infect any friend they visit in privates.

#

(It got kinda buggy though after I tried eliminatin the cheaters. The logic is not as straight forward..)

#

Ah, and name changes is what I'm trying to cover too right now.

errant token
wispy latch
#

From what I understand, this is a fools errand anyways and I probably shouldn't waste more time on this? lol

errant token
silent echo
#

or they are just directly editing memory

#

simplest way to avoid memory manipulation is don't store the value as is

wispy latch
#

Yeah... I would love to see what tools they use, but I have not even thought about pursuing them because of potential bans.

wispy latch
silent echo
#

so say you are storing a score. and my score is 1300, if I want to find the value in memory I just have to look for 1300. if I find multiple of those slots in memory then I just wait till my score increases to 1550 and search again. if a previous memory slot was 1300 and is now 1550 I've found the memory slot and just overwrite that memory with 99999.

if you don't store the score as the value the player sees, like store 130 instead of 1300 as the 0 is not used, it starts to obfuscate to the cheater and they have to search harder. this is like a level 1 obfuscation

a level 2 method would be to xor the score with a static number like 0xABABABAB that would make the score hardly ever resemble it's actual value

a level 3 might be to store a hash of the score every time you update it and every time you need to use the value you check the current value against the hashed value and if they don't match then some manipulation has possibly happened and you can invalidate the data.

I protect several important values in my world with a random value xored with the data's value. but keeping things synced when like a master switch occurs takes some thinks to keep things sane. And nothing is foolproof, this will only keep the script kiddies out and increase the difficulty so it should prevent all but the most determined.

wispy latch
silent echo
#

it's always a tradeoff on complexifying your code vs hardening it against attacks

wispy latch
#

I wish people just didn't attack, but I know that's not reasonable zzz

light laurel
#

are there any video tutorials on persistence yet? i feel like i really don’t understand the system just by reading about it

errant token
subtle quest
# wispy latch I wish people just didn't attack, but I know that's not reasonable zzz

Yah you need to think about the difficulty of hardening vs the actual benefit for players. With a shared global highscore this is going to be hard because even rare cheating is going to be exposed to everyone. But for cheating behavior that will only impact those in the same instance and the impact on other players being low, then you can get away with a lot less hardening. For example, it's pretty easy to cheat in ToN but the adverse effects of a cheater are usually handled with a quick vote kick or group ban.

light laurel
errant token
velvet barn
# wispy latch I let people infect each other with their top most known scores. It's like a vir...

I'd been toying with that idea, but for the reasons you gave (cheating) as well as concerns of running out of space given enough time (though this could be resolved by culling lower scores), and the networking cost for each sync, I decided against it. Even with some cheat protections in place, I still require video submissions for global high scores. I do still want to explore the idea of decentralizing data and using that as a mechanic incentivizing joining strangers in publics to "sync up", but I'm still workshopping both what it would do as well as good ways to do it well.

north apex
#

i also had some interesting ideas along the lines of letting persistent data spread between users like that, although not just for scores

#

but yeah, time is limited, and hard to secure it enough to be taken seriously

wispy latch
wispy latch
#

Would be an interesting social experiment at least.

dense owl
wispy latch
dense owl
#

Name spoofing is a thing in some clients yeah, A lot of cheaters used it to access admin panel in Aincrad. ^^"

#

So, it wouldn't be surprising they also make shenanigans like this in other places

#

Or maybe you can't find it because it was reported and banned already?

safe wagon
#

Why does the player-object documentation say the following:

"OnPlayerRestored includes a reference to the player whose data was just restored. You can use this to make the Owner of the GameObject"

Which implies that this event is useful, because you can just write something like:

Networking.SetOwner(player, gameObject);

To make sure that each player is now the owner of their player object, yet... on the very next line of the documentation, it states the following:

"In the Start and OnDeserialization events, ownership is guaranteed to be correct."

If ownership is guaranteed to be correct, then why would it be useful to 'make the Owner of the GameObject" as it is written in the prior statement?

wispy latch
safe wagon
#

Are we assuming that it isn't required to set the owner prior to use, or are we assuming that ownership isn't guaranteed

wispy latch
wary summit
safe wagon
#

Thanks for the help Puppet and the clarification Phase!

subtle quest
#

I've been wanting to complain about that line in the docs for awhile now but didn't know who to complain about it towards

errant token
subtle quest
#

Also the links in the pinned comments to Persistence docs link to the beta version of the docs, if those are different

errant token
# subtle quest

Thanks - time for a new post to pin, I think, consolidating the info.

#

Persistent Pin

Persistence for Player Data is now available in the SDK.
Learn how to use it by reading through our docs, visiting VRChat worlds that use it, and exploring example Unity scenes built with UdonSharp.

Docs

Persistence Docs

  • PlayerData is a key-value database for storing persistent data about players, such as their score in a game or their preferences in a world.
  • PlayerObjects allow you to automatically give each player who joins your world a copy of a GameObject, such as a flashlight, a health bar, or a sword.

VRChat Worlds

Visit the Persistent Worlds Showcase thread on our forums to find persistent worlds to visit.

Examples

You can open Example Central from the menu under "VRChat SDK > 🏠 Example Central".

Or browse through them first:

blazing raptor
#

how can I see the values that are stored wihle in client sim?

It should appear in your ClientSim Player Data window as a float named
There is no such window

blazing raptor
#

okay I found it, its hidden there:

#

expected it to be in the Window tab

errant token
blazing raptor
blissful crow
#

Does the Persistence works with CyanTrigger?

#

I wanna make a gameobject toggle but seems to not be working at all

blissful crow
#

there is no tutorial anywhere

light laurel
blissful crow
#

Whaaaat? Thats so sad! All my worlds work with CyanTrigger! D:

light laurel
#

i know someone else who does the same. it does suck, but either someone else might pick it up, or everyone will have to learn udon

blissful crow
#

Welp, Ive tried a simple Udon object toggle and still persistence seems to not working

#

Still getting this

light laurel
#

that’s just the info message telling you how it works, it’s not something you’re β€œgetting.”

and, for a persistent toggle, you would use PlayerData. not a PlayerObject

blissful crow
#

how?

#

Im lost

wispy latch
blissful crow
#

This is my Udon Graph

wispy latch
blissful crow
#

Nope.

light laurel
wispy latch
#

Seems like it's a bit rough in this case because they are using Cyan Triggers πŸ€”

#

I feel like you could just use PlayerObjects for sure. No idea if the PlayerData API is exposed there though.

blissful crow
#

So- what I need to do?

wispy latch
#

Actually, I don't know if you have any callbacks for the PlayerObject either πŸ€”

#

Sorry, I have very little clue about CyanTrigger, it might not work for all I know

blissful crow
#

Im asking right now with regular Udon

blissful crow
wispy latch
wispy latch
# blissful crow .

Try typing in PlayerData in the graph. You should find SetBool or something

blissful crow
#

Those examples arent self-explanatory

#

nothing tells me what triggers the persistence

wispy latch
#

Then you can use the OnPlayerRestored event to GetBool

blissful crow
#

what now?

#

like this?

wispy latch
blissful crow
#

where

wispy latch
#

Because it will get called for every single one

#

the player node thing, you should extend it and find IsLocal

blissful crow
#

The OnPlayerRestored?

wispy latch
#

You should make a branch node too

blissful crow
#

im still lost

wispy latch
blissful crow
#

Ohhhh

#

the branch what is for?

wispy latch
blissful crow
#

I just want a simple toggle with persistence 😩

wispy latch
blissful crow
#

Basically, to those who worked with CyanTrigger since its release we have nothing to do but learn everything from 0? That sucks

#

I wish the devs implement CyanTrigger as an option the same way they added CyanEmu

wispy latch
#

(I haven't touched graph in a while though. Try this out, I hope it works for your usecase)

#

This will allow a player to toggle something, rejoin and have the same toggle state as when they left.

blissful crow
#

lemme try

wispy latch
#

Make sure to use a unique save name though. If you use the same save name on each toggle, then they will overwrite each other.

blissful crow
#

Whats this node?

wispy latch
blissful crow
#

And these should turned on?

#

It's giving me this error

#

And the toggle is not working

wispy latch
#

Did you assign anything to gameobject?

blissful crow
#

assign what?

wispy latch
#

if you go into your inspector you should see it. Drag and drop the target game object you want to toggle in there

#

You can also just use this instead of creating the variable though

blissful crow
#

Oh, its not empty

#

Also, in Play Mode, the trigger cube turns off itself, however its still visible in the scene

#

and can interact with it

wispy latch
#

You named the variable "Cube1" for some reason, but that's not how variables work.

blissful crow
#

oooh

#

How then?

wispy latch
#

Make it public too just like the game object and then in the inspector you write the unique name

blissful crow
#

it works now!!! thanks!

#

So from this I can guide myself

#

thanks a lot!

dense owl
#

Looks like a lot of basics are needed here.
If you want some simple examples on PlayerData, there are pictures of a few of my graphs (with comments) in my persistant world (in the world showcase thread). It isn't worth much for experienced users but can help if you learn from observation more than explainations. ^^"

modest stirrup
#

Just wanna point out that NONE of the examples in the new Example Central thing in the SDK actually have graph examples. That, and the docs are literally just docs, and lack any sort of step by step tutorial. The jump counter picture in the doc is about the only thing graph users have to go on. Basically anyone not using U# is on their own, good luck learning persistence

dense owl
#

They don't? I just messed with nodes till I got stuff working so I missed that xD

#

I kinda wish there was a good way to take pictures of the graphs, it's always a bit blurry depending on zoom level

#

But yeah, my world have graph pics for:
"click to add point"
"automated action on loop"
"give to all"
"give to specific player"
"give to spefific team"
"achievement (multiple levels) depending on score"
"unlock trophy for X point" (shop)
and an example of saved door you have to use a key to unlock

There's lot of other stuff but they're 99% not persistance, only the end "is unlocked?" state is saved so it's the same as a "add point" then OnPlayerRestored check if not equal to zero. This alone is enough to make a lot of basic stuff.

#

there's a whole maze with puzzles but again the only persistance related part is the "check if solved" and reloading part

#

Also I'm self-taught so take my graphs with a grain of salt, there's probably better ways to do stuff

dense owl
#

.
Man, persistance is suuuuch a blessing.
My internet died for a minute while I was in one of those "mine some ressources" world. Not having to restart farming three tiers of ressources is so good compared to before. Finally, rejoining don't feel like a complete defeat making it not worth resuming playing the world.

dense owl
light laurel
#

does anyone have an example of a basic persistent toggle? like, for a menu button that toggles a gameobject.

dense owl
#

for the specific example you ask for, that's just be that added:

wary summit
# dense owl You mean something like this?

I'd probably recommend onplayerdataupdated for that use case instead of onplayerrestored. You can simplify it a lot by not having to make a separate event, as onplayerdataupdated will trigger every time the variable changes, no matter what source

#

Also, request serialization is unnecessary as playerdata is independent and manages its own serialization

light laurel
#

how would i set a default value for a key? like, if i want the toggle to be enabled by default for example

light laurel
#

is this correct? also, would it be fine to use this same script on 10 different toggles? would the key names not conflict?

private const string Toggled_Key = "toggled";
bool state = true;

public void OnClick()
{
    state = PlayerData.GetBool(Networking.LocalPlayer, Toggled_Key);
    PlayerData.SetBool(Toggled_Key, !state);
}

public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
    if (!player.isLocal) return;
    _ApplyState();
}

public void _ApplyState()
{
    foreach (GameObject target in targets) target.SetActive(state);
    if (state) image.color = onColor;
    else image.color = offColor;
}
#

it seemed to work in clientsim, although the key was reversed from what was actually happening, and it took about two or three button clicks to get it to actually sync up with how to toggle should work.

wispy latch
wispy latch
#

Right now you are only saving the flipped state, then on DataUpdated you just apply the old state even though the loaded state is flipped.

#

Sorry, hope that makes sense. For some reason I struggle explaining it. It's rather simple, just a tad bit mind bendy.

light laurel
#

oh, no i thnk i understand it now

#

makes sense why the data would be true but the toggle be false

#

so i just have to add _ApplyState; right after i do SetBool then?

#

if iunderstand correctly

wispy latch
#

You are not updating it's "flipped" state anywhere. Either do it right after you SetBool, or in OnPlayerDataUpdated. I think both can work.

light laurel
#

OH, so like, add state = !state into OnClick

wispy latch
#

Yes, since if you don't do it then _ApplyState will use the old value, which will appear like nothing has changed

magic lichen
#

How hard would it be, to change the leaderboard example to show how many times a person has visited the world?

magic lichen
#

it in fact

#

was super easy

dense owl
dense owl
# wary summit I'd probably recommend onplayerdataupdated for that use case instead of onplayer...

Any time any PlayerData changes yes. But there's always cases you'd rather have it not be called all the time, for example if people add admin override that doesn't affect the player's saved preference, for whatever reason, or idle games since they're so easy to make them relevent for players to stay (similar to how UdonChip have a passive income gimmick) now...

For a general example I'd rather keep it at "call it only when needed" because you never know what kind of conflicts with calling it at OnPlayerDataUpdated could cause in the long run when not knowing the user's complete design. ^^"

fading hawk
#

Is it safe to destroy the networked player object for the person that left the world?

silent echo
#

It will be destroyed automatically.

light laurel
# dense owl You mean something like this?

so, would i not want OnPlayerRestored and OnPlayerDataUpdated in the same program? if Restored is for when the data is first available, and Updated is for every time it changes, it sounds like i'd want to apply the state for both of those events

#

this is what i currently have. it works for the most part, the only catch is that; if the playerdata is enabled on start, the first time you click the button it does nothing, but any time after it works as intended. but if it's off on start, it always works as intended

#

i think i can see why it's doing what it is, but i don't really know how to fix it

dense owl
#

OnPlayerDataUpdated is good, it triggers every time a PlayerData variable is changed

#

but it triggers when any PlayerData is changed without distinction, so it depends on your world's design

#

if there's just a setting menu it's not gonna be enough calls to cause issues

#

but in cases with way more frequent calls, it's a lot that adds up

light laurel
#

well, i see it has an Info local variable, so couldn't i check what exactly changed?

dense owl
#

I'm not advanced enough to use that part, probably if you know exactly what you're doing with it

#

but the initial check still occurs on any changed anyway

light laurel
#

docs say it contains all the keys that were a part of the update, so i'd assume i can check if the updated keys are the same one as PersistenceKey in my script

dense owl
#

My stuff is far from being the best way to approach it, experiment if you think something else is better and compare I guess

#

I mean you're using U# it's already more advanced than what a Graph user like me can do xD

light laurel
#
public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
    if (!player.isLocal) return;
    foreach (var info in infos) {
        if (info.Key != PersistenceKey) return;
    }
    _ApplyState();
}
#

pretty simple

#

probably works

dense owl
#

only one way to find out, iterate until it does what you want ^^

light laurel
#

i can also have it log every key that it goes through, so i can see what it sees

light laurel
light laurel
dense owl
#

before it, the game only have the default values (which is a problem once your player saved changed values, you don't want defaults xD)

#

so it's a key piece to loading any persistance data

light laurel
#

i'm still not sure how i would set a default value for a persistent value. like, right now all my toggles would be off the first time someone joins, but i want them all to be on by default instead

dense owl
#

reverse the wiring?

#

the player don't have to know that the button that looks like it's on sends a "false" and when it's off a "true" xD

#

even tho...not recommanded, that type of mental gymnastics can lead to bad habits

light laurel
#

yeah that's a bit strange. is there really no other way?

dense owl
#

I guess you could set the values (without saving them) OnStart, so a player without saved data would recover it...

#

since OnPlayerRestored is called only after, it updates anyway

#

It's true that it's a bit akward how we can't just set the default values like for normal variables...

light laurel
#

yeah, it really is

dense owl
#

could be a good tool to add, something that tracks the PlayerData + allow to set defaults

#

similar to the normal variables list on graphs

#

even using a notepad I lost track of my keys past a certain point

#

still no idea how close to the limit I am for my world

light laurel
#

somehow, this doesn't either. i really don't know why, but it seems to pass through the If statement regardless of whether or not the keys are actually the same

public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
    if (!player.isLocal) return;
    foreach (var item in infos) {
        if (item.Key == PersistenceKey)
        {
            Logger.Log(name, $"Data updated! ({item.Key})", LogColor.Orange, false);
            _ApplyState();
        }
    }
}
dense owl
#

I'm just a weirdo that rather use custom events because graphs

wispy latch
#

I think you should look at persistence and the rest of your code as separate systems. The reason there are no default values for saving is you need to explicitly save something for it to be stored. Therefore, you should set and handle the default value in your own system, and only when the save data is loaded do you change the state.

light laurel
#

and, i'm practically doing the same thing as you with the separate custom event. it just looks different because it's code, but my _ApplyState is basically the same as having a separate event in graph

dense owl
wispy latch
#

Same for saving, do your code first and then save as an afterthought. I personally wouldn't ever use the on update event. But maybe that's just me,

#

And rely on other features like synced variables.

dense owl
#

Honestly, at that point like I said before just iterate till it does what you want. Only you knows the exact specifics of your systems beyond the snippets you can share here

#

Like, what i sent works fine for bools but if something have more states obviously it would need to be modified anyway

wispy latch
#

Here is actually a good example of what I'm talking about:

dense owl
#

I also have an habit I don't know if good or bad, or using a generic "send custom event (eventname)" as my whole button, and resolve everything in a graph used as state manager, rather than putting actions on the buttons so...yeah xD

light laurel
dense owl
# wispy latch

oh, using the object's state directly to define the saved value, rather than the value to define the object's state? Yeah that's also a good way to do it

#

at this point we're just comparing "A+B" to "B+A" here xD

light laurel
#

this is what i ended up with, it works perfectly and i basically use persistence saving and loading as a separate system now

#

i feel like i understand persistence a lot more now after talking today

dense owl
#

Yup they're their own thing, treat it as an add-on and it's way easier to visualise

#

and to integrate to old systems also xD

wispy latch
light laurel
#

when i first saw persistence, it seemed like you had to make your whole systems based around it, but now i can see how it's just like an add-on

#

honestly easier than networking

dense owl
#

I can see where the confusion comes from, it was intimidating at first for me too before I realised "wait, I can just make an "set 'Pdata' to 'Variable's value' at the end" and call it a day xD

dense owl
light laurel
#

god i hate networking but it makes games so much more fun

dense owl
#

I made so many systems that works for single player but breaks as soon as I try to add a multiplayer aspect it's not even worth joking about anymore. Networking is just...painful

wispy latch
#

I love(hate) networking code

dense owl
#

I just hate it, no ambiguity needed 🀣

#

but again I'm just a graph user, so it's pretty reasonable at my level

#

it's a miracle my ressource nodes + inventory + build a house + shops even works as graphs...

#

funny enough, adding persistance to all of them was pretty simple

#

for the saving side

#

for the loading part oh boy...making sure only the steps you brought are played on joining, while not draining the inventory since you already paid of course...

#

that was quite an adventure to figure that one out

wispy latch
#

Yeah doing that in graph I'd actually hate

dense owl
#

it was not as bad as I pictured it to be

#

but required a complete change to how i approach my graphs

#

and also some unholy "string+int = custom event name" for the calls

wispy latch
#

My opinion, if you can make something complex in graph you can easily transition to code

dense owl
#

if I ever learned to code maybe, but my brain just...it don't stick when it's just words in a long list..

#

I know it's a me problem, been messing with all kind of game engines since 2009... xD

wispy latch
#

Of course it's a bit of effort, but it is just muscle memory. You already know English and most basic programming concepts from graph

#

Just need to spend some time to connect it all

dense owl
#

yeah reading scripts isn't even the problem, it's just when I work in this format my brain just blanks

#

when it's reading one and messing with it, I've been messing with other people's stuff since my RPGmaker days

wispy latch
#

Imagine it like trying to ride a bicycle for the first time. You just need to start moving

#

Well I don't want to mean to be annoying, I just like talking about brain stuff. The decision is ultimately up to you

dense owl
#

I know, that's the most frustrating part. The Aincrad devs did try to help me on that front when I was still in their team, poor programmers lost some sanity seeing what i can do in graph but how helpless I am in front of U# xD

#

I was spitting full working mockups but none of it was usable because graphs

#

Like the Vket huge booth, I made it completely, sent it, and they had to redo it...
(3D team also lost sanity sseeing I was using sketchup but...that's another story xD)

wispy latch
#

Yeah, you have to understand they experience the same effect of lacking the muscle memory for graph just like you do for code.

#

And since code is more powerful, it's a lot harder to justify messing with graph at that point

dense owl
#

the funniest thing that happened was when someone jokingly said "just wait till he learn about concats" when seeing my old stacks of string.addition and I was like...wait, THAT THING EXISTS?! 🀣

#

I'm that level of "no programming backgrounds before messing with game engines"

#

funny enough that's how I learned most of what I know from them, snarky remarks like this, because it's so common sense for programmers that, they just can't imagine someone don't know it

#

and again I can't blame them for it, I have the same problem with teaching art to newbies because it's been so many years that basics are just "common sense" to me

#

it's just normal past a certain point

wispy latch
#

Pretty much, but that example is also more so knowledge versus actually writing code.

dense owl
#

(that's also what made me build my persistance example the way I did, trying to not assume the person knows how a thing work)

wispy latch
#

You can't know what you don't know

dense owl
#

yup

#

that's why nodes works for me

#

from a starting point I see a whole list of things I can mess with

wispy latch
#

Nope, that's where I disagree lol

dense owl
#

even if I didn't knew any of their names before opening the search

#

I mean once I know what a thing is yes, it would translate

#

like the concat example, it would only "be useful to learn" the first time if I found it before being told

#

after that graphs becomes inferior

wispy latch
#

Most of the API names are the same as in graph.

dense owl
#

also let be honest, things are working in graph so, yeah I'm good in my confort zone... know it's a me problem on that side xD

wispy latch
#

Even a lot of the basic building blocks like Branch and loops are the same, it's literally just a matter of muscle memory, which has to be attained by slowly walking like your are drunk until you can walk straight again

dense owl
#

drunk would be an understatement at this point xD

#

but yeah I know, that's the worst part

#

(let free the channel since it have nothing to do with persistance anymore, tho)

dense owl
light laurel
#

in total this is probably about the 6th iteration of that world

#

i figured with my better general world knowledge and persistence, it was due for an update

#

i was working on a different game using persistence, but development has been very slow on that one, cause it has a few issues i have no clue how to solve

light laurel
#

oh, what sync mode should i use for something that's local,but has persistence?

#

manual i would assume?

wispy latch
light laurel
#

oh i see

dense owl
#

when in doubt, manual is always the safest choice

#

just test it with none and compare if everything behave normally. if not, manual it is I guess

magic lichen
#

i broke it

#

πŸ€”
Im trying to use the example leader board prefab, but change it to adding 1 every time they visit the world. Instead of every time they jump.

I somewhat got it to work, but it added a point for every person in the instance instead of just themselves.

velvet barn
magic lichen
#

I got it to work

#

big hype

wanton current
dim coral
#

Not sure if this is the right place to post this, but why do they swap the return values on "GetBool" and "TryGetBool"?
They list things in the same order in the documentation as the normal "GetBool" which would suggest the "success" parameter is the second passed parameter. They don't mention anywhere which value is returned where. It took me ages to figure out why I could not get persistence to work, and the only thing that tipped me off is when I dived into VRchat's API code to see what it was actually doing.

#

Obviously at this point the persistence library is out, so it is what it is. But it would be nice if the documentation was more clear. I think swapping the values on the "Output" heading to read bool success, string value would help clear things up a bit. Maybe some example code showing the difference between "Get" and "TryGet" would be nice too.

#

eg:
Correct:

bool doesValueExist = PlayerData.TryGetBool(Networking.LocalPlayer, keyValue, out yourData);```
Incorrect: 
```bool yourData= PlayerData.GetBool(Networking.LocalPlayer, keyValue);
bool yourData= PlayerData.TryGetBool(Networking.LocalPlayer, keyValue, out doesValueExist );```
wary summit
# dim coral Not sure if this is the right place to post this, but why do they swap the retur...

That's a valid critique of the docs, but you should really be using an IDE which shows you the correct ordering. The excellent intellisense and autocomplete is one of the big draws to C# in the first place

VRChat does not have doc pages auto generated by API scanning, so these kinds of issues (let alone the functions not even mentioned in docs) so you're going to have a rough time with things like this if you don't have a proper IDE set up.

wispy latch
#

πŸ€” Why?

crystal condor
#

It's said error

dim coral
#

So its a Logic error that is created. Since I am new to using persistence, I had to go through checking all of my code, rereading the docs, checking that I was testing properly, checking that I didn't need to enable it on the dashboard, double checking I had the right WorldsSDK version etc. If I didn't have a background in programming, I can imagine something like this being a roadblock for many people.

#

The word "value" perhaps should have tipped me off, but its so generic it could mean anything (all booleans are a value), and VisualStudio gives no name to the boolean being returned to compare "value" against.

silent echo
#

Try* functions return a success/failure.
The out should tip you off that it is where the data will go.
All try functions are like this even in base c# with for example float.TryParse

dim coral
#

Tbh I've not used the base Try* functions in c# before, so perhaps an oversight on my behalf

wispy latch
#

Something spooky happened with my player objects...

A value on one of the scripts (set to sync None) magically changed by itself. I know for sure it wasn't any other script or me doing it. The value was set to public, but I did not use it anywhere outside of that single script.

#

Is there any sort of explanation for this?

cursive barn
#

where is persistence stored locally?

abstract narwhal
#

It isn't.

silent echo
#

its not stored locally, unless your in clientsim

abstract narwhal
#

Persistent data is stored on VRC's servers.

wary summit
#

it's in the same folder as your logs

broken wadi
#

is it possible to use OnPlayerLeft() for updating PlayerData? Or is this too late?

visual cargo
#

The docs specifically state you cannot save persistent data with OnPlayerLeft.

light laurel
#

what would be a replacement way to save data when a player leaves, if possible?

#

for example, if you want to save the location of something, but rather than running a loop that updates every 5s, you save when the player leaves

elfin wren
#

You can't

light laurel
#

wow, that seems like something that should exist

hardy onyx
#

saving on leave sounds.. like a bad idea. Relevant checkpoints only

agile coral
#

As long as it isn't mission critical, a loop will work

light laurel
#

well yeah i know, but i feel like a way of saving before a player leaves would be cleaner and more efficient than making a loop to save that data

visual cargo
#

I'm no dev but I guess the problem is OnPlayerLeft fires after the player has already completely left the world, not existing anymore, so there's nothing for you to access and say "save this player's data".
Maybe if there was something similar to OnPreSerialization but for leaving, like PreOnPlayerLeft, but thinking about it I think a function like that could possibly be abused

true patio
#

if someone loses their internet connection, and it doesn't come back fast enough, there wouldn't be a way to send data over

broken wadi
broken wadi
#

Hmm... but why not VRChat implement booth options - direct persistence data saving if important and saving persistent data on player exit for not so important data (maybe with a new event befor PlayerLeft) to reduce network transmisson?

silent echo
#

You could base the method of saving on what you are saving.

Saving the players position? Wait for the player to stop moving and be in the same general spot for a number of seconds and then don't save again until the player leaves that area.

Saving Something that only changes after a game round ends? Then save only after the game round ends.

Contextualize your saving and you can reduce your potential network traffic depending on what accuracy you need.

Will a player notice if they respawn in the world .25 meters away from where they were standing previously? Unless your world has something where standing someplace specific down to the millimeter has purpose, they are unlikely to notice the slight discrepancy from geofencing your save check.

rocky frigate
# hardy onyx saving on leave sounds.. like a bad idea. Relevant checkpoints only

Why? It's far more efficient than having to guess when to save otherwise. There's obviously times when you know when it's a good time to save but otherwise having to save seemingly at random and potentially constantly when not needed isn't ideal.

The way JavaScript handles it is it fires as you leave, before you actually leave.

visual cargo
#

if you really want your data to "autosave" without having to have a script do it every X seconds, you could just use a PlayerObject with Continous sync to store the data instead. Any synced variables on a PlayerObject can be persistent, and they will always persistently save their last-synced network state.
In some tests I persistently saved some pickups, so with both ObjectSync and VRCEnablePersistence, it will always persistently save its last-synced position.

rocky frigate
visual cargo
#

anything? anything at all? there is no situation where continuous sync is ideal?

rocky frigate
#

It's the most expensive form of sync, so no. You use it when you have to, but you'd typically not want to unless you have to.

severe wing
#

I may not have been following the thread but... wouldn't one "save" the state when the state changes? There should be few reasons to save when exiting if the value was saved when the user "got the high score" or "set a user option".

visual cargo
#

that'll be the most common use of persistence and Player Data especially, should be values that are not often changing and should be saved as soon as they change. Perfect for doing manually.
Some have had ideas of things they want to be persistent that are constantly changing, though, for example the player's or a pickup's position. This value could easily be changing every frame, and that's too fast for manual sync to be ideal.
So in a perfect world, it would be nice to save these values not every frame, but rather right when the player leaves. The problem is trying to save persistent data while the player is leaving is not possible.
So you either A.) save this value every few seconds, or B.) Let UdonSynced variables do it for you. Other ways are described above, depending on what you're doing there is probably even more efficient ways to save values.
Continuous sync is explicitly described to be best used for frequently changing variables.
It also isn't any more "expensive" than manual. You just have far less memory to work with. You'd easily clog the network if you tried to manual sync something that was changing every frame.
They both have their use cases. It isn't good advice if you tell everyone (or tell yourself) that you should only be manually syncing everything.

severe wing
#

It would be nice to sync videos with everybody in the VR world watching exactly the same frame but it is not realistic. It would be nice if VRC could keep track of where your friends were at any given moment but even that is impractical. It takes time to save state so one balances what info would be lost against the time needed to update the state. If a value changes "on every frame" and such information is important then you are stuck either saving it (note you couldn't have done this before) or developing for another platform where frame-based changes can be persisted.

visual cargo
#

pretty much yeah
we gotta work with our limitations

dense owl
#

Don't let the search for "perfection" hinder your designs.

Players arn't gonna notice if they're half a centimeter behind where they left. If anything they may just think you're rounding the saved values for optimisation.

Same for video players, it is common for chat to have a 3-15sec delay when looking at livestreams, people online are used to having a small delay on such things. If a late-joiner gets updated to only be half a second behind that would already be an improvement compared to that.

Aside of idle games that fire real fast, or for achievements checking time played, it should be fine to just save on value changed.

#

Now if the check for the action to occurs needs continuous checks...is another story

#

Did anyone made tutorials or write a guide on good practice and workarounds for common situations people default to continuous checks?

#

(Guess this is beyond just persistance, but such ressource would be useful to many creators. The more we learn when there's better ways than always checking on "Update" the less we need to abuse continuous checks.)

severe wing
#

There can be a "significantly changed" check as well. If a bool toggles from true to false one would generally save it, if an int setting increments/decrements one can do the same, if a float goes from 1.001 to 1.002 there is probably little reason to bother to persist the value and it can wait until some threshold of change is met.

dense owl
#

Yeah, and even then a simpler stat affecting calculation can be saved sometimes. Especially for RPG maps, it's surprising how little "base stats" need to be saved and can be used for the calculations, instead of saving every each calculated scores

#

Abstractions also, players would get used to having a set spawn point in their house (saving the house ID, then set position according to index(houseID) rather than save their precise position) if it stays consistant with how other checkpoints works

lusty lion
#

Does anyone know how I can add the persistence on this script? This script just activate a gameobject and I want it to be saved with the player

light laurel
# lusty lion Does anyone know how I can add the persistence on this script? This script just ...

first of all, that get isLocal check doesn’t do anything. Interact is already called by the localplayer by default

second of all, think about persistence as saving your methods as different data types. like, you’re just toggling an object, so you could say that a bool is the data type you can save it as. or if it was a slider, you would use a float as the data type.

then, once you have your data type in mind, think about how you can apply that to whatever you’re doing with your script. in this case, you want to create a bool, on interact set that bool to the value you want to change it to, then apply that bool to your object’s active state.

and if you’re using a simple data type in your methods like that, then adding persistence becomes trivial. all you have to do to make it persistent is;

  1. create a β€œkey” variable to store the name of your value. keys are made with a string, and you need a unique name for each one in your world.
  2. after you apply your bool with the changed state, use PlayerData - SetBool to save the value. all you plug in is your key variable, and the bool variable
  3. you need to load any saved data the player might have when they join, and as soon as they load in fully. to do this; create the OnPlayerRestored event, and connect an β€œisLocal” check to the VRCPlayerApi output on it. then, set your bool value to be equal to the output of a PlayerData - GetBool node
#

oh, and to elaborate more about the key variable; if you are only going to use this program on one object, you can leave it as private (so, don’t check the β€œpublic” box), and give it a default value. the name can be anything, but for your sake, you should name it something related to what the key is storing. if you’re making a settings menu toggle program, so you’ll have this same graph on multiple objects, you’ll need to make the key variable public so you can give each object a different key name

#

and the name of the variable itself doesn’t matter, only what the string text is set to

thorny bough
#

Is it normal to just be flooded with this error

visual cargo
#

probably not!

#

I've never seen that before though. It's angry at one specific file, maybe temporarily move it out of that directory

visual cargo
thorny bough
#

Oh, silly notepad++

#

Well that didn't help very much, I've tried deleting it as well so it could re-create the file normally but that also does not work

#

ClientSim persistence is just cooked bro

visual cargo
#

it gives the same error even though the file no longer exists?

thorny bough
#

The file is created on Play but doesn't input anything I believe, it just has {} in it

thorny bough
#

Okay, how do I delete ClientSim persistence data?

#

Alright, deleting the entire folder worked

#

Deleting the individual files did not

light laurel
broken wadi
#

A question about the OnPlayerRestored() event: is the event always called for every player? Even if a player enters a persistent world with PlayerData for the first time in which it has no stored data yet?

silent echo
#

Yes, it is always called

visual cargo
#

highly doubt you'd be able to read someone else's persistent data directly from the servers, but they can certainly view their own memory and edit it

#

so what is your question exactly? just if this method would work?

#

you didn't actually ask one

visual cargo
#

I think there's technically a possibility. but they'd have to be pretty damn dedicated

#

if the persistent data isn't hashed, then you'll start using it on OnPlayerRestored; on that frame I assume you'll start hashing it. So theoretically they have 1 frame to read the data?

#

you can store a wide array of types with persistence, I think it will all come down to if you have enough memory to store it

#

I'm not sure the docs explicitly say the memory limit... I think it's 1MB? or maybe smaller. it's not very big

#

storing the hash as a string could get kinda big

#

I think it'd fit though

#

a SHA256 hash I think is what Unity uses, it'll be 64 bytes?

#

per hash yeah

#

I think there's enough room. Doing a persistent save "has a similar bandwidth cost as one UdonBehaviour with synchronization set to 'Manual.'"
which is 280,496 bytes

#

no wait bandwidth. 64690 bytes

#

I've never pushed the limits of it ever so not 100% if I'm reading the terminology right

#

oh wait it does list the limit, "Each world may store 100 kilobytes(KB) of PlayerData and 100 KB of PlayerObject data per player on VRChat's servers."

#

it will log an error if you attempt to save more than you're allowed to

#

so you should see clearly if it's too much

#

yep "per player" it says

#

yea plenty of room lol

#

I think limits are only gonna be reached if you're trying to store large arrays of large data types

#

ikr it's awesome stuff. I hope more people start using it

silent echo
#

persistence limit is ~100KB compressed

junior goblet
#

Has anyone had issues with player objects with persistence resetting for some people but not others?
I have a very simple instance time leaderboard that ticks up once a min for each person and it works great 90% of the time.

But for some people they load in and it just resets back to 0

fading totem
limber goblet
#

I'm also very interested in having a bit more cheat protection/prevention... Just that they probably can use any public exposed functions or variable at all times. I wouldn't be surprised if they could log that in realtime and just use the previous called functions (that requires these hashes) and start spamming them. We're also planning a huge world and it's soooo hard to come up with ideas to prevent any client users to abuse things like spawning synced item drops... We haven't found a solution yet... and I start to think that theres no good way, if we at all times, trust the user at the end

Also persistance will send all persistance related local player data out, once one of the local player keys has been set. While the 100kb max save limit seems plenty, you still want to keep the payload low with like 2-3kb/s so other objects syncs have some air as well

Woops I learned some things:

  • Public functions starting with _ can't be called over the Network
  • Not any public variable can be called/modified at any time- only synced ones could be potentially changed by remote. (for some reason I thought public variable = anyone can access and modify it, but that's luckily not the case)
  • When setting a variable via public function, use a _, so no one else on the network can call it via RPC to forcefully overwrite it (not even sure if that would be possible in the first place, if the function was never compiled in udon to do so)
dense owl
wispy latch
sturdy veldt
#

How many total bytes of data can be stored in a single player's save? I'm trying to come up with ways to set up saving for a procedurally generated grid, and I'm wondering if I could save a char array of vast lengths, like up to 2000?

visual cargo
sturdy veldt
#

2000 chars * 2 bytes per char = 4000 bytes which is 4 KB, right? That's awesome if so :D

visual cargo
#

I think that math checks out
though you'd have to save an array in a PlayerObject, since PlayerData doesn't take arrays

#

huh well actually PlayerData does allow byte arrays

sturdy veldt
#

I haven't really touched it yet but my current project I intend to include saving and I might have just worked it out in my head πŸ€”

visual cargo
#

hmm for a procedurally generated grid though, wouldn't all you'd need to save is the seed?

sturdy veldt
visual cargo
#

ooh I see that makes a lot more sense. Seems like the array thing will work in some capacity though, can't wait to see that
I still feel like not enough people have done things with persistence

sturdy veldt
#

I have no clue because I am not active enough in the world exploration space, but I'm happy to know this is considered a cool way to use persistence :3

silent echo
#

There is a lot to consider with persistent world changes. Consider this scenario.

Player a joins world 1. Player a digs a crater in their instance. They now have a crater in their persistence.

Player b joins world 2 and builds a castle. They now have a castle in their world.

Both players now join world 3, what happens to the world? So you get a crater or a castle? or a castle floating over a crater? Saving world changes gets complicated fast

dense owl
# sturdy veldt 2000 chars * 2 bytes per char = 4000 bytes which is 4 KB, right? That's awesome ...

Ah, I see you have grid questions as well.
I was thinking of making a grid-based system as well for an art world, (closer example I know is Prismic's VRcanvas world, the one where we invaded with a logo back when we were still in Argus) except instead it would be small player saved canvases that gets loaded in the world's gallery when they rejoin.

I was talking about it to Casual and BocuD the other day in DMs, asking how big the canvas could afford to be with similar color palette limitations.
Good to see actual numbers here tho. πŸ‘

dense owl
# visual cargo ooh I see that makes a lot more sense. Seems like the array thing will work in s...

If my compuer didn't die just before christmas I'd be experimenting more with persistance...That thing is finally back from repairs but I'm busy with the backlog of world updates that were due for december. xD

Also not being a programmer but just some graph user means I'm limited in what I can achieve...but ideas are here. I don't want to bloat the computer with too many projects at once tho, better focus on one idea at a time or else it's just gonna be a pile of unfinished prototypes.

#

The question will be silly but are there any way to search for specificallly persistant worlds? Or just "persistance" tag and let the luck decides if people forgot to add it (or worse, added it to clickbait in non-persistant worlds)

velvet barn
# dense owl Ah, I see you have grid questions as well. I was thinking of making a grid-based...

Funny enough someone suggested yesterday for me to make an alternate version of my world that allows you to save canvases using persistence, but I don't have any plans for that currently anyways, just coincidental πŸ™‚

There's 16 colors in the VRCanvas palette which is 4 bits each, so using a bit array and bit packing you could store 2 pixels per byte (instead of 2 bytes per pixel with a string). You could also alternatively extend the palette up to 256 colors per pixel and still use only a byte each (only reason VRCanvas is restricted to 16 is network limitations from remote web requests). With 256 colors and without compression though, a 256x256 pixel canvas would use 65KB worth of data. With 16 colors and bit packing, it would be 32KB per canvas. 2000 bytes would be around 44x44 pixels (62x62 if 16 colors).

dense owl
#

thanks for the answer ^^

#

Actually, a few years back I made this thing in Construct2. For context, my mother is eldery so using normal drawing softwares is too hard for her, so when her eyesight became too bad to make her usual craft I made that simplified grid with palette corresponding to out physical beads (since bead art is more forgiving than embroidery)

#

So I guess that's what I'd aim to recreate

#

...wait didn't someone made a bead art prefab last year? ._.

#

Anyway, can't be original on generic ideas, it's bound to happen... xD

dense owl
#

Pretty sure that if the frames are sync to the instance owner (but not saved obviously) you can have a gallery way larger than the number of players and just sync on join so the longer an instance lives the more art there is in it.

#

.
And yeah the networking limitations are a pain...I made a book system back then and it takes so long to load a single image book I gave up on making a webcomic-cafe world. Even if I get permission to link pictures from the artists people will just open a 2nd book while the 1st one load, then a 3rd one, not realising how much of a mess the queue becomes...

limber goblet
#

Still using 3.7.2-persistence-beta.1
Does anyone know of a bug, that's caused by trying to retreive the PlayerObject script after it the OnPlayerRestore has been fired (which means it should be ready to use?)
Regenerating the Network ID doesn't fix it- this playerobject has been added in runtime soo... just dunno if that has been fixed by now. Build works but editor doesn't .
All these console messages come from udon:

Found 1 possible matches for VRCPlayerObject in player objects for Kyrowo
Network ID 196 for VRCPlayerObject does not match desired ID 195
Failed to find component UdonBehaviour in player objects for Kyrowo

Edit: Updating to 3.7.5 fixed it.

limber goblet
#

Is it intentional that if you implement OnOwnershipRequest on that Persistance Player Object, a remote user can still get the ownership of it, even if I override it with return false (false or true doesn't seem to mattervrcRat )? It would have some use cases for me haha but would not be cool, if it'll be patched one day.
This also seems to be some sort of fake ownership, like the remote user has locally ownership, but it won't be synced for others.

public override bool OnOwnershipRequest(VRCPlayerApi requester, VRCPlayerApi newOwner)
   {
       return false;
   }
valid frost
#

OnPlayerRestored is only called if the gameobject the script is attached to is active at the moment of joining?

#

and therefore missable, even if the gameobject is turned active later on it will never get OnPlayerRestored called?

silent echo
visual cargo
valid frost
visual cargo
# valid frost I understand it wont work because it's an event. Thanks. On the other note, scri...

sorry I thought you were asking a question. And I was curious and did some testing, like you said stuff like Update and FixedUpdate only run when the object is active, Start doesn't fire if the object starts inactive, but does fire once for the first time it is active, OnEnable fires every time it's enabled (makes sense), but OnPlayerRestored does not get re-fired like Start does if the object isn't enabled at the time of the event being received. Theoretically, when a new player joins it should fire OnPlayerRestored again, but needs to be enabled in order to receive it
No wonder I've had inconsistent results with inactive objects, I often would have stuff not work at all if the object the script was on wasn't active. I made a habit of just putting the script on a parent empty GameObject, and just make the stuff I wanted to toggle children of that GameObject lol
You can also set variables while it is inactive! This is previously all I thought you could do

valid frost
# visual cargo sorry I thought you were asking a question. And I was curious and did some testi...

Yes, I was asking a question... more of a clarification of how "OnPlayerRestored" gets called since I didn't find a doc/fact that the script's gameObject needs to be active first. Your answer was good enough for me to move on, until the last bit made me question my existence. lmao. I had an existential crisis whether or not this is due to it because it's Udon or something else. So I had to test my scripts with inactive gameObjects, but are still working as normal since I know for a fact that MonoBehavior methods dont work if the gameobject inactive, but user-made methods still works regardless (except for code that rely on changes to variables within MonoBehavior methods which cause unwanted results if the gameObject is inactive).

dense owl
#

Somehow I wasn't aware of OnEnable, so it's like OnStart but that fire every time instead of just once?

visual cargo
#

yea it fires as an object is enabled, so every time it's toggled it'll fire again

dense owl
#

That can be really useful, I had lots of problems even with a manager to sync my node system, but that could at least give a temporary fix for the collective object's states.

#

That thing is the basics to aquire the items saved in persistant inventory so fixing it would really help in the long run. As a bandaid "fix" I made the whole thing local for now but...That kinda ruin the "first come first served" aspect of having collectibles that regrows in a multiplayer map

#

I guess that for the "disable" event I could fire it throught the "Interact" part, sending a networked event whenever someone claims the collectible

hearty cedar
#

Oh i fixed small bug in demo Leaderboard. There is chance Siblingindex can be too high. Add extra check to prevent crash will help it.

hearty cedar
#

Found out prefabs are not adjusted in scale. Might update it because some players who want try it will see weird look like here. (leader board scale was already scaled to 0.005 but slot prefab is still at 1) - just hint to developers.

#

And positioning issues too. Ugh, not great prefabs.

wary summit
# limber goblet Is it intentional that if you implement OnOwnershipRequest on that Persistance P...

As Ximmer mentioned, PlayerObjects are not designed to be transferred. They should be locked to the player that they're assigned to. Because of that, there is no intended reason to have OnOwnershipRequest on them, and using it may cause undefined behavior, like what you're experiencing with the fake ownership. Usually the fake ownership is something done on normal objects in order to make ownership transfers work faster (you assume you have ownership before you get the confirmation) but on PlayerObjects that'll just cause bugs because you're never supposed to transfer them. Based on what you described, it sounds like OnOwnershipTransfer makes it use a code path that gets around that restriction somehow. That is definitely something that might get patched and you should not rely on it, if that's what you're asking.

solemn violet
#

hey if you know about how this work im trying to learn this can you Message me i have question for you if its okay

wary summit
solemn violet
#

So im trying to learn how to set this up to save the players Data Causing im trying to setup a Leveling/xp system so im trying to learn how to do it

wary summit
#

Do you have any specific questions?

#

If not, I'd probably recommend reading the docs

solemn violet
#

Can you provide the link to it please

#

and the question is where do i start

wary summit
solemn violet
#

i have the Xp and level system made its just the saving the data

#

Soo when they Rejoin all the Playerstats are saved

wary summit
#

You're going to have to choose between PlayerObjects or PlayerData. There are some considerations but ultimately:

PlayerData is a simple, easy to use key-value database, like a dictionary. In the SDK's example central, UnlockItems uses PlayerData.

PlayerObjects are a more complex power-user feature, but they allow you to more directly control how you do networking and also gives you per-player objects, which can be useful for lots of things even outside of networking. In the SDK's example central, RPGExample uses PlayerObjects.

If your data is small (<1 KB) and/or doesn't need to be saved very often (>10 second interval) then PlayerData is the right choice.

If your data is larger and/or needs to be saved often, then PlayerObjects are the right choice. However that's not because PlayerObjects simply have higher limits, rather it's because you can split up your fast data and your large data into separate objects, so that they can each do their own separate job better.

solemn violet
#

Okay i understand that so im trying to learn the player data so the stats are saved in the database but im having trouble setting it up i dont know what things i need too look up

wary summit
#

On the page I linked, there is a subpage for playerdata which shows you the functions and events that you can use to work with playerdata

solemn violet
#

would it be the Simple RPG page i need to look into

#

since im doing leveling system?

wary summit
#

Eh, if that's valuable to you then sure. But it's not a guide, it's just an example. And given that you're interested in PlayerData, it might not be as useful because it primarily uses PlayerObjects. Though if you ever want to add features to your RPG system where players can see some of the systems on other players like a healthbar, PlayerObjects would be the best way to do that and the RPG example will show you how. But even if you use them for that, it doesn't mean you need to use PlayerObjects for persistence as well. You can still keep using PlayerData for saving your persistent data if that's the right choice for your data

solemn violet
#

so im going for a Fps kinda thing but its abit confusing to me

wary summit
#

yeah, PlayerObjects are a great platform for building hit detection systems, per-player guns, healthbars, and other visual things that you need to be able to see attached to other players, or associated with other players like leaderboard entries

#

Those kinds of things used to be handled by a player pool prefab, but PlayerObjects are a direct replacement that is better. PlayerObjects also have the ability to handle persistence data, but you don't have to use them for that. PlayerData is easier to use for simple, small persistent data

#

but ultimately it's gonna be hard for anybody to help you if you're not able to express why you're confused

#

If my answers are not making sense to you it's because I'm guessing what you're asking

limber goblet
#

Am I missing something here? This is the second (and non master) client.
Whenever I change / add the key for my any non master clients, it calls OnPlayerDataUpdated twice.

When they join we block everthing until OnPlayerRestored(VRCPlayerApi player) has been called for this player. After that, we use the OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos).

So
OnPlayerRestored -> player is ready -> PlayerData.SetBytes("P_V0.1", new byte[5000]); -> OnPlayerDataUpdated is getting called twice (but only, if for the remote player)

This causes some issues for me because I thought it would only be called once vrcRat Maybe it's just me and I goofed up but I'm trying to figure this out for hours now vrcTupCry Using 3.7.5 SDK
I'll try it with a new u# script tomorrow vrcRat

valid frost
#

So thats why 2 logs poped up from that.

broken wadi
#

Quick question, can I rely on the fact that in Event OnPlayerRestored() the instance master (player.isMaster) is already set even if a group of players enters a new instance at the same time?

limber goblet
limber goblet
#
  public bool hasRestored = false;

  public override void OnPlayerRestored(VRCPlayerApi player)
  {
      if(player.IsLocal)
      hasRestored = true;
  }


  public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
  {
      NimbusLogger.Log("OnPlayerDataUpdated for playerID: "+ player.playerId);

      if(!hasRestored)
      {
          NimbusLogger.Log("Not restored yet. Returning");
          return;
      }

      foreach (PlayerData.Info info in infos)
      {
          switch (info.State)
          {
              case PlayerData.State.Unchanged:
                  NimbusLogger.Log("key: " + info.Key + " Unchanged - " + player.playerId);
                  break;
              case PlayerData.State.Added:
                  NimbusLogger.Log("key: " + info.Key + " Added  - SKIP - " + player.playerId);
                  break;
              case PlayerData.State.Removed:
                  NimbusLogger.Log("key: " + info.Key + " Removed - " + player.playerId);
                  break;
              case PlayerData.State.Changed:
                  NimbusLogger.Log("key: " + info.Key + " Changed - " + player.playerId);
                  break;
              case PlayerData.State.Restored:
                  NimbusLogger.Log("key: " + info.Key + " RESTORED - SKIP - " + player.playerId);
                  break;
          }
      }
  }

  private void Update()
  {
      if(Input.GetKeyDown(KeyCode.L) && Networking.LocalPlayer.isLocal)
      {
          PlayerData.SetBool("MY_TEST_KEY", false);
      }
  }

valid frost
#

That is strange...

Is it ok to add a...

public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
if(player != Networking.LocalPlayer())
return;
....

at the beginning so that only one call can go through the rest of the code?

limber goblet
valid frost
#

I mean, it's not might not stop the 2x triggering issue, but a workaround for it to prevent a 2nd one to go through the rest of the code....

#

unless, you want only the owner to handle some special code during the trigger, then my suggestion wont be good.

limber goblet
valid frost
#

Sounds like a bug

limber goblet
sturdy veldt
#

Is the process of creating/instantiating a player object for a new player heavy for other players if the object is large and complex or consists of many objects, or does VRC have some sort of internal handling to make sure new player objects load in smoothly?

#

More just a curiosity question but I’m reading up on them now so I can get started with them soon

solemn violet
#

So im still new to this im trying to Save the data Buts its Having trouble can i get a Bit of support

#

im stuck on the part of trying to call Players but its breaking it for some reason

#

im trying to do the PlayerData to Save players Lvl/Xp but its not finding the player

limber goblet
solemn violet
#

Are you Free to let me show you my setup and you revise it me and my buddy cant figure it out

visual cargo
#

post your code where you're trying to save the data

solemn violet
#

okay so imma have to start it all over But the point of the code is to save players Level Rank And XP

visual cargo
#

idk how you came to that conclusion. saving playerdata can only be done by the local player, so you'll have to design around that

solemn violet
#

okay thank you

#

Would this Save Across World if player Rejoins?

visual cargo
#

as in, if the player was in a new instance of your world? yes

solemn violet
#

okay i have Code made ill show you

#

So its not saving atm which im trying to figure out right now

visual cargo
#

PlayerPrefs? Isn't it PlayerData?

wary summit
#

looks like PlayerPrefs is some abstracted thing built on top of playerdata? You're gonna have to show us the code for playerprefs as well since that's probably the root issue

#

I'm not sure what PlayerStatsMain is but it doesn't look like it has the same functions that you're calling on PlayerPrefs so I assume it's something different?

#

I see a few issues off the bat with PlayerStatsMain but it's hard to give any specific recommendations because I don't know what your intention is with it. In particular if you're putting that on a player object then my advice would be different than if it's standalone. But regardless of which way, the OnPlayerJoin function is definitely a problem

solemn violet
#

So this is the ranking system i made

#

to work with the UI i made

solemn violet
#

Okay now im at the point where the Persistence object is Disabling its self not allowing me to save data

solemn violet
solemn violet
#

Saving and loading Is the issue im having

solemn violet
#

got it working

naive gale
#

replying to this because it's the one of the few messages in the history remotely related to info I'm trying to find

#

what's the guideline for adding udonsynced fields to an existing persistent player object?

#

one of my player object became read-only after I added a field to it, the data can only be viewed but never updated, unless all persistent data of the entire world is reset through the UI (tested this on two accounts), this happened during normal development

#

keywords: "Serialization failed", "Parameter name: Received a null value when encoding a Boolean" Google.FlatBuffers8

#

I understand that if serialization fails then the server never receives it so it won't affect the saved data, but that doesn't help when the currently saved data being restored is itself not serializable somehow

visual cargo
#

null values can't be saved persistently, that's why it's failing

#

that bool would need to be at least initialized

dense owl
#

PlayerObject is such a pain to manage compared to PlayerData..

#

where exactly "enable persistance" have to be put?
It says "this object and its children" but don't seem to work on the root/parent object...

visual cargo
#

put it in the same place you added VRCPlayerObject

dense owl
#

that's what I did yes

#

hence my confusion

#

I placed both in the root object of the template

visual cargo
#

and then your scripts have UdonSynced variables on them?

dense owl
#

have I to place more in every children with a different UdonBehavior?

visual cargo
#

no

#

all child object's UdonBehaviour's Synced variables will be persistent

dense owl
#

My graphs do have Sync variables and i request serialisation when i modify them

#

or is "UdonSynced" something else specific?

#

for sure I'm missing something here

visual cargo
#

synced is right, UdonSynced is just how it's done in U#

#

you could open the ClientSim PlayerObject debugger and see if values are being written

dense owl
#

also do the root object have to have an UdonBehavior? or having some only on the chils makes it skip them?

visual cargo
#

doesn't have to

dense owl
#

ok at least that rules this out

#

do have the sync variables to be public?

visual cargo
#

no unless you expect another script to edit those variables

dense owl
#

nah, the "buttons" only send a custom event to tell it to act, but all changes are made internally to the object

#

found the problem

#

requestserialisation was just at the wrong spot (which worked for PlayerData but not PlayerObject somehow)

#

but eh that works now xD

visual cargo
#

that would do it

#

I usually add debug logs every time I'm requesting serialization and doing deserialization, at least until I know it's firing properly

dense owl
#

I barely understand networking at all, this world is the first one where I even bother with multiplayer... ^^"

#

but I start to notice patterns in why my stuff didn't work in the past

visual cargo
#

it takes a bit of experience for the networking stuff to "click"

#

I made two or three test projects before I fully understood how it worked

dense owl
#

also like discussed last time "OnEnable" instead of "OnStart" would help a lot... xD

#

so many things broke because, well of course it only triggers the first time

#

the hardest part sometimes is not knowing a node does exist, unless I find it by chance

#

or a friend with more experience mention it by chance "why you're not using X" moment

visual cargo
#

it all comes down to amount of experience lol

dense owl
#

yup xD

visual cargo
#

the VRC and Unity docs are a godsend to know what you can and can't do

#

Unity docs are written with C# in mind but the nodes are essentially the same

dense owl
#

that's really helpful when not redirecting to a missing page indeed

#

joke aside, yeah when you know what area to search at and go link to link it really helps understand the details

visual cargo
#

I usually just google search "[unity thing] unity docs" or "[VRChat thing] vrchat docs" and pretty much find what I'm looking for

naive gale
# visual cargo that bool would need to be at least initialized

thanks, this looks like that's what's happening, the [UdonSynced] public bool field is initialized to false, but it seems like whatever VRChat does right before OnPlayerRestored is invoked sets the program variable for that player object bool field to null, so it would fail to serialize it on further attempts

#

this is really confusing

visual cargo
#

Are you editing that variable on the original? Or the copies?

#

I don't think PlayerObjects will be safe to access until OnPlayerRestored fires

unique rune
#

it's documented that you shouldn't trust any of the persistence until after (or within) OnPlayerRestored

naive gale
#

nah I don't edit the variable on the template, only the copies, during or after OnPlayerRestored; I know it is set to false by default because false is the value that correctly syncs the first time a user joins the world if they had no prior data/if they reset their persistence data in the UI and rejoin the world, and it remains false when that new player joins the world again

naive gale
# naive gale what's the guideline for adding udonsynced fields to an existing persistent play...

this is the workaround/fix I chose to apply, it works now:

// Persisted
[UdonSynced] public short lastInitializedVersion;
[UdonSynced] public long firstTimeJoined;
[UdonSynced] public int countJoined;
[UdonSynced] public bool everInstanceOwner;
// ... etc.

public override void OnPlayerRestored(VRCPlayerApi player)
{
    if (player != Networking.GetOwner(gameObject)) return;
    
    // HACK: VRChat might restore the persisted program variables of U# primitives to null (currently unknown why,
    // but introducing new fields is likely to make this happen), even if the value contained within the program variables
    // of those U# primitives had previously been confirmed to be non-null.
    // This will cause a "Serialization failed" to occur. VRChat servers will never receive this data, causing the persistent
    // data to become read-only.
    // For this reason, re-initialize those fields to default values if the program variables backing those fields became null.
    if (GetProgramVariable(nameof(lastInitializedVersion)) == null) lastInitializedVersion = 0;
    if (GetProgramVariable(nameof(firstTimeJoined)) == null) firstTimeJoined = 0;
    if (GetProgramVariable(nameof(countJoined)) == null) countJoined = 0;
    if (GetProgramVariable(nameof(everInstanceOwner)) == null) everInstanceOwner = false;
    // ... etc.
    
    // ...
}

still unclear whether somehow getting null values restored into newly introduced synced fields is expected behaviour/what's the guideline for introducing new synced fields to persisted player objects

dense owl
#

Finally works and changes are shown for both players in real time. Thanks for earlier L.E.G.O.S. ayaka

visual cargo
# naive gale this is the workaround/fix I chose to apply, it works now: ```csharp // Persiste...

At the top, is that how you've declared those variables in your real script? Those variables are uninitialized and will evaluate to null
But the bool is false when they join for the first time....in your check for their persistent data, are you setting the bool to false if they have none?
You've got something working but I'm just curious, I occasionally have just the strangest nulls that seem to come out of nowhere

visual cargo
#

oh and you also don't need to GetProgramVariable in U#, you can just use the variable

wary summit
# naive gale what's the guideline for adding udonsynced fields to an existing persistent play...

Yeah in theory it should be just adding udonsynced fields and it's done. There's even metadata that is used to match up an old serialization to a new script to ensure compatibility (and this code has been in use for over a year when two people in the same instance are on different versions of the world). There seems to be some bugs with that system occasionally and it's definitely got room for improvement. Providing a default value rather than straight null would definitely be a good start, and I assume that's 90% of the issues, but it's hard to quantify so I'm not sure exactly what other issues might exist

dense owl
#

finally done for today, pixel-canvas works and can be saved/loaded

#

I just need to investigate "width*PlayerID" offset because PlayerID always increment even if a player keeps rejoining so...the PlayerObject can end up out of bounds 🀣

#

Also PlayerObjects despawn on player leaving (expected behavior) so I could use it and PlayerID behaviors to my advantage for spawning dummies with the last state where the player's canvas was...Maybe? To display work of everyone who joined the instance, not just active players. (that also means lots of dupes if a player spam-rejoin but meh..)

#

that's worries for another day, already 1am right now

#

good day/night everyone

dense owl
#

Dummy frame when player leaves, done. (at least the parent object for visual cue of where the canvas will be)
But PlayerObject is confusing to work with compared to PlayerData to retrieve the PlayerObject tile's states to transfer to the dummy's tiles...

With PlayerData I could just say "get this key from player X" and it's done.

Now getting the PlayerObject that's a manager so its variable is the array of Tile objects,
Which then run a FOR to get their "state_current" variable to update the dummy,
BUT on the correct player, not the template...
It's such a pain in the butt in graph, I'm probably missing something again but I don't seem to get it to work because I'm pretty sure it's currently trying to find the template instead of pointing to the correct player...or something stupid like that.

I'll be restarting the dummy updater from scratch, so no graph to show for now, but it's like working with a completely different set of tools even tho it's still persistance.

I don't really understand why they don't just follow the same standard, just one being "PlayerData" set of keys and the other "PlayerObjectData" set of keys. That would be much simpler to call them the same way, PlayerData was a huge welcome QoL on that regard.

#

sorry for the rant, I just have a hard time understanding this one xD

wary summit
#

if you want key-value data, then just use playerdata? Playerobjects are a different feature, of course it doesn't have the same interface

dense owl
#

I'm already using PlayerData for other things and was running out of it, hence the switch. Else I'd be only using PlayerData yeah. ^^"

wary summit
#

if that's the case then you can implement your own synced dictionary on a playerobject and then the interface is the same

solemn violet
#

So i have a question so im running into an issue with my Persistence Not letting my Xp And Level Save When i Take off the Player Object and Enable Persistence The System works but when its on the player object its not working

wary summit
solemn violet
#

Yea sure

solemn violet
wary summit
# solemn violet This the Code im Trying to use

I'm not certain exactly but here are a few things I notice:

  • Keep in mind that everyone in the instance will load and run code on everyone else's playerobjects, so if you have things you want to do only to your own playerobject you need to do if (Networking.IsOwner(gameObject)) in order to filter out so only the owner of it will run that code.
  • SetOwner does not work on PlayerObjects, their ownership is locked
  • Your LoadPlayerData function is just resetting back to defaults.. but only if all the values are already default? But even then, it shouldn't be necessary
solemn violet
#

okay thank you

#

im going too make those changes ill let you know if i have anymore issues

safe wagon
#

If you create a world with 2 persisted variables on a playerobject, and someone plays the world thereby creating the saved data for those 2 variables... and then you update the playerobject so that it has 2 new additional variables that didn't exist before.. how do you handle a player joining a world who already has the old data where there was 2 variables, and now suddenly the playerobject has 4?

I ask because I have a Text UI in the world that displays all the persisted data for a players playerobject.
The old version of the world had 2 booleans that were persisted. So they see 'true, true' in the UI.
But when I added 2 additional bools to be persisted, when players join the new upload, they only see 'true, true' instead of 'true, true, true, true'.

However, if they delete all their data, and rejoin the world, only THEN does the display show 'true, true, true, true'.

I'd expect that for the new variables, if they didn't have it before in their save, the Text UI would at least display 'true, true, false, false', a default value for the new variables. But instead it's acting like the new variables do not even exist.

Is there some kind of check and initilization we are supposed to do in OnPlayerDataRestored() for new variables added, to make sure they are ok for players with old data that didn't have the new variables?

quaint night
#

udon will write null, even for value type variables which is very wrong

safe wagon
#

🀦

#

What in the world, let me read thru the canny and see if it's whats happening for me...

#

Ok yeah, that seems like it might be the issue

#

Seems like the best option is to never ever add new keys/variables, and just go with 1 single json string that is persisted... then whenever you need new variables, you just add that to the json.

#

So basically, anyone who ever uses persistence, should only ever have 1 json string for playerdata persisted, and 1 json string for playerobject persisted... and never add anything else, except to the json string data itself

quaint night
#

helps for player data too because it lets you actually remove variables

safe wagon
#

I've done this before for my playerdata at the advice of bobystar, but now I see it is nessesary for playerobject's too. That's a shame... a LOT more boilerplate code that is more involved than it should be, especially for those who are newer to programming.. and also a hidden gotcha that isn't documented

quaint night
#

I mean at least it seems like a bug, dunno when it will be looked at though

safe wagon
#

Valve Time tm

#

The issue is that this is a world with creator economy, and the bug breaks the world, so how can we just have people wait around for a bug to maybe, perhaps, might get looked into someday

quaint night
#

yeahh

#

there's also the issue with network IDs being super fragile which can cause you to inadvertently nuke all of the persistent data in the world if you're not careful

safe wagon
#

Yeah that's why I never add extra undon behaviours to an object that already has one or change the hierarchy between PC/Quest builds

quaint night
#

I think you can work around it technically, hai showed an example when they posted about the issue initially. You can do GetProgramVariable on the variable and check if it's null, then reset it if it is

#

still annoying that it's an issue with how long persistence was worked on

safe wagon
#

Oh, does that also make it that you can save new data to the persisted variable again?

quaint night
#

I think so

#

it's probably failing to serialize when it saves because it's trying to save null into value type synced variables

safe wagon
#

Ok, so then I take it I can just do that in OnPlayerDataRestored(), since I already make sure I don't touch the variables until that has run

quaint night
#

yeah

safe wagon
#

Ok great, I'll try that

#

Thanks for the information

quaint night
#

πŸ‘

dense owl
limber goblet
#

byte[] my beloved

steep dragon
#

Is this the proper way to make persistent mirror or any type of gameobject toggle? I am asking because I have one issue with it which is that sometimes I have to click it twice to activate one of the states after loading to the world.

fading hawk
elfin wren
#

Could probably simplify it with try get bool too

safe wagon
#

The networking documentation claims the max data allowed per serialization is 64,690 bytes when performing manual sync. But the persistence documentation claims the max data that can be stored is 100,000 bytes. How can persistence load anything above the 64k limit of manual sync serialization if you have more than that saved since you are allowed 100k saved data?

Is that data just going to be clipped, will persistence fail, or does persistence just use more than one serialization to cover the whole size but therefor take much longer? Likewise, if it just takes longer because it uses more than one serialization, is it possible that you might read from saved data in a frame where only half the data was retrieved through serialization?

visual cargo
#

sounds like something fun to test

#

for your second question, this shouldn't be a possibility because you're meant to use OnPlayerRestored to wait until the data is ready to use right? So no matter how much it is, you're waiting for it to all be ready anyway

safe wagon
#

OnPlayerRestored is only useful for when the player first joins though, correct? It isn't called every single time there is new data available as far as I am aware.

visual cargo
#

yeah that's for the start, then OnPlayerDataUpdated would fire while data was changed while they were in the world

#

I assume this has the same guarantee of OnPlayerRestored that, this event won't fire until all of the data is ready? I mean, it can't provide the Playerdata.Info array without everything being ready, no?

safe wagon
#

Well OnPlayerDataUpdated makes sense for the last question when working with 100kb of playerdata, but for playerobjects that event doesn't exist, so the question still remains at least for playerobjects. But yeah, I guess it would need to be tested.

visual cargo
#

while the player is in the world, they aren't going to be loading persistently anymore, they're just using normal networking for PlayerObjects, so maybe OnDeserialization would be the equivalent

safe wagon
#

Yes, it seems to be the equivelent, which means PlayerObjects are likely limited to the same of manual serialization. Which is why 100kb limit for PlayerObjects doesn't make sense, seems like the limit should match and be lowered to 64kb.

visual cargo
#

I'm gonna try to test it

safe wagon
#

Alrighty, if you weren't I would have

#

Let me know what you find

visual cargo
#

you can too lol, no guarantee I'll get meaningful results

safe wagon
#

I will once I get to that point, at the moment my code is all PlayerData. I'm about to do PlayerObjects, which is why I am asking, so I can get my code right. Once I get further along in PlayerObjects I'll have a setup where I can test, but you may be quicker to get an idea

visual cargo
#

hm so far interesting find.

#

people have always asked what the limit for manual syncing is, since the documentation has 2 different values listed, 64690 bytes and 280,496 bytes

#

so the latter must be correct

quaint night
visual cargo
#

I'm starting to believe no one knows the limit

#

~280496 is too high, serialization fails. But I can send it when it is over 64,690

#

and can confirm this 100,064 does restore persistently successfully for a PlayerObject

#

oh I forgot persistent data is compressed though so it can probably go a lot higher

#

so I think it all depends on what you're saving persistently. I just have an array of longs; successfully sends 200,064 bytes in one serialization, on one behavior. This data compresses down to 42,456 bytes saved persistently

#

numbers are gonna crunch easily

#

so it seems the 280,496 limit is technically correct. sort of. I guess there's a handful of other data sent that isn't tied to your behavior; I sent exactly 280,480 bytes and it failed, but 280,064 succeeds

#

but this takes a whopping 35 seconds to arrive

#

which saves persistently successfully, compressing to 54,726 bytes

visual cargo
# safe wagon The networking documentation claims the max data allowed per serialization is 64...

so to conclude and answer your original questions, the limit for manual syncing is ~280,000 bytes, not 64kb. This already exceeds the persistence limit of 100k, but persistent data is compressed, so in the majority of cases you can save over 100kb and it will succeeded.
This data was an array of 35,000 longs so..... unless you're planning to save even MORE data than that, I don't think anyone should really be worried about hitting storage limits

safe wagon
#

Wow nicely done @visual cargo , that's pretty conclusive I'd say. Alright, so that info combined with what @quaint night pointed out, pretty much answers my questions. Thank you so much for the effort

elfin wren
#

Where VRCUrl cat_cry

hardy narwhal
#

not yet...

quick imp
#

So Im having a bit of an issue here, Im working with numerous persistent objects in my game world, the object that triggers is different in editor than the object in Game mode so if you look at the video it explains in greater depth, TLDR I have to incorrectly set the value to X to make it work for testing in the Client simulator and then set it to Y to make it work in game! Anyone got any ideas whats going on?

#

Im led to belive this is a bug or something :L

hardy narwhal
silent echo
quick imp
silent echo
#

it's in the networking namespace

quick imp
#

Oh I see, not sure how I would implement this into the graph though :L

silent echo
#

recommend a public transform variable to point to the referenceComponent
Put your playerobject into the transform you made

quick imp
#

I assume like this?

#

Hmm no that made an udon exception

#

Maybe target needs an input, though ive never used target as of now

visual cargo
#

you need to get the player obj first so you can put it in the target

twilit meteor
#

how do you get the owner of a player object?

quick imp
visual cargo
quick imp
#

This Gameobject[] just seems to be looping back around to the original thing that was causing issues, itl ask for an object ID right? well its not consistent between editor and in game

twilit meteor
#
 GameObject[] gameObject = Networking.GetPlayerObjects(Networking.LocalPlayer);
 foreach (GameObject go in gameObject)
 {
     if (go.name.Contains("FootStep"))
     {
         footStepsMaster = go.GetComponent<FootStepsMaster>();

         loaded = true;
     }
 }

IDK graph but this is an example of how you do it in U#

quick imp
#

Hmmm, will have to look into that, having another issue which I kinda thought wouldnt be a problem considering the objects are player persistent objects but basically I fire my own gun and it also makes other peoples's guns start shooting, the gun they use is also synced with the gun im using etc, like the variables are being shared and saved across one another, I thought the idea of persistent objects was supposed to do the opposite :L

#

Im using networked events in an attempt to make it so that when someone pulls the trigger on their gun bullets come out which works but it makes the bullets come out my gun too lol

twilit meteor
#

You need to check at the start of the events if the player is local

quick imp
quick imp
#

Hmm

twilit meteor
#

I just hate graph. I can't read this.

I would imagine the instance of the sendcustomnetwork event would need to be itself

#

Actually idk.

quick imp
#

Oooof

twilit meteor
#

This is the pattern I normally do w/ local and global stuff

quick imp
#

Im the opposite, never could get literate with code, graphs on the other hand :L

twilit meteor
#

There is always time to learn 🫑

quick imp
#

Gettin too old now :L Im trying to do all this stuff as a sort of bucket list really :L

dense owl
#

One day, I'll actually list my key names instead of going "ConfusingKeyName_WhateverUnusedNumber" then duplicating the base object god knows how many times...
Such a pain to track down now that I want to make an achievement tracker... 🀣

quick imp
#

Im just in a bit of a dilema here, so my persistent objects in my world while able to sync their position and whatnot dont sync any variables stored inside them, or at least they dont appear to, even basic debug data thats shown via a Text mesh pro such as its position and rotation data doesnt update when I see someones object being used and moved around, can persistent objects even have their values synced at all? It seems very limiting if you cant sync Udon data to other players if not!

visual cargo
#

you can

#

up to you to sync it

viral linden
quick imp
#

Dont know what any of that is, I just see synced on variables in the udon graph but it doesnt seem to do anything noticeable

visual cargo
#

can you show graph plus script in the inspector

quick imp
#

The graph is a bit massive to show on one screenshot but I can paste the assembly

#

1 moment

twilit meteor
quick imp
twilit meteor
#

The example for persistence shows you how to save the data

quick imp
#

I dont need to know that

quick imp
#

So this object is for a player held item that changes into the different items you use in the game using weapon/item ID's

#

you see it in local view but others dont see what weapon you're using

visual cargo
quick imp
#

Its quite extensive lol, is there something you need to see in particular?

#

This is the main one, it dictates what the item the player is using, I figured "Synced" had something to do with....... syncing but Im still unaware of what its supposed to do lol

light laurel
# quick imp This is the main one, it dictates what the item the player is using, I figured "...

simply marking a variable as Synced does it automatically do all the networking for you. you have to tell it who’s setting the variable (the owner of the object/script), tell it when to update the variable for all players, and tell it what to do for those other players when the variable is updated. in other words, set someone as the owner of the object, set your variable, request serialization, do what you want with the new value of that variable, in the OnDeserialization event, do the same thing you did with the new value of your variable.

visual cargo
#

and check in the Inspector where you've put this script, is it set to Continous or Manual sync?

quick imp
#

Its continuous

visual cargo
#

could you just like post the graph file itself

light laurel
#

if you use Request Serialization and OnDeserialization in your udon program, you should be using Manual Sync

quick imp
#

Ive never used serialisation or deserialisation yet

light laurel
#

well, once you do.

visual cargo
#

hmmm these variables are probably better suited to be manually synced

#

you're currently updating, I count 6 or more? synced variables every frame

quick imp
#

Im trying to understand this in laymens terms, is serialisation the act of syncing variables locally to be transmitted globally?

twilit meteor
#

Whenever you call PlayData sets, it sends ALL the data you have to everyone in the lobby. Atleast I think that's how it works

#

nvm this is totally diff then what I thought

quick imp
#

Im thinking, whenever a player changes their gun it could call a serialisation maybe to then send the data of the Item id, all the customisation option ID's etc

wary summit
# quick imp Im trying to understand this in laymens terms, is serialisation the act of synci...

RequestSerialization means "I am the owner and I am ready to send these variables off" and then OnDeserialization means "I have received the packet of data and synced variables are now the latest state".

It's important to understand that the code is running independently on different computers, and the networking is the only thing that communicates between the different clients. All of the code you write in udon is just the instructions for how their client is going to handle inputs and outputs. Networking is the glue that allows different clients to communicate in order for them all to be painting the same picture.

There's a lot of ways to do networking, but the tools provided in VRChat's SDK for networking are best suited to making state-based networking. It's easy to fall into some other pattern that technically works but is sub-optimal because the underlying networking is not built to serve that paradigm well. Certain optimizations on the server side like caching for late joiners only really works if you're writing a state-based system, and those savings can have a big impact.

As a result, when people (like me) say that state-based networking is the "best" thing to do, that's not because other strategies don't exist or are "wrong", it's just because if you follow state-based networking concepts, it will be the path of least resistance as you develop stuff.

So with all that said, here's the basic concept of state-based networking that I would recommend you follow:

  • Before you even write code, think about how to express the feature as state or degrees of freedom.
    • For example you're making a door. A door is either open or closed, and that is a boolean. Or, if the feature calls for it, the door might be represented as a float so it can rest in any position.
    • Or you need everyone to know who is signed up for the next round, so if a single person is controlling that list then it's an int array. If everybody needs to be able to write their own independently, they each get their own playerobject or playerdata entry with a synced bool.
  • When you do write the code, separate it into two parts:
    • The part of code that works with the data and operates the state. (take ownership, set variables, requestserialization)
    • The part of code that applies the state to the world (ondeserialization, onvariablechanged, onplayerdataupdated, set gameobject active based on synced bool, set color based on float, create strings that display text in the world)
quick imp
#

I have an idea on how to implement it but we shall see if it works :3

wary summit
# quick imp I think I sorta understand, Just need to learn how to correctly use request and ...

Here's a video with some specific U# examples and more theory about why to do state-based networking https://youtu.be/mNYt11OPXhI?si=A3vkstFNrk95rd2U

TLX

In this TLX Spring 2021 session, FairlySadPanda explains how to use the UNU update to make networked games and content that doesn't suck or break.

Check out other talks from the TLX Spring 2021 event at https://www.youtube.com/playlist?list=PLTgqlzYxsEMxiUvVaqBcD5OL9MpdmkiSH

Learn more about Prefabs TLX at https://tlx.dev

Follow TLX on Twitte...

β–Ά Play video
quick imp
#

Nice, thanks!

quick imp
#

So I set this up, whenever the local player changes their weapon it requests a serialisation, then proceeds to further tell everyone that a new weapon is being displayed, would this be a correct way to do this?

visual cargo
#

dont SendCustomNetworkEvent the RequestSerialization, you only want the Owner to run that (they're the only one that can)

quick imp
#

Oh i see ok

visual cargo
#

and do whatever needs to be done with the variables on OnDeserialization; and I think you want to call that second event, the item one, OnPostSerialization if it and only if serilization was successful? I'm still a bit sloppy with network stuff

#

remember that the sender doesn't run OnDeserialization. so any setting you do with the variable still needs to be done, but also have things getting done on OnDesrialization (so it runs for everyone)

#

oh and it doesn't need to be a CustomNetworkEvent, just a regular CustomEvent

#

the event itself may arrive before the variables (the serialization) does, so things won't get set right