#world-persistence

1 messages · Page 2 of 1

modest stirrup
#

This seems like a really important thing, but it's only noted on what is effectively the footnotes of the Simple RPG example page? This isn't even mentioned on the PlayerObject page itself

#

It also just seems like a bad idea to do something like this automatically without any indication

subtle quest
#

That must be out of date and needs updating

#

Or is typoed

#

"If you have a PlayerObject with an UdonBehaviour that has some synchronized variables, and you want to persist those variables across instances, then you must add the VRCEnablePersistence component to the object with the UdonBehaviour. You can have multiple independent objects within a single PlayerObject where some have persistence, and others do not." - https://vrc-persistence-docs.netlify.app/worlds/udon/persistence/player-object

GameObjects with the VRCPlayerObject component are treated as PlayerObjects. PlayerObjects are automatically instantiated once for each player that joins, providing each player with their own copy of that GameObject, its UdonBehaviours, other components, and children. Each PlayerObject is instantiated with the same position, rotation, scale, and...

#

Although rereading the documentation for PlayerObject, some of it implies that instance unique synched variables (aka persistence that won't carry over to different instances of the world) are automatically persisted

#

I imagine this temporarily persisted stuff won't count against your persistence limit

modest stirrup
#

yeah the wording here is pretty confusing. So with PlayerObjects, persistence is automatically done to synced variables on the same object as a VRCEnablePersistence component? It's a bit strange

#

Okay yeah that's what it says on the main Persistence page. So the wording on the Simple RPG page seems to just be missing the VRCEnablePersistence component part

idle obsidian
#

Would there be any reason why the save data of someone(or in this case, the master of the instance) loaded for another user, instead of theirs in a PlayerObject?
I have the object with PlayerObjects and also Enable Persistence. The code for my object with the data looks like this:

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

        ConnectStats();
        LoadStatsData();
    }

    public void ConnectStats()
    {
        statsTracker.saveData = this;
    }

    public void LoadStatsData()
    {
        //Transfer all the variables from this Persistence Enabled script to the statsTracker script
    }
#

For some reason, my friends had some bits and parts of data that seemingly transfered from my file to theirs somehow

#

Though it seems like achievements (from PlayerData) were actually working as intended, as in, there was no mixed values between players. Everyone had their own progress properly saved (or nothing at all since it was their first visit)

idle obsidian
#

Oop, seems like nothing saved from last night with my friends, great 🙃

idle obsidian
#

How do we know if we are running into some sort of bottle necks with the Persistence data?

subtle quest
#

if you mean that 100kb limit, iirc the docs said udon would report an error if it is hit

idle obsidian
#

I'm really lost again then :c The world used to save both PlayerData and PlayerObjects properly, then it just suddenly stopped doing so, and I got a weird bug where everyone kept getting my save data? But like, corrupted?

#

It's also weird that I couldn't try to reset my save from the VRChat Website of my world link. I had to delete my entire user data of all worlds

#

And now, nothing saves. I don't see any errors anywhere either so I'm back to being stumped

idle obsidian
#

Okay so something is not letting my objects persist, and I just saw the cyan errors (though, those did not appear on the beta client, only in test clients)

#

I dont see any errors being thrown when writting the data, which seems ok

#

I printed out my values and they are def being written onto the scripts and player data

#

Then I see this messages when joining back

#

And ultimately I see this. All this for the player who rejoined.
the byte[] array I used to write achievements seems to be working? as it was able to restore them
The stats on the other hand... are not :S

errant token
subtle quest
long mirage
#

would this mean that the persistence stuff will soon merge with the open beta branch?

errant token
idle obsidian
#

Wait, that last notice. Does that mean I wasn't at a fault here? Or not ; - ; I dunno anymore

graceful compass
idle obsidian
#

Oooh okay! I'll hold until I hear more about this, thanks :>

abstract narwhal
subtle quest
#

They'll give us an update when they're ready to, I imagine

dense owl
#

Let's hope this gets resolved soon, Persistance (PlayerData side, havn't tested PlayerObject yet) is fun to experiment with so far.

#

Being able to write the variable from anywhere, without having to cross-reference objects all the time is honestly such good QoL compared to normal variables on objects.

#

(U# people probably laugh at this statement but I'm just a Graph noob... xD)

lime delta
#

The server-side issue should be fixed by now, as far as I can tell.

dense owl
#

only one way to find out if it is... xD

#

Where is supposed to be the "reset user data" button again?

#

@lime delta Testing in my world. The save seeems to work again now.
Also "reset user data" seems to be back on website as well 👍

#

now reseting

#

Reset do be reseting 👍
Saving again afterward seems to work fine 👍

#

(Would have tested earlier but I missed the message. you shoud @ the testers for something important like this. xD)

dense owl
#

Hm...I wonder how much impact would have idle/clicker worlds saving on every action, with more and more of them going faster and faster. I limited my test world to 0.5sec for the fastest autoclick but let's be real, some people will try to go WAY faster and on a way larger scale than 8 objects.

errant token
dense owl
#

Oh ok, well that shouldn't be a problem then.

subtle quest
#

You might have to worry about overloading networking though if you're doing networking stuff faster and faster

dense owl
#

Yeah of course, but that's not different from usual

#

I guess a single "serialize once a second" regardless of everything else could work as well then?
Even if the player leaves they'd never lose more than 1sec of progress.

heady patio
#

A world that was working earlier stopped working for me. It supposed to save urls to images and display them but they don't save anymore. Real bummer since the idea of being about to load photos into my home world of me and my bf was cool. Hope it's fixed

#

Although a pen world had the "I love Penta" (my bf) message disappear so might be a wider issue

heady patio
#

Yeah a few worlds like the Idle Cheese game work but more and more ain't saving and loading stuff

idle obsidian
#

Okay I just tested and it seems like data is being restored properly woo

#

I think I finally understand what is going on and where my problem lies.

I forgot that PlayerObjects are instanciated for every player, and when OnPlayerRestored fires, it fires for everyone. What I thought the method did is that OnPlayerRestored would fire with the player parameter being tied to whoever was restored, but I didn't take into account the ownership of these objects. I thought only the owner would fire the event for their object.

Is it safe to check that the object has the correct ownership on OnPlayerRestored ? So instead of checking if the player is local, I check if the player is the owner of the object?

heady patio
#

Ahh now see, they only load if I am in desktop mode. That's a bug

#

Stuff loads in that world on desktop no problem, but while in VR mode nothing does. Then I go back to desktop and it loads again

#

Okay found another world like this. Saved it over on desktop but not on VR.

#

Another thing I am finding is in a couple worlds some stuff loads but other stuff that use to work failed to load.

trim cradle
#

You should probably write canny posts about the issues you found ^^

dense owl
digital scroll
#

can i ask if 304ing api.vrchat.com to api.vrchat.cloud was a part of the fix?

dense owl
#

Hm, PlayerData seems to be only single variables, so for an array (working on an inventory system) should I just make an array of strings (the "item ID" variable's names) then go from there? Or is it a recipe for disaster? xD

silent echo
#

If you want to use PlayerData, I would recommend serializing to json, converting json to utf-8 and storing that byte[] in the PlayerData

#

Strings are actually 2 bytes per character so you waste a lot of space storing them. Better to convert to utf-8 byte array

#

I do beleive that persistence compresses the stored data, but the best compression is to just not store unnessisary information

dense owl
#

trying to port some of my old systems to persistance and there's arrays everywhere but I'm too lazy to restart from scratch

#

So in a way, I already have the indexes, just need to make the saved variable equal to the one in the array

#

(but later I'd totally need to make a cleaner version ment for persistance..)

idle obsidian
#

You could use the Set along with a mixed(?) string so if you have an index, you can just do PlayerData.SetString($"myStrings{i}", stringValue)

#

I wouldnt advocate for this if you are managing massive arrays (personally) since that is a lot of keys and values that could be neatly put in a byte array

dense owl
#

Yeah, I guess most of my headaches comes from the fact I'm not a programmer, I'm using Graph and it's a bit complicated to visualise what I should do xD

idle obsidian
#

Ooph ><

dense owl
#

especially since inventory interact with shop, upgrade, construction systems. trying to figure out what parts specifically I should change is a bit challenging on such a scale

#

But that's a group of mechanics I really want to port over

dense owl
#

Also not gonna lie, what makes me lose time right now is super silly but really doesn't help making sense of my old graphs. Unity displaced the nodes and put everything on top of each others when I imported the objects as a package.
I have to unjumble this mess of spagetti now. ^^"

velvet barn
#

But yeah, if data storage efficiency isn't your goal, what Ximmer suggested initially is your best bet for a simple and mostly straightforward. JSON encode your array and store it as a string, then load and decode it back into an array later.

#

So your string array string[] {"value1", "value2", "value3"} would transform into "[\"value1\",\"value2\",\"value3\"]" which is itself a single string you can store, then just decode it back into an array of strings on load.

silent echo
#

Yeah sorry, I think in U#
Graph would be a nightmare for me with the amount of code I have.

dense owl
#

I can imagine, since it is already a nightmare at my scale xD

digital scroll
#

ah, alright

stark nexus
#

I'd just serialize the strings into a byte array with their respective lengths, gives you full control over the serialization format:

    public string[] GetPlayerDataStrings(VRCPlayerApi player, string key)
    {
        if(!PlayerData.HasKey(player, key)) return null;

        const int intTypeSize = 4;

        byte[] buffer = PlayerData.GetBytes(player, key);
        if(buffer == null || buffer.Length < intTypeSize) return null;

        int bufferPosition = 0;

        // Read value count
        int valueCount = BitConverter.ToInt32(buffer, bufferPosition);
        bufferPosition += intTypeSize;

        // Read values
        var values = new string[valueCount];
        for(int i = 0; i < valueCount; ++i)
        {
            // Value length
            int valueLength = BitConverter.ToInt32(buffer, bufferPosition);
            bufferPosition += intTypeSize;

            // Value data
            values[i] = Encoding.UTF8.GetString(buffer, bufferPosition, valueLength);
            bufferPosition += valueLength;
        }

        return values;
    }

    public void SetPlayerDataStrings(string key, string[] values)
    {
        const int intTypeSize = 4;
        
        int bufferLength = intTypeSize;
        var bufferValues = new byte[values.Length][];
        
        for(int i = 0; i < values.Length; ++i)
        {
            bufferValues[i] = Encoding.UTF8.GetBytes(values[i]);
            bufferLength += bufferValues[i].Length + intTypeSize;
        }
        
        int bufferPosition = 0;
        var buffer = new byte[bufferLength];
        
        // Write value count
        Array.Copy(BitConverter.GetBytes(bufferValues.Length), 0, buffer, bufferPosition, intTypeSize);
        bufferPosition += intTypeSize;

        // Write values
        for(int i = 0; i < bufferValues.Length; ++i)
        {
            var bufferValue = bufferValues[i];
            var bufferValueLength = bufferValue.Length;

            // Value length
            Array.Copy(BitConverter.GetBytes(bufferValueLength), 0, buffer, bufferPosition, intTypeSize);
            bufferPosition += intTypeSize;

            // Value data
            Array.Copy(bufferValue, 0, buffer, bufferPosition, bufferValueLength);
            bufferPosition += bufferValueLength;
        }

        PlayerData.SetBytes(key, buffer);
    }
velvet barn
stark nexus
#

Ah yeah have fun converting that into graph nodes

long mirage
#

So is there going to be a persistence beta update soon so the beta branch can be updated as well?

heady patio
#

Don't know, but currently persistence seems really bugged anyhow

idle obsidian
wary summit
idle obsidian
#

Oh neat! Thank you

quick imp
#

I have a couple questions regarding persistence as I cant really find much info on it.

1 - Will it be usable in Udon graphs (Visual scripting)?
2 - Will existing Udon behaviours be able to get updated for implementing persistence features?

Im kinda prototyping a game in alpha form with fingers crossed that it will be able to have the persistance added to it down the line when it becomes fully available without having to remake everything from scratch.

wary summit
quick imp
#

I see, will be interesting figuring out how to implement it into my behaviours

#

I assume it will be like a checkbox similar to where the "Public" and "synced" checkboxes are?

wary summit
#

not exactly. There's two forms of persistence - playerobjects and playerdata.

  • PlayerObjects are objects that you define which get instantiated for everybody in the instance, so that everybody has their copy of the playerobject and you can see everybody else's playerobjects. If you're only using playerobjects on their own, they're not persistent. Despite being instantiated, they can sync as normal with the one exception that they can never transfer ownership. Everything inside a playerobject is locked to forever be owned by it's original owner. If you add the VRCEnablePersistence component to a playerobject, then any syncing that happens on that object or any of it's children will be persisted. In other words, it's not a checkbox similar to public and synced, but rather it's an additional component you add which makes synced also be persistent.
  • PlayerData can effectively be considered a playerobject that you don't create, it exists as a more convenient method to save data than making a whole playerobject. There are PlayerData.Set functions and PlayerData.Get functions which allow you to manipulate playerdata from any udonbehaviour, and then you can react to changes with OnPlayerDataUpdated which happens after any changes.

Additionally, OnPlayerRestored is an event that will happen soon after someone joins the world, and indicates that the data on both their playerdata and playerobjects has been loaded from persistence and is ready to work with

lime delta
quick imp
#

Oh that sounds baller, Might make it substantially easier to make what I had in mind because of the object persistence. wait can I do that now? I tried searching for a component "VRCEnablePersistence" to no avail, I assume I'm not using the correct SDK? but I was able to get the persistence examples.

wary summit
#

Persistence examples are distributed through example central, and I think that might be out now in regular SDKs so you can get them even if you don't have persistence SDK

#

make sure you install 3.7.2-persistence-beta.1 in VCC

quick imp
#

It doesnt appear that I have the persistence beta available to select from the drop down menu, I updated VCC.

#

Unless 3.7.3 has it included?

wary summit
#

can you show what you see?

wary summit
quick imp
wary summit
#

oh you need to enable pre-release packages in settings

quick imp
#

Ah there we go, thanks for the assistance!

dense owl
# quick imp I have a couple questions regarding persistence as I cant really find much info ...

Having to update graphs over right now I can tell you, how "easy" it is depends on how much of a mess your graphs are. 🤣

If it's just a matter of score, it can be as simple as adding a "set 'PlayerData_Score' to 'Score' value" to whatever event you had (don't forget to request serialization) then OnPlayerRestored "set 'score' to 'PlayerData_Score' value" to initialize it back to the saved state when you rejoin.

Now if you need something to change to another state (door opening, some throphy spawning once you reached a milestone, etc) it's a bit of a can of worms...
Depending on the nature of the events you can:

  • Have a separate manager that "updates everything" depending on the PlayerData's value
    Or
  • You put a set of bypass CustomEvent (starting the same event but after the inventory management nodes so it won't make you pay score) and call these bypasses OnPlayerRestored(local player) depending on the PlayerData's value
quick imp
#

Im pretty early on the main logic, so any "major" work shouldnt be a setback lol, thanks for assisting

dense owl
#

In my case I have shops that also need their button (single use) updated depending on what is unlocked, and a shop that only unlocks when you finished buying everything on the previous one, so it's a huge can of worms

dense owl
#

PlayerData is pretty straight forward to use, and since you can use it from everywhere you don't need to consantly cross-reference objects to change the data

#

You just...change it

#

Then it's the new value for everywhere, it's super neat

quick imp
#

Ive got alot to learn, so Il be reading up one evening lol

dense owl
#

just try something small as learning project, I went for "make a simple idle game" because it have most base logic you'd need

#

Then now that I understand, I try porting over an old system that was more complex

quick imp
#

I can already pretty easilly make a fully comprehensive inventory system with ammo, health and the such, Im getting around the daunting task of making it all networked, but considering that this persistence objeect is being built literally around my whole idea, thatl make it a ton easier 😛

dense owl
#

(spoiler alert, everything broke 🤣 )

#

nice! ^^

quick imp
#

I now know why most horror maps never bother making their "coop" maps networked

dense owl
#

to be fair networking is its own can of worms...

quick imp
#

Ye... my game kinda mandates it be networked haha...

#

But Im getting there

dense owl
#

I wanted to add networking to mine (team achievment: "one player have it = bonus for everyone in same team") but I'm just too bad with networking for that and synced ressource nodes xD

quick imp
#

Sounds like quite the vrchat world!

dense owl
#

Nah, just a bunch of cubes. It's already in the labs tho, just search my name (same as in Discord) you'll find it

rocky frigate
#

Why is non-synced local player data not a thing?

dense owl
#

If I have to guess it's to allow floating progress between milestones?
Let say you want the game to only save on level up, not every time you get exp ever, because the gameplay loop makes you always start from fresh exp. Then you can sync PlayerData only if a level up is triggered if you want. Instead of obligatory save every time it changes.

#

(this example is level up but it can be every 10% of the exp bar or whatever arbitrary gap you need)

trim cradle
rocky frigate
unique rune
#

yeah, like a local-only world setting

lime delta
#

That's what we call "player-local persistence" (or variations). It's currently not implemented, but on the roadmap - that doesn't mean it'll come right after, but eventually we might implement it.

#

Won't be in the first release AFAIK.

rocky frigate
#

That's the bit that I thought was weird, as it seems like that would have been the easier part.

trim cradle
#

as right now they are essentially just doing normal networking and in addition to that are also saving the networked info

#

Don't get me wrong I personally would have also loved local only persistence (and that's technically what I am doing), but with the way it's currently implemented that would be difficult

lime delta
#

Thing is, other than for a questionable security benefit, there really isn't much difference here. Networked persistence is a strict superset of local persistence. I.e. you'd still have the same outbound network rate limits and persistence size limits, etc. You can just ignore other player's data locally and get pretty much the same result. Or am I misunderstanding the request?

burnt knot
#

is there a way to detect if a player isnt able to store data?

#

like for example a way to make a save button appear to only those who can use it?

trim cradle
#

I feel like that would only be usefull during the beta

#

but you could use a player oebject to check that

#

if it's there they are on persistence if not they aren't

burnt knot
#

i just want to make it so my scripts dont get errors or something. what even happens if i try to save data to a player with no beta active? will that player ever run the on restored event?

#

i cant find this information anywhere

trim cradle
#

that script would just crash as the method it's trying to access doesn't exist

burnt knot
#

that makes what i want to do so so hard

#

im making a script where if the host has savedata, all players will sync with it, but in a world where the host has no savedata the object doesnt appear

#

and what it sounds like is the script will crash before even checking who is the host

subtle quest
#

If the world is reliant of synching save data with the host, the host not being on the beta would break the world regardless, right?

quaint night
# lime delta Thing is, other than for a questionable security benefit, there really isn't muc...

You're putting the cart before the horse. The limits of persistence are set because it's tied to networking. If you had persistence that's not tied to networking it'd be able to take different compromises that'd be better for persistence than if it's tied to udon sync. If you want to be flippant about your platform's security that's something you're free to do, but I will remind you that private persistence is the most requested feature for persistence on the canny at the moment. So even if security is not important to vrchat, it is important and a blocker for many of its creators.

burnt knot
#

so i made a houseplant, it uses the persistent data of whoever is the "host" of the world. The plant will grow when you are offline and must be watered at least every week or so. if it isnt watered it slowly decays over a week and halts growth. once watered again it will slowly turn green over the course of a week and resume growth

#

it will take a year for it to reach full size

lime delta
quaint night
#

please mark the canny as tracked at least

lime delta
#

Oh is it not? I don't manage the persistence beta board, but will bring it up

quaint night
#

it's not

elfin wren
#

Basically parity with the current vrcx implementation

sage raptor
#

same here, i cant do most of the things i wanted while its limited by udon sync

velvet barn
#

Networked persistence means being able to keep things consistent across devices, as well as some protection against tampering, and being able to more easily share states with others. It makes sense why they'd roll that out first.

Local storage would be great for caching things or for simpler implementation of inconsequential local states, but I'm definitely happier to have the extra functionality that networked brings first.

long mirage
quaint night
#

To be clear, if you don't need to sync it to other players immediately the contract of the API changes significantly. You can prioritize the traffic differently and take longer to sync the data while not impacting Udon sync at all. The limits are just set by VRC as they are at the moment because you expect the data to be realtime

#

local storage would be good, but VRC has not been interested it either historically

#

also on the other side, Udon sync now has a bunch of overhead that should really not need to be there in order to make sure variable name/type changes are handled properly, but is there adding that overhead to udon sync because it needs to be there for persistence

#

a lot of the concerns about Udon sync overhead that people have are because of things that it needs to do due to persistence

trim cradle
quaint night
#

I mean it could just use steam cloud for locally saved persistence yeah

trim cradle
#

Locally Saved persistence is a whole other topic

quaint night
#

I mean that's what you were replying to

trim cradle
#

I know, I was just saying that I'm fine with it not being stored locally

quaint night
#

yeah that's fine that it's not local

stark nexus
#

All I was looking for out of persistence was a PlayerData.Set/Get API for the local player that saves to a local file or VRC backend and the syncing of that data would be handled by existing Udon syncing mechanics if it was needed

#

It's like the priority of the implementation is backwards

quaint night
#

yeah, I don't know why it had to be tied to player objects, it would've been released years ago if it wasn't bundled with all the complexity there. And it means users need to understand sync to use it "properly"

lime delta
#

I actually agree with that sentiment, personally. My take right now is that the method we have in beta right now works, and does not in any way exclude adding the other way later on. I don't think what we have now is bad, just a different take on persistence. Ideally, we could have both - maybe in the future, we'll see, and not my call.

#

That said, definitely not local - any method of persistence should be on VRC servers. Steam Cloud wouldn't work for other platforms either, for example.

quaint night
#

That's fair, the point with the security concern is well, first it feels backwards like Genesis said. But my concern is also that you will need to deprecate the existing APIs if you want to make user-private persistence properly useful from a user standpoint

#

if the "new" private persistence stuff is under a name that's named like PrivatePlayerData.<function> or something that's missing the goal because people will likely default to 'public' since they don't know when to use private

lime delta
#

Will we? 🤔 I'm imagining something like a PlayerDataLocal object with the same API as PlayerData. Extend, not deprecate.

quaint night
#

as mentioned in the canny this is a "security by default" thing

#

if you don't name the APIs accordingly you are shuffling people into the option they should avoid most of the time

lime delta
#

Don't think that's missing the goal, if there's a PlayerData and a PlayerDataLocal it's easy to pick which one you want? The default is you choosing.

quaint night
#

that requires understanding of what local means. Is local saved locally? May as well use PlayerData because I want it to be saved on the server

lime delta
#

Well, naming TBD - but the idea is there I think

quaint night
#

you can go down the list of names and have similar issues. Maybe there's a name that avoids confusion

#

it'd be easier to just have it named appropriately in the first place though

lime delta
#

I'm still not convinced that local data in this sense is something that really needs to be a prominent default, there aren't that many cases where this type of security is necessary.

#

It's valid feedback though to rename it, I suppose now's the last chance for us to do so if we want.

quaint night
#

I mean it's not just a security issue, it's also just wasteful

#

why sync data to everyone when you don't need to?

#

on the contrary I'd say there aren't many cases where you need to sync persistent data by default

lime delta
#

from a creator perspective I'd agree, but from a development perspective, the synced way is still the first stepping stone towards that

quaint night
#

I mean yeah but that comes back to the backwards part

lime delta
#

As I already said, we're not against it, and it is on our roadmap somewhere (genuinely don't know where, not my department really), and there is definitely a valid point in what a default should be. But the way we built it now, making it local is additive, so we'd have to potentially delay persistence as a whole to implement it. But we can also ship it later.

#

As for the question of "was it a good idea to build it like this in the first place": 🤷

#

We won't do it over now that we're this far

#

That's mulling over the past IMO

quaint night
#

it's not mulling over the past if you had every opportunity to plan it differently. It's asking you to get your project management in order

#

it seems backwards because to most creators it's apparent that VRC chose the route that's both adding overhead to creators and adding overhead to VRC's implementation

lime delta
#

"Mulling over the past" is not meant in a negative light here, I genuinely mean it. We will learn from this and do better in the future - but as far as persistence is concerned the deal is done.

#

Hope you understand I won't debate our project management strategy here on the persistence channel with you right now though.

quaint night
#

Sure, I don't really expect it to change anything either.

stark nexus
#

Being in beta there's still time to make changes based on feedback before it goes live and becomes tech debt later

dense owl
quaint night
#

of course it'd be nice to have private persistence just be there, but it doesn't seem like that's on the table despite you having had half a year+ of people asking for it

quaint night
# lime delta As I already said, we're not against it, and it is on our roadmap somewhere (gen...

sorry, just re-reading this and I didn't want you to feel ignored about it being roadmapped. It's great that it's roadmapped, I'd be surprised of anything otherwise. However it's cheap to say something is roadmapped. VRChat said 3.5 years ago that persistence was the "next big update" and that it already had a lot of the code written. A year and a half ago VRC said it was "1.5 to 3 months, maybe." It's difficult to put much weight in something as non-committal as "it's roadmapped." Especially with the pushback you're doing instead of just telling us it's roadmapped. I hope you understand the skepticism when VRC has missed every more-specific roadmap by incredible margins on something that should be as basic as a database store and a few RPCs.

long ginkgo
#

already asked this in #avatar-help message but I thought i'd ask here as well since this is where my issue is fixed. Having some weirdness in relation to physbones + constraints, they work perfectly fine in the editor and in the persistence beta but in open beta and stock they're borked. Are the open beta and standard branches going to get updated so the physbones/constraints behave like they do in the persistence beta or the other way around?

iron shell
#

That would explain the difference in behavior

long ginkgo
#

OH .

iron shell
#

Yeah, this branch is only for testing specifically persistence and has not kept up with the other two as much

long ginkgo
#

alright then, i'll make a canny post for the bug then. Should it be under open beta or avatar bugs? (since it affects both standard and open beta branch)

#

ahhh alright, thank you very much for the info 👍

iron shell
shell frigate
#

Hi! Is persistence exclusively per-world? Would it be possible to make multiple worlds that share their persistent data?

#

Nvm I just didn't read

civic saffron
#

I hope physbones and constraints get fixed cause in persistence it works fine that means some of the models I made were effected and I didn’t know and the frieren model i got on gumroad has the issue too so its disappointing having paid for something that has issues cause of vrchat

mild dragon
iron shell
dense owl
#

I had a mini jumpscare logging into my world and first thing I see is EVERYTHING was broken!
Then I remembered I went back to normal VRchat last time because of the "instance not compatible with this version" bug each time I passed a portal (like others said, persistance beta didn't update in a while, so it's normal)

Took me a minute to realise what was going on...quite the scare. xD

lime delta
heady patio
dense owl
#

Cross-worlds (at least limited to same creator's maps to avoid people making "save rewrite" worlds made by smart cheaters) is vital to some projects, that would be great to have in the future.

#

Like mentionned in the comments, that would also be very cool for muti-map puzzles. Unlocking a tool in inventory at floor X to be able to open something back at floor Y (loor Y's save would need to check the common unlock flag) is a classic.

cunning hamlet
#

im pretty surprised persistence wasnt just copied 1:1 from roblox

dense owl
#

Never played it, how does it works there?

cunning hamlet
#

its the perfect solution imo

dense owl
#

So, straight up same save instead of one per game/world checking if they have a same variable name in common?

#

As I responded to someone in the comments, automation like that is great for game saves, but for application in causal prefabs like there are so many in VRc it may be better to use the "compare if same variable name" method. Just because you like world 1's bloom/effects settings does not means you want to use the same ones on every worlds of a same creator.

#

Something like:

Get (variable name)
instance: <world ID>

Then you set on the local one, so you can still fine-tune locally but have an easy way to make QoL button (or automate still if you want)

#

Giving both options have more value than full automation

subtle quest
#

Is FindComponentInPlayerObjects suppose to also support getting a component in a child object of a template?

If so, it appears to be bugged for me.

running this code:

public override void OnPlayerRestored(VRCPlayerApi player)
{
    if (Utilities.IsValid(player))
    {
        PlayerManager playerManager = (PlayerManager)Networking.FindComponentInPlayerObjects(player, _playerManagerTemplateReference);
        if (Utilities.IsValid(playerManager))
        {
            playerManagers[player.playerId] = (PlayerManager)Networking.FindComponentInPlayerObjects(player, _playerManagerTemplateReference);
        }
        else
        {
            LogError($"Failed to load player manager for {player.displayName}");
        }
    }
}

produces this:

#

It works fine if the PlayerManager behavior is attatched to the parent object that has the VRC Player Object script

#

And yes, I am updating the reference between the two tests

#

regenerating scene ids also does not fix the issue

olive tree
subtle quest
uncut obsidian
#

Wait I missed a few announcements, what is the open beta persistence for?

abstract narwhal
abstract narwhal
#

Sure

hardy narwhal
#

You've been very patient... so we've got something special for you: Persistence Open Beta!

First off: this beta will be operating on its own branch, much in the same way other long-running betas have (remember the IK 2.0 or networking beta?). We anticipate this one will be in testing for a bit, as it's a new, complex system and we want to make sure that everything is kosher before pushing it live.

This beta should be network compatible... with some caveats! Obviously folks outside of the beta won't be able to see Persistence-based features in worlds. Likewise, given the amount of Stuff going on, things could break. But theoretically, it should be okay!

What is Persistence?

Persistence gives world creators the ability to save certain bits of information to individual users. This allows users to essentially load into a world, and immediately have that world recognize them.

On a small scale, world creators could use this system to allow users to save their settings in their world. So, every time you jumped into your home world, for example, all of your post-processing, chair, and mirror settings would automatically load in.

World creators could also use this system to create inventory and XP systems. If you wanted to, you could theoretically create a whole full-featured RPG in VRChat.

Some of the possibilities these new features unlock go far beyond the examples here. We anticipate that creators will use persistence in novel ways that we can’t quite predict – and we’re really excited to see that happen in real-time!

For the more technical minded folks out there that might want to know how persistence works, this is done through both PlayerData and PlayerObjects.

PlayerData is a synced and persistent key-value store, perfect for storing preferences and progress that you want any script in your world to access easily, from anywhere.

PlayerObjects are objects which get automatically instantiated (with networking) for every player in the instance, similar to existing player pool prefabs and all their various uses. All synced data inside of PlayerObjects is also persistent by default, though you can disable that per-object if you want sync but not persistence.

How Do I Test Persistence?

You can jump into the Persistence open beta by selecting the persistence-beta branch on Steam (not the "open-beta" branch).

Creators! To use Persistence, you will need to download the beta SDK. To do so, navigate to the "Packages" tab in the VRChat Creator Companion. Scroll down to, "Pre-Release Packages," and check the box next to "Show Pre-Release Packages." The beta SDK is named 3.7.2-persistence-beta.1. Go have fun!

If you are a creator, we strongly suggest reading through the documentation, which you can find here!

Given the nature of Persistence, we expect this to be a very creator-heavy beta for the time being. If you aren't a creator, you eventually will have a lot of cool worlds to explore (and we'll be highlighting them, too -- so if you're a creator, keep your eyes peeled for us asking for your Persistence-focused creations!).

Where Should I Post Feedback?

Here. Also, if you're talking about Persistence specifically, please use the ⁠open-beta-persistence channel! https://feedback.vrchat.com/persistence).
VRChat
https://vrchat.canny.io/api/og/company?v=MjAyNC0wMy0yOFQxNzozNzo0My4zNzBa

abstract narwhal
#

I would've forward it here, but I don't have the permissions to do so.

hardy narwhal
#

sad...

trim cradle
#

I really hope we are going to get some persistence updates soon

trim cradle
#

I mean I read that, which is why I was asking for beta updates in here :D

subtle quest
#

when/where was that posted?

flat mist
#

dev update today

hardy narwhal
#

I believe....this

sand lantern
#

Is there a persistance F&Q?

sand lantern
#

I guess not then

abstract narwhal
abstract narwhal
trim cradle
#

hope so

trim cradle
abstract narwhal
hardy narwhal
#

ah

#

patch note time...

hardy narwhal
trim cradle
#

new SDK let's go

#

now it's waiting for patch log time

hardy narwhal
#

yeah, pi usually want few minutes so I wait few minutes then.

lime delta
trim cradle
#

Was about to say

lime delta
#

Cmon everyone, post the changelog, I want to see what changed xD

trim cradle
#

don't think this is yours yet

abstract narwhal
#

Sorry Pi, you're the Patch Notes vtuber now

hardy narwhal
#

xddd

trim cradle
#

I want another clock update video xD

lime delta
trim cradle
#

I see a strasz typing

trim cradle
#

pi got replaced xD

visual sapphire
#

we can never replace pi

idle jackal
#

👀

#

New beta

true patio
#

I'm guessing the new SDK doesn't have the contact/contraint fixes yet?

idle jackal
#

Anyways clearing a world seems pretty nice

lime delta
trim cradle
#

What build is this build based on?

lime delta
#

live, p2

trim cradle
#

I guess I'm not on the updated persistence beta branch

abstract narwhal
#

Wew, gonna finally switch back to the persistence branch~

trim cradle
#

indeed

#

my stuff is still working

#

good

lime delta
#

build number should be 1535

true patio
trim cradle
#

Well, other than Discord URLs expiring, but that's not a persistence issue

true patio
trim cradle
ornate mango
#

hey guys there's a new beta....ohh you're too fast

true patio
#

the busted constraints (when there are physbones under it) is really annoying

#

tried downgrading and all VRC constraints lost their sources

trim cradle
true patio
#

so I'm basically stuck on 3.7.3 until a fixed SDK comes out

trim cradle
#

amazing

fresh mica
#

funny face

sand lantern
trim cradle
#

:p

ornate mango
quartz raft
#

it is pretty late

ornate mango
#

btw, if you haven't seen our Dev Update yet, we're looking for more demos of cool stuff you're building with Persistence (worlds & tools!). We want to showcase more like we did last time so if you have something you'd like us to feature, make sure its on the Persistence Showcase thread vrcChicken

sand lantern
#

Dev update link goofed?

hardy narwhal
#

haha....that's internal channel mate.

ornate mango
#

...no it isn't and no I didn't fix it

quartz raft
#

you saw nothing

sand lantern
#

Mb must've imagined it

ornate mango
hardy narwhal
#

yeap, I just smiling while watching audit log, done.

hardy narwhal
dense owl
#

This said, fixes will have to wait tomorrow. It's sleepy time!

#

have a good day/night everyone

south night
#

not sure if the issue was affecting anyone else, but just incase, the broken Manual Sync using OnPreSerialization() and OnDeserialization() in the previous persistence build is now fixed with the new persistence build today (Build 1534)

stiff estuary
#

Is there anywhere to tell how much Data is currently being stored for a user?

trim cradle
#

no

#

also, if you lose or change the key for some data you wont be able to get rid of it unless the player manually resets their data

dense owl
#

what would be the best practice to keep track of that for now?

subtle quest
#

Use Player Objects instead 🙂

But if you must use PlayerData you can try keeping a text file of used keys

#

There's also ways to check all of a user's keys during runtime, but you'll have to decide how useful that is

#

Personally I'd likely organize my keys via an enum

stiff estuary
#

Hm using player objects for something like an inventory seems kinda strange as it gets duplicated for every player when I dont really have a need to show everyone else their data

subtle quest
#

Everyone gets everyone else's PlayerData as well

stiff estuary
#

true... though I feel like people having an extra bunch of objects with scripts getting turned on per player has some kinda performance impact?

subtle quest
#

I suspect that behind the scenes PlayerData works the same way as PlayerObjects

wary summit
#

If your concern is about not being able to delete keys in the future, you're always welcome to store your own json inside of a playerdata string where you have full control over it

subtle quest
#

Oh yah, good point!

silent echo
#

Also would recommend converting the string to utf8 byte[] to save space.

Now if only someone would go and remove the spaces from the json serializers minify implementation.

wary summit
#

json is a pretty wasteful standard. Would be even cleaner to write your own serializer to bytes directly

silent echo
#

True, I might move to a buffer reader/writer at some point, but json is just way too flexible for adding data without having to change the data version and dealing with reading old formats, so long as I'm only adding data.

#

Now to figure out how I'm hard crashing VRC with Udon

dense owl
#

I mean the text file, not PlayerObject. I dn't even know where to start with PlayerObject but was able to use PlayerData just fine somehow

#

Also, duplicating a store window or a mechanism on a wall wouldn't make sense, it's fine as existing locally

stark nexus
#

If you delete and recreate a GameObject in your project that previously had persistent data associated with it, would the reference to that persistent data be lost? And if the reference would be lost, where is that reference stored so it can be kept track of so it can be relinked up to the recreated object?

subtle quest
#

That is a good question. My guess is that all PlayerObject persistent data will be lost/overwritten with the new state of PlayerObjects in your project on the next save

quick imp
#

I just noticed that on runtime my persistence object is cloning itself, what object should players use at runtime the original or the instantiated clone? The clone doesnt seem to react to custom events that the original is called upon so Im kinda confused.

#

If players use the original I assume that I could just make a "Fake item" script that just yeets it out the instance but Im guessing it exist for a reason?

wary summit
wary summit
quick imp
#

So I have "PLAYER PERSISTENCE OBJECT" as the original, this reacts to udon events that I call upon and "PLAYER PERSISTENCE OBJECT [1]" that it creates, but it doesnt follow any custom events called upon by another Udon, how can I make that work for me?

wary summit
#

the original should be doing absolutely nothing

#

the one that gets instantiated is where you should be running stuff. Here are a couple examples:

If you were to have a playerobject with a mesh and a script that on update did transform.position = Networking.GetOwner(gameObject).position then everyone in the instance would see that mesh at every other person's feet. Because even though there is only one copy of that mesh in your world at edit time, it gets duplicated for every person who joins, and ownership is assigned to them.

If you were to have a playerobject with a script that on start did Debug.Log(Networking.GetOwner(gameObject))
then what you would see is every time someone joins, their name gets printed to everyone's log. This is because everyone is running that code on the object that they just instantiated for the player that just joined

#

The original does not get an owner and instead gets disabled at runtime, so it should never be seen nor should it run code. It is only used as a template

dense owl
#

That sounds complicated compared to how PlayerData works...
For multiple players interacting with a same button I just use Player_ID to switch owner before the calculations starts so it knows to whose data to write to.

quick imp
#

This is troublesome news... I have a thing where you can teleport your object to you, it works for the original, but the clone that you say is the one that should be used doesnt teleport...

#

Should I destroy the original on runtime leaving the clone behind will that work?

wary summit
wary summit
wary summit
quick imp
#

Its already enabled, I do a custom event to teleport it to myself

dense owl
wary summit
#

Do you have a script somewhere in your scene which has a specific reference to the playerobject at edit time?

dense owl
#

I know that clones don't react to checking exact name because they have this "(clone)" part added to the name, it used to throw me off xD

quick imp
#

Yeah... how am I going to call upon the cloned objects Udonbehavior if its targeting the original

subtle quest
wary summit
#

don't use a scene reference directly to the original. Instead, you can use Networking.GetPlayerObjects and sort through to find the correct one, or you can use Networking.FindComponentInPlayerObject to translate a specific component from the original to the clone associated with the player provided

quick imp
#

ACTIVEOBJECT is the original object in question, Im guessing you are suggesting I replace the ACTIVEOBJECT with what you mentioned above?

wary summit
#

yes

#

so you do set it active

quick imp
#

I did before and thought that was causing the bug, removed it and was doing the same thing so i enquired lol

wary summit
#

you shouldn't need to set playerobjects active manually

quick imp
#

Like this? Im not too sure

wary summit
#

Not exactly. You should plug the localplayer into the GetPlayerObjects function instead. That will then give you an array of all the objects associated with that player. If you only have one playerobject, then you can just access the array at index 0. If you have more playerobjects, you'd have to sort through the array to find the one you want

quick imp
#

Oh you can have more than 1 networed object per player? interesting...

wary summit
#

yep

#

Alternatively, you can go the other direction and have the playerobject reach out to this script to tell it which one is correct. Would be something like start > if networking.isowner > udonbehaviour setvariable to self

quick imp
#

Alright nice, thats working, so what ive done is Ive just disabled the original object is this fine?

wary summit
#

doesn't matter. It gets disabled by the system anyway

quick imp
#

it didnt, I had 2 of them just hanging around in the runtime

wary summit
#

probably something else you're doing somewhere

quick imp
#

Unless maybe now that its set up correctly it will? lets see...

#

Oh yeah it does, nice

#

Well thats cleared up alot, thanks

dense owl
#

I feel like trying to "port over" my old graphs by just adding additional checks for persistance is damaging everything...
Or at least confusing me more than anything. Maybe I should just start over, everything was working fine except completion checks, and now everything is a complete mess

leaden flax
#

any news on a potential executionorder fix? it still behaves weird on the persistent beta and sdk

wary summit
leaden flax
#

same on the right picture

wary summit
#

so the issue is simply that it's different between client and clientsim?

leaden flax
#

not exactly, both behave different and both behave wrong in my opinion

wary summit
#

Neither is really guaranteed to initialize in any particular order right now

leaden flax
#

oh... that is unfortunate, especially as live behaves as expected when each scripts has an executionorder value defined (including subclasses)

wary summit
#

It really doesn't sweat

#

if it's behaving as you expect, that's probably more by chance than by it actually working

leaden flax
#

that is either very lucky on my end or idk... I have tested this extensively

wary summit
#

it's definitely something we want to fix, but it's deep at the core of some of the most fragile systems. Multiple people have tried heh

leaden flax
#

is there a chance that this will at some point be deterministic? 😅

#

I don't want to have to create my own system in udon, that would be bad

wary summit
#

I recommend that world creators just build scripts which are resilient to any initialization order. It's not too difficult, all you have to do is move your initialization code to a separate function, like this:

private bool isInitialized;
private void EnsureInitialized()
{
  if (isInitialized) return;
  isInitialized = true;
  //All your other initialization things like populating arrays, getting components, etc
}```

Then any time you have an external function that might come from another script, you just do ```EnsureInitialized()``` before trying to access the things that needed to be initialized
leaden flax
#

In that case, please remove the the ability to use DefaultExecutionorder attributes 😄

wary summit
#

default execution order is still valid for the execution time within a frame

#

it does not explicitly indicate that it applies to initialization order

leaden flax
#

ok, let me check if what I saw up there is multiple frames, this is new info

wary summit
#

once all your udonbehaviours have been fully initialized and you're running normally, execution order should be respected

leaden flax
#

to my understanding all my scripts of that screenshot are enabled/active in the scene when it initializes, that is why I was so confused to see some init late (in respect to the order value)

wary summit
#

yes, if you have a lot of udonbehaviours, it takes multiple frames to dispatch the initial starting event to every single one

leaden flax
#

you are right

#

this is 2 frames

#

I don't know why though

#

do maybe child objects get initialized one frame delayed?

#

that would make sense

wary summit
#

because the thing that triggers initialization is a coroutine of some sort and when it's taken too long in one frame, it waits till the next

leaden flax
#

ahh more information, thanks!

#

slowly I understand the current behaviour I am seeing 😄

wary summit
#

here, I've got a prefab I can send you that might help make more sense of it

quaint night
#

udon 2 should fix it unless it's been re-broken

leaden flax
#

hopefully.... 🤞

quaint night
#

went out of my way to make sure it follows Unity behavior

leaden flax
#

I was hoping for that to be the case here, ngl 😄

quaint night
#

👉 👉

gusty elm
#

Udon 2 might as well go back to being a rumor at this point 😢

leaden flax
#

regarding the initialization across multiple frames: it looks like it stops after 10ms and continues in the next frame (I can see that in that log shown in the picture)... my question is now only, why does for example the WorldVersionEventListener, that initializes in the second frame, not get initialized in the first frame? I see that within the same frame the order is correct, but I don't understand the criteria for slicing then I guess...

subtle quest
#

I imagine we'll see Udon 2 sometime in the first half of 2025

stark nexus
#

Udon 3 when?

wary summit
leaden flax
#

I hope this behaviour will align with Unity's behaviour in the future, alternatively documented. A lot of the features require kind of trial and error to find out the actual behavior and that can be pretty time-consuming...

wary summit
#

And that's exactly why it is much easier and more reliable to say that initialization order is simply not guaranteed and you should build scripts that do not depend on any specific order

quaint night
#

it's because the coroutine checks if direct children have network behaviours (udonbehaviours) on them and yield their update to the next frame

#

it's a VRC thing, Unity would never initialize stuff across frames in the default case like this

#

I think orels tested it way back for that old canny and if you don't have it be a direct child, like have GameObject (with udonbehaviour) -> GameObject (without udonbehaviour) -> GameObject (with udonbehaviour) then it actually wouldn't yield

idle obsidian
#

Do I have to make sure my world was uploaded with the latest Persistence SDK in order for data to save?

#

I was testing a shop system and a few new things.
But after rejoining, everything was wiped :<

trim cradle
#

No it should still work

#

My stuff is fine for example

quick imp
#

I think Ive encountered a bug with the persistence object, So I have a gun as the persistence object with a limited magazine size of 6 right, so I fire the gun and I have it with a "Magazine" Int = 6 I run it through a Subtraction of 1 and set itself as its new value from Get "Magazine" Subtraction 1 Set "Magazine"

Its new value wont correct to 5, it just stays as 6, I had the same issue with total ammo only this time it was setting itself to 0 no matter what I did so I fixed it by having it read the ammo value from a different source other than itself, I swear this isnt something on my end unless I'm going crazy

#

Left works, right is bugged

quick imp
#

I also seem to have run into what I think is another bug with Udon in general? Its kinda hard to explain, Please observe the images

#

This is again on persistence object, not sure if its a related issue with non persistence but seems kinda wierd I have to go the unoptimised route of using the same node 3 times next to each other.

coral creek
#

Will persistence allow custom spawn locations. Curious because it might be useful on worlds with sleeping rooms

dense owl
#

For things supposed to trigger only once I usually have a bool, the event triggering "if false" then immediately setting it true (normal bool, not persistant)

coral creek
#

But others wont respawn there by default

dense owl
#

Well yeah, they would have to register the checkpoint like you did

#

which can be done with a button to press or even entering the room (trigger area)

#

if you want automation

coral creek
#

Because it would be an interesting concept on worlds made for their group specifically where the group has a house and the players will spawn, respawn or rejoin on their bedrooms.

dense owl
#

I did a checkpoint system a while ago, basically an array of trigger areas taking their transform as reference and one variable to say "set reference to X", X being the index of array. then OnRespawn TP the player to reference pos/rot

#

it's a pretty crude method but so far I never had problems even with 20+ checkpoints in my world

#

only the reference variable would need to be saved in persistance (then called like I told before)

#

again, you can make buttons to register a room instead of automated trigger areas

coral creek
#

Yeah

#

Thats what I am thinking

dense owl
#

Should be pretty simple, it's more of a matter of planning xD
Now group/VIP rooms it requires a bit more work to know if the player is valid or was just a guest

coral creek
#

Lets say I want room 1 in the group world

#

And I press that button and it'll assign me to the room 1 spawn. So, every time I join or respawn, I should go back there

dense owl
#

yup

#

OnPlayerRestored (when your PlayerData finished loading) trigger once on start

#

then OnRespawn if you want to also be a checkpoint

#

you just TP there

#

For stuff like that it's important to not overthink it, just make it like normal and use a persistant variable instead for the one part that need to be saved.

dense owl
# coral creek Lets say I want room 1 in the group world

One thing to be aware tho, I know it since we had the same dillema when working on hospitality mechanics for Aincrad, is that anyone can register the same room on another instance then end up inside yours when they join your instance. Which...Can be annoying. xD

#

That's the whole reason why rental/inns arn't a thing in 1.6 actually, since we know that would create drama in the community if multiple players want the same house (even tho there's WAY MORE houses than we can host players)

#

One idea would been to rent houses via the Patreon subscription since the bot can keep track of that, but that would create a precedent too close to P2W so we did put the whole system on the backburner

#

For other worlds who don't have our kind of concerns that's not a problem tho. If the button is only valid for VIP, only VIP can register there, the end

#

Dunno how well being in a specific VRc group can be tracked, but if it's possible that also can be a condition

#

Now whitelisting individual people is tedious, and names can be spoofed (so many players abusing the admin menu... xD)

leaden flax
coral creek
#

Its a group world after all.

#

I could have say have the buttons locked behind a code that only certain members can know

dense owl
#

or codes yeah, that usually works too

coral creek
#

Yeah. I am not a guy who would start monetizing on worlds

#

That would be mostly the monetizer problem

dense owl
#

Isn't there a "become member for free" option? Like, equivalent to just a follow? Or does it always starts at 1$?

coral creek
#

I would see VRChat doing in the future

dense owl
#

Well, you could see how things works for VRCgroups, if you can already track this there's no need for external bots

coral creek
#

Allowing creators to lock behind spawns behind a paywall

dense owl
#

again checkpoint systems are very simple, it's all the planing around additional features that is complicated

coral creek
#

Yeah. But I want to make it simple

dense owl
#

on the user's end it will always be simple, it's on the creator's end that the planning is always complex

#

I'm not VRC+ so I have no experience with managing groups, but you should investigate to track something on that side

#

should be the easiest for your users

#

if it's possible to check that in the world

#

Actually...I did a "give score to same team" button in my idle game, maybe I should look into "give to same (represented?) group members" at some point xD

sonic swift
#

I wanted to try out the "RPG" example, in Example Central. My SDK isn't providing it. Is this a Persistence SDK closed beta feature at the moment? I'm not sure if it's available yet, let alone, I'm not sure how to switch the SDK version to open beta, if I need to.

unique rune
#

(not sure where it is, I don't use VCC)

idle obsidian
wary summit
#

Local testing for example is only persistent within the same client launch. Once you close vrchat, it loses the record

trim cradle
idle obsidian
#

Oop sorry, I had a lunch.

So it seems like neither Data nor Objects are saving (again)
I am aware you can only rejoin the world to test if persistence works in Local testing, and that didn't work.

And yes, I am on Persistence Beta, not Open Beta

idle obsidian
#

The only thing I think I remember doing is that I had previous save data before updating the world, but I would imagine it wouldn't matter as long as I hadn't changed the variables names or key names

idle obsidian
idle obsidian
#

I'm not sure if I am a big fan of the lack of errors that VRC reports around Persistence
It works in Unity. It does not work in Local Testing nor Client and I get no errors about it on the logs.

trim cradle
#

What are you even trying to do?

idle obsidian
#

I'm simply trying to use PlayerData and Objects, nothing crazy :S
I'm RequestingSerialization once values are set for Objects, and for Data, I am very certain the values and keys are there.

#

Nothing is saving, nothing throws an exception, nada

dense owl
#

I am working on my own world and saving works locally

dense owl
#

that could explain the lack of errors

#

since there's none, just you not reading the data

idle obsidian
#

I am infact, waiting for OnPlayerRestored to fire in my scripts yes!

dense owl
#

that's only when loading, like player rejoining

#

OnPlayerDataUpdated is for just after you do an action

#

these two threw me for a loop at the start...I didn't knew about OnPlayerDataUpdated so it was only updating on rejoins xD

idle obsidian
#

Wait, OnPlayerDataUpdated?

dense owl
#

yes, that one fire when PlayerData have any changes

#

I don't know how it works with PlayerObject tho, I only messed with PlayerData so far

idle obsidian
#

I dont think I would have a use for that then. All PlayerData is handled as if it was local data

#

I don't have a need for Persistent elements to also do networked sharing. For that I would use another script to actually relay just what I need

dense owl
#

I'm not sure what you mean, this thing comes attached with a way to check for local player only

#

so it just operates locally as normal when you do that

#

it just fire whatever comes next when your data changes

#

(ignore the thing under, basically the same but "trigger only once" because I had a loop (upgrade_action) to set on this graph)

#

OnPlayerDataUpdated is what fire during normal gameplay

idle obsidian
#

So wait

#

I think this is not what I need? Because I thought you could just use GetKey and SetKey

#

Or sorry

#

Set[Variable] and Get[Variable]

#

what the fuck?????

dense owl
idle obsidian
#

So here's how I am saving achievements rn

#
 public void WriteData()
    {
        if (!gotNewAchievements) return;
        if (manager.playerManager.pp == null) return;

        byte[] saveData = new byte[savedAchievementsDate.Length * 8]; //LONG SIZE = 8

        Buffer.BlockCopy(savedAchievementsDate, 0, saveData, 0, saveData.Length); 
        PlayerData.SetBytes(unlockedDateKey, saveData);

        gotNewAchievements = false;

        Debug.Log($"[ARENA ACHIEVEMENTS] Saving Achievements");
        manager.Log("Saved BattleSquare Achievements!");
    }
#

pp = PersistencePass. So players not in the beta won't crash here

dense owl
#

are you sure the problem is in saving? not accessing the data during normal gameplay?

#

because I had the same problem of "saving not working" then I added a text object with Get[Variable] and they were updating, but only on rejoin because I was only using OnPlayerRestored, not OnPlayerDataUpdated

idle obsidian
#

Oh, I have lots of text elements to check if its working

#

And I print out in the logs directly what are the values when I load in

dense owl
#

"when you load in" as in OnPlayerRestored or any change?

idle obsidian
#

OnPlayerRestored

#
2024.11.16 01:56:53 Log        -  Games = 2
        Wins = 1
        Score = 5
        Coins = 84

        Knight Games = 2
        Mage Games = 0
        Bandit Games = 0
       
2024.11.16 01:56:53 Log        -  [ARENA ACHIEVEMENTS] Saving Achievements```

All this tells me, is that I know I should have all this data for my stats, and that the achievements did in fact save, because I got one of them.

But after reloading the wooorld....

2024.11.16 02:00:46 Log - [ARENA ACHIEVEMENTS] Player was local with name Kinix~
2024.11.16 02:00:46 Log - [ARENA SHOP] Player was local with name Kinix~
2024.11.16 02:00:46 Log - [ARENA STATS] Player object found for Kinix~!
2024.11.16 02:00:46 Log - Data Connected OK
2024.11.16 02:00:46 Log - [ARENA STATS] Loading Data
2024.11.16 02:00:46 Log - Games = 0
Wins = 0
Score = 0
Coins = 0

    Knight Games = 0
    Mage Games = 0
    Bandit Games = 0
#

And keep in mind the message [ARENA ACHIEVEMENTS] Player was local with name Kinix~ fires when OnPlayerRestored actually fires off

#
public override void OnPlayerRestored(VRCPlayerApi player)
    {
        if (!player.isLocal) return;
        Debug.Log($"[ARENA ACHIEVEMENTS] Player was local with name {player.displayName}");

        ReadSaveData();
    }```
#

And this for reading the data

public void ReadSaveData()
    {
        if (loaded) return;
        if (manager.playerManager.pp == null) return;

        if (!PlayerData.HasKey(Networking.LocalPlayer, unlockedDateKey)) return;

        byte[] readData = PlayerData.GetBytes(Networking.LocalPlayer, unlockedDateKey);

        var size = readData.Length / 8; //LONG SIZE = 8

        for (int i = 0; i < size; i++)
        {
            savedAchievementsDate[i] = BitConverter.ToInt64(readData, i * 8); //LONG SIZE = 8
            //if (savedAchievementsDate[i] != 0) Debug.Log($"[ARENA ACHIEVEMENTS] Achievement {achievements[i].achievementName} was recovered");
        }

        loaded = true;

        Debug.Log($"[ARENA ACHIEVEMENTS] Loading Achievements");
        manager.Log("Loaded BattleSquare Achievements!");
    }
dense owl
#

just try using it at least to test if it works..

#

if it don't at least you can rule it out

idle obsidian
#

Sure, I'll try it out if it helps to rule it out then

#

Though, still unsure how I should go about this

#

Let me dive real quick in the documentation

#

I dont think I really should use OnPlayerDataUpdated

#

This is mostly useful when others need to know if your data got updated or not, which I don't need to do so

#

I appreciate the help though

#

I also want to point out that "[ARENA ACHIEVEMENTS] Loading Achievements" is a log message that should happen, should I have the key for the array of achievements, which does not fire, meaning the PlayerData does not get saved at all.

You could argue that magically my array turns 0 and that's why there's nothing to read, but even if the values were gone, the key should've existed before rejoining the world, thus the KEY and PlayerData and everything just disappears

#

This behavior actually reminds me of the same behaviour I had when they announced Persistence hadn't been working properly for a weekend.
My previous save file seems to load, but anything new I do does not save.

silent echo
#

You should print debug statements on if (loaded) { Debug.LogWarning("Already Loaded"); return; } and if (!PlayerData.HasKey(Networking.LocalPlayer, unlockedDateKey)) { Debug.LogWarning("Missing unlockedDateKey"); return; } to make sure you are not just abandoning the function

#

and I assume that unlockedDateKey is a const?

idle obsidian
#

Correct!

#

and sure, I'll try jamming some debug logs in there

dense owl
#

it's important to actually rule out stuff like that

idle obsidian
#

Okay let me try something for reals then. I'm simply going to print out the values I get from that method then

#

But like I said, I have no use to read the data across the network

#

nor do I want to

dense owl
#

again, you can set it to fire only for local player

idle obsidian
#

Let see

dense owl
#

I just know I had a similar problem and it solved it on my world, no idea about specifics

idle obsidian
#

Well, I think I found the problem and I am really upset about it

dense owl
#

what was it?

idle obsidian
#

To appease for having the world be compatible with stable build I had a system to give players an object. This object would bind itself to the manager after OnPlayerRestored but due to a certified Race Condition™️ moment, they scripts that loaded in the data executed FIRST before the one that binds itself to the manager, essentially acting as the gate keep

#

Now here's a fun one.
PlayerData is saving and Restoring.
PlayerObject is not saving and/or restoring

silent echo
#

do you have the enable persistence component on the player object?

dense owl
idle obsidian
#

Only the Player Object, since I dont want it to have any persistence data

#

The docs say its optional to add the persistence component to the--

#

Oh sorry, if you are refering to the actual object with data

#

Yes, yes it does

silent echo
#

so any field in any udonbehaviours on the object or it's children that has the UdonSynced decorator should be restored

idle obsidian
#

Yep, everything has [UdonSynced]

#
  Wins = 0
  Score = 0
   Coins = 0

This runs directly on the script with the Persistence and PlayerObject Components

#

And it prints me out the values

#

and yes, I am also doing RequestSerialization();

silent echo
#

You are not reading/writing to any of the UdonSynced variables before OnPlayerRestored is called right?

#

none of the values are guaranteed to be sane until after OnPlayerRestored

idle obsidian
#

Nope, in fact its the job of the PlayerObject to send over the saved data to the stats manager

#

er

#

PlayerObject Script send data to a non-Persistence Script

#
    [UdonSynced] public int d_totalGames;
    [UdonSynced] public int d_totalWins;
    ///And lots more variables

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

        Debug.Log($"[ARENA STATS] Player object found for {player.displayName}!");

        ConnectStats();
        LoadStatsData();
    }

    public void ConnectStats()
    {
        statsTracker.saveData = this;
        Debug.Log($"Data Connected OK");

    }

    public void LoadStatsData()
    {
        if (loaded) return;

        Debug.Log("[ARENA STATS] Loading Data");

        statsTracker.totalGames = d_totalGames;
        statsTracker.totalWins = d_totalWins;
        ///Etc for loading all the values
#

Restoring, checking if I am the owner of the object so I know its my PlayerObject
Then I link it to the statsTracker that displays all the data and interacts with the rest of the systems.
And then try to load the data, which I see the debug log every time

dense owl
#

On another note, I finally finished fixing my stuff (at least for solo player, not the networking...) so one of the goals will be reachable and saved. The other goal already worked.
I only need to work on saving the inventory and it will be finished

#

but it's 2am so it will have to wait for tomorrow

idle obsidian
#

don't work too hard too late :P

#

I know I regret it sometimes

dense owl
#

I really wanted to get this part out of the way today xD

#

even if the inventory isn't saved, players can sell their stuff before they leave so at least it's converted into gold

#

So, in a way it's still playable

idle obsidian
#

Well now I know how to make my PlayerData work, but I'll keep hoping to find a way to make PlayerObjects work for me

dense owl
# idle obsidian Restoring, checking if I am the owner of the object so I know its my PlayerObjec...

Oh, right the ownership transfert for Set. I forgot about that one!
Yeah for the "pay X points to give score to all players" event I had to make a loop going throught all players and it only worked if switching ownership each time before the calculation, because of how Set[Variable] works in PlayerData.

Right, it doesn't have a "player" thing to attach to unlike the Get[Variable] function...
I forgot that could been the issue, just assumed that was already checked correctly since you sounded you know what you're doing better than me. ^^"

dense owl
#

Inventory finally made to be persistant. Ok I'm done for now, time to update and see how broken it is in live build... ^^"

barren elbow
#

I don't see this "bool value" in the docs. is this bool not supposed to be here or am I failing documentation reading comprehension

dense owl
# barren elbow I don't see this "bool `value`" in the docs. is this bool not supposed to be her...

To be fair, bool-related nodes having a "value" part or not have been inconsistant.
I wish it was in all the related not to not have to call a bool.const every time I want to set one. 🤣

But in the case of this node, it have two parts on the right side.
If I had to take a guess, it compares the key's state to the "value" state, for the "bool" on the right like a built-in bool.equality check?

#

Like, not just get the value (top part) but compare that it is the intended result (bottom part) as well?

wary summit
#

We could remove it but then if anybody happened to plug anything into that value, the update would break their graphs heh

dense owl
#

better remove it early before even more people use it then xD

subtle quest
#

Way too late for that I imagine

dense owl
#

Well let's hope it will be fixed for the Udon2 version then... 🐸

subtle quest
#

You'll probably be allowed to use normal lists and dictionaries once Udon2 comes out

wary summit
#

it's not specific to playerdata, it's across the board anything that has out params

thorny bough
#

How can I program a button so that when a player presses it, the code calls a custom method inside a script that is a part of a their pooled object? I'm confused on how to have the code reference that specific player's pooled object to call a custom method for them only.

errant token
thorny bough
#

Great, this worked, but I have another issue:

Player Objects instantiate in the Unity Editor but not in-game. It's as simple as that and I am clueless as to why.

#

I figured that having this component on my desired pooled object should handle everything effectively. Am I missing something?

wary summit
thorny bough
#

I'm using Boby's World Debug Inspector to make sure that everything is working as intended in-game

wary summit
#

That inspector is likely not updated to support playerobjects. There are no functions exposed to udon to just get all objects in the entire scene, so these tools tend to build a list of objects at build time instead. So anything instantiated at runtime will not be found. It's possible that if you're seeing it in editor in this tool, it's just because during the build process to enter play mode, the instantiation of player objects happens before the inspector gathers a list of all objects

#

this inspector would need to be updated to utilize Networking.GetPlayerObjects and add them to the list

thorny bough
#

I see, another thing probably worth noting are these errors that appear.
[ClientSimPlayerObjectStorage] Error saving PlayerObjects: Sharing violation
What might be causing these?

#

They appear after a few seconds during runtime

wary summit
#

did you open the json files in another app and leave them open?

thorny bough
#

That's weird, I don't recall ever touching them at all

wary summit
#

could try restarting windows

thorny bough
#

Same issue occurs after restart

#

There's so much wrong going on at this point I'm confused as to what is causing everything

#

Could it be the way I laid out the prefab? I'm re-reading the message underneath the VRC Player Object component and am wondering if how I did everything is the reason things are just falling apart

olive tree
#

do you have enable persistance on anny object?

thorny bough
wary summit
#

you're not trying to run play mode inside of prefab mode are you?

thorny bough
wary summit
#

ok that's fine

olive tree
#

are you running a hdd? or an ssd?

thorny bough
#

hdd I believe

olive tree
#

ah

#

i think i know whats going on, you can create a ticket on canny

wary summit
#

try doing a simple interact > getplayerobjects > debug log the names of everything it gives you

thorny bough
#
    public void Interact()
    {
        var playerObjects = Networking.GetPlayerObjects(Networking.LocalPlayer);
        foreach (var obj in playerObjects)
        {
            Debug.Log($"Player Object found: {obj}");
        }
    }
wary summit
#

and how about in client?

thorny bough
#

Huh, it throws an error

thorny bough
wary summit
#

oh... you're not in the persistence beta

thorny bough
#

oh...

#

Yup... that 100% fixed it!

#

Let's just pretend that never happened alright?

#

Hah...

wary summit
#

😅

subtle quest
#

Reminds me of the time I almost made a canny after I failed to notice the if (objectReference = null) in my code

graceful compass
#

time for this channel to grow up

dim hemlock
abstract narwhal
#

We do it! vrcLike

trim cradle
#

they grow up so fast 🥹

meager minnow
#

Growing up, son

lime delta
trim cradle
#

no really lmao

#

my stuff has been ready for like half a year lmao

idle obsidian
#

Is there a way as creators to reset every player's progress at some point? Or would we have to reupload the world entirely?

light laurel
#

ooo a new channel i have access to

meager minnow
#

That's also a good question

#

I wonder if it's gonna be a hash setup to clean everything

ripe kindle
wary summit
idle obsidian
#

Ahh hmmm

#

Okay, I guess I hadn't prepared my world for that kind of setup

#

If I get logs that says that the network run out of IDs, what does that mean?

wispy latch
#

Wow didn't expect it to go live already >_>

#

And the init order is actually an interesting change.

#

Looks like I have to do some testing so stuff still works lol

light laurel
#

wait i didn’t even realize it went live, i just read the announcement

#

that’s a surprise, but a welcome one

#

(especially because i just started a project that has a couple systems that rely on persistence features)

#

so, persistence question now that it’s live. i have a world where, every two minutes an object spawns from an object pool, and there’s about a hundred of objects that can be unlocked. these unlock globally, using a timer run by the instance master, and once a duck gets spawned, it enables a bool on a script unrelated to the spawn timer one, so i should be able to save that as playerdata pretty simply.

my question being; if the ducks unlock globally, how would persistence work for individual players? as in, if someone who has never been there joins an instance where every duck is unlocked, would all of them automatically get unlocked and saved because it’s globally synced?

second part of the question, if a new player has a player with everything unlocked that joins, would that then spawn all the ducks, because they had it unlocked with their playerdata?

#

actually, thinking about it now, i could just have the unlocked objects index UI be persistent, but not the physical spawned ducks. but i think it’s still a good question if the spawning was controlled by persistence

subtle quest
#

I'm pretty sure persistence data for a specific player can only be changed/saved locally for that player. With that said, if you have a globally sync variable that triggers local code that modifies each person's persistence data, then yes you would be modifying everyone's persistence data

light laurel
#

okay, that’s what i expected, but i thought i should ask.

practically though, i’ll just make the UI for unlocked objects locally persistent instead

abstract narwhal
ripe kindle
#

Notably here

idle obsidian
#

Huh so, a lot of things have broken my world and I have no idea why. From the last beta to this release, something changed that caused really strange and unusual behaviours to happen.

I'm just starting to investigate, but the weirdest errors I encountered is being unable to get data from synced scripts that always Serialized properly to other players (for example, joining a world does not give me the state everything should be on, when it normally did)

abstract narwhal
#

Relevant?

sand lantern
#

Can't wait to make people's late night drunken antics be stored for when they come back the next day

idle obsidian
# abstract narwhal Relevant?

Im aware. I wished this was given to us before the update. I suspect some worlds will get hit by this change out of where. Though I think my problem still relies else where

#

Mobile moment

subtle quest
#

Wait after all this time they were finally able to fix the Start order bug? Based.

abstract narwhal
#

I wish it was easier to tell if a world supported persistence. :'v

wispy latch
#

OnPlayerRestored isn't called for me 🤔

#

It works in ClientSim but not when I do local testing or live

#

Turns out steam never gave me the new update... It appeared after I restarted steam though. I haven't had to do that in a while.

trim cradle
abstract narwhal
#

Worlds need a feature list like avatar details does. :v

trim cradle
#

that would be cool yeah

elfin wren
#

Guess its time for me to implement vrcx migration into ls media and enable persistence for settings dead

abstract narwhal
#

From the world author (GiGiSpahz) of Ocarina of Time 3D world

#

They're not in this Discord so I'm just forwarding this from them.

trim cradle
# abstract narwhal

OnDeserialization currently is fucked up for late joiners when doing stuff in the actual client

#

My world also no longer works due to that

abstract narwhal
#

How do you interact? Raycast doesn't seem to work on the sync tests?

elfin wren
#

Pushed the persistence update for LS Media, migrates all saved settings from vrcx -> vrc too when loaded

#

:3

trim cradle
#

Everything runs on start, the only thing you can interact with is the force sync button

abstract narwhal
#

Oh lol

trim cradle
#

the point of this test was to see if syncing stuff on start works

subtle quest
# trim cradle OnDeserialization currently is fucked up for late joiners when doing stuff in th...

Funny thing a year ago I actually discovered that networking doesn't work properly on the very first frame an object becomes active, maybe even more frames after. I had assumed at the time this was a known and expected behavior.

I wonder if doing networking stuff in the Start of objects active during scene load only worked because of the previous bug that caused scene load Start functions to take a few frames to trigger

quaint night
#

It's possible, I ran into and fixed that issue when I fixed the initialization order stuff for udon 2

subtle quest
#

Did it get fixed for Udon 1 as well?

quaint night
#

I mean yes, but the udon 2 branch never got merged so that's a moot point

trim cradle
#

Udon 2 still feels like a distant dream…

rocky frigate
#

😦

#

So regarding OnPlayerRestored, how do I check if a specific key was just restored? I'm reading the docs but not connecting the dots right now.

trim cradle
#

I mean if it’s restored than every key has been restored

rocky frigate
#

Won't work.
"Wait for the OnPlayerRestored event before using Player Data. OnPlayerRestored indicates that the player's saved data has been loaded and is safe to access with Udon. This event will run even if you have no save data."

#

I guess trying to read something would be null if it's never been saved before, so that should be enough.

subtle quest
#

Oh, are you actually asking how do you know if a player actually has a specific key not not?

rocky frigate
#

Yes

subtle quest
trim cradle
subtle quest
#

Also it looks like they wouldn't return null but instead a default value

trim cradle
#

OnPlayerRestored essentially just behaves as OnDeserialization

#

It just tells you you can try to do stuff now with the data

rocky frigate
#

Which is a pain as that means I won't know if it's unset, or set and was the default value for that type. That seems like a terrible choice :/

trim cradle
#

It doesn’t guarantee that it’s vaild

trim cradle
rocky frigate
#

Ah "If default values are undesirable, use TryGet or Queries to distinguish default values from missing keys." sorry I was going by the "would return default value" comment.

trim cradle
#

That’s the case for if you use get

rocky frigate
#

What is the correct usage of TryGet in this context? I can't find any examples anywhere and the existing Unity TryGet functions looks like they work differently.

velvet barn
#
if (PlayerData.TryGetInt(player, "savedInt", out tmpInt)) {
  Debug.Log("Loaded non-default int value of " + tmpInt + " to tmpInt");
}```
#

Basically the function itself returns a boolean of whether or not the value was found. The out param then assigns the returned value to the specified variable (in this case, tmpInt). The assignment only occurs the function would return true, so:

PlayerData.TryGetInt(player, "savedInt", out tmpInt);
Debug.Log(tmpInt)```
In this case if "savedInt" is valid, then `tmpInt` will be set to the value. Otherwise it wont be set/changed.
velvet barn
subtle quest
trim cradle
#

Wait

#

aren't the persístence docs merged into the main docs yet?

subtle quest
#

In my testing for an object active on scene load, when I attempt to serialize at start it doesn't work. But when I attempt another serialization on the frame after it goes work. Also the logs look like this between those two attempts:

trim cradle
stable crane
#

i was reading the persistence docs and i have a question about the keys, specifically the part where it says they can be overwritten but not deleted. what should happen in the event that a key and its data is no longer needed/used by the world? quick hypothetical example: if you had a feature that used PlayerData but the feature is removed from the world completely, what should happen to the remaining PlayerData keys that it previously used (but no longer does)?

trim cradle
fresh narwhal
#

I was looking around for a bit and couldnt find an answer, does persistence data reside in the user's local cache? If I clear cache, will I lose the persistence data?

idle obsidian
#

This data is stored on VRChat's servers and is accessible on all platforms and in all instances of the same world. It is connected to the user's account, so if a player visits a world on a completely different device, Udon will still have access to that world's data.

fresh narwhal
velvet barn
fresh narwhal
#

also hi vincil 👁️ 👃 👁️

subtle quest
subtle quest
stable crane
stable crane
subtle quest
#

you can also use player objects instead (probably the recommended choice)

or use a single key that is fed a JSON serialized DataDictionary

or use an enum to link keys to desired set values, making keeping track and recycling keys easier

#

But if you're not planning to get close to the data cap it probably doesn't matter much

stable crane
#

oh using a data dictionary on a single key sounds brilliant, i definitely want to try that

#

thats an interesting approach to bridging the keys and data, the enum to link the keys and values

#

yeah idk if my current project will get close to the cap but i would hate to run into a wall later on

subtle quest
#

I also use enums a lot for organizing my data in DataDictionaries

downside though is that it involves a lot of manual casting to turn enums into DataTokens

#
dataDictionary[new DataToken((int)Enum.EnumValue)]
#

every time you want to use an enum key, lol

stable crane
#

vrcAevSlap i could actually really use enums in this project

#

much more work to do

#

thanks everyone for the help i have a better idea of how to plan what im doing now vrcHappy

light laurel
#

how expensive of an operation is PersistenceUtilities.GetPlayerObjectComponent<>(Networking.LocalPlayer) ?

for example, right now i’m instantiating objects every ~8 seconds, and each one has a script that runs GetPlayerObjectComponent on start, so that it can access the local player’s healthbar script. is this bad, if so is there a better way i could do it?

wary summit
fading totem
light laurel
fading totem
#

Pretty much!

light laurel
#

yeah that sounds more optimized

#

what i’m doing is, it starts off spawning them every 12 seconds, but as you play and increase a “power level”, it decreases the time it takes for obstacles to spawn, probably up to 0.1s at some point

#

so i can see the use of GetComponentInPlayerObjects being excessive after a while

elfin wren
#

Btw for like the 5 people who were using vrcx persistence beforehand

#
    public void doVRCXMigration()
    {
        if (!vrcxPersistenceManager.Initialized || !_loadedInitialPersistData)
        {
            Debug.Log("[VRCX Migration] VRCX not initialized or initial vrc persist data not loaded");
            Debug.Log("[VRCX Migration] VRCX Initialized: " + vrcxPersistenceManager.Initialized + " VRC Initialized: " + _loadedInitialPersistData);
            return;
        }
        
        // Migrate data from VRCX to VRChat Persistence
        Debug.Log("[VRCX Migration] Migrating data from VRCX to VRChat Persistence");

        var allTokens = vrcxPersistenceManager.cachedTokens;
        var keys = allTokens.GetKeys();
        var values = allTokens.GetValues();
        
        for (var i = 0; i < keys.Count; i++)
        {
            var key = keys[i];
            var value = values[i];
            Debug.Log("[VRCX Migration] Migrating key: " + key + " with value: " + value);

            if (value.TokenType == TokenType.Boolean)
            {
                PlayerData.SetBool(key.String, value.Boolean);
                vrcxPersistenceManager.DeleteData(key.String);
            }
            else if (value.TokenType == TokenType.Float)
            {
                PlayerData.SetFloat(key.String, value.Float);
                vrcxPersistenceManager.DeleteData(key.String);
            }
            // Blabla do for the rest of token types
        }
    }
elfin wadi
#

Hey frens, gettin started with Persistence, small question: I see we have GetType & TryGetType for getting data on PlayerData, is this local or does it make a network request? e.g Can I fetch a lot of data at once with GetType or is it gonna be slowed down by networking?

I see in the docs:

Wait for the OnPlayerDataRestored event before using PlayerData. OnPlayerRestored indicates that the player's saved data has been loaded and is safe to access with Udon.

So I am assuming it's loaded once and stored locally for access, so no networking until you try to set something?

silent echo
#

The set data is not sent to the servers until you leave the world

elfin wadi
#

but good to know ty ty 😄

light laurel
#

i would assume crashing counts as leaving

#

it’s probably more accurate to say “once you disconnect from the world”

silent echo
#

I would hope that there is some sort of “Data has not been updated for X time, push changes to the server” kind of system, but I have only heard that it updates on leaving the world normally.

I have yet to actually test if setting data has crash resistance and will be unable to do so until I am back home.

subtle quest
# elfin wadi oh really? what if you crash tho?

I'm pretty sure the way it works is that an object that contains persistent data is more-or-less just a normal networked UdonBehavior that is running in either Manual or Continuous sync (PlayerData is Manual , but automatically syncs whenever the local owner of that data changes it).

In the process of syncing that data between the players in an instance it needs to pass through the VRC servers where it gets temporarily stored. Now once OnPlayerLeave gets triggered on the servers themselves (so it would still get triggered even if the player dcs or crashes) that temporary data marked as persistent is saved and stored.

#

Or TL;DR if the data has been syched with the servers already then it would still save, even in the case of a crash or dc

elfin wadi
subtle quest
#

TBH, the technicality that it doesn't permanently save until the player leaves doesn't really matter from our perspective. You can view calling RequestSerlization() on a manual sync object with persistent data as making a save

elfin wadi
#

Related to my previous question around getting data, I'm assuming if no networking (excluding join and leave) option a would be better for storing data. thinknig in terms of filesize and access speed?:
option a)

vector3 data-obj1-pos
vector3 data-obj1-rot
float data-obj1-ammo
float data-obj1-another-example

option b)

string data-obj1-json
subtle quest
#

Oh wait, you're talking about PlayerData specifically; yah, if you want to do it in batches to avoid constantly doing the full manual sync you would need to wrap it

elfin wadi
silent echo
#

I suppose in a way there is crash resistance in that all the other players do have a copy of your persistent data (PlayerData and PlayerObjects) and I beleive that it is sent across the network whenever you update it, but I don't beleive it hits the persistence store itself until you leave. If you crash it should be possible for another client to resend the data. But the inner workings are a mystery.

subtle quest
#

PlayerData is good if you want something simple; but the recommendation seems to be to use PlayerObjects instead as PlayerData is basically a pre setup PlayerObject that you have less control over

subtle quest
#

Oh wait, you're asking what is the better way to store your data. In PlayerData, yah the first option is maybe better (I am by far not an expert on how data is transferred over the internet or how the VRC server compress said data on their servers), but has the downside of needing to create a bunch of keys you'll never be able to remove.

elfin wadi
elfin wadi
subtle quest
#

Yep

#

If you do want to take a multikey approach, though, one potential way to help automate and reuse keys is by using an enum

#

Since keys can be reused for any valid type you want

silent echo
#

In playerdata I assume you can just set something null and get space back

You can also iterate over all the keys stored in playerdata

Player Object is more of a black box and likely just a serialization of the object, so if you change a sync property and then bring it back you’ve probably lost whatever it was you changed

subtle quest
#

That that is how a PlayerObject works afaik

#

And thinking about it, the null thing should probably work; the value is a DataToken after all

#

At least I'm assuming it is one

fading totem
fading totem
silent echo
#

Then I will stand corrected, and it's probably better that way to not lose data in a craah

fading totem
#

and it's probably better that way to not lose data in a crash
I disagree

silent echo
#

My use case is save codes, so I'm just setting a single field each time a save point is reached

fading totem
#

The best way would be to just keep the data cached locally while getting setting and then storing it on VRChat's content servers when you disconnect.

#

But that's unfortunately not how persistence was implemented.

elfin wadi
silent echo
#

I imagine it would batch things just like how RequestSerialization works

fading totem
#

Afaik PlayerData behaves just like a manually synced behaviour with a single [UdonSynced] DataDictionary.
When you set any key on the data dictionary in PlayerData the whole dictionary will be synced.

elfin wadi
#

One other thing, opinions:
I've got 1600+ objects and the player can "fav" each of them:
option a)

obj-fav-1: false  // bools
obj-fav-2: false
obj-fav-3: false

^ these will get looped through and assigned on load, on save just update the ones which change
option b)

obj-favs: 00000100010001000100 // string of every item by id

^ store it all as a massive string of some kind then just fetch that and parse it, same for saving

or something else? 🤔

#

Basically, I'm just worried about large amounts of keys and needing to fetch large quantities from it

fading totem
#

byte[]

silent echo
#

Would recommend bit packing in a byte[]

#

You'll get 8 items per byte

fading totem
#

This is the way

elfin wadi
#

Does PlayerData support arrays?

fading totem
#

yes

elfin wadi
#

Amazing

fading totem
#

It's just a DataDict

silent echo
#

I think it's called SetBytes?

elfin wadi
#

So SetBytes exists but nothing likeSetFloats, SetInts, etc. So seems like you can only save an array of bytes (or convert from and to bytes to save)

silent echo
#

Maybe someone has something like a buffer reader/writer they would be willing to share that could support such things for custom serializing down to a byte[]

#

Myself I am just storing a json string (as a byte[] converted from string to utf-8) but you seem to have way more data than me, so getting more custom of be warranted

fading totem
#

Still wip though, but it will be released publicly soonish

fading totem
elfin wadi
#

Just thinking, if I combine my 1600+ items into an array of bytes and lets say i also need to store some float data so i do a similar thing converting to bytes[]. Would this be more efficient than just using unique incremented keys in the save system float-1, float-2, float-3, etc

Because the advantage of float-1,float-2 is no conversion needed, i just grab the values i need. and i wouldn't need to store all 1600 items, just the ones i want to reduce filesize. E.g:

var score = PlayerData.GetFloat("prefix" + id);
var fav = PlayerData.GetBool("prefix" + id);

etc

fading totem
elfin wadi
fading totem
#

The main reasons we're developing a versioned scheme-based serialization package is because Project Aincrad has these requirements:

  • We often change the layout of our save data, and having residual keys hogging net bandwidth is a no-go.
  • Need to support multiple saving backends. (NUSS, Persistence, etc.)
  • We have to handle save data migration from version to version.
#

Afaik there is no way to remove a single key from PlayerData once it's been written.

#

The only way to do so is to wipe all your persistence data for the world.

elfin wren
elfin wadi
#

That feels like a Vrchat plz add an Unset(key) 😭

elfin wren
#

So your entire save can be a single key

fading totem
#

Ye

#

We opted to just write a byte[] to a single key in player data.

elfin wren
#

Ye

elfin wadi
#

The conversion from datadict to key straight forward or did you have to write like an entire package to handle it?

fading totem
#

That would be very straight forward

elfin wadi
#

Oh, any docs on this anywhere? In my mind you'd have to loop through the datadict and convert everything manually but 100% feels like that's wrong lol

#

inb4 converting it to json then to byte[] but ewww because then json key length will buff the output size

fading totem
#

Yeah

elfin wadi
#

Ah rip, okey thank you for the help btw 😄

#

I think I might just write a hash table to shrink all the keys or something. Maybe spit out the kilobyte count and let users decide if they want to risk getting close to 100kB

fading totem
#

But yeah, still risky.

elfin wadi
fading totem
elfin wadi
#

Ah rip, okey okey I'll look into that. I gave it a brief glance but I thought PlayerObjects was something to do with multiplayer and syncing but I'll give it a re-read

fading totem
elfin wadi
#

Actually, i don't think PlayerObjects might be the way to go... It looks like it's still just an UdonSynced object, so won't this eat into my multiplayer networking budget?

subtle quest
fading totem
fading totem
#

:)

elfin wadi
#

But i mean like if I use PlayerData when someone joins the world it'll nuke just the local user while fetching all the data. With PlayerObjects it'll nuke everyone when someone joins to sync between everyone

fading totem
elfin wadi
#

Ahhh alright, so if i use lots of playerobjects I can chunk up what's being sent like manual syncing

fading totem
#

Yes!

elfin wadi
#

Hurray!

subtle quest
dense owl
dense owl
#

I ran in an unexpectedly silly problem earlier.

I have collectibles "coins on the ground" divided in two types:

  • single coin (respawn when you rejoin)
  • stack of coins (supposedely saved to be disabled on rejoin)

The stacks of coins outside the house, who are active on start,
do behave as expected (you collect a stack then it won't respawn on rejoin)

The stacks of coins inside the house, who are active on start
BUT their parent (the room) is an unlockable so it is turned off in the inspector, and thus is disabled on start,
Those coins don't get disabled on rejoin after being collected...

OnPlayerRestored the game replay the current unlocks (without asking for currency) as a mean to "load the current state" corresponding to the house upgrade's variable.

Turns out...I just needed to add a "OnStart" to run the check of the "stack of coins" object whenever the first time the object gets enabled (which always happens after OnPlayerRestored with my current loading method)
I can't believe I got stuck for hours trying to figure out what I was doing wrong just because I forgot that OnStart means "first time it gets enabled" and not "on game start" like in some other engines...In the end, it have nothing to do with persistance itself! 🤣

wary summit
dense owl
#

Yeah, I figured that part out the hard way, on the stores that kept breaking. I then separated the manager (parent) from the UI and buttons (child)

#

But somehow I had a brain fart with the coins

#

Hopefully, the coins are a simple enough object (only 2 states possible) so it works fine this way.
The data saves as intended, and can still be accessed just fine once they get enabled.
If the "already collected" state is true there's no interaction with the player needed ever again afterward.

But for more complex objects, yeah for sure I need to keep in mind making managers, that would also help keeping track of the keys.

fading hawk
#

What's the purpose of the PlayerObject JSON file in the clientsim storage folder?

elfin wren
fading hawk
velvet barn
#

Not that we're aware of.

severe wing
#

Has something happened to Example Central? I'm getting a blank screen and it doesn't seem to find anything.

severe wing
dense owl
errant token
# severe wing

thanks for the heads-up, we ran out of credits for the search provider and needed to purchase more. It should be working again now.

severe wing
#

Unrelated but while I've got you here... perhaps someone could generate a note about this...

#
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Cysharp.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using VRC.Core;
using VRC.Economy;
using VRC.SDK3.Network;
using VRC.SDK3.Platform;
using VRC.SDK3.Video.Components.AVPro;
using VRC.SDKBase;
using VRC.SDKBase.Platform;
using VRC.Udon;
using VRC.Economy;
using VRC.SDKBase.Network;
using VRC.Economy;
using VRCNetworkBehaviour = VRC.SDK3.Network.VRCNetworkBehaviour;
using System;
using VRC.SDK3.Components;
using VRCStation = VRC.SDKBase.VRCStation;
#if VRC_ENABLE_PLAYER_PERSISTENCE
using VRC.SDK3.ClientSim.Persistence;
using System.Linq;
#endif
#

Note that using VRC.Economy occurs 3 times in ClientSimMain.cs

quick imp
#

How do I define what local player networked objects are which in the index? do I need an object with a script defining as such?

#

I have a handheld object which uses 0, I added another persistence object which i hoped would be called upon if I set the "Get" to 1, but it didnt

errant token
light laurel
#

so, looking at the persistent post processing example, i see that the weight value has an appliedValue variable. how necessary is it to not update the value if it's the same? does it cause that much issue updating a slider if you have to load the value?

#

also, i assume yes, but do i need a key variable for each value i want to store with playerdata?

#

and final question, how necessary are these checks? will i have to check against every variable i make, whether or not the type is correct and the player has that key?

light laurel
#

i'm not sure what's wrong with my script. i see the playerdata values getting set correctly, but when i enter playmode, the animator values aren't getting set. it seems like they're all getting set to 0 instead of the playerdata values on start

#

also, the ToggledKey is getting set to false on default, but i want it to be true by default. i don't know how to set a default value for a persistent bool

#

and for context, each slider sends their value to the HUDSettings through the corresponding method. each slider sends, for example, SetPosX(slider.value) and the hud toggle button calls ToggleHUD though the unity event OnClick()

light laurel
severe wing
wispy latch
#
In the Start and OnDeserialization events, ownership is guaranteed to be correct. However, you should avoid reading or writing a PlayerObject's User Data before the OnPlayerRestored event. You might accidently read or write outdated User Data, and your changes may be overwritten when User Data is received from VRChat's server.
#

At least it sounds that way. But yeah, I'd still use the restored event before I'd do anything, just in case.

subtle quest
#

Also try inserting logs in each of the different parts of your code to see what is getting triggered when

light laurel
subtle quest
#

Oh really? Seems pretty useful, wonder why it's not mentioned in any of the documentation

wispy latch
subtle quest
#

So is it or is it not part of the SDK?

severe wing
#

It isn't part of the SDK simply a static class that demonstrates a few principals while offering a solution to a common need. You certainly don't have to use it.

#
using VRC.SDKBase;

public static class PersistenceUtilities
{
    public static T GetPlayerObjectComponent<T>(int playerId)
    {
        VRCPlayerApi player = VRCPlayerApi.GetPlayerById(playerId);
        if (!Utilities.IsValid(player)) return default;
        
        return GetPlayerObjectComponent<T>(player);
    }
    
    public static T GetPlayerObjectComponent<T>(VRCPlayerApi player)
    {
        if (!Utilities.IsValid(player)) return default;
        
        var objects = Networking.GetPlayerObjects(player);
        for (int i = 0; i < objects.Length; i++)
        {
            var component = objects[i].GetComponentInChildren<T>();
            if (Utilities.IsValid(component))
            {
                return component;
            }
        }
        return default;
    }
}
subtle quest
#

what's the default for a custom class? null?

severe wing
#

I would suggest that it must be

#

OK I'd like a small sanity check before I make this mod but I believe it will work fine. It concerns using PlayerData to send logging messages in-game.

#

I'm currently using a pool of logSync objects with all the nonsense needed to get from and restore to the pool. The purpose is to give each player a unique object to send messages on and every player a way to obtain (read) those messages and to apply them to their local text object.

#

I believe I can remove all the pooling stuff and simply have each player write to a known PlayerData key. Every player will have the OnPlayerDataUpdated event called when any player changes the value of that key. And of course they can update their local log. The sync'ing basically comes free (so to speak). A bit of logic is required to determine if the Key state is Changed indicating that it wasn't already appearing on the log.

#

Does this seem reasonable or am I crazy? And both might be true.

subtle quest
#

Seems like it works. You probably already know this, but this wouldn't have late join support. But I don't think giving something like this late-join support would be reasonable in VRC's current networking environment

severe wing
#

The docs essentially outline such a scenario using a "total dogs pet" score which would be shown for all users.

severe wing
#

I think the PlayerData system is as close as we can get to passing parameter data in an event.

subtle quest
#

Mmhm. Might also be how third party systems do the same

severe wing
#

Yeah I don't know. Bear in mind that Photon PUN has support for remote procedure calls with parameters. They just haven't been exposed for us to use.

subtle quest
#

I often wonder what is the reason for all the restrictions networking has

silent echo
#

I suspect it's because it was designed for udon graph, udon sharp wasn't even a consideration back then.
Adding dynamic parameters to a node system does not sound fun

light laurel
#

in place of Start

severe wing
flat mist
#

With the persistent pen Example, can I just duplicate the Pen object to create more pens for the world? Or would I have to do more?

severe wing
#

I'm repeating myself but I long ago stopped putting "app logic" in my Start methods. There is no real way to coordinate the startup between lots of objects.

flat mist
#

I figured it out. Just duplicated the pens and it seems to be ok.

#

Just wanted to say examples are really really really cool and I hope to see many more examples in the future!

sullen flax
#

for a script with VRC Enable Persistence, is there a way to make a udon synced variable not persist?

sullen flax
#

thats a little annoying....

fading totem
#

Yes!

wary summit
severe wing
# wary summit Well that technically just changed, since start now obeys execution order! Thou...

I have what I think is a very robust system that works so far and can be extended when the need occurs. It permits objects to "register" as "initiable" and as a result their Init and InitMaster methods will get called during startup. I also had a IsWorldLoaded flag which I converted to IsPlayerRestored (they occur at approximately the same point during startup). That has proven valuable in Update methods shouldn't execute until the dust has settled.

#

And I just now have my in-world logger using OnPlayerUpdated so one pooling project has been removed!

subtle quest
#

I barely use Update, myself. Instead I use SendCustomEventDelayedSeconds to make psudo coroutines

wary summit
severe wing
#

I am sure that works but I can do a lot more in my system. it is far more than a flag. It supports for instance Init method for all players and an InitMaster which only the master will run insuring that any settings ordinary players need are set before they join.

wary summit
#

sure, networking initialization is a whole other thing, and in that case that infrastructure sounds a lot more appealing. I'm just talking about script initialization though, and for script initialization it's more important to keep it simple and robust

severe wing
#

And I believe I may have spoken too soon on using OnPlayerRestored in place of my IsWorldLoaded logic. I have to run a few more tests but it seems all sorts of avatar processing happens after it.

wary summit
#

OnPlayerRestored has absolutely nothing to do with avatars so I would definitely not recommend tying the two together ^^

severe wing
#

It is the order of events that I'm concerned with not avatars.

wary summit
#

any correlation between them happening in any particular order with any particular timing is merely an artifact of code that roughly takes the same time to execute each time. And when we change that code, it may change the timing or even the order because they are not related.

severe wing
#

I need to know when the world has settled down and all initial sync has occurred. There was a message in the log but no event is fired for us to monitor.

wary summit
#

yeah, that's a bit of a white whale that we've tried to hunt a couple times over the years too. Turns out it's really not something that VRChat can determine in a generic way because we don't know which behaviors are important. It's entirely possible for some scripts to never receive sync and that's intentional because they're unimportant. But if we make an event for "all initial sync has occurred" then it would be waiting indefinitely for that script to receive something that will never come

#

However, you can absolutely build that yourself by making scripts which report their status back to a central system, and the central system only proceeds if everything that needs to happen has happened

#

the message in the log that you're referring to is a complete red herring, sorry

severe wing
#
2024.11.26 18:15:13 Log        -  [Behaviour] Finished entering world.
2024.11.26 18:15:13 Log        -  [Behaviour] Enabling physics
2024.11.26 18:15:13 Log        -  [Behaviour] World download took 1,504.7ms
2024.11.26 18:15:13 Log        -  [Behaviour] Spent 0.0ms waiting for GO
2024.11.26 18:15:13 Log        -  [Behaviour] World loading took 3,345.7ms
#

I think these always occur in the same relative spot... My artificial OnWorldLoaded would be called right after this in the log. I know at that time that Start was called, Init and InitMaster were called and all the objects are ready for use.

#

Hi

#

The trick (I think) is to participate. Ask questions when you have to, answer questions when you can. If you solve a problem let us all know the problem and your solution.

wary summit
# severe wing I think these always occur in the same relative spot... My artificial OnWorldLoa...

again, any correlation is because it's parallel processes that happen to take roughly the same time. It is not an indicator that it is actually tied together. It's like a pot of water starting to boil immediately after your microwave dings. It happens the same way every morning, but what happens when you get a stronger stove, or a different pot, or put something different in the microwave?

#

VRChat updates are like coming in and upgrading the stove occasionally. Yay, you have a stronger stove! You can boil water faster! .... what do you mean you were relying on the microwave timer to know when your water is done boiling?

severe wing
# wary summit again, any correlation is because it's parallel processes that happen to take ro...

Hmm, I'll have to think about the analogy. I understand it but I'm not entirely certain it applies. One might argue that nothing can be done with playing YT videos because the target is always moving but in reality when the stove is upgraded you modify the dinger and it is working again. Seriously though if you want to boil water in your microwave do you not set a timer? Is 3 minutes at 100% not going to work each morning? Do they make a water boiling detector?

#

I believe we're stuck in suboptimal stuff might fail in the year 2000 sort of world.

wary summit
#

unfortunately it's stuff that I see allll the time. Every single time we upgrade something and it loads faster, some world breaks because they were relying on it to take exactly 3.5 seconds when now it takes 2.5 seconds. I have a bit of a stick up my ass about this because I have seen this exact same thing many dozens of times over the last 4 years

severe wing
#

If a change makes it take 7 units instead of the 6 it takes now I will update the constant 🙂

wary summit
#

whether it's timing or counting, the source of the issue is still that it's two independent systems. I'm primarily referring to how you mentioned that your code relates OnPlayerRestored with avatars in some way, but it's not like I've actually seen your code so I'm not entirely certain how

severe wing
#
2024.11.26 18:15:07 Log        -  [API] Websockets set up PhoneBook
2024.11.26 18:15:07 Log        -  [API] Websockets set up PostOffice
subtle quest
severe wing
wary summit
subtle quest
#

I see, the description does make it sound a bit like magic. Is IsClogged something that can be reliably used, I hope?

wary summit
#

yes, isclogged is supported properly. It indicates that the local player has recently sent too much networking traffic and is currently being rate limited

severe wing
#

Ok I can confirm that my artificial IsWorldLoaded event occurs just prior to OnPlayerRestored so I believe for all intents and purposes I can place code in OnPlayerRestored knowing the data has at least momentarily sync'd up. This was all related to pooling as I recall. The sync'd data had to have been called or we couldn't rely on sync'd objects knowing they had been allotted to a player.

wary summit
#

If the answer to that is no, or includes anything else then it is different from OnPlayerRestored