#world-persistence

1 messages · Page 5 of 1

quick imp
#

Woahhhh!!!

#

Thats crazy man

unique rune
#

and 8 times as many bits!

visual cargo
#

confusion might be coming from the fact that PlayerObjects can store whatever, since they just save the network state

quick imp
#

Oh the issue I had was me just assigning 60 addreses when it needed 61 :3

visual cargo
#

that would do it

whole plinth
#

Would it be hypothetically possible to save your data for one world and use it in another

visual cargo
#

no

whole plinth
#

Noted

fading totem
lone leaf
fading totem
lone leaf
#

what other approach do you suggest

#

you could certainly use textures and videos as well but i would group those under the same exact solution

fading totem
lone leaf
#

youre right i shouldnt assume they want it to be fully integrated

#

i never tried to imply it was the only solution i simply provided one as nobody else had bothered to do it

#

saving to an avatar is a good solution and having the user store it themselves is fine if the data isnt that important, i wouldnt consider using any other option

dense owl
#

password-based saves are meh...people always miscopy or lose those xD

stable slate
#

if anyone makes a prefab for a local object toggle with persistence I would love to know

dense owl
#

what do you mean? That sounds basic for PlayerData

stable slate
#

Ive bearly messed with anything in the udon behaviours and even the basic stuff is very confusing to me. Ive been using this that i followed a step by step tutorial to make but would like to see if theres a way to either turn it persistent or if anyone has a prefab for an alternative version

dense owl
#

that sounds overly complicated for a local toggle...Do you modify a lot of objects with a single button instead of having them as child of another one?

#

(I can be free in, about 10min if you want to work on that)

#

(still need to finish something right now)

#

but yeah it's easely doable to make it persistant

stable slate
#

I have a second one I also used, I just didn't know which was better
Take as long as you need, i dont mind at all since i have plenty of other things to do atm

dense owl
#

Yeah second one is more direct

#

Let see how new you are to this, do you understand the difference between the two versions you used? (I mean, difference in what they do, not just "there's that node here that isn't there")

#

(guess i'll have to open Unity if I wanna give examples later.. xD)

stable slate
#

I think i understand the end result of what it does. One toggles multiple at once while the other toggles one. Both when toggling turn off everything parented under it aswell. As for the individual nodes i have a very rough understanding solely based on their names alone. Not the greatest

dense owl
#

nope

#

both do the same

stable slate
#

Rip me then lol

dense owl
#

it's just that n°1 is an indirect call (so you can wire a string to call other custom events to Send node) and n°2 is what you'd call hard-coded aka this intecact can do this and only this

stable slate
#

Yeah id want to use the second one then

dense owl
#

usually the sender/button and manager are separate graphs

#

yeah unless you don't care and just hard code the buttons xD

#

is that big list of gameobject the lights, by any chance?

#

(just trying to figure out what you're doing)

stable slate
#

Its not, i only had those out while i was editing an area, going to delete them after. Im wanting to make a toggle that turns off a water object have and persistantly keep it off as it cuases some people performance issues

dense owl
#

Also for persistance you'll probably need a variation of the first method, not the second, since loading the data should activate the effect like an indirect call

stable slate
#

Alrighty

dense owl
#

wait a single object? not multiple ones?
I mean an array means you can add more to it later if needed I guess...

#

ok let not overengeener this xD

#

So, first of all the persistant part

stable slate
#

I also wouldnt mind taking the time to do multiple, would be helpful to know

dense owl
#

your version is for multiple since an array is a list of objects

#

instead of just listing one

#

so i guess it's future proof

#

For now the persistant part should be
Interact -> Playerdata "Set bool"

#

then Playerdata "Get bool" -> unary negation -> wire into "Set Bool" value

#

(same principe as what you're currently using, but we'll be changing the value that is checked instead of the objects directly)

#

PlayerData can be a bit of a mess to search, try "Pers" you'll see "SDK3PersistencePlayerData" category

#

I'll wait to see what you do before sending what the graph look like. Take your time to experiment yourself

#

@stable slate you still there? ^^"

stable slate
#

Still there, messing with it. Im a hint slower at things like this but ill get there, apologies

#

I know this is wrong but its what I could manage and from what i could understand

#

couldnt get interact to connect to the get bool any

dense owl
#

interact go to Set bool

#

you're almost there

#

then you connect the unarynegation to SetBool's value

stable slate
#

Like this?

dense owl
#

nah, it's still in the PlayerData category like the GetBool

stable slate
#

ohhhhh

dense owl
#

I forgot to mention it, when you're too used to the system it's easy to overlook it as being common sense xD

stable slate
#

I get that alot youre fine

visual cargo
#

this was supposed to be a local toggle, right?

stable slate
#

Yes

dense owl
#

yes but we were not at the part to chance networkedevent to a local one yet, we're arriving to it

visual cargo
#

there's no reason to do a network event, or serialization, then

dense owl
#

guy seems new to it so I'm going step by step

dense owl
visual cargo
#

no, it's separate, it'll be saved as soon as you SetBool

dense owl
#

oh, neat to know...guess i have ton of unnecessary requests in my graphs then...

#

ok so like this then

#

@stable slate that will be the new call, separate from the action, you'll see why as we continue.

#

For the world to auto call the action, you need "OnPlayerRestored" -> SendCustomEvent

Which is a node that means "when player data have finished being initialised and is ready to use"
Don't use "OnStart" because the data may not be ready before OnPlayerRestored.

#

Make sure to check about local player so it doesn't trigger each time someone else joins ^^"

stable slate
#

attempting lol

dense owl
#

take your time to find the nodes ^^

stable slate
#

Like this?

dense owl
#

you don't nees the "send" to be networked, since it's only for a local call

#

look at the pic I sent just earlier

stable slate
#

gotcha

dense owl
#

then for now you need a key name, aka the variable's unique name, so the game knows what to call/save

#

be sure it matches in Get and Set

stable slate
#

Ill set them both as cupcake for now

dense owl
#

you can put any name just make it something easy to remember

stable slate
#

done

dense owl
#

that one should be easy to find

#

search "restored"

stable slate
#

want me to also add the getislocal and branch in that image aswell?

dense owl
#

yeah like that it only triggers for the local player, and not when the game "restore" anyone else joining

#

that's be redudant calls

stable slate
dense owl
#

ok, that part is done

#

now let rewrite the custom event

#

since the unary negation is done in the interact event, it is not needed in the custom event

#

instead, the SetActive value will be your new key's value

#

So you copy your GetBool and wire it there

#

the rest of the FOR loop stay the same

#

(except you can remove the request serialisation)

stable slate
#

alrighty, attempting

dense owl
#

Don't worry it's simpler than how I worded it xD

stable slate
#

Im processing it slowly but surely
Uncertain what to do with get active self

dense owl
#

almost, you forgot to keep track of the local player

#

fell into the trap <w<

stable slate
#

fudge

dense owl
#

and getactiveself isn't needed (but I guess you'd realise it afterward since it isn't wired to anything anymore)

#

The only problem with current graph is that "cupcake" is false by default. So players will have your object disable by default and need to activate it themselves. Which is good for those who have performance issue but maybe not for everyone else

#

Is it ok with you?

visual cargo
#

you could actually use that default value to your advantage, if the intended effect is to have it disabled by default anyway

dense owl
#

yup

stable slate
#

I would like it the other way around. have the water on when you first load in and have the option to turn it off

dense owl
#

@visual cargo for that we just need the SetActive to be the invert of the bool's value, right?

stable slate
#

sounds right to me (dont trust my word for it lol)

dense owl
#

Since you don't "set" your actual value doesn't change here, it just reads as "do the invert as the variable's state"

stable slate
#

Ahhh ok

dense owl
#

that's the thing with playerdata's default state that can't be set in editor, need a lil workaround but it's not too bad

#

setting the state during OnStart "could" work, but you risk to overwrite your data

#

So, for now your thing should be working

#

but we can push it one step further if you want

stable slate
#

So what exactly is the difference that the one step further does?

#

reread I *think i get what you mean, im willing to if youre willing to

dense owl
#

making the graph reusable for different keys, without having to write a new one for each button

stable slate
#

Ah yeah, definitely a good idea

dense owl
#

it's usually a good thing ye xD

#

ok so you'll create a normal variable on the top-left

#

a String

#

and make it public

#

Then you wire it to the "key" on the nodes

#

Now your key name is this "string" you can set in the inspector

#

so you can set a different name per button, to be saved separately

#

for different settings to be saved as the player see fit

visual cargo
dense owl
visual cargo
#

if you want to default it as on, you can use TryGetBool to check if the key exists; if it doesn't, then enable it to true

dense owl
#

oh, so that's what the "try" are for? ._.

#

@stable slate see, everyone is learning a thing xD

stable slate
#

Always and forever

dense owl
#

did we just got hit by an ad...?

stable slate
#

I guess so

#

And struggling to find the string you are wanting me to add

dense owl
#

oh...a bot then?

dense owl
stable slate
#

So string > normalize???

visual cargo
#

you're searching for nodes, not a variable

#

hit the little plus button in the Variables section in the top left

stable slate
#

Ohhh ty

#

Like this?

dense owl
#

yup

stable slate
#

🎉

dense owl
#

now your graph should look something like that

#

you can define the objects and key name in inspector (on the right side)

#

be sure to use names that make sense to understand what does what when you'll look at the graphs again in 5 years... xD

#

thanks

stable slate
#

Looks like its working, and thank you for taking the time to walkthrough it like that, that and for the patience, lets me really soak in what im seeing

dense owl
#

one last question before you leave, now that you understand a bit better

#

you see why we usually separate the "interact" part and the rest?

#

you can have a generic graph just calling the key name and event, that's just "button" graph.
Then whatever effect is on the manager graphs

#

there's also an actual button UI element in Unity but it's a bit confusing to use for a newbie

stable slate
#

Vaguely yeah, definately a lot more than before, very important

dense owl
#

For now this method will work for you

stable slate
#

yeah im sticking to my weird ass clickable cubes for now lol

dense owl
#

can't blame you for that, been sticking to weirdass clickable cubes for 5 years... xD

stable slate
#

xD

dense owl
#

all right if it's all let not flood the channel more, have fun making new graphs with that knowledge ^^

#

After all you just need to change the custom event's part for other effects to happen

mental torrent
#

I feel like I don't understand the basic flow of playerObjects.
I have a player object that has the persistence component. In postLateUpdate, I'm getting the players location and saving to a synced vec3, then requesting serialization. According to the docs, I should be able to use OnPlayerRestored, check for local, and then do my functions with the data.
Instead of teleporting to the location that should be loaded, I'm not moving at all.

If I instead do it with OnDeserialization it works. Are the docs not right, or is OnPlayerRestored just mean to be used to flag that the player data has been loaded in the first place?

visual cargo
#

OnPlayerRestored only runs the first time that a player's persistent data is ready

fading totem
visual cargo
#

also requesting serialization every single frame probably isn't a good idea

#

may just want to have that continuous sync instead

viral linden
#

players location? for what? why not make remote attach to your player location, no network needed

visual cargo
#

sounds like to save the player's position and restore it later

#

and putting that functionality into a PlayerObject requires you to network the data

viral linden
#

oh

visual cargo
#

this might be easier to do with PlayerData honestly

viral linden
#

was the value overwritten by accident in postlateupdate? before OnPlayerRestored?

fading totem
#

No??? They were just asking about the purpose of the OnPlayerRestored event.

fading totem
#

You'll be synchronizing the entire thing constantly.

#

A player object is the sane way to approach this.

mental torrent
#

yeah, that's why I moved it

#

I didn't know writing the data before load stopped the load data from applying, that helps

visual cargo
#

......and a PlayerObject isn't being synced constantly?

#

you don't need to sync every frame. You can easily have an autosaving interval

#

and this component should be duplicated for every single player?

#

and every single object should be running a sync every single frame?

mental torrent
#

If you have any other big data in the playerData, that also gets network synced to everyone

#

So doing that at like 10hz would eat all the bandwidth

#

I'd love to just sync the position on playerLeave, but I don't think that's reliable?

visual cargo
#

sadly that is a specific time that does not work

mental torrent
#

Kind of frustrating, because that's exactly when I'd love to save data

visual cargo
#

just set the PlayerObject to have Continuous sync and you won't really need to worry too much about it

fading totem
#

If you have multiple systems all using Player Data it will very quickly get expensive on bandwidth to update it regularly.
PlayerData should usually not be used by prefabs, and definitely not for data that changes often.

fading totem
visual cargo
#

and are those instances going to be running a check in Update to find a player's position?

#

this all depends on how fast you want to save this information

#

I've got a world that syncs the data every 5 seconds with zero issues

#

if you need it multiple times a second, then a PlayerObject makes more sense

#

my main issue here is the skull react and implying that my suggestion is insane? Was that really necessary? There's clearly a use case for PlayerData here that makes sense. An implementation can always change significantly depending on the desired outcome

mental torrent
#

If it was only the player position, then I'd agree that I could probably get away with it. I also have like, multiple race courses worth of record data that's in playerData, which I'm at fault for not mentioning, that makes the playerData direction kinda really bad

fading totem
visual cargo
#

you didn't say that, this is how VR Floorsign described how their current code is running

#

In postLateUpdate, I'm getting the players location and saving to a synced vec3, then requesting serialization

fading totem
#

Exactly! I didn't say that! :D

fading totem
viral linden
#

There really is no need for persistence player position to be anything faster than once every a few seconds
No game is that critical that you lost a few seconds of progress is end of the world

#

You can even have one respawn point every room, and only save which room you are in, so you dont even need to sync more than once

fading totem
#

Correct! I don't think this should be saved more often than once per a few seconds at most!

#

And I still think updating player data at that rate is ✨not good

viral linden
#

yeah serialization wont even handle 15 20 Hz, it certainly cant serialize every frame

visual cargo
# fading totem I skull reacted you suggestion to use PlayerData for persisting values that upda...

can I kindly suggest that you stop doing that then? At least towards me.
It's obvious that certain methods of achieving something won't work under certain conditions, but work perfectly fine under others. As you're even just saying here.
Drilling down and suggesting multiple methods in order to achieve a particular goal is inherently going to produce suggestions that end up not working. To just react with a blanket-statement of denial at a suggestion is just rude and unhelpful.

fading totem
#

Noted! Sorry that I made you upset. I will avoid using silly emoji reactions and instead explain my thoughts like a normal person.

viral linden
#

but if one really need absolute latest data saved, continuous will be the least impact mode because it is bandwidth managed automatically

#

playerdata is just like manual request serialization, so it will be impacted by how much data is in your playerdata

fading totem
viral linden
#

not necessarily lower, just automatically managed and can't throttle and queue up and lag everything else like manual if you sync too much or too often

#

or rather it is throttled by itself

#

so it will be latest possible, whereas manual you can tune your frequency all day to try to get close to the bandwidth limit and not affect gameplay and try to get more frequency but you are going to taking up bandwidth if you try to go fast... it can only be as fast as the bandwidth can handle, and continuous only sends as fast as the bandwidth can handle

#

for transient data like player position continuous is more suitable because it can drop packet if needed and interpolate... and that is still true for persistence (not the interpolate part but you dont want to lag the game just because you want to save the last possible position for the next game)

#

so if you want latest, continuous is practically latest possible

#

whether you need to or not is another matter, but if you want latest, that is the theory

mental torrent
#

Might change to manual on a two-three second timer

fading totem
fading totem
viral linden
#

you can sync with manual faster and use more bandwidth yes, but if you want low bandwidth, continuous is the best low bandwidth it can get because it only takes up as much bandwidth as it needs

fading totem
#

So you admit that continuous sync is not the "latest possible" option?

viral linden
#

we are talking low bandwidth situation because you asked about lower bandwidth, with the same bandwidth, continuous is better at handling latest than manual (at a certain frequency) because continuous is throttled automatically to be the best frequency it can get

#

you can use more bandwidth to go fast, at the cost of more bandwidth, but for as low as possible bandwidth but still giving latest data, continuous is practically designed to do that

#

if you are going to request serializations at 15 Hz for the latest possible option and lag the game then by all means do what you want, but for non-impacting bandwidth, just let continuous does its job

#

that is practically latest possible

fading totem
viral linden
#

i dont, ask a dev

fading totem
#

Lmao

viral linden
#

that is what i was told or what i can understand

#

also playing with manual serializations, it doesnt have any magic power to send more data at the same frequency than continuous, it is only how they are managed by vrchat

#

you can go high frequency and get throttled and get delayed and actually make everybody use more of their bandwidth too

#

continuous is just a mode for syncing things that needs to be continuous and it is optimized to do that job

#

at least in theory

#

so if it isn't, it is the devs fault

lyric kraken
#

because i don't usually use continuous i was a little curious to measure it; at the same data size (a long in this case) continuous has more overhead than manual at the same send rate. it can step back on the send rate to some extent from what i'm seeing, but doesn't prevent clogging

mental torrent
#

Oh neat, like a %6~ improvement

lyric kraken
#

in reality if your sends are sparse it's not really good to use continuous (it sends continuously as the name suggests); if they're continuous i think that's up to the user (i don't like continuous personally)

mental torrent
#

I find I have to bang my head into the same problem a few times to understand the reasoning behind why I'm doing certain things to solve it. The latest things has been "Do I really need continuous on this?", looking at prefabs I've pulled in like six months ago

fading totem
#

Afaik Continuous sync is mainly designed for use on pickups / objects with rigidbodies.
For example if there's a sleeping rigidbody it will pause syncing entirely.
It also has special handling for pickups. Continuous sync will match the sync rate to the player's sync rate when held.
This is to better align the pickup with the player's hands.
Desktop players and VR players have different sync rates, so it will behave differently when picked up by a VR player vs a Desktop player.

viral linden
#

For high frequency updates, the sync rate (or the interval or the Hz) is dynamic and adaptable and managed by vrchat on continuous mode, but is static on manual mode (and optionally managed by you in Udon which is not practicable)...
So by design continuous should behave better for such purpose for most people for most situations, that is a "feature". If it actually performs worse than manual generally for high frequency updates, that is a "bug".
So I suspect it is only sending more stuff than manual under ideal and highly controlled test conditions, whereas on the road, it should cause less delay and use less bandwidth overall because it is supposed to be adaptable as a feature.
@lyric kraken Just curious how was your test setup, was the continuous interval constant during the entire period (I doubt it because network condition is never constant)? And how did you set the manual to match the interval at 4.26 Hz (auto or by hand)? I suspect the difference may be due to variations in conditions (or vrchat hiccups with its background network stuff)

#

And at another level, I feel we should leave network balancing to vrchat, if we are manually doing it, we are essentially replicating the same function, and 1. it may throw off or conflict with vrchat's balancing and 2. if vrchat updates in the future and have better balancing, the manual balancing might need to be retuned therefore not future proof
We can make the design decisions to have less updates needed and choose manual update every few seconds, and manage network usage this way, rather than actually doing balancing by hand, which I feel can be too unpredictable because vrchat networking is too much of a black box to be engineerable

dense owl
# mental torrent I feel like I don't understand the basic flow of playerObjects. I have a player ...

Considering the discussion seems to be sidetracked, a lil recap about the actual question:

  • OnPlayerRestored
    ("the data has been loaded and is now safe to use" aka load after joining,
    basically persistence's "on start" equivalent, trigger once)
  • OnPlayerDataUpdated
    ("do something IF a change in data have been detected"
    useful to save "when a value changed" rather than every frame, and do checks without a loop)

Looks like your system could use a slow loop (custom event calling itself every X seconds) that you initialise with OnPlayerRestored to avoid overriding the data early.
For the part saving the Vector3
(don't need a DataUpdated here, just wanted to include it earlier if you're not aware of it)

Then you add a "teleport player to [Vector3] position" to OnPlayerRestored to TP them on join.
But don't overthink it, you don't need to save every frame for a custom respawn.

Addditional notes:
||You could also add the same teleport to "on respawn" if you want a dynamic respawn point I guess? (but you need a check if you exclude situations you may be stuck in a falling loop then. checking if the player is grounded is the simplest one even if not foolproof)

You could however have a bool (a normal one, unsaved) to define if the system is on pause, for areas the players can't save in. No idea if you're making a social or adventure world, but it have its uses (like not saving in a private/VIP room, or in a puzzle room that may accidentally softlock).||

The fact it worked during "OnDeserialisation" is unrelated to being saved. It's simply because you made it read the data after changing the value. Could be a custom event, would have worked as well.

#

(you guys can go back to the ted talk about continous sync, sorry for interrupting. xD)

viral linden
#

I guess it is a matter of use case, and some use cases are what continuous was meant for... transient and or interpolatable states, that needs to be latest possible, but flexible and responsive to variations in network conditions, then continuous should be the choice.
If something is high frequency but needs to be distinct and precise and not flexible to network conditions e.g. real time stock market values, then I can see the merits of high frequency manual sync, because the latest data is important enough and rate of data is constant enough to justify being sent constantly at a certain high frequency.

#

Thank you for coming to my ted talk, please clap

lyric kraken
# viral linden For high frequency updates, the sync rate (or the interval or the Hz) is dynamic...

i'd urge you to measure it yourself if you think averaging isn't sound; for reference this was done with a synced long (int64). the difference should be apparent just from the fact that the total bytes have a gap for ~the same number of sends

the interval is now minus then, with "now" being the current time at the point of send and "then" being the last send time, the middle 3 stats are provided by the new network stats when passed a gameobject, the count is incremented post serialize

at minimum, one can work out the average bytes used per send without relying on the measured interval at all. divide the total bytes by the send count. that's the average byte cost per send. multiply that by some rate if you want the bandwidth use for a given rate

i would agree if someone said using continuous is easier to get something running or for someone who doesn't want to manage their own request serialize calls, but it's kind of irresponsible in terms of bandwidth for something that doesn't require constant updates or use vrc's object sync

saying that continuous is "latest possible" isn't true and i'm not sure how else to put it. continuous does have some level of flexibility for the rate from what i've seen, but it doesn't step back enough to stop clogging if that's the concern

continuous can't use arrays, is limited to 200 bytes and blasts data continuously outside of object sync. i don't think the overhead shown is even much of a factor against continuous, it may vary for different data types, but it using data constantly is the important thing to consider

continuous is okay if you're doing something it's good at and don't need any manual control. i personally don't use continuous very much

viral linden
#

btw OnPostSerialization fires for continuous sync?

viral linden
#

huh, it does, i thought it is manual only

subtle quest
#

I'm pretty sure it fires all those events including OnPreSerialization and OnPostSerialization

thin plume
#

What kind of rate limits are in place for save frequency of persistent data?

fading totem
left mesa
#

If I ever do any persistent stuff I really hope it’s just bools. Reading this channel has me slightly worried.

dense owl
fading totem
fading totem
left mesa
#

Oh, nice
I want to use the bools to store whether or not somebody’s completed an achievement

#

So hopefully won’t be having too many of them

light laurel
#

it’s really easy to save persistence data for basic variable types like bool, int, float, etc.

it’s practically just save after changing the value, load the value in OnPlayerRestored

dense owl
# light laurel it’s really easy to save persistence data for basic variable types like bool, in...

Yeah, depends on what you do with them.
I remember it was a hassle to make the "upgrade tree" to building a house because things don't just get turned on, but some stuff also turned off (to be replaced) so to be sure everything was correct I had to basically run the effects in sequence instead of just highest number.

A whole wacky set of custom events ("load_0", "load_1" "load_N") linked past the inventory values changes of the corresponding action event ("upgrade_0", "upgrade_1" "upgrade_N")

So when you rejoin you'd basically see your building be speed-built for free instead of just appearing complete. Which not gonna lie is accidental but would look cool if it wasn't happening behind your back... xD

copper girder
dense owl
shell marsh
#

I just begun to use persistence and have a question. That graph here works so far...you become owner of a coin, it get's deactivated by bool, that is then shared. Also coin as value is grabbed, 1 is added and it's set to it and then it does 'pling'!

It works...but does it mean if there was no coin data before in playerdata, but I SetInt with the name 'coin', it's just created then? and if it's already there, Unity just grabs it?

viral linden
#

If a key does not exist, the default value for that type is returned. For example, calling PlayerData.GetInt() would return 0.

shell marsh
#

interesting, so it's kinda created in the moment it's GetInted or the moment it's SetInted? I read there is a limit of some KB of how much UserData is available for a world? ^^ Anyone has an example how many Ints that would be?

visual cargo
#

It's a lot

#

And even more because persistent data is compressed

lyric kraken
viral linden
#

each world can save 100 KB*, each int is 4 bytes, so that is at least 25600 ints
* or 200 KB if you combine PlayerData and PlayerObject, and before compression

wary summit
fading totem
viral linden
#

oh? so the length of the key (string) also takes up quota? interesting, so there is incentive to use a shortest name possible?

fading totem
#

Yes

shell marsh
#

Sounds cool tho, so I can store multiple scores if I'd like in future. I have not found much documentation about persistence. More like what it is, but not much about a tutorial how to use it nicely (and if, I bet it would be no tutorial stuff with graphs then for advanced stuff like persistence x3)

wary summit
viral linden
#

so assuming someone uses key names like "GameName.IntName" which is say ~16 chars, so 32 bytes, plus the 4 bytes for the int, that is 36 byte per int saved, that is ~2844 ints which is still quite a lot

wary summit
#

yeah, 100KB is a pretty hefty amount for preferences, settings, and state. It's certainly not enough for large-scale data like images, but if you're individually inputting bools and ints and floats, you pretty much never have to worry about it.

If you do want to worry about it and you have large string keys, you could build a little translation layer for your internal systems that converts large strings into small hashes. But I wouldn't bother unless you have hundreds

muted compass
#

Does OnPlayerRestored trigger for the owner when they create the instance? Been noticing that when I load up 2 clients in a build & test it only seems to be interested in loading the remote player's data instead

#

or is this something to do with the remote player hijacking the owner's method that is being called?

narrow tree
#

I havent used it yet, but If it does, then id assume its called globally, so youll have to use an islocal check

#

Feel free to test it by output logging its displayname or something

muted compass
#

well rn it looks like this

    public override void OnPlayerRestored(VRCPlayerApi player)
    {
        Debug.LogWarning($"{player.displayName}");
        if (player.isLocal)
        {
            player.Immobilize(false);
        }
        else if (Networking.IsOwner(gameObject))
        {
            
            playerListLocal = playerData.SetupPlayer(player, playerListLocal);
            gameManager.SyncData(playerListLocal);
            OnPlayersChanged();
        }
    }

It does yeah, but basically I'm getting the Owner to load in not just their own persistent data but also everyone else's that loads in to the world through the method SetupPlayer, then after that it sends the data over to request serialization in another script in the method SyncData.

muted compass
#

I had the thought that maybe it could be placed into a queue system when loading the data one player at a time but idk how that would look or if it would work in this context

muted compass
#

🤔 I guess a player queue system wouldnt be a bad idea for this

dense owl
muted compass
#

is GetPlayerObjectComponent supposed to be local only or am I doing something wrong here?

#

I am constantly getting errors when testing in-game about Object Reference not set to an instance of an object :/

craggy obsidian
#

Also detail of the error.

muted compass
#

from what it seems it knows player exists since I did a isValid check earlier in the code, so my gut feeling tells me _player is not valid because it can't find the object component

#

mainly what ReturnHasPlayerLoaded is doing is returning a simple bool from the player object

#

unless there's a different way to get this variable without GetPlayerObjectComponent 😓

lyric kraken
#

that's not like a vrc call, you're using something extra there (persistence utilities), it might search through, but it's not apparent

if it's possible for that to not be instantiated yet (like for a player object) you should be checking against that being null after the get and early returning false prior to trying to call into the script

muted compass
#

Yeah. I'll give that a try after the get to see if that works. Originally the code anyway there was originally meant to ensure the player data has actually loaded but I guess it still somehow bypassed that check 😂

muted compass
twilit meteor
#

How difficult is it to save a string (URL) via persistence and load it as a URL?

sturdy veldt
twilit meteor
sturdy veldt
#

Uhh I know there’s a difference but I can’t explain it 🙏

wary summit
twilit meteor
#

i figured as much

wispy latch
#

I have a very peculiar issue that happens within my PlayerObject.

Here is what happens:
I have a Pickup with an udon script on it. That script calls update each frame. I do that in order to add a value to different script.
BUT it turns out, that method is being called more than once each frame! This only starts to happen after a while of playing, and in PC VR from what I've been told.
It could happen on other platforms. I'm not sure and it's obviously difficult to replicate this issue because it happens basically randomly.

And yes, I have confirmed that this happens for sure. I logged some information, like which instance ID of the component is calling it and even the path of the hierarchy, just in case. We observed multiple calls come from the same object each time. I even saw someone have 3 calls each frame.
Afterwards I started tracking the amount of calls to see if there is a timing issue. It's usually green (1 call each frame) but at some point it starts to be red (2 calls each frame):

#

A user even managed to get magenta (3 calls) like this:

#

I'm not sure what I'm even asking here to be honest. This is just such a weird issue I don't expect anyone to have an answer. There is still a chance I overlooked something in my own code, but I suspect something else is going on.

visual cargo
#

it's definitely Update where it's being called, and not FixedUpdate?

wary summit
wispy latch
wary summit
#

so that would be measuring the number of calls script one gets per call of script two, but that's not inherently connected to the number of calls per frame

#

which means there's got to be something weird in the difference between those two scripts

visual cargo
#

is it a race conditition with Update? like the order that Update gets called across scripts isn't guaranteed?

wary summit
#

could be something like that. Do these two scripts have a defined execution order?

wispy latch
#

Ah just to clarify, green means 1 call and grey means 0 calls. There are no greys so it means there are no race conditions, unless my mind is getting something wrong 😅

wary summit
#

yeah it's definitely something weird, if it was only race conditions you'd see gray

#

I'd take both scripts and compare them against a more solid baseline, like a render event

wispy latch
# wary summit yeah it's definitely something weird, if it was only race conditions you'd see g...

I'm also 95% confident it's not the code doing something wrong. I obviously left out some of it, but it's basically just an if statement if the button is held, and then some calculations for the speed. But the debug text definitely showed me that the speed is doubling/trippling. That's how I got to know this issue because users got surprised by the reel speed increase.
The only notable thing is that the pickup (script 1) switches its parent from time to time.

wispy latch
wary summit
#

well, if your goal is to measure "events per frame" then your baseline reset should be an event connected to rendering a frame, not some other script which is also on update

wispy latch
#

Unfortunately, this takes a long time to even encounter, so this will take some time

#

Ah, but it should be the event that is being called multiple times. Because the same value is being passed multiple times. In script 1, I do a calculation of movement and track the previous position after all, so it shouldn't pass the same value if it gets called immediately again.

wary summit
#

hm, could also attempt to measure weirdness by tracking time since last event

wispy latch
#

But I'll check it anyways just in case. I'm realizing it's not as clearcut.

#

It's just honestly, I feel like I'm wasting too much time on this when I could just block the call when it already received one that frame.

wary summit
#

Time.time is the frametime, so it'll give you a consistent timestamp no matter when it's fetched in the frame. Getting multiple updates with the same Time.time would definitely be weird.

Time.RealTimeSinceStartup is slightly different in that it calculates the time at that very specific moment, and you can use it to measure imperceptibly small units of time. Stopwatches operate similarly. You could use those to see the precise gap of time between updates, even if they're multiple times per frame

wispy latch
lyric kraken
# wispy latch Okay, I'll use that instead. I see there is a double version too for that. Let's...

i think you should consider frameCount if i'm understanding what you're trying to test, something like this would automate it decently on the script alone

private DataDictionary framesFired = new DataDictionary();
private void Update()
{
    RecordFrame();
}
private void RecordFrame()
{
    int thisFrame = Time.frameCount;
    int count = 1;
    if (framesFired.TryGetValue(thisFrame, out DataToken countToken))
    {
        count = countToken.Int;
        count += 1;

        Debug.LogWarning($"[{thisFrame}] has fired {count} times!");
    }

    // Always record count
    framesFired.SetValue(thisFrame, count);
}
private void LateUpdate()
{
    // This clears on n Entries
    if (framesFired.Count > 64)
        framesFired.Clear();
}
wispy latch
# wary summit Time.time is the frametime, so it'll give you a consistent timestamp no matter w...

So the test gave the following results:
I added a second tracking of calls, but this time inside of script one update. It has the same pattern so we can assume that update is called multiple times here...
The second screenshot shows the time when calls were made. These were tracked in script two. I know it's a bit difficult to read, but basically the arrow means it's the first call. Further calls in that same frame don't have an arrow. I tracked it again by using script two's update method to reset the amount of calls.
As you can see, the difference between script two updates is 0.03, but double calls have a difference of 0.0003.

wispy latch
#

But yeah, I think this kinda proves that update is being called twice on that pickup?
I don't really want to spend more time hunting this down. I hope this can help VRChat or even other people be aware of potential issues.

lyric kraken
wispy latch
lyric kraken
#

i just find it odd that they'd be seeing a ~0.03 interval between update calls, if they were actually running 30 then it's valid. i don't think measuring the interval is definitive that you were getting multiple updates per frame as i'd consider that somewhat unlikely, but i can't say it's impossible in some odd udon situation either

wispy latch
# lyric kraken i just find it odd that they'd be seeing a ~0.03 interval between update calls, ...

No, I already counted the amount of calls in a frame and identified that there are multiple ones (for whatever mysterious reason).
I'm doing the interval test afterwards, to try and see if there is a timing difference between the calls.
And the result is that there is really a pattern there. Each frame is 0.03 apart, which is pretty reasonable at around 30 fps in PCVR, probably in a full instance. The problem comes from those duplicate calls that are called much much more closely to each other.

lyric kraken
wispy latch
#

Yeah, I should've probably also tracked that or the frame counter just in case. That would've been more clear 😅 But I think the pattern can be seen already even without it.

lyric kraken
#

i don't think it'd be impossible to get a call on the back of update one frame and another on the front of update 300 microseconds later in realtime, like what you're saying makes sense, but i think without seeing like "this update loop prints frame, frame is shown multiple times" it isn't apparent that update is firing more than once

like without a defined execution order it'd be possible for your script 2 to clear prior to an increment call on script 1; i think you said you already accounted for that with gray color prints, but it's not apparent how large your color swaths are for a frame

wispy latch
# lyric kraken i don't think it'd be impossible to get a call on the back of update one frame a...

Of course it's possible, but is exactly what the colored line is for. As I said, gray is 0 calls, green is 1 and red 2. There is also magenta for 3 which definitely happened and should not be possible even if as you say the update execution order flips from time to time. In the end, it should always math up to 1. The problem is that it just turns red, so it always calls 2 times each frame. Sometimes even 3...

wispy latch
#

Okay, I'll do a final test. This time only in the pickup update. I feel like what I've done so far was to complicated, since I had to slowly feel myself into wtf was even going on.

wispy latch
# lyric kraken i don't think it'd be impossible to get a call on the back of update one frame a...

Okay, so I vastly simplified the test. FYI @wary summit, I got more clear results:

When the bug happens, Update is being called twice permanently! So the debug text just shows a 2 while LateUpdate still shows a 1. FixedUpdate jitters a little so it's not as clear from the screenshot I received if there is also an issue there.

Again, I don't know how to replicate it just seems to happen randomly for PCVR players at some point during gameplay, usually in bigger instances from what I understand. It could happen on other platforms but who knows.
The only remarkable thing about this setup: The script is on a pickup that is being reparented from time to time. It's also attached to a PlayerObject.

lyric kraken
#

that's odd, i wasn't able to replicate it with a player object pickup rapidly changing parents, but i wasn't testing with more than 2 clients; about fixedupdate, it can fire multiple times by design, but i guess the implication is that you suspect it could be doubling?

wary summit
#

Hm, that sounds like it's getting subscribed to the dispatcher twice over

#

Perhaps there's something you are doing to it which causes that. Do you ever disable it and then re enable?

wispy latch
wispy latch
lyric kraken
# wispy latch Yeah, this would've been impossible for me to track down if I didn't have help f...

i was able to replicate it somewhat reliably in a test, going to test it a bit more though

i will say that it's not actually the same frame in my case, but an increment in update and a reset in lateupdate sometimes results in a value of 2

here are 2 instances of it happening

as there are also cases of a 0 value as well at the point of lateupdate; i suspect (in my case at least) update is triggered, the gameobject is disabled, lateupdate fails to clear the value or something similar?
(in unity, disabling an object in update will actually fail to fire lateupdate for that frame, i don't know what vrc does in comparison)

i haven't been able to lock the state into consistently firing multiple updates per clear at this point

disabling the parent object (on top of reparenting) happened to be the additional thing that started causing it to happen in my test (and checking for an increment/clear value, checking frame count alone in update never raised flags)

i suppose the practical effect is that update can fire "multiple times in a row" if you rely on a later loop to clear something. i don't have the exact conditions to trigger it yet

lyric kraken
#

this is what prints in mono vs udon typically (the choice of frame 816 is arbitrary)

light laurel
#

are synced variables on playerobjects persistent, regardless of whether or not i manually save them with playerdata?

#

#udon-networking message
in my LeaderboardSlot script, the regular score variable seems to be persistent, even though i never directly tell it to save in playerdata. i want the score to display for all players on the leaderboard, but i don't want it to be persistent

#

the "score" is supposed to reset every instance, but the "highscore" should stay persistent

visual cargo
#

synced variables on playerobjects are always persistent, if you add the VRCPersistence component

#

otherwise they aren't

light laurel
#

oh okay, so i should remove EnablePersistence then. would the highscore still save in playerdata if i remove that though?

visual cargo
#

yes, PlayerData is separate

light laurel
#

got it

wispy latch
lyric kraken
# wispy latch So the double call happens only once? This does sound a bit different from my is...

in my case it's happening in single instances, but i'm also rapidly reparenting and periodically disabling the parent object to trigger it at random, i don't have a concise way to make it happen atm

vrc's loops seem to differ from unity in how they're handled (as in, disabling an object in update in vrc does not normally seem to stop it from running lateupdate that frame), not sure if it's the same thing as yours or not

wispy latch
lyric kraken
#

it being disabled didn't seem to directly affect it being triggered, idk, like in my second picture i don't know that it's related to it being disabled (the disable running every 100ms on the original parent is debug logged, but it wasn't parented to it at that point), the presence of it being disabled at some point did alter it though

wispy latch
# lyric kraken it being disabled didn't seem to directly affect it being triggered, idk, like i...

I'm a bit confused what you mean by altered. It's not like the object just changed by adding the disable? But either way, this feels like it's going to be rough to debug for sure, I'm already exhausted talking about this. As I said, it just randomly starts calling it twice, independent of any calls or toggling. The reason I discovered this is because people reported the mechanic literally being twice as fast permanently for some reason. And the debugging showed update is being called twice.

lyric kraken
#

stopping the periodic disable no longer prints a double increment in my case, that's all, sorry; i wanted to show that i found something that exhibited similar behaviour and why i felt it could be behaving that way

wispy latch
#

Like are you doing it multiple times in one frame?

lyric kraken
#

on the object itself (the player object, pickup), this is the parenting behaviour, in my case it's about 14 scene objects, it happens to include null (base scene/no parent) as well as the original parent and some other unrelated scene objects that are always active

private void FixedUpdate()
{
    if (!shuffleParents) return;

    if (Random.Range(0f, 1f) > 0.75f) return;

    transform.parent = targetParents[Random.Range(0, targetParents.Length)];
    Transform parent = transform.parent;
    string parentName = parent == null ? "null" : parent.name;
    Debug.Log($"[{Time.frameCount}][{nameof(FixedUpdate)}] Reparenting to: {parentName}");
}

on its original parent, the toggling behaviour (i forgot that i'd randomized this within a lower range)

[SerializeField] private GameObject parentToEnableDisable;
[SerializeField] private Vector2 minMaxActivationTime = new Vector2(0f, 0.333f);
private bool currentState;

private void Start()
{
    currentState = parentToEnableDisable.activeSelf;
    SendCustomEventDelayedSeconds(nameof(_ToggleParentObject), Random.Range(minMaxActivationTime.x, minMaxActivationTime.y));
}

public void _ToggleParentObject()
{
    currentState = !currentState;
    parentToEnableDisable.SetActive(currentState);

    Debug.Log($"[{Time.frameCount}][Disabler] Setting state of original parent to: {currentState}");

    SendCustomEventDelayedSeconds(nameof(_ToggleParentObject), Random.Range(minMaxActivationTime.x, minMaxActivationTime.y));
}
wispy latch
#

I guess that makes sense but I don't see how it would double increment it.

lyric kraken
#

it's pretty chaotic, i was attempting to trigger it, i would like to get it down to the exact order of operations required; as vrc doesn't seem to follow the same disable behaviour for loops within a frame i'm not sure how it's happening at this point

#

and as you said it's not permanently stuck in the same way, i had a version where i tried to keep it in that state after finding a double value, but i wasn't able to

wispy latch
# lyric kraken it's pretty chaotic, i was attempting to trigger it, i would like to get it down...

Well again, it sounds different then. Unless you accidentally found a way to reverse the behavior or something lol... A user did get it up to 3 calls an update btw.
Also, did you use Player Objects? Because that's the parent of the parent of the structure I use. The reason why I used this channel is because I was suspecting that there is some faulty registration of the update method related to Player Objects going on.

lyric kraken
subtle quest
#

Have you tried looking at Time.time of each incident to make sure it is actually the same frame and not the result of something else being weird?

quick pine
#

whoops, not search bar

zinc roost
#

Does anyone have any ideas or thoughts on how to save URLs persistently?

I'm basically wanting to be able to Save/Load persistent URLs, so everytime players come back to a world they can load those URLs, but I'm having trouble figuring out a successful way to do this.

Any help would be appreciated! I'll also provide any info you'd like or need additionally!

The only world that immedately came to mind as doing this and it working is one of the Karaoke worlds, where you can save custom URLs.

raw sigil
broken wadi
cloud fjord
#

Does anyone know the caveats of having multiple UdonBehaviours on one GameObject that's using VRC Player Object/VRC Enable Persistence? Yesterday I tried using multiple behaviours for a persistent object but found that the object's state wasn't saving properly with more than one behaviour on it. I've since made a few modifications so I don't remember what the component ordering looked like.

For context, I'm implementing a shop system where players get persistent pickups/cosmetics. I want all items to run the same common script to manage persistent variables (mainly if the player has purchased the item or not) and their active state in the hierarchy, and then a second script that provides item-specific functionality.

Right now I'm using a workaround where each item's root GameObject is a (otherwise) empty GameObject only running the common script, and then a child is present that has the Pickup components + the item-specific script. This seems to work but I'm curious to see if I can just have both scripts on the same object.

sturdy veldt
#

I don’t understand your reason for using two scripts in the first place?

wispy latch
#

Ah, but if you manage synced toggle state then you should probably attach that to another object, not the pickup you're toggling. This way you will always ensure the toggle sync sends and receives data properly.

cloud fjord
cloud fjord
wispy latch
cloud fjord
#

yea, currently they're just on different gameobjects

cloud fjord
#

didn't do any testing, but as I implemented something else I realized there is a non-zero chance my common script was on manual sync mode and I never called RequestSerialization

cloud fjord
#

Just posting my observations regarding an item I want to allow my players to purchase and persist across instances:

  1. If the player purchases the item, and the item is set to be inactive initially, the player's purchase does not save if they never toggle the item on (since I used an UdonSynced "purchased" boolean that is meant to synchronize on purchase). Therefore, the player will have wasted their currency if they leave without ever toggling it on.
  2. The item's active state persists; that is, if the player has the item active before they leave, it will activate itself when they (re)join an instance. If the item is inactive before they leave, it's supposed to be inactive when they (re)join. However, disabling the item prevents it from saving its inactive state via RequestSerialization, so the item will enable itself on join even if it was disabled on leave.

I think this is ultimately what caused my issue above (regarding using multiple behaviours on one object). I'd have to create a multi-behaviour object to test and see for sure though

fading totem
fading totem
cloud fjord
#

ooo yeah, good point

shell marsh
#

I use the Jump Counter from the persistence preview packages but instead of jump I count the variable "coin" with it. It's so often weirdly close to each other with numbers and I was already way higher, so something is resetting it and I wonder why?

#

if you run into coins, you get one...

#

enemies and falling out of the level costing some...

#

But there is nothing that could cause numbers to align...cuz then all makes no sense ^^"

noble nova
#

can you have a persistence array?

#

or is it only individual like floats or ints

visual cargo
#

on PlayerObjects it's the same as any usual synced variable
for PlayerData, the only available array is a byte array

visual cargo
#

I did figure out a dumb/lazy way of having an array in PlayerData.
Just looped through the array, and saved every element as an individual variable, and named it as Item[array Index]
then when you load PlayerData, just loop through again and assign all of the values back to the array.
arguably less work than figure out splitting up your data into bytes and putting that into a proper array

visual obsidian
shell marsh
# visual obsidian is ur world public? I wana try it :3

yes it is but I am afraid that the coin thing is not working. I just used the prefab of the jump counter in persistence examples and used another variable, grabbing not jumps, but the coings I was counting in that logic. But I am not resetting it in code or saying all players should get the one of the player currently getting coins. I need to fix that, cuz it otherwise makes collecting coins absurd xD
https://vrchat.com/home/world/wrld_9cda943f-92d2-4fe8-a2a1-0c046e0c2768/info

visual obsidian
shell marsh
#

A friend sayed he is already over 600 coincs and no problems, but I check with some different friends and we're often close to each other in points...makes no sense >.>

lyric kraken
# shell marsh if you run into coins, you get one...

on your coin enter trigger, you're getting any player from the enter event

that means if Player B sees Player A walk into a coin, both players will attempt to set themselves as the owner. Player B will also get Player A's current score and increment it, then assign it to their own score

it looks like they're overwriting their own score with another user's score

shell marsh
lyric kraken
shell marsh
#

Oh damn...I see that the enemy that takes points is asking that...the coin is not x_x

#

so that lil thing might rescue the coin thing xD

#

I always wondered why Interact and stuff is local, but Trigger starts global if you're not doing the thing with branch + get is local

jagged ravine
# wary summit VRCurls can be stored as persistent data in playerobjects

how does this work, exactly? i'm struggling to figure this out with no access to TryCreateAllowlistedVRCUrl. i'd love for users of my world to be able to have a high level of interactivity based on their own playerdata but i cant seem to reason how to do this without user input each time

#

(also if anyone else knows please ping me 🙏)

#

intuitively it seems like you'd have to prepopulate an inputfield and then store the result of that in vrc's persistent layer? is that the way until the trycreate method becomes available to udon?

stable slate
#

No rush for a responce, but if anyone could tell me how to make this toggle first default as on as it always defaults as off no matter what ive done to the scene

visual cargo
#

you call the event to toggle objects in OnPlayerRestored, which pretty much happens as soon as a player joins.
If the player doesn't have the key, the default value will be false, which is what is causing everything to be default off

#

I guess you could just UnaryNegation it there

stable slate
#

I should have probably mentioned that i have a very barebones understanding of all this. had help in this channel before practically step by step to even make this toggle to begin with. Where exactly in there would I connect a UnaryNegation to without breaking everything?

visual cargo
#

try it there. flip this bool before putting it into SetActive

#

I've put like 10% thought into this

#

this should at least default on when the key comes back false

#

but is kind of a messy way to do it.

stable slate
#

It worked! Thank you very much!

lime tree
#

anyone know if ProTV has a volume persistence plugin yet?

#

would be super cool if it was just a little prefab that did it, and you could change it's key name (to not conflict with any other keys you may already have)

unique rune
#

I actually implemented that in the world I'm working on, then realized that's a bad idea when I left it set to the max and got blasted with way-too-loud music upon joining

lime tree
#

My world's default volume is 0.1

#

and I still sometimes get people saying that's too loud xD

#

Like wtf, are their computer volume and headphone volume at 100 or something? hahaha

unique rune
#

yeah my default was like .25 but I went in game and played something quiet and cranked it up, then the next day went back and the default thing playing was way too loud.

lime tree
#

lmfaoo

unique rune
#

so I removed it from persistence 🙂

lime tree
#

maybe I shouldn't have that persistent then

unique sandal
#

im trying to add persistence to my world, to save a day night toggle, but i keep getting this error "CS0103: The name 'PlayerData' does not exist in the current context"
im using the latest sdk

visual cargo
#

make sure your script has using VRC.SDK3.Persistence; at the top

unique sandal
visual cargo
#

sounds like you aren't providing a variable for the out bool

unique sandal
#

not sure what could be wrong, here is where im getting the error (37,23)

public override void OnPlayerRestored(VRCPlayerApi player)
    {
        if (!player.isLocal) return;

        bool savedNight;
        if (PlayerData.TryGetBool(player, playerDataKey, out savedNight))
        {
            isNight = savedNight;
        }

        ApplyLightingImmediately();
    }
unique sandal
visual cargo
#

ah.

#

is this a C# script (says MonoBehaviour near the top somewhere) or an U# script? (says UdonSharpBehaviour near the top somewhere)

unique sandal
#

its u# my bad

#

i thought udon was just a compiler

visual cargo
#

depends on the context

#

I'm testing your code now

unique sandal
#

want me to send everything?

visual cargo
#

no I don't need it

light laurel
visual cargo
#

if it's used as an argument and the variable had already been declared, it should be fine

light laurel
#

nope, it usually makes me declare the type too

#

i would expect it to not need that, but it does

unique rune
#

huh I've done that without a type declaration, fascinating

visual cargo
#

really.... hm, it's let me compile and run with it

unique rune
#

suppose I should look at that

visual cargo
unique sandal
#

idk if i did something wrong but i changed from out savedNight to out bool savedNight and im still getting the same error

light laurel
#

maybe it's an IDE thing?

unique sandal
#

ill attach the whole thing here one sec

light laurel
#

visual studo just tells me to do that but it isn't required, i don't know

visual cargo
#

doing out bool savedNight just gives an error with multiple declarations

unique sandal
light laurel
#

not sure, but i know i've declared the type before and it works fine, and i thought if i didn't it gave me an error

visual cargo
#

yeap my function is identical to yours and it runs

light laurel
#

definitely feels like GPT code

unique sandal
#

yeah i said im using chatgpt

light laurel
visual cargo
#

try removing the line for bool savedNight, and edit the function to have out bool savedNight

unique sandal
#

still the same thing

visual cargo
#

which line is line 37 for you?

unique sandal
#

sorry o thought i included the lines

#

its the first line in the screenshot

light laurel
#

probably not this, but what if you try Networking.LocalPlayer in place of "player"?

visual cargo
#

then that will always evaluate as true

#

I just managed to get the same error hold on

light laurel
#

oh sorry, i meant within the TryGetBool line. not the If statement

unique sandal
visual cargo
#

huh.... I changed it to out bool savedNight, got the same error, switched it back to out savedNight, no error, switched it back again to out bool savedNight, no error.

light laurel
#

oh.. okay

unique sandal
#

huh? lol

light laurel
#

me when the compiler isnt consistent

unique sandal
#

neither works for me

light laurel
#

what if you try a bit of a workaround?

bool success = PlayerData.TryGetBool(Networking.LocalPlayer, playerDataKey, out savedNight);
if (success) isNight = savedNight;
visual cargo
#

is your scripts actually compiling? I'd double check and force it to compile
find any of these green files and click on it

#

then look in your Inspector and click "Compile All UdonSharp Programs"

unique sandal
#

it does nothing

visual cargo
#

you should at least see it load, and a message appear in the console when it's done

unique sandal
#

oh sorry, on the console it says
[UdonSharp] All Unity C# compiler errors must be resolved before running an UdonSharp compile.
UnityEngine.Debug:LogError (object)

visual cargo
#

the same error we're seeing is probably further up

#

you're definitely saving the file after editing?

unique sandal
#

yes

#

ill reopen vscode just in case its something to do with it

visual cargo
#

vscode....

unique sandal
#

whats wrong with it

visual cargo
#

VSCode doesn't do any syntax or error checking for you, so mistakes in the code can easily be missed that would otherwise be caught

#

it's easier to install and work with though

light laurel
unique sandal
visual cargo
#

if you click on the .cs file in Unity, what does it look like in the Inspector? does it match what is in VSCode?

unique sandal
#

it does

visual cargo
#

do you have any other additonal errors above the one about the C# compiler errors?

unique sandal
visual cargo
#

no, in your console

unique sandal
#

forgot about the console

visual cargo
#

you still need to declare the variable for savedNight by adding bool savedNight; above it

#

that third error may also be a problem

unique sandal
visual cargo
#

ChatGPT moment

light laurel
#

oh yeah, it did use three even though it shouldnt

visual cargo
#

all of the Set functions only take 2 arguments, but it's decided to shove in the out bool for no reason. lol

unique sandal
#

lol

visual cargo
#

check near line 75 for that SetBool function, and remove the player from it

#

isNight is actually supposed to be there

light laurel
#

i guess it thought it needs the player there because TryGetBool needs a player

visual cargo
#

should be just PlayerData.SetBool(playerDataKey, isNight);

#

don't really blame it for that mistake. a toddler could easily make the same assumption

#

it kept giving you the same error because all errors need to be fixed before it tries again

unique sandal
#

lol, okay i did what you said, but im still getting the error on line 37

#

and on the console it says
SkyboxController.cs(77,28): error CS1503: Argument 1: cannot convert from 'VRC.SDKBase.VRCPlayerApi' to 'string'

visual cargo
#

you sure you change SetBool to how I specified?

#

because that sounds like you kept the player, when that's specifically what you need to remove

unique sandal
#

its this right?

visual cargo
#

nope

#

PlayerData.SetBool(playerDataKey, isNight);

unique sandal
#

oh, the other player

#

my bad

#

the error changed
"SkyboxController.cs(79,8): UdonSharp does not currently support node type 'ConditionalAccessExpression'"

visual cargo
#

can you post that line

unique sandal
visual cargo
#

ah that yeah

light laurel
#

oh, just remove the ?

visual cargo
#

yeah why is that there...

unique sandal
#

lol, i imagined that could be the issue, but didnt wanna break it any more than it was

light laurel
#

it's basically a short way of writing if toggleSound is not null, run Play()

#

but it's unnecessary most of the time, especially if you're not making a script which you'll be distributing

unique sandal
#

okay, not more errors i think

light laurel
#

if it's for your own project, you don't usually need null checks for variables which you know you'll assign in the inspector

unique sandal
#

thank you everyone

light laurel
#

chatGPT just really likes making sure any nullable type is not null before doing anything with it

#

even if it doesn't make sense, like where it assigns localPlayer to Networking.LocalPlayer and instantly checks if it's null, even though it should use Utilities.IsValid rather than a null check for a VRCPlayerApi

visual cargo
#

which is why @unique sandal that I highly, highly recommend that you do not use GenAI to write code for you, especially in the context of VRChat Udon
it just doesn't know a lot of the nuances of it, you don't actually learn anything, and in the end when it inevitably doesn't work, it can often be difficult and confusing to bugfix.

unique sandal
#

yeah, im aware of that, but it know very little about coding in general and i needed a very specific thing and couldnt find it online

unique rune
#

I've been reading AI generated slop at work lately... this has been a huge waste of time and resources.

unique sandal
light laurel
#

you should at least try and learn the graph if not code

unique sandal
#

i thought it wouldnt work as good as code

unique rune
#

there shouldn't be much difference for basic things

unique sandal
#

ill try it next time

light laurel
#

it's a little slower and doesn't have quite as many features as U#, but it's not bad

covert drum
#

Ye, functionally they're both fine. Main struggles I noticed when I tried graphs:

  1. Math equations that are one line in code are a BUNCH of nodes in graph. Can be unwieldy.
  2. Escape sequences in text don't seem to work in some nodes? I had trouble with string and date formatting because of this.
  3. Huge large graphs can lag or crash the editor.
raw sigil
#

256 nodes is a main limiter id say, you hit it way too fast (and it works slow as hell in editor when reachin the limit). so graph is just a u# tutorial, perfectly usable tho until some point

#

but life is way easier with u# (once figured out syntax and boundaries), especially now with llm stuff that can generate you some code (not the whole script in case of u# i guess, but tedious parts of it), while getting graphs from em is not a thing

trim cradle
#

Ah dang it, my discord didn't fully scroll down lol

unique sandal
narrow tree
subtle quest
light laurel
#

i do too, i just don't bother sometimes

subtle quest
#

Is OnPlayerRestored suppose to be able to be called twice on the same object by the same player?

#

For context this only happens on player #2's side, not player #1

lyric kraken
#

it's called per player in the instance and new joiners, unless you mean it's calling multiple times while passing the master as the player on your 2nd client (which is a bug that can occur in build & test)

subtle quest
#

ahh that would be it

light laurel
#

(and no, i don't have any other bools in this script named "savedState")

visual cargo
#

right, because you didn't declare the variable yet

#

if you do bool savedState; in the line right before it, it'll work

light laurel
#

ah okay, i thought you were saying it wouldn't require me to use that at all

visual cargo
#

but it's more compact to just do it there, especially if the variable only needs to exist within that scope

indigo wing
#

Is there currently a way to have PlayerData (or another method of saving player data) accessible from multiple worlds?
I can theoretically go the GET request route, but since it's going to be player's coins, I don't want people being able to easily modify them.

visual cargo
#

not in an easy or in a supported way, no; persistent data is purely per-world

subtle quest
#

the manual route would be having the world output a save data string that the player can then input in a different world

lime tree
#

Can anyone help me with what i'm doing wrong here? I'm trying to have LTCGI start disabled when the user doesn't have a PlayerData for it but it's always starting out Enabled and it's really getting on my nerve. Keep in mind, I have limited code knowledge and basically everything here, I had lots of help with (They're asleep rn but I wanted to get this working tonight)

lime tree
#

I have no idea how else to tell LTCGI to stay off other that literally LTCGI_Controller._SetGlobalState(false); lol

unique rune
#

huh I'm glad you mentioned this - apparently I started implementing a toggle for that but never hooked it up

#

but yes, that looks like the right way, according to the LTCGI docs

lime tree
#

So, idk how to explain this other than, I moved my project to my m.2 nvme (I was tired of it taking 10 years to open), opened it, cleared my local playerdata, hit play and.... the ltcgi is now properly off lmao

lime tree
#

Nevermind, I lied. The toggle is frickin backwards. I don't understand this. Why is it now, when I enable and disable this, it's now backwards? I enable it, LightVolume enables and ltcgi disables. I disable it, LTCGI enables and lightvolume disables. hecc

grand pollen
#

I’m sorry if I’m asking this in the wrong channel, but I was wondering if anyone knows how to do this. I want the character that I have in the world to look at the players and look at them as they’re moving kind of like the clip I attached. I did watch a tutorial, but I’m a little bit lost on the script part and how to attach it to my model. Here’s the link for the video: ( https://youtu.be/aAauz34t1gU?si=iNy6ycmsarkChQPJ ). I’m stuck on 3:08 of the video because I don’t know what script they used or how I’m supposed to write it like am I supposed to write something before it or am I supposed to write it in like a certain place? If anyone knows what I’m supposed to do, it would be really appreciated < 3

I have not seen alot of tutorials explaining this concept, so I have decided to share my "how tos" with you guys :)
Feel free to ask in the comments, if something is not clear or could be done a better way

the game from this video:
https://store.steampowered.com/app/3124820/Tartaros/

▶ Play video
wary summit
# grand pollen I’m sorry if I’m asking this in the wrong channel, but I was wondering if anyone...

In this tutorial they are likely creating a C# monobehaviour, which is the standard unity scripting class which can be attached to gameobjects as components. However, they also don't have much in the way of security so in VRChat you can't use those.

However, you instead have UdonBehaviours. Udonbehaviours are a lot more locked down and don't have access to all the same capabilities, but they are nonetheless a general scripting interface that can absolutely achieve the same effect.

Udonbehaviours come in multiple different flavors, either udon graph or udonsharp. Udonsharp is the one that is most closely similar to regular unity monobehaviours, so for this case I would recommend using that, as you'll be able to use mostly the exact same code as in this example.

To start with making an UdonSharp script, you would find a place in your unity editor project file browser and right click > create > U# script.

Once you have that script created, you'll need to edit it. You can get away with notepad, but it's not going to have any syntax highlighting or autocomplete which is IMO the best part of writing C#. To do that, you can connect visual studio or jetbrains rider to unity. Let me know if you have further questions on how to do that.

Once you're actually set up to write the script, it will be mostly identical to the script in that screenshot. The one difference is that you won't have access to the player transform - instead, in VRChat you have player APIs. In this case you would replace player_trasf.position with Networking.LocalPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position and it should work exactly the same.

Once you have the code set up, you would simply put it on a gameobject as a component. Scripts don't do anything unless they exist in the world. You can put it anywhere but for organizational sake it would make the most sense to put it on the same gameobject as the animator that it's plugging into. Then you would need to drag the animator into the public reference of the script so it knows where to send the data, and you're done.

grand pollen
wary summit
#

on line 25, you are missing a semicolon at the end

grand pollen
#

Thank you!

wary summit
# grand pollen ;; I did get a couple more errors

attributes not valid: You put all of this inside void Start() when it should be directly inside the class instead.
facingpoint: the example from the video forgot to include the definition of facingpoint. Looks like that should be another public transform which is then pointed at the head of the animator.

grand pollen
tulip river
#

Just double checking with persistent real quick

If I want to store any (supported) data on a player, permanently, all i need to do is create an object with:

  • VRC Player Object
  • UdonBehaivor that has a synced variable
  • VRC Enable Persistence

and on RequestSerialization(), the values of said variables will be saved, even when the player leaves and rejoins, and I'll know when said values are ready to be used using public override void OnPlayerRestored(VRCPlayerApi player)? not sure if that last method is an actual method or if its just part of the demo scene

wary summit
onyx ingot
#

oh, that's neat: so receiving persisted data is exactly the same as receiving serialized data from someone who ran a manual sync?

onyx ingot
#

nice (:

subtle quest
#

A word of warning, there are circumstances where PlayerObject persistence data can be lost that, unfortunately, the documentation makes zero mention of.

For example if the network id assigned to it in editor changes, all its associated data can be lost. Also I have no idea how it will handle the PlayerObject's synced variables changing. So I'd test that before trying to change them if you have a live version of the PlayerObject and don't want to risk wiping everyone's data.

tulip river
#

Just a quick question about what you guys think would be the best way to handle this situation:
My game has a bunch of roles, and each time you dont get a role, your bias/weight for that role goes up (its a persistent value). What would be best/most efficient way to store these values on my PlayerPersistentData object?

I was thinking a byte array, but, i wasnt sure what I would do if I wanted to add a new role
My second thought was just a bunch of bytes for each role, eg [UdonSynced] public byte RoleNameBias;, but was wondering this would be slow/inefficient to save

and, i guess, also a bonus question, how do I set default values for players who have never joined the world before? can I just do something like [UdonSynced] public int testVariable = 5; and it works?

lyric kraken
# tulip river Just a quick question about what you guys think would be the best way to handle ...

you might consider using playerdata to link keys with values more explicitly, you can access whether it's been set before with TryGet

https://creators.vrchat.com/worlds/udon/persistence/player-data/#accessors

playerdata has some caveats that make it undesirable if you have a large amount of persisted data, so if you were to use player objects with persisted variables, setting some default value should inform you that they've never written a value before when you access it in the player restore event

#

as far as actually saving it, i'd personally use bytes as a data structure of some kind

tulip river
#

I mean- i guess changing the name doesnt work with player objects either, bad example

#

What If I input the wrong name somewhere and create a new key by accident? Can I, as the developer, delete that value on all players?

lyric kraken
#

the interface is more like a dictionary, which is easier if you're doing more explicit linking of like a specific key to a value as opposed to writing bytes to a variable in player objects or creating x amount of variables for the amount of persisted vars you need. if you're using bytes it doesn't really matter i guess. it's probably more simple to start with keys ("someRole") and assign a value to them if you don't anticipate needing a lot

you can't delete a key afaik, i'd normally make a const var in a script defining the key and use that to avoid mistakes

tulip river
#

I like the idea of a using a const to not make a naming mistake

#

oh but uhh

#

there might be a problem?

#

I dont know if I did something wrong- but I dont know if I can test player data in the editor on linux

#

When I went to the example scene and pressed the "Set Player Data" button, nothing happened

lyric kraken
#

i forget exactly where it is, but in the top vrchat dropdown there's a way to browse your clientsim playerdata, which would probably indicate whether it worked

tulip river
#

re-opening example scene to test

#

oh

#

maybe something just borked last time

#

because it worked with no issues this time

#

Okay, so I guess, to confirm:

  • Create a bunch of public const variableKeyName = "KeyName"
  • On OnPlayerRestored(VRCPlayerApi player), check if the keys dont exist, if they dont exist, set default value?
subtle quest
#

The biggest strength and issue with PlayerData is that is it basically an untouchable PlayerObject. Plus side you won't have to worry about accidentally wiping all the data. On the down side, you can't intentionally wipe all the data either.

tulip river
#

i just did
so much scripting

and i feel like a player object could have worked

subtle quest
#

you can use both; they have seperate data limits

tulip river
#

This is what I did to make sure that, when a player joins the world, if they are missing keys, set keys to their default values

#

there is currently an error in it that im fixing by just having a hardcoded value in the RoleHandler (the first for loop)

#

Also- will probably swap Awake to Start

tulip river
#

sure took a long time to figure out, but i fixed all the errors, and now:

#

yippeee

tulip river
#

oh wait

#

this isnt the dev server

#

LOL

#

lets go post that update in the dev server, not here (said i published a new branch)

west gorge
#

Maz

left mesa
left mesa
#

Keep at it man! And good luck

left mesa
#

What’s some stuff that will break persistent variables? From my understanding they’re similar to synced variables, but I’ve heard that there was ways your project could get screwed up and then mess the variables up as well.

elfin wren
#

Dont reset your network ids

left mesa
#

Is there any problems that would force me to reset me ids to fix?

visual cargo
# left mesa Is there any problems that would force me to reset me ids to fix?

technically no; resetting IDs is just the quickest and easiest way to fix conflict issues with network IDs. But it is also like taking a sledgehammer to a wall in an attempt to fix it.
Any network ID conflicts can be fixed manually, if needed, by using the network ID utility; it will tell you which objects have the conflicts, and you can go in and manually fix the IDs rather than nuke all IDs when regenerating
so if you have a world that doesn't have persistent data (or at least data you don't care about), you can save yourself the effort of manually fixing it and just reset IDs

onyx ingot
#

I noticed that it's possible for people to be in an instance while loading different versions of the same world

#

But I guess the networking IDs still match up!

quick imp
#

So If I have an existing world but want to add new persistence data types on top of whats already existing, will everyones persistence data be safe?

#

Will it work or get corrupted?

visual cargo
#

adding on top is fine, if it's new keys and/or new variables

quick imp
#

Worried if this goes wrong and peoples saves get deleted my world wont be so popular anymore :L

#

Its gunna be a tense moment when I have to update

#

Oh also while Im here, Ive been having reports of persistence save data getting lost, I feel kinda powerless whenever people report that

#

Was hoping there would be like a backup system or something

upbeat dagger
#

You can make a backup system with how people used to do world saves. Using text inputs that people can save. Though that would require the users to take that step, which you can't really rely on

quick imp
#

I wouldnt even know how to begin with that

upbeat dagger
#

You'd just have a program that would gather all saved info and convert it all to a simple string of characters. Then loading it would do the reverse. Could be as simple as that or you could take it just a little bit further and introduce some level of obfuscation of the string to keep people from just manually editing their values to keep people from tampering

quick imp
#

That would be alot of parsing hmmm

upbeat dagger
#

Yea, honestly might not even be worth the effort to some extent. Could spend that time trying to make your current system more robust

quick imp
#

Imean its uncommon, but seems to randomly happen, people come to me "My data gone nuuuu" and I cant figure out a cause cause its rare 🙁

upbeat dagger
#

Just a simple idea would be to make a check method that wouldn't allow the data to save if it looks like it's going to be a problem. Like when a player joins the world and data loads, save some sort of overall idea of what that data looks like and then compare when you go to save it again. If the new data is basically empty or just not what you should expect, don't save at all. The player will loose some progress but not everything at least

elfin wren
quick imp
#

It loads data at the start and thats it

elfin wren
quick imp
#

I dont know what you mean, when they load in it recovers their persistence data just fine, they play for a bit in which theres a combination of automatic and manual saving and then they leave.

Only to find the next time they play their data gets wiped...

elfin wren
quick imp
#

No, it only auto saves after having completed a match, by then youve long loaded your save.

subtle quest
#

what happens if a player joins just as a match is ending?

onyx ingot
#

You cannot make any assumptions here.

quick imp
#

I mean... I dunno, Last I check it automatically runs the on player restored node, upon doing that it sets all the save variables up as intended, all within like a second, theres no way anyone could save in that amount of time

onyx ingot
#

you should not rely on timing like that

#

it works until it doesn't™

quick imp
#

Well... is there a way to manually call upon a restore player?

#

I'm not seeing the problem, as soon as the world loads in, the players data gets loaded and then applies to the variables

#

It only saves when they complete a game or they want to manually save and thats long after they have loaded...

raw sigil
bronze prawn
# upbeat dagger You can make a backup system with how people used to do world saves. Using text ...

(this solution is not practical unless you have lots of free time) isnt it also possible to make a custom server tht listens for GET requests and pass in some encoded string in the get request, your server picks it up and understands its not actually meant to be a get and stores their string, then can return it when the game calls a get request formatted in a different way so your server knows the difference

upbeat dagger
#

If your going that route, you could also have multiple saves/backups. But yea, that's possible. Though I've never done anything with servers and so couldn't tell you the specifics.

bronze prawn
#

Me either but i had the thought:p

left mesa
#

Just a couple quick questions for people more experienced than me. How expensive is it usually to use the find function to find a game object.
Also, what’s a good way to accomplish windows that work for quest as well

subtle quest
#

It's going to depend on how many game objects it might need to iterate through before finding the object

left mesa
subtle quest
left mesa
left mesa
#

Interesting thank you for the references

noble nova
#

How many bytes is one bool

visual cargo
noble nova
#

Even in a script?

visual cargo
#

I think in a script it should only take up 1 bit

#

but if you network it, it's 1 byte

noble nova
#

Ohhh opps I got it mixed up that sounds right I think

#

1 byte makes sense

cold echo
# visual cargo I think in a script it should only take up 1 bit

Memory access is by the whole byte, though. I think realistically, even if it only uses one bit, it still stores the value in 8 bits I would think. The alternative is when you use an integer to pack bits, which you can toggle with bitwise logic. Or when bools are packed into a C struct. And the reason is that a byte is the smallest amount of data you can store or retrieve in an operation. Secondly, if the bool was alone, it would force other data to be misaligned to the nearest byte, making memory access slower, hence, at a minimum, everything is byte-aligned.

#

My explanation touches on how data is stored in computer memory. As to why it makes sense based on data byte alignment and storage and retrieval size.

visual cargo
#

that makes sense. sizeof returns 1 byte for a bool

cold echo
#

Now if you have a ton of bools to store in state, you can learn to pack and unpack them from an integer.

#

I think that is how avatar parameter values are transmitted over VRC too. You get 256 bits. Floats and ints take 8 bits, bools take 1. They're all packed into a data structure up to 256 bits long. On the contrary, you'll have 32 bits in one integer to mess with packing and unpacking booleans in your code.

noble nova
#

so im trying to save a float array, bool array, and texture array i can see anything for this in the documentation so im guessing its not a thing? also if i do the vrcobject does it just keep a clone of a game object and its componets and settings? and if so how would i update this player object or does it update per frame

visual cargo
# noble nova so im trying to save a float array, bool array, and texture array i can see anyt...

it depends on if you're using PlayerData or PlayerObjects
the only available array type in PlayerData is a byte array. So you'd possibly need to break up your data into bytes, store that in an array, then save that to persistence
Another option would be to just save a separate variable for every element in the array
PlayerObjects however can persistently save any data that can be networked, so arrays of syncable types work perfectly fine with those

to reference a PlayerObject through a script, you'd need to obtain a reference to it by using GetPlayerObjects

When you enable persistence on a PlayerObject, all it does is persistently save the last network state. So how often it saves purely depends on how often you are doing a network sync on the PlayerObject

noble nova
visual cargo
#

yes

#

everything udon-wise works as you'd expect on the PlayerObject

noble nova
#

or just duplicate for the local player and nothing more

visual cargo
#

that would just be.... a normal object, no?

noble nova
noble nova
#

is there a way to check if its someones first time joining the world?

lyric kraken
noble nova
#

can i reset all players presistance in my world?

lyric kraken
#

not directly, you could include a version number on players when you save. if you need to reset, you could increment that number in editor and update the world. if it's lower than expected when you read from the player, you can reset their keys (this is assuming playerdata)

#

you would have to define how to reset their keys though, which might be a pain if you use keys throughout your scene

glossy belfry
#

Question what is the limit of world persistence
My only knowledge of something similar is of rec room stuff which technically you can use 16.384 gigabytes of storage for one player
-# yeah I know it's extremely high but there's literally no need for that amount
-# I'll also explain it, basically you can save 20k characters of utf-16 in one string, you can save 4,096 of those strings, and you can save a hundred of those 4,096 which one converted that's about 16 GB

visual cargo
glossy belfry
noble nova
#

Not just reset a float to 0 or something

lyric kraken
# noble nova Not just reset a float to 0 or something

that isn't the case no, you can't forcibly "remove" a player's data unless you reupload the world afaik

you could instead use player objects for the persistence, that allows more control over the data, but it's not as simple to work with if you're already using playerdata

if you actually want to enforce like a full clear, you could disable a lot of the systems in your world and inform the user to reset their data manually if they're seen with an out of date version on their persistence

(some people don't know how to access the data reset in the options for the world, so it may be useful to provide images that show how)

noble nova
#

Ooof vrchat should add

whole plinth
#

Anyone have a simple graph script that just persists a single bool locally

#

Or does persistence do some black magic?

light laurel
#

persistence is literally just

  • have a value
  • change the value
  • save that change
  • apply the change to the scene
  • load that value when a player joins
#

there's not much magic or complexity to it

#

this is a very basic example

lyric kraken
visual cargo
#

it definitely improves the readability of the graph

lyric kraken
#

yeah it might get a bit cluttered with the key/local player nodes in more places, idk

visual cargo
#

oh I was referring to how zSkull did it as more readable. I hate having to chase lines all across a graph to see what the inputs are

#

anything relevant to the node should be near it, even if you're reusing it somewhere else

light laurel
#

it's how i tend to do it in my scripts too, it just makes sense to me

#

because i usually use persistence just for UI toggles, so having that separate bool means i'm not loading the persistent value every single time i make a change, i'm only saving it's last state

#

i only load it when they initially join and actually need it loaded

lyric kraken
#

it makes a lot more sense (to cache the state) if you're only saving the playerdata in larger intervals instead of setting it each time yeah

light laurel
#

for stuff like a leaderboard i made a while back, that i just had to save and load values every time, no intermediate variable. cause in that case, i was actually needing to access the saved value consistently

lyric kraken
#

like accessing it across players?

light laurel
#

well, it had an option to change from a regular scoreboard to a highscore board, and when it swapped, i just loaded the saved persistent highscores, i didn't need to have an intermediate synced variable

#

it was saved on a playerobject with enable persistence anyway, so the highscores were already synced

golden cosmos
#

E

hasty dome
#

I have a problem related to synced variables in PlayerObject.

I have a script which is attached on the same game object as VRCPlayerObject and VRCEnablePersistence.
What I am doing in the script is initialize all of these synced variables on OnPlayerRestored, and call RequestSerialization to sync those initialized variables to other players.
The trouble is, even if SerializationResult says it was successful, none of those variables are synced.

And what I found was, decreasing the number of synced variables makes syncing successful.
I thought at first like "Is it a rate limitation thing?" but SerializationResult.byteCount is only around 150.

Does someone notice anything wrong with my script?
Is there any special limitation for synced variables specifically on PlayerObject?

#

Early joiner's log

#

Late joiner's log

#

When I comment out some variables like this, it somehow works fine.

hasty dome
#

That might be the exact same case as mine…
Thank you…!

#

I am relieved to know that I didn’t code wrong but it’s sad to know as well that there’s a very inconvenient bug…

subtle quest
noble shell
#

i'm needing help with making my code save and load persistence data, the interaction works and adds value but i want to save values when people leave and rejoin

visual cargo
noble shell
#

so public void OnPlayerRestored

visual cargo
#

I think it needs override but yeah

noble shell
#

okay perfect i'll work on it shortly

#

just public void

visual cargo
#

whatever the autocomplete suggests

noble shell
#

yeah i removed the override and it working thank you

fading totem
quick imp
#

I think I am going to need some assistance with creating a string based save system for my world, Persistence is proving... rather volitile as reported by players 🙁

velvet barn
quick imp
#

People randomly lose data for an unknown issue, very variedscenarios every time they report 🙁

hallow storm
quick imp
#

Using player data

wary summit
quick imp
#

Ive changed and cut some features of the game to reduce the save files size for new players, as it was locally saying that the player data was in excess of 151kb which exceeds the 100kb limit (Im using quite a few arrays) Im guessing thats likely the cause, Kitkat told me that this data gets compressed in the actual game though though Im kinda annoyed i wasnt warned about my save data size in the build window, It likes to be pedantic when it comes to android world build sizes but itl let an oversized save file slide no problem ):(

#

I am hoping this was the issue, otherwise I have no idea

fading totem
#

I honestly don't know what it might be, because if your save format was too big it should fail to save at all.

velvet barn
#

Getting a rather frustrating error:

2025.10.22 18:19:37 Error      -  [Behaviour] Value cannot be null.
Parameter name: Received a null value when encoding a Byte
System.ArgumentNullException: Value cannot be null.
Parameter name: Received a null value when encoding a Byte```
#

It doesn't tell me what Byte is null, just that the entire PlayerObject is failing to serialize

#

I do know it's only for players with data before this started occurring, it started occurring when I added a few additional values to the object, and they are all initialized to a non-null value

lyric kraken
#

if you've added any new fields, this might help (basically check them for null on restore), not sure if this is like still an issue or not

#world-persistence message

velvet barn
#

I think that's exactly what I needed, thank you!

elfin wren
fading totem
noble shell
#

so i've been messing with persistence and during testing in unity and vrchat when another player joins this happens to my code is there a way to fix tbhis

#

this only happenes with these added once removed it looks normal

wary summit
noble shell
#

well the code works the player object is my issue

wary summit
#

Putting a playerobject on something means that it gets duplicated, once for every player in the instance. If you're expecting this UI to only exist once then dont put a playerobject on it

trim cradle
#

Also, if you just want persistence you don't have to / probably shouldn't use player objects. For just storing e.g. what a player has unlocked or how many credits thex have player data is usually the better way.

livid nimbus
noble shell
livid nimbus
noble shell
livid nimbus
noble shell
#

thats my world

livid nimbus
#

so nice

proper sequoia
light laurel
#

literally just replace the bool for a float

#

use a float variable instead of a bool, and use SetFloat and TryGetFooat

proper sequoia
#

ive done most of it, the unary negation part is the bit im stuck on if that helps

light laurel
#

just don't use unary negation. all it does is flip the value of the boolean, so it does not apply to a float

proper sequoia
#

do i just skip the unary negation and just connect that straight to savedfloat?

light laurel
#

in the top of the graph where i have the Set (Variable) node (the red one), you set it to be whatever value you want

proper sequoia
#

okay, think ive got it, one more try before i go off, 5:30am rn

#

thank @light laurel

wispy latch
#

So is this change to lock out players out of older instances just for player objects? Because I don't see how player data would've been affected by layout changes.

#

And I guess what I'm wondering is if network layout changes in general will affect the lockout? Or only changes to persistence enabled player objects?

subtle quest
#

"All objects that exist in both versions (selected by Network ID) must have a matching variable and component layout. Udon changes that don't update variable types, order or count will not break compatibility. Updates to non-networked parts of a world are also fine, e.g. simply moving an object in the scene."

Seems to include every networked object, not just persistence ones

wispy latch
lyric kraken
wispy latch
#

But yeah, I hope it's not too limiting. Testing to figure out the nuances also seems like a pain.

#

Maybe it's just also just my case being a bit unique. I update a lot and have a decent amount of publics I don't want to ruin for people.

subtle quest
velvet barn
#

Or just a general desire for overall stability. Even outside of persistence, I can see there being benefits to ensuring people in the same instance are running compatible networked code. A lot less for world creators to have to account for when pushing an update (or at least, a more predictable thing to account for).

dense owl
#

Yeah the "lost data" thing happens in more worlds...Recentely in the umamusume themed game world people's saved loadouts started to get erased at random.

Sometimes it's just that (and not the current build equipped) sometimes it's both.

Worst part is that "Kyoto Racecourse" have not been updated since september, and everything was fine. This save lost thing just started recentely.

fading totem
#

:)

lime tree
#

is persistence saved on cloud or machine

fading totem
verbal spire
#

How would one go about doing an "or" logic with graph?

#

Id love to do if month 10 OR 11

#

Is this basically it?

light laurel
#

what you have may work, but i'm not sure how exactly that Logical Or for the Int works, so i wouldn't know

jagged ravine
#

there's no way to persist data on a per-world basis right now, is that correct? curiously thinking about cross-instance leaderboards. my understanding is it can only really be instance-based at the moment 🤔

#

well, persist *cross-instance data in a tos-friendly way of course

raw sigil
#

its persistent per-player per-world. for leaderboards only untrusted urls with voluntary submissions

velvet barn
jagged ravine
#

it is a bit unfortunate there is no world global data store yet; even if the size were extremely limited like <5kb, it'd be nice to be able to pack some stuff in there

velvet barn
# jagged ravine it is a bit unfortunate there is no world global data store yet; even if the siz...

There's not really a good way to pull off world storage like that though anyways.

VRChat's network model for instances is basically brokered p2p. The code you write is only ever run on client's machines, one is just designated by the photon layer (which handles the brokering) as the master of the instance and is considered authoritative to the other clients for some things.

This means:

    1. there's no reliable way to trust any client is going to run the code you want and only the code you want.
    1. race conditions could occur if multiple instances are trying to run the same code to update the same stored world space at the same time.

There's really just no feasible way to implement useful global world storage in a way that can be updated by arbitrary players via udon unfortunately.

jagged ravine
#

just gotta make concessions that feel ok 🙏

velvet barn
# jagged ravine these don't seem insurmountable <a:vrcDanceRat:1356753465002692811>

It kinda is insurmountable though in brokered p2p 😅 Without an authoritative server, you can't run trustable code. At the end of the day there's not getting around that you're having to trust arbitrary client inputs to your global storage. Leaderboard hacking is already a problem on standalone games as is, but it would be many times worse here.

There are ways to get around the race condition issue, but we'd have to leave behind the generic storage and instead have a specific implementation of a storage space that can intelligently handle simultaneous inputs and know how to resolve them. Ie, a specific leaderboard API as opposed to a generic world storage one, which takes submissions and just inserts them in order of value.

#

In essence, people would ask for this feature, would get it, then complain even more about all the hackers making their leaderboards entirely useless, and no one would use the feature because it's unreliable.

wispy latch
#

It would be cool if they could spin up like a fake client where we could run world code on or something. Sounds like it could be possible theoretically, but would probably take a ton of work.

jagged ravine
#

idt it would be reasonable for players to have to build out a distributed model themselves in udon; that'd all be on vrc's end, at least in my mind. unless you meant something else?

velvet barn
# wispy latch It would be cool if they could spin up like a fake client where we could run wor...

Photon has a concept of a Photon Server which can run its own code and behave in ways more akin to a traditional client-server model. But to my current understanding that would be a fundamental overhaul of VRChat as it is, and cost them significantly more in money and resources to run for every instance. It would be nice for a lot of other reasons if it were viable, but yeah, not realistic to happen.

velvet barn
# jagged ravine with regards to leaderboard hacking, are you saying vrc allows clients to run co...

Proof by counter-example: are you saying that EAC has been effective at stopping all hacking on VRC? 😉

Yes, people still use hacked clients and can execute arbitrary code. EAC and signed code are barriers of entry, but it's a cat and mouse game that even billion dollar competitive multiplayer game companies fight constantly and still can't defeat.

With a server, you can at least do the logic there to say "user did <thing> that was doable for them, add # points", then pass the final score to the database in the end and the client can't touch any of that directly.

With brokered p2p the way things are now, the only world data getting saved to VRChat is the user data to your own account. No one else in an instance can set that except your own client. Worlds can make your client run code based on inputs from other clients, but in the end each client can only touch its own data directly. With a global world persistence however, everyone would be allowed to modify that shared data freely. At that point it's just an API endpoint and an assumption that we can trust what the client says is true for everyone (which we can't).

#

I can give a direct example with VRCanvas. Technically that world does have global persistence: things users do in any instance affects all other instances, since the canvas itself is global state. However:

  • People can and have called those endpoints directly, and not just from within VRC. You can do that with photon too, VRChat just asks you nicely not to and it's just a banable offense if you get caught, but not everyone gets caught.
  • The canvas is a specific type of global persistence with custom server code, not a general data store. And I have to pay to host it.
  • The canvas is not a leaderboard. I'm not trusting people to say what their score was and ranking them, or letting them tell me the state of the entire canvas, I'm just getting requests for individual pixels on the whole.
  • It's an obscure system for a small world, meaning it's got a relatively small threat surface compared to a theoretical global system used by all worlds.
jagged ravine
visual cargo
#

I tihnk you can generally trust me to hold onto your wallet and important items

velvet barn
#

To be clear I'm not talking about hacking in terms of the gameplay, I'm talking about people having access to the values sent to the API directly. Again, assuming this is a general data store, then 1 person could simply replace it with an empty string and delete everything for everyone too. Narrow it to a specific implementation like a leaderboard, then they can just send bogus data to put them on top. That's the issue of trust.

jagged ravine
visual cargo
#

while that's true, Prismic has made like a dozen good points why it's not as easy as it sounds

#

people already get upset because 1 person poisons their highscore system with a cheat score

jagged ravine
velvet barn
#

Easy makes it seem like it's a problem you can just throw more time and resources at though. This is a network design/architecture problem, not a resources one.

jagged ravine
#

it's a solved problem... idk 😅

visual cargo
#

gives examples of why it isn't a solved problem
"it's a solved problem"

velvet barn
# jagged ravine it's a solved problem... idk 😅

We'll agree to disagree lol. I'm not going to pretend I'm a total expert on these things, but my career background is client/server software. If it is a solved problem, I'm unaware of such a solution within the constraints of VRC.

#

But if one could be found that'd be cool. Would love what you described if such were feasible.

jagged ravine
velvet barn
#

For race conditions yes that's solved: you just have to constrain things from a general store to a specific one with insertion/update logic.

#

But again, we'd be straying away from general world storage at that point and into more of a leaderboard API. Which would solve that problem, but the bigger one of trust remains.

jagged ravine
#

idt it's necessary to dump a full spec for this but i do think vrc's existing relationship with AWS would lend well to literally just a lambda. validating inputs is another topic but not terribly difficult depending on the constraints, if you're ok to guarantee MOST (not all) inputs are valid

jagged ravine
visual cargo
#

I think the lessons learned from recent events is that VRChat wants to have less stuff relying on AWS lol

jagged ravine
#

i agree, but lambdas are cheap. you could setup a single server in your* bedroom to handle all updates for US east for global data storage with some concessions

#

it's not too big a deal

velvet barn
# jagged ravine no it's the same issue. distributed global storage is *so* solved that it's some...

Wasn't talking about the storage infrastructure itself, was talking about the structure of the data. For a general storage, suppose you have an object that says the number of total fish caught in the world is 1000, and one person says it should be updated to 1003, and another says it should be updated to 1001. The correct answer we'd want is 1004, but in order to determine that we'd need logic that is aware of the structure of the data being saved and the updates being received to reconcile them.

jagged ravine
#

that isnt a schema issue; it's the same thing from earlier

jagged ravine
velvet barn
#

Yeah, apologies for the circles too haha. I enjoy the discussion, and like I said I certainly don't know everything, so if there's stuff I'm not aware of for me to learn that's always a boon. I'm just presenting what I know. I'm also content with my current swe position hehe 😊

jagged ravine
#

feel that

fading totem
velvet barn
#

Yup, they gain the performance and trust benefits of a client-server architecture, but along with the costs.

#

I'm still keeping an eye on S&box, supposing they keep VR support as a priority.

lyric kraken
subtle quest
#

if you wanted to do leaderboards in a vrc world, probably the only realistic approach is the one speed runners use: runs are recorded and submitted for humans to validate and add to the globally viewed leaderboard

lyric kraken
#

the propagated leaderboard mentioned above is actually viable, but the data becomes very sticky. it'd require maintaining it if people did spoof entries

raw sigil
#

i mean first you make it notable, then you spoof-prove it.😉 vrc got anticheat in 6 years or smth

dense owl
#

It's funny how everyone seems focused on leaderboards when it comes to persistance.

Meanwhile I just want a same PlayerData key marked as "shared" to be useable in multiple worlds (of same author, important distinction) locally so a set of worlds can act as areas of a big adventure...

Y'know, transferring your stats/inventory over, RPG stuff. Or farming in a map and using the ingredients to cook in another, stuff like that. ayaka

unique rune
velvet barn
next arch
sturdy veldt
#

Yeahhh, would be nice if they cared about that

next arch
#

This is heartbreaking that you cant even come up with your own solution because the data is cached per window and you dont get actual live data when calling get methods. This breaks so many possibilities and games... They're probably trying to save on bandwith but this is too far

sturdy veldt
#

Yeah that post exists because of active cheaters in existing worlds, really sucks we can’t do anything about it

dense owl
#

multiple instances....Oh, that's what they do? Playing in one till inventory is empty or they die then just do an action in the other instance where they had all full to refill the inventory?

#

considering nobody can interfer with a private one where they're alone...sounds nasty

brave ravine
#

I am hoping to get it to where my and my friends can decorate our homeworld within vrchat (using gravity-less items and such), how would I enable persistence so those object stay in place for everyone?

visual cargo
#

are these decorative objects also PlayerObjects?

brave ravine
#

Not as of now? Right now, I just have a button that each time its push it spawns said decoration that both plays can see/manipulate

visual cargo
#

to sync those you'd have to save both the position and rotation, and then load it when the player joins

#

but if they were PlayerObjects, and are also pickups with VRCObjectSync, all you have to do is at the VRC Enable Persistence component and then its position and rotation will automatically be saved

#

both approaches have their pros and cons

brave ravine
#

"to sync those you'd have to save both the position and rotation, and then load it when the player joins"
How does one do this?

visual cargo
#

position is a Vector3, rotation is a Vector4, so each object would need to save it

#

you might want to have a button that the player would press to do the saving, or you'd setup an autosaving interval

#

i.e. every few seconds, it saves the values

#

loading the data would be done in the OnPlayerRestored event; you'd then retrieve these saved values, and set the object to the position and rotation that was stored

#

on the other hand, if they were PlayerObjects instead, all you'd have to do is add the VRCEnablePersistence component. This will cause it to persistently save the values of the VRCObjectSync component

brave ravine
#

Whats the downside to the playobjects cause that feels easier?

visual cargo
#

a PlayerObject creates a duplicate object for every player that joins; if that's exactly what you want anyway, then there isn't really a downside, but if this isn't desirable, then it could be considered a downside
Another notable downside is that Ownership of a PlayerObject is strictly enforced, the network owner can only be the player that owns the PlayerObject. This means that, for pickups, only the assigned player can successfully pick up and move the object

brave ravine
elfin tree
#

Something I've been wanting to test and intend to is compressing data as a single string and writing a small string parser to save and load larger databases in one small area.

Has anyone messed with this before?

Think of it similarly to a password system, but the passwords setting the data directly through string parsing.

sturdy veldt
elfin tree
wary summit
#

And if you still want it as a string to copy/paste you can convert byte array to/from a string using base64

elfin tree
dense owl
#

FFS...more "fun" persistance news...
So, the Umamusume-themed running world will have a tournament soon-ish. For this players fine-tune their builds (stats and skills) and train a lot.

Guess what happened now that it's almost tournament time?
Some pseudo-crasher is going around deleting the data whish should be persistant...Man I swear cheaters are such a plague

#

I have no idea if it's just that the save/clear buttons can be accessed easely to modded clients but that sucks...

#

So, any tips about making persistant systems stronger against this type of attacks? I'll try passing the advices around devs I know. Cause really that kind of sabotage isn't fair, there should be ways to defend against it

#

(I mean general tips, I didn't personally see the dev's code)

visual cargo
#

a function that clears the player's data should never be callable over the network in the first place; they should start the name of the function with a _

dense owl
visual cargo
#

you can also further safeguard network functions by checking for NetworkCalling.CallingPlayer, and checking if the player that called the function is also the player that is meant to have the data deleted (so if you're deleting your own data, the CallingPlayer should be the local player)

#

but my guess is, their deleting function both doesn't start with an underscore, and is also public; so even if their own code doesn't call it over the network, malicious clients can

dense owl
visual cargo
#

somehow, sure. But for that to happen, they would have to modify other player's clients, the creator's code somehow has a network function that lets them call a local function, or VRChat has a HUGE security hole in their networking

#

so, very very unlikely

dense owl
#

Also I agree the saving itself should be all local to begin with.

I wonder if there's an exploit since the stats need to be read at race start...The most worrying part is that it isn't just the equipped/current build but also the loadouts that get reset...So not even a case of "let make this people stats X and when they save that save this instead of their choice" it's without players interacting with the objects. Just instant wipe

visual cargo
#

that all sounds to me that they haven't properly protected some functions to prevent them being called over the network

dense owl
#

but again we'd need to talk with the dev directly, here we can just shoot in the dark....

dense owl
#

(also for personal notes...is there even an equivalent to this protection for the graphs...?)

visual cargo
#

Graph follows the same rule, you name the event starting with an underscore

#

the only other thing I could think of is checking in OnPlayerDataUpdated for any unexpected, drastic changes in saved data

dense owl
#

ok

#

thanks again for the answers

sturdy veldt
visual cargo
#

I would assume not? Just one more extern on the pile

agile coral
#

Best way to prevent scripters from messing with stuff in my experience has been to never use public networked events

#

All events should start with that _ like Legos was saying

#

That underscore prevents those events from being called over the network and locks them down to local only

#

When possible, use data instead of events for networking things (like turn a bool on or off or send out a specific int to cause an event, then turn it back off)

#

It takes a bit more planning and troubleshooting but it's made the world a better experience in the end

sturdy veldt
#

I’ve heard multiple times lately that client abusers can still take control and misuse any udonsynced variables as well

#

If that’s the case then it’s an unsolvable problem, no?

visual cargo
#

the ultimate best solution is: never assume your code can't be broken or exploited, people will always find a way

agile coral
#

So true

unique rune
#

100%, code defensively

visual cargo
#

the games I make literally don't give a shit if you cheat. big woop, you skip progress. what do I care

agile coral
#

Just make it so when it is broken in a way only a script could cause, something really funny happens

#

If you try to spoof my username in my world the whole place turns upside down lmao

unique rune
#

haha that's great

sturdy veldt
#

He did attempt to add a solution for client abuse in that too but in the end couldn’t do anything about it

agile coral
#

I've been thinking about making some watchdogs too, that'll check the status of various security things Incase something happens

#

Yeah that's the main issue with client abuse, an anti client thing can't do much if they just turn it off

#

(which is something they very much can do)

sturdy veldt
#

Yeeeep

visual cargo
#

I have very limited energy, and you might be the same, and it's up to you to ask, what's more worth the effort: developing my game/world/etc, or trying to develop anticheat?

sturdy veldt
#

Exactly why I use the prefab for it 🙏

agile coral
#

I just like watching my anticheat things turn on when someone tries to be a goober it's so funny

sturdy veldt
#

I’ve made a similar (but way worse) anticheat before and using someone else’s is way better than trying to solve that problem myself lol

agile coral
#

That's true

#

Sometimes it's just not worth the effort

#

But imo it's worth it to preemptively code in a way that makes it difficult to break stuff

#

Never impossible, but make it not worth the effort

sturdy veldt
#

But there’s no way of knowing if I’m actually doing something that has any affect on clients without having one to tell me

#

It would be a waste of time to try adding that and then find out it does nothing, hence why I don’t try at all lol

visual cargo
#

I've had the same logic, like what do I even do if I'm not aware what clients are fully capable of

agile coral
#

Valid, I was lucky enough to have 1 or 2 people who liked helping with the security stuff.
Basically just avoid events that are called over the network at all costs

visual cargo
#

pretty challenging for a multiplayer game, no doubt

agile coral
#

And make it so stuff goes based off of names or roles or data rather than whether or not something is turned on

sturdy veldt
agile coral
#

Networking is okay

#

It's just the events that aren't

#

Ah

#

I'll say this, data is pretty safe

#

And add redundant checks occasionally

#

Like most of my "important" security stuff has 3 or 4 checks at bare minimum

wispy latch
agile coral
#

It'll check like if you are the one actually sending stuff, who's the master, if you are close to the item being toggled or not, stuff like that

#

Oh yeah, forbidden pickups are fun

#

Pickups that do silly stuff if touched

#

Put em far enough away no one would find em without a client

sturdy veldt
sturdy veldt
#

Put a forbidden fruit somewhere far away and inaccessible and completely obliterate anyone who picks it, I’ll try to memorize that lmao

agile coral
#

So like when someone runs that thing to pick up all pickups, they get yoinked

agile coral
sturdy veldt
#

Exactlyyy

unique rune
#

ooh I didn't know there was such a thing, I'm totally going to have fun with that

agile coral
#

Just make sure whatever happens it still abides by tos ofc

#

Like my go to is flipping the world upside down

sturdy veldt
#

Yea I knew folks could claim ownership over anything they want but all my pickups in my current world are only grabbed with playerobjects 😅

unique rune
#

send 'em off to the floating point precision madness zone

agile coral
#

Or destroying the objects that make up the world