Added Unity project files

This commit is contained in:
2026-06-07 16:58:24 +01:00
parent 3cc05d260b
commit 23bbcab156
3942 changed files with 453676 additions and 0 deletions

View File

@ -0,0 +1,406 @@
#if VRC_ENABLE_PLAYER_PERSISTENCE
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace VRC.SDK3.ClientSim.Persistence
{
public class ClientSimPlayerDataConverter : JsonConverter<ClientSimPlayerDataTypeUnion>
{
public override ClientSimPlayerDataTypeUnion ReadJson(JsonReader reader, Type objectType, ClientSimPlayerDataTypeUnion existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JObject jsonObject = JObject.Load(reader);
if (jsonObject["type"] == null)
{
Debug.LogError("Failed to deserialize PlayerData: No type found");
return null;
}
if (jsonObject["value"] == null)
{
Debug.LogError("Failed to deserialize PlayerData: No value found");
return null;
}
try
{
ClientSimPlayerDataType type = GetPlayerDataType(jsonObject["type"].ToString());
object value = GetTypedValue(type, jsonObject["value"], reader, existingValue, serializer);
return new ClientSimPlayerDataTypeUnion { Type = type, Value = value };
}
catch (Exception e)
{
Debug.LogError($"Failed to parse PlayerData {objectType} : {e.Message}");
return default;
}
}
private ClientSimPlayerDataType GetPlayerDataType(string typeName)
{
return typeName switch
{
"Color" => ClientSimPlayerDataType.Color,
"Color32" => ClientSimPlayerDataType.Color32,
"Quaternion" => ClientSimPlayerDataType.Quaternion,
"Vector2" => ClientSimPlayerDataType.Vector2,
"Vector3" => ClientSimPlayerDataType.Vector3,
"Vector4" => ClientSimPlayerDataType.Vector4,
"Bool" => ClientSimPlayerDataType.WrappedBool,
"UByte" => ClientSimPlayerDataType.WrappedUByte,
"Byte" => ClientSimPlayerDataType.WrappedByte,
"Bytes" => ClientSimPlayerDataType.WrappedBytes,
"Float" => ClientSimPlayerDataType.WrappedFloat,
"Double" => ClientSimPlayerDataType.WrappedDouble,
"Long" => ClientSimPlayerDataType.WrappedLong,
"ULong" => ClientSimPlayerDataType.WrappedULong,
"Int" => ClientSimPlayerDataType.WrappedInt,
"UInt" => ClientSimPlayerDataType.WrappedUInt,
"Short" => ClientSimPlayerDataType.WrappedShort,
"UShort" => ClientSimPlayerDataType.WrappedUShort,
"String" => ClientSimPlayerDataType.WrappedString,
_ => ClientSimPlayerDataType.Color
};
}
public override void WriteJson(JsonWriter writer, ClientSimPlayerDataTypeUnion value, JsonSerializer serializer)
{
JsonConverter converter = null;
if (value.Type == ClientSimPlayerDataType.Color) converter = new ColorConverter();
else if (value.Type == ClientSimPlayerDataType.Color32) converter = new Color32Converter();
else if (value.Type == ClientSimPlayerDataType.Quaternion) converter = new QuaternionConverter();
else if (value.Type == ClientSimPlayerDataType.Vector2) converter = new Vector2Converter();
else if (value.Type == ClientSimPlayerDataType.Vector3) converter = new Vector3Converter();
else if (value.Type == ClientSimPlayerDataType.Vector4) converter = new Vector4Converter();
writer.WriteStartObject();
writer.WritePropertyName("type");
writer.WriteValue(value.Type.ToString().Replace("Wrapped", ""));
writer.WritePropertyName("value");
if (converter != null)
{
converter.WriteJson(writer, value.Value, serializer);
}
else
{
writer.WriteValue(value.Value);
}
writer.WriteEndObject();
}
private static object GetTypedValue(ClientSimPlayerDataType type, object deserializedValue, JsonReader reader, ClientSimPlayerDataTypeUnion existingValue, JsonSerializer serializer)
{
switch (type)
{
case ClientSimPlayerDataType.Color:
ColorUtility.TryParseHtmlString("#" + deserializedValue, out Color loadedColor);
return loadedColor;
case ClientSimPlayerDataType.Color32:
ColorUtility.TryParseHtmlString("#" + deserializedValue, out Color loadedColor32);
return new Color32(
(byte)(loadedColor32.r * 255),
(byte)(loadedColor32.g * 255),
(byte)(loadedColor32.b * 255),
(byte)(loadedColor32.a * 255));
case ClientSimPlayerDataType.Quaternion:
JObject quaternionObj = (JObject)deserializedValue;
return new Quaternion(
(float)quaternionObj["x"],
(float)quaternionObj["y"],
(float)quaternionObj["z"],
(float)quaternionObj["w"]);
case ClientSimPlayerDataType.Vector2:
JObject vector2Obj = (JObject)deserializedValue;
return new Vector2(
(float)vector2Obj["x"],
(float)vector2Obj["y"]);
case ClientSimPlayerDataType.Vector3:
JObject vector3Obj = (JObject)deserializedValue;
return new Vector3(
(float)vector3Obj["x"],
(float)vector3Obj["y"],
(float)vector3Obj["z"]);
case ClientSimPlayerDataType.Vector4:
JObject vector4Obj = (JObject)deserializedValue;
return new Vector4(
(float)vector4Obj["x"],
(float)vector4Obj["y"],
(float)vector4Obj["z"],
(float)vector4Obj["w"]);
case ClientSimPlayerDataType.WrappedBytes:
JValue byteArrayObj = (JValue)deserializedValue;
return Convert.FromBase64String((string)byteArrayObj.Value ?? string.Empty);
case ClientSimPlayerDataType.WrappedBool: return (bool)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedUByte: return (byte)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedByte: return (sbyte)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedFloat: return (float)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedDouble: return (double)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedLong: return (long)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedULong: return (ulong)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedInt: return (int)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedUInt: return (uint)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedShort: return (short)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedUShort: return (ushort)(JValue)deserializedValue;
case ClientSimPlayerDataType.WrappedString: return (string)(JValue)deserializedValue;
default: return deserializedValue;
}
}
}
internal class ColorConverter : JsonConverter<Color>
{
public override Color ReadJson(JsonReader reader, Type objectType, Color existingValue, bool hasExistingValue, JsonSerializer serializer)
{
try
{
ColorUtility.TryParseHtmlString("#" + reader.Value, out Color loadedColor);
return loadedColor;
}
catch (Exception ex)
{
Debug.LogError($"Failed to parse color {objectType} : {ex.Message}");
return default;
}
}
public override void WriteJson(JsonWriter writer, Color color, JsonSerializer serializer)
{
string val = ColorUtility.ToHtmlStringRGBA(color);
writer.WriteValue(val);
}
}
internal class Color32Converter : JsonConverter<Color32>
{
public override Color32 ReadJson(JsonReader reader, Type objectType, Color32 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
try
{
ColorUtility.TryParseHtmlString("#" + reader.Value, out Color loadedColor);
return loadedColor;
}
catch (Exception ex)
{
Debug.LogError($"Failed to parse color {objectType} : {ex.Message}");
return default;
}
}
public override void WriteJson(JsonWriter writer, Color32 color, JsonSerializer serializer)
{
string val = ColorUtility.ToHtmlStringRGBA(color);
writer.WriteValue(val);
}
}
internal class QuaternionConverter : JsonConverter<Quaternion>
{
public override Quaternion ReadJson(JsonReader reader, Type objectType, Quaternion existingValue, bool hasExistingValue, JsonSerializer serializer)
{
try
{
Debug.Log($"Quaternion: type={objectType} ({existingValue.GetType()}), val={existingValue}");
float x = 0f, y = 0f, z = 0f, w = 0f;
while (reader.Read())
{
string propertyName = (string)reader.Value;
reader.Read();
switch (propertyName)
{
case "x":
x = Convert.ToSingle(reader.Value);
break;
case "y":
y = Convert.ToSingle(reader.Value);
break;
case "z":
z = Convert.ToSingle(reader.Value);
break;
case "w":
w = Convert.ToSingle(reader.Value);
break;
}
}
return new Quaternion(x, y, z, w);
}
catch (Exception ex)
{
Debug.LogError($"Failed to parse quaternion {objectType} : {ex.Message}");
return default;
}
}
public override void WriteJson(JsonWriter writer, Quaternion value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(value.x);
writer.WritePropertyName("y");
writer.WriteValue(value.y);
writer.WritePropertyName("z");
writer.WriteValue(value.z);
writer.WritePropertyName("w");
writer.WriteValue(value.w);
writer.WriteEndObject();
}
}
public class Vector2Converter : JsonConverter<Vector2>
{
public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
try
{
float x = 0f, y = 0f;
while (reader.Read())
{
string propertyName = (string)reader.Value;
reader.Read();
switch (propertyName)
{
case "x":
x = Convert.ToSingle(reader.Value);
break;
case "y":
y = Convert.ToSingle(reader.Value);
break;
}
}
return new Vector2(x, y);
}
catch (Exception ex)
{
Debug.LogError($"Failed to parse Vector2 {objectType} : {ex.Message}");
return default;
}
}
public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(value.x);
writer.WritePropertyName("y");
writer.WriteValue(value.y);
writer.WriteEndObject();
}
}
public class Vector3Converter : JsonConverter<Vector3>
{
public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
try
{
float x = 0f, y = 0f, z = 0f;
while (reader.Read())
{
string propertyName = (string)reader.Value;
reader.Read();
switch (propertyName)
{
case "x":
x = Convert.ToSingle(reader.Value);
break;
case "y":
y = Convert.ToSingle(reader.Value);
break;
case "z":
z = Convert.ToSingle(reader.Value);
break;
}
}
return new Vector3(x, y, z);
}
catch (Exception ex)
{
Debug.LogError($"Failed to parse Vector3 {objectType} : {ex.Message}");
return default;
}
}
public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(value.x);
writer.WritePropertyName("y");
writer.WriteValue(value.y);
writer.WritePropertyName("z");
writer.WriteValue(value.z);
writer.WriteEndObject();
}
}
public class Vector4Converter : JsonConverter<Vector4>
{
public override Vector4 ReadJson(JsonReader reader, Type objectType, Vector4 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
try
{
float x = 0f, y = 0f, z = 0f, w = 0f;
while (reader.Read())
{
string propertyName = (string)reader.Value;
reader.Read();
switch (propertyName)
{
case "x":
x = Convert.ToSingle(reader.Value);
break;
case "y":
y = Convert.ToSingle(reader.Value);
break;
case "z":
z = Convert.ToSingle(reader.Value);
break;
case "w":
w = Convert.ToSingle(reader.Value);
break;
}
}
return new Vector4(x, y, z, w);
}
catch (Exception ex)
{
Debug.LogError($"Failed to parse Vector4 {objectType} : {ex.Message}");
return default;
}
}
public override void WriteJson(JsonWriter writer, Vector4 value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("x");
writer.WriteValue(value.x);
writer.WritePropertyName("y");
writer.WriteValue(value.y);
writer.WritePropertyName("z");
writer.WriteValue(value.z);
writer.WritePropertyName("w");
writer.WriteValue(value.w);
writer.WriteEndObject();
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d47ee12e2ac84367b7e3ced0dc76be28
timeCreated: 1708717453

View File

@ -0,0 +1,20 @@
using System;
#if VRC_ENABLE_PLAYER_PERSISTENCE
namespace VRC.SDK3.ClientSim.Persistence
{
public class ClientSimPlayerDataPair
{
public string Key { get; set; }
public ClientSimPlayerDataTypeUnion Value { get; set; }
public DateTime LastUpdated;
public ClientSimPlayerDataPair() {
Key = null;
Value = null;
LastUpdated = DateTime.Now;
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7da7fcefc4434d3e9b5e16edf46de990
timeCreated: 1708638443

View File

@ -0,0 +1,884 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using UnityEngine;
using VRC.SDKBase;
#if VRC_ENABLE_PLAYER_PERSISTENCE
using UnityEngine.SceneManagement;
using VRC.SDK3.Data;
using VRC.SDK3.Persistence;
using VRC.Udon;
#endif
namespace VRC.SDK3.ClientSim.Persistence
{
[AddComponentMenu("")] // hides component in Add Component menu
public class ClientSimPlayerDataStorage : ClientSimBehaviour
{
#if VRC_ENABLE_PLAYER_PERSISTENCE
public static string PlayerDataFolder => Path.Combine("ClientSimStorage", "PlayerData");
internal static string PlayerDataFilePath(VRCPlayerApi player)
{
string root = Path.GetDirectoryName(Application.dataPath);
string path = Path.Combine(root, PlayerDataFolder);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path + "/PlayerData_" + $"{player.playerId}" + $"_{SceneManager.GetActiveScene().name}" + ".json";
}
private VRCPlayerApi _player;
private IClientSimUdonEventSender _udonEventSender;
private IClientSimEventDispatcher _eventDispatcher;
private readonly Dictionary<string, ClientSimPlayerDataPair> leData = new();
private readonly Dictionary<string, PlayerData.Info> localInfoChanges = new();
private readonly List<PlayerData.Info> queuedPlayerDataUpdates = new();
private bool doDecode;
private bool isDoneDecoding;
private bool hasPostedPlayerDataDecoded;
private bool hasPostedPlayerRestored;
public int Size
=> System.Text.Encoding.UTF8.GetByteCount(JsonConvert.SerializeObject(leData, Formatting.Indented, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}));
public IEnumerable<string> GetKeys()
{
return leData.Keys;
}
public bool HasKey(string key) => leData.ContainsKey(key);
public Type GetType(string key)
{
if (!leData.TryGetValue(key, out ClientSimPlayerDataPair value))
return null;
switch (value.Value.Type)
{
default:
case ClientSimPlayerDataType.None:
return null;
case ClientSimPlayerDataType.Vector2:
return typeof(Vector2);
case ClientSimPlayerDataType.Vector3:
return typeof(Vector3);
case ClientSimPlayerDataType.Vector4:
return typeof(Vector4);
case ClientSimPlayerDataType.Quaternion:
return typeof(Quaternion);
case ClientSimPlayerDataType.Color:
return typeof(Color);
case ClientSimPlayerDataType.Color32:
return typeof(Color32);
case ClientSimPlayerDataType.WrappedString:
return typeof(string);
case ClientSimPlayerDataType.WrappedShort:
return typeof(short);
case ClientSimPlayerDataType.WrappedUShort:
return typeof(ushort);
case ClientSimPlayerDataType.WrappedInt:
return typeof(int);
case ClientSimPlayerDataType.WrappedUInt:
return typeof(uint);
case ClientSimPlayerDataType.WrappedLong:
return typeof(long);
case ClientSimPlayerDataType.WrappedULong:
return typeof(ulong);
case ClientSimPlayerDataType.WrappedFloat:
return typeof(float);
case ClientSimPlayerDataType.WrappedDouble:
return typeof(double);
case ClientSimPlayerDataType.WrappedBool:
return typeof(bool);
case ClientSimPlayerDataType.WrappedByte:
return typeof(sbyte);
case ClientSimPlayerDataType.WrappedUByte:
return typeof(byte);
case ClientSimPlayerDataType.WrappedBytes:
return typeof(byte[]);
}
}
#region Setters
private PlayerData.State _SetWrapper(string key, Func<ClientSimPlayerDataPair, bool> set, bool isRestore, bool flushChanges, DateTime lastUpdated)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key was invalid");
if (!isRestore && leData.TryGetValue(key, out ClientSimPlayerDataPair value))
{
if (set(value))
{
value.LastUpdated = DateTime.Now;
localInfoChanges[key] = new PlayerData.Info(key, PlayerData.State.Changed);
if (flushChanges)
{
FlushLocalInfoChanges();
}
return PlayerData.State.Changed;
}
return PlayerData.State.Unchanged;
}
value = new ClientSimPlayerDataPair { Key = key, LastUpdated = isRestore ? lastUpdated : DateTime.Now };
leData[key] = value;
set(value);
var state = isRestore ? PlayerData.State.Restored : PlayerData.State.Added;
localInfoChanges[key] = new PlayerData.Info(key, state);
if (flushChanges)
{
FlushLocalInfoChanges();
}
return state;
}
public PlayerData.State SetBool(string key, bool value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedBool)
{
if (pair.Value.AsWrappedBool() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedBool,
Value = value
};
return true;
}
}
public PlayerData.State SetByte(string key, byte value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedUByte)
{
if (pair.Value.AsWrappedUByte() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedUByte,
Value = value
};
return true;
}
}
public PlayerData.State SetSByte(string key, sbyte value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedByte)
{
if (pair.Value.AsWrappedSByte() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedByte,
Value = value
};
return true;
}
}
public PlayerData.State SetBytes(string key, byte[] value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedBytes)
{
if (pair.Value.AsWrappedBytes().SequenceEqual(value))
return false;
pair.Value.Value = value.ToArray();
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedBytes,
Value = value.ToArray()
};
return true;
}
}
public PlayerData.State SetString(string key, string value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedString)
{
if (pair.Value.AsWrappedString() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedString,
Value = value
};
return true;
}
}
public PlayerData.State SetShort(string key, short value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedShort)
{
if (pair.Value.AsWrappedShort() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedShort,
Value = value
};
return true;
}
}
public PlayerData.State SetUShort(string key, ushort value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedUShort)
{
if (pair.Value.AsWrappedUShort() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedUShort,
Value = value
};
return true;
}
}
public PlayerData.State SetInt(string key, int value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedInt)
{
if (pair.Value.AsWrappedInt() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedInt,
Value = value
};
return true;
}
}
public PlayerData.State SetUInt(string key, uint value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedUInt)
{
if (pair.Value.AsWrappedUInt() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedUInt,
Value = value
};
return true;
}
}
public PlayerData.State SetLong(string key, long value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedLong)
{
if (pair.Value.AsWrappedLong() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedLong,
Value = value
};
return true;
}
}
public PlayerData.State SetULong(string key, ulong value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedULong)
{
if (pair.Value.AsWrappedULong() == value)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedULong,
Value = value
};
return true;
}
}
public PlayerData.State SetFloat(string key, float value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedFloat)
{
if (Math.Abs(pair.Value.AsWrappedFloat() - value) < 0.000001f)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedFloat,
Value = value
};
return true;
}
}
public PlayerData.State SetDouble(string key, double value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.WrappedDouble)
{
if (Math.Abs(pair.Value.AsWrappedDouble() - value) < 0.000001)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.WrappedDouble,
Value = value
};
return true;
}
}
public PlayerData.State SetVector2(string key, Vector2 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.Vector2)
{
if (Math.Abs(pair.Value.AsVector2().x - value.x) < 0.000001f
&& Math.Abs(pair.Value.AsVector2().y - value.y) < 0.000001f)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.Vector2,
Value = value
};
return true;
}
}
public PlayerData.State SetVector3(string key, Vector3 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.Vector3)
{
if (Math.Abs(pair.Value.AsVector3().x - value.x) < 0.000001f
&& Math.Abs(pair.Value.AsVector3().y - value.y) < 0.000001f
&& Math.Abs(pair.Value.AsVector3().z - value.z) < 0.000001f)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.Vector3,
Value = value
};
return true;
}
}
public PlayerData.State SetVector4(string key, Vector4 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.Vector4)
{
if (Math.Abs(pair.Value.AsVector4().x - value.x) < 0.000001f
&& Math.Abs(pair.Value.AsVector4().y - value.y) < 0.000001f
&& Math.Abs(pair.Value.AsVector4().z - value.z) < 0.000001f
&& Math.Abs(pair.Value.AsVector4().w - value.w) < 0.000001f)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.Vector4,
Value = value
};
return true;
}
}
public PlayerData.State SetQuaternion(string key, Quaternion value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.Quaternion)
{
if (Math.Abs(pair.Value.AsQuaternion().x - value.x) < 0.000001f
&& Math.Abs(pair.Value.AsQuaternion().y - value.y) < 0.000001f
&& Math.Abs(pair.Value.AsQuaternion().z - value.z) < 0.000001f
&& Math.Abs(pair.Value.AsQuaternion().w - value.w) < 0.000001f)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.Quaternion,
Value = value
};
return true;
}
}
public PlayerData.State SetColor(string key, Color value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.Color)
{
var color = pair.Value.AsColor();
if (Math.Abs(color.r - value.r) < 0.01f
&& Math.Abs(color.g - value.g) < 0.01f
&& Math.Abs(color.b - value.b) < 0.01f
&& Math.Abs(color.a - value.a) < 0.01f)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.Color,
Value = value
};
return true;
}
}
public PlayerData.State SetColor32(string key, Color32 value, DateTime lastUpdated = new DateTime(), bool isRestore = false, bool flushChanges = true)
{
return _SetWrapper(key, Set, isRestore, flushChanges, lastUpdated);
bool Set(ClientSimPlayerDataPair pair)
{
if (pair.Value?.Type == ClientSimPlayerDataType.Color32)
{
var color32 = pair.Value.AsColor32();
if (Math.Abs(color32.r - value.r) < 0.000001f
&& Math.Abs(color32.g - value.g) < 0.000001f
&& Math.Abs(color32.b - value.b) < 0.000001f
&& Math.Abs(color32.a - value.a) < 0.000001f)
return false;
pair.Value.Value = value;
}
else
pair.Value = new ClientSimPlayerDataTypeUnion()
{
Type = ClientSimPlayerDataType.Color32,
Value = value
};
return true;
}
}
#endregion
#region Getters
private ClientSimPlayerDataPair _GetChecked(string key, ClientSimPlayerDataType expectedType)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key was invalid");
if (!leData.TryGetValue(key, out ClientSimPlayerDataPair data) || data.Value == null)
{
return null;
}
if (data.Value.Type != expectedType)
{
this.LogError($"Data at {key} was not a {expectedType}, it was a {data.Value.Type}");
return null;
}
return data;
}
public bool GetBool(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedBool)?.Value.AsWrappedBool() ?? default;
public byte GetByte(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedUByte)?.Value.AsWrappedUByte() ?? default;
public sbyte GetSByte(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedByte)?.Value.AsWrappedSByte() ?? default;
public byte[] GetBytes(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedBytes)?.Value.AsWrappedBytes().ToArray();
public string GetString(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedString)?.Value.AsWrappedString();
public short GetShort(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedShort)?.Value.AsWrappedShort() ?? default;
public ushort GetUShort(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedUShort)?.Value.AsWrappedUShort() ?? default;
public int GetInt(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedInt)?.Value.AsWrappedInt() ?? default;
public uint GetUInt(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedUInt)?.Value.AsWrappedUInt() ?? default;
public long GetLong(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedLong)?.Value.AsWrappedLong() ?? default;
public ulong GetULong(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedULong)?.Value.AsWrappedULong() ?? default;
public float GetFloat(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedFloat)?.Value.AsWrappedFloat() ?? default;
public double GetDouble(string key)
=> _GetChecked(key, ClientSimPlayerDataType.WrappedDouble)?.Value.AsWrappedDouble() ?? default;
public Vector2 GetVector2(string key)
{
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Vector2);
if (data != null)
return new Vector2(data.Value.AsVector2().x, data.Value.AsVector2().y);
return default;
}
public Vector3 GetVector3(string key)
{
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Vector3);
if (data != null)
return new Vector3(data.Value.AsVector3().x, data.Value.AsVector3().y, data.Value.AsVector3().z);
return default;
}
public Vector4 GetVector4(string key)
{
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Vector4);
if (data != null)
return new Vector4(data.Value.AsVector4().x, data.Value.AsVector4().y, data.Value.AsVector4().z, data.Value.AsVector4().w);
return default;
}
public Quaternion GetQuaternion(string key)
{
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Quaternion);
if (data != null)
return new Quaternion(data.Value.AsQuaternion().x, data.Value.AsQuaternion().y, data.Value.AsQuaternion().z, data.Value.AsQuaternion().w);
return default;
}
public Color GetColor(string key)
{
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Color);
if (data != null)
return new Color(data.Value.AsColor().r, data.Value.AsColor().g, data.Value.AsColor().b, data.Value.AsColor().a);
return default;
}
public Color32 GetColor32(string key)
{
ClientSimPlayerDataPair data = _GetChecked(key, ClientSimPlayerDataType.Color32);
if (data != null)
return new Color32(data.Value.AsColor32().r, data.Value.AsColor32().g, data.Value.AsColor32().b, data.Value.AsColor32().a);
return default;
}
#endregion
#region DataPropagation
// can't decode json here because this is called before scene manager is ready
public void Init(VRCPlayerApi player, IClientSimUdonEventSender udonEventSender, IClientSimEventDispatcher eventDispatcher)
{
_player = player;
_udonEventSender = udonEventSender;
_eventDispatcher = eventDispatcher;
_eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
_eventDispatcher.Subscribe<ClientSimOnPlayerDataClearedEvent>(OnPlayerDataCleared);
_eventDispatcher.Subscribe<ClientSimOnPlayerRestoredEvent>(OnPlayerRestored);
}
private void OnDestroy()
{
_eventDispatcher.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
_eventDispatcher.Unsubscribe<ClientSimOnPlayerDataClearedEvent>(OnPlayerDataCleared);
_eventDispatcher.Unsubscribe<ClientSimOnPlayerRestoredEvent>(OnPlayerRestored);
}
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent payload)
{
// remote player test data is decoded via ClientSimPlayerDataWindow.OnPlayerJoined
if (payload.player.playerId == _player.playerId)
doDecode = true;
}
private void OnPlayerDataCleared(ClientSimOnPlayerDataClearedEvent payload)
{
leData.Clear();
}
private void OnPlayerRestored(ClientSimOnPlayerRestoredEvent payload)
{
if (payload.player.isLocal)
hasPostedPlayerRestored = true;
}
private void Encode()
{
// PlayerData updates before OnPlayerRestored are ignored
if (!hasPostedPlayerRestored)
return;
string json = JsonConvert.SerializeObject(leData, Formatting.Indented, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
File.WriteAllText(PlayerDataFilePath(_player), json);
}
internal void Decode(bool isRestore)
{
string path = PlayerDataFilePath(_player);
if (!File.Exists(path))
{
File.WriteAllText(path, "{}");
}
else
{
try
{
string json = File.ReadAllText(path);
var data = JsonConvert.DeserializeObject<Dictionary<string, ClientSimPlayerDataPair>>(json);
foreach (KeyValuePair<string, ClientSimPlayerDataPair> kvp in data)
{
Set(kvp.Value);
}
FlushLocalInfoChanges(); // bulk flush changes
}
catch (Exception e)
{
this.LogError($"Error initializing PlayerData: {e.Message}");
}
}
isDoneDecoding = true;
PlayerData.State Set(ClientSimPlayerDataPair pair)
{
try
{
switch (pair.Value.Type)
{
case ClientSimPlayerDataType.Color: return SetColor(pair.Key, pair.Value.AsColor(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.Color32: return SetColor32(pair.Key, pair.Value.AsColor32(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.Quaternion: return SetQuaternion(pair.Key, pair.Value.AsQuaternion(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.Vector2: return SetVector2(pair.Key, pair.Value.AsVector2(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.Vector3: return SetVector3(pair.Key, pair.Value.AsVector3(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.Vector4: return SetVector4(pair.Key, pair.Value.AsVector4(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedBool: return SetBool(pair.Key, pair.Value.AsWrappedBool(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedByte: return SetSByte(pair.Key, pair.Value.AsWrappedSByte(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedUByte: return SetByte(pair.Key, pair.Value.AsWrappedByte(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedBytes: return SetBytes(pair.Key, pair.Value.AsWrappedBytes(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedFloat: return SetFloat(pair.Key, pair.Value.AsWrappedFloat(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedDouble: return SetDouble(pair.Key, pair.Value.AsWrappedDouble(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedLong: return SetLong(pair.Key, pair.Value.AsWrappedLong(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedULong: return SetULong(pair.Key, pair.Value.AsWrappedULong(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedInt: return SetInt(pair.Key, pair.Value.AsWrappedInt(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedUInt: return SetUInt(pair.Key, pair.Value.AsWrappedUInt(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedShort: return SetShort(pair.Key, pair.Value.AsWrappedShort(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedUShort: return SetUShort(pair.Key, pair.Value.AsWrappedUShort(), pair.LastUpdated, isRestore, false);
case ClientSimPlayerDataType.WrappedString: return SetString(pair.Key, pair.Value.AsWrappedString(), pair.LastUpdated, isRestore, false);
default:
case ClientSimPlayerDataType.None: return PlayerData.State.Unchanged;
}
}
catch (Exception e)
{
this.LogError("Error reading PlayerData: " +
$"key={pair.Key}, " +
$"type={pair.Value.Type} ({pair.Value.Value.GetType()}), " +
$"value={pair.Value.Value}, " +
$"error={e.Message}");
return default;
}
}
}
private void FlushLocalInfoChanges()
{
var infos = leData
.Select(kvp =>
localInfoChanges.TryGetValue(kvp.Key, out var value)
? value
: new PlayerData.Info(kvp.Key, PlayerData.State.Unchanged))
.ToArray();
QueuePlayerDataUpdate(infos);
localInfoChanges.Clear();
}
internal void QueuePlayerDataUpdate(PlayerData.Info[] infos)
{
if (infos.Length == 0)
return;
queuedPlayerDataUpdates.AddRange(infos);
}
private void RaisePlayerDataUpdated(PlayerData.Info[] infos)
{
_udonEventSender.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATAUPDATED, ("player", _player), ("infos", infos));
_eventDispatcher.SendEvent(new ClientSimOnPlayerDataUpdatedEvent
{
player = _player,
playerData = leData
});
}
private float lastCheckTime = 0;
private void LateUpdate()
{
if (queuedPlayerDataUpdates.Count > 0)
{
Encode();
RaisePlayerDataUpdated(queuedPlayerDataUpdates.ToArray());
queuedPlayerDataUpdates.Clear();
}
if (doDecode)
{
doDecode = false;
Decode(true);
}
else if (isDoneDecoding && !hasPostedPlayerDataDecoded)
{
hasPostedPlayerDataDecoded = true;
_eventDispatcher.SendEvent(new ClientSimOnPlayerDataDecodedEvent { player = _player });
}
if (Time.realtimeSinceStartup - lastCheckTime > 30f)
{
lastCheckTime = Time.realtimeSinceStartup;
float dataLimit = ClientSimMain.TryGetInstance(out var instance) ? instance.GetPlayerDataUsageLimit() : 0;
float sz = Size;
if (sz >= dataLimit)
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATASTORAGEEXCEEDED, ("player", _player));
else if (sz >= dataLimit * 0.7f)
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATASTORAGEWARNING, ("player", _player));
}
}
#endregion
#endif
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0ef04bdeba52437cb909dc841044fd57
timeCreated: 1708619978

View File

@ -0,0 +1,28 @@
#if VRC_ENABLE_PLAYER_PERSISTENCE
namespace VRC.SDK3.ClientSim.Persistence
{
public enum ClientSimPlayerDataType : byte
{
None = 0,
Vector2 = 1,
Vector3 = 2,
Vector4 = 3,
Quaternion = 4,
Color = 5,
Color32 = 6,
WrappedString = 7,
WrappedShort = 8,
WrappedInt = 9,
WrappedFloat = 10,
WrappedBool = 11,
WrappedByte = 12,
WrappedBytes = 13,
WrappedUShort = 14,
WrappedUByte = 15,
WrappedUInt = 16,
WrappedULong = 17,
WrappedDouble = 18,
WrappedLong = 19,
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ee1e38bff02f4812896fb863095a29ad
timeCreated: 1708638423

View File

@ -0,0 +1,39 @@
#if VRC_ENABLE_PLAYER_PERSISTENCE
using Newtonsoft.Json;
using UnityEngine;
namespace VRC.SDK3.ClientSim.Persistence
{
[JsonConverter(typeof(ClientSimPlayerDataConverter))]
public class ClientSimPlayerDataTypeUnion
{
public object Value { get; set; }
public ClientSimPlayerDataType Type { get; set; }
public ClientSimPlayerDataTypeUnion() {
this.Type = ClientSimPlayerDataType.None;
this.Value = null;
}
public Vector2 AsVector2() => (Vector2)Value;
public Vector3 AsVector3() => (Vector3)Value;
public Vector4 AsVector4() => (Vector4)Value;
public Quaternion AsQuaternion() => (Quaternion)Value;
public Color AsColor() => (Color)Value;
public Color32 AsColor32() => (Color32)Value;
public string AsWrappedString() { return (string)Value; }
public short AsWrappedShort() { return (short)Value; }
public ushort AsWrappedUShort() { return (ushort)Value; }
public int AsWrappedInt() { return (int)Value; }
public uint AsWrappedUInt() { return (uint)Value; }
public long AsWrappedLong() { return (long)Value; }
public ulong AsWrappedULong() { return ( ulong)Value; }
public float AsWrappedFloat() { return ( float)Value; }
public double AsWrappedDouble() { return (double)Value; }
public bool AsWrappedBool() { return (bool)Value; }
public byte AsWrappedByte() { return (byte)Value ; }
public sbyte AsWrappedSByte() { return (sbyte)Value; }
public byte[] AsWrappedBytes() { return (byte[])Value; }
public byte AsWrappedUByte() { return (byte)Value; }
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 53d85c02f89642f49608b67f2788c77a
timeCreated: 1708638433

View File

@ -0,0 +1,472 @@
#if VRC_ENABLE_PLAYER_PERSISTENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim.Persistence
{
public static class ClientSimPlayerDataWrapper
{
public static void ConfigureSDK()
{
System.Func<VRCPlayerApi, IEnumerable<string>> _getKeys = GetKeys;
FieldInfo getKeysInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getKeys", BindingFlags.Static | BindingFlags.NonPublic);
getKeysInfo?.SetValue(null, _getKeys);
System.Func<VRCPlayerApi, string, bool> _hasKey = HasKey;
FieldInfo hasKeyInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_hasKey", BindingFlags.Static | BindingFlags.NonPublic);
hasKeyInfo?.SetValue(null, _hasKey);
System.Func<VRCPlayerApi, string, Type> _getType = GetType;
FieldInfo getTypeInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getType", BindingFlags.Static | BindingFlags.NonPublic);
getTypeInfo?.SetValue(null, _getType);
System.Action<string, bool> _setBool = SetBool;
System.Func<VRCPlayerApi, string, bool> _getBool = GetBool;
FieldInfo setBoolInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setBool", BindingFlags.Static | BindingFlags.NonPublic);
setBoolInfo?.SetValue(null, _setBool);
FieldInfo getBoolInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getBool", BindingFlags.Static | BindingFlags.NonPublic);
getBoolInfo?.SetValue(null, _getBool);
System.Action<string, byte> _setByte = SetByte;
System.Func<VRCPlayerApi, string, byte> _getByte = GetByte;
FieldInfo setByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setByte", BindingFlags.Static | BindingFlags.NonPublic);
setByteInfo?.SetValue(null, _setByte);
FieldInfo getByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getByte", BindingFlags.Static | BindingFlags.NonPublic);
getByteInfo?.SetValue(null, _getByte);
System.Action<string, sbyte> _setUByte = SetSByte;
System.Func<VRCPlayerApi, string, sbyte> _getUByte = GetSByte;
FieldInfo setUByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setSByte", BindingFlags.Static | BindingFlags.NonPublic);
setUByteInfo?.SetValue(null, _setUByte);
FieldInfo getUByteInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getSByte", BindingFlags.Static | BindingFlags.NonPublic);
getUByteInfo?.SetValue(null, _getUByte);
System.Action<string, byte[]> _setBytes = SetBytes;
System.Func<VRCPlayerApi, string, byte[]> _getBytes = GetBytes;
FieldInfo setBytesInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setBytes", BindingFlags.Static | BindingFlags.NonPublic);
setBytesInfo?.SetValue(null, _setBytes);
FieldInfo getBytesInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getBytes", BindingFlags.Static | BindingFlags.NonPublic);
getBytesInfo?.SetValue(null, _getBytes);
System.Action<string, string> _setString = SetString;
System.Func<VRCPlayerApi, string, string> _getString = GetString;
FieldInfo setStringInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setString", BindingFlags.Static | BindingFlags.NonPublic);
setStringInfo?.SetValue(null, _setString);
FieldInfo getStringInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getString", BindingFlags.Static | BindingFlags.NonPublic);
getStringInfo?.SetValue(null, _getString);
System.Action<string, short> _setShort = SetShort;
System.Func<VRCPlayerApi, string, short> _getShort = GetShort;
FieldInfo setShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setShort", BindingFlags.Static | BindingFlags.NonPublic);
setShortInfo?.SetValue(null, _setShort);
FieldInfo getShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getShort", BindingFlags.Static | BindingFlags.NonPublic);
getShortInfo?.SetValue(null, _getShort);
System.Action<string, ushort> _setUShort = SetUShort;
System.Func<VRCPlayerApi, string, ushort> _getUShort = GetUShort;
FieldInfo setUShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setUShort", BindingFlags.Static | BindingFlags.NonPublic);
setUShortInfo?.SetValue(null, _setUShort);
FieldInfo getUShortInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getUShort", BindingFlags.Static | BindingFlags.NonPublic);
getUShortInfo?.SetValue(null, _getUShort);
System.Action<string, int> _setInt = SetInt;
System.Func<VRCPlayerApi, string, int> _getInt = GetInt;
FieldInfo setIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setInt", BindingFlags.Static | BindingFlags.NonPublic);
setIntInfo?.SetValue(null, _setInt);
FieldInfo getIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getInt", BindingFlags.Static | BindingFlags.NonPublic);
getIntInfo?.SetValue(null, _getInt);
System.Action<string, uint> _setUInt = SetUInt;
System.Func<VRCPlayerApi, string, uint> _getUInt = GetUInt;
FieldInfo setUIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setUInt", BindingFlags.Static | BindingFlags.NonPublic);
setUIntInfo?.SetValue(null, _setUInt);
FieldInfo getUIntInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getUInt", BindingFlags.Static | BindingFlags.NonPublic);
getUIntInfo?.SetValue(null, _getUInt);
System.Action<string, long> _setLong = SetLong;
System.Func<VRCPlayerApi, string, long> _getLong = GetLong;
FieldInfo setLongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setLong", BindingFlags.Static | BindingFlags.NonPublic);
setLongInfo?.SetValue(null, _setLong);
FieldInfo getLongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getLong", BindingFlags.Static | BindingFlags.NonPublic);
getLongInfo?.SetValue(null, _getLong);
System.Action<string, ulong> _setULong = SetULong;
System.Func<VRCPlayerApi, string, ulong> _getULong = GetULong;
FieldInfo setULongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setULong", BindingFlags.Static | BindingFlags.NonPublic);
setULongInfo?.SetValue(null, _setULong);
FieldInfo getULongInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getULong", BindingFlags.Static | BindingFlags.NonPublic);
getULongInfo?.SetValue(null, _getULong);
System.Action<string, double> _setDouble = SetDouble;
System.Func<VRCPlayerApi, string, double> _getDouble = GetDouble;
FieldInfo setDoubleInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setDouble", BindingFlags.Static | BindingFlags.NonPublic);
setDoubleInfo?.SetValue(null, _setDouble);
FieldInfo getDoubleInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getDouble", BindingFlags.Static | BindingFlags.NonPublic);
getDoubleInfo?.SetValue(null, _getDouble);
System.Action<string, float> _setFloat = SetFloat;
System.Func<VRCPlayerApi, string, float> _getFloat = GetFloat;
FieldInfo setFloatInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setFloat", BindingFlags.Static | BindingFlags.NonPublic);
setFloatInfo?.SetValue(null, _setFloat);
FieldInfo getFloatInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getFloat", BindingFlags.Static | BindingFlags.NonPublic);
getFloatInfo?.SetValue(null, _getFloat);
System.Action<string, Vector2> _setVector2 = SetVector2;
System.Func<VRCPlayerApi, string, Vector2> _getVector2 = GetVector2;
FieldInfo setVector2Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setVector2", BindingFlags.Static | BindingFlags.NonPublic);
setVector2Info?.SetValue(null, _setVector2);
FieldInfo getVector2Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getVector2", BindingFlags.Static | BindingFlags.NonPublic);
getVector2Info?.SetValue(null, _getVector2);
System.Action<string, Vector3> _setVector3 = SetVector3;
System.Func<VRCPlayerApi, string, Vector3> _getVector3 = GetVector3;
FieldInfo setVector3Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setVector3", BindingFlags.Static | BindingFlags.NonPublic);
setVector3Info?.SetValue(null, _setVector3);
FieldInfo getVector3Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getVector3", BindingFlags.Static | BindingFlags.NonPublic);
getVector3Info?.SetValue(null, _getVector3);
System.Action<string, Vector4> _setVector4 = SetVector4;
System.Func<VRCPlayerApi, string, Vector4> _getVector4 = GetVector4;
FieldInfo setVector4Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setVector4", BindingFlags.Static | BindingFlags.NonPublic);
setVector4Info?.SetValue(null, _setVector4);
FieldInfo getVector4Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getVector4", BindingFlags.Static | BindingFlags.NonPublic);
getVector4Info?.SetValue(null, _getVector4);
System.Action<string, Quaternion> _setQuaternion = SetQuaternion;
System.Func<VRCPlayerApi, string, Quaternion> _getQuaternion = GetQuaternion;
FieldInfo setQuaternionInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setQuaternion", BindingFlags.Static | BindingFlags.NonPublic);
setQuaternionInfo?.SetValue(null, _setQuaternion);
FieldInfo getQuaternionInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getQuaternion", BindingFlags.Static | BindingFlags.NonPublic);
getQuaternionInfo?.SetValue(null, _getQuaternion);
System.Action<string, UnityEngine.Color> _setColor = SetColor;
System.Func<VRCPlayerApi, string, UnityEngine.Color> _getColor = GetColor;
FieldInfo setColorInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setColor", BindingFlags.Static | BindingFlags.NonPublic);
setColorInfo?.SetValue(null, _setColor);
FieldInfo getColorInfo = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getColor", BindingFlags.Static | BindingFlags.NonPublic);
getColorInfo?.SetValue(null, _getColor);
System.Action<string, UnityEngine.Color32> _setColor32 = SetColor32;
System.Func<VRCPlayerApi, string, UnityEngine.Color32> _getColor32 = GetColor32;
FieldInfo setColor32Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_setColor32", BindingFlags.Static | BindingFlags.NonPublic);
setColor32Info?.SetValue(null, _setColor32);
FieldInfo getColor32Info = typeof(VRC.SDK3.Persistence.PlayerData).GetField("_getColor32", BindingFlags.Static | BindingFlags.NonPublic);
getColor32Info?.SetValue(null, _getColor32);
}
public static int GetUsage(VRCPlayerApi player)
=> FindStorage(player, out ClientSimPlayerDataStorage storage) ? storage.Size : 0;
private static bool FindStorage(VRCPlayerApi playerApi, out ClientSimPlayerDataStorage storage)
{
ClientSimPlayer player = playerApi.GetClientSimPlayer();
if (!player)
{
UnityEngine.Debug.LogError("Could not locate player with id " + playerApi.playerId + " for PlayerData storage.");
storage = null;
return false;
}
storage = player.PlayerDataObject;
if (!storage)
{
UnityEngine.Debug.LogError("Could not locate PlayerData storage for player " + playerApi.playerId);
return false;
}
return true;
}
public static IEnumerable<string> GetKeys(VRCPlayerApi playerApi)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return Enumerable.Empty<string>();
return storage.GetKeys();
}
public static bool HasKey(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return false;
return storage.HasKey(key);
}
public static Type GetType(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return null;
return storage.GetType(key);
}
public static void SetBool(string key, bool data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetBool(key, data);
}
public static bool GetBool(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetBool(key);
}
public static void SetByte(string key, byte data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetByte(key, data);
}
public static byte GetByte(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetByte(key);
}
public static void SetSByte(string key, sbyte data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetSByte(key, data);
}
public static sbyte GetSByte(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetSByte(key);
}
public static void SetBytes(string key, byte[] data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetBytes(key, data);
}
public static byte[] GetBytes(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetBytes(key);
}
public static void SetString(string key, string data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetString(key, data);
}
public static string GetString(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetString(key);
}
public static void SetShort(string key, short data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetShort(key, data);
}
public static void SetUShort(string key, ushort data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetUShort(key, data);
}
public static short GetShort(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetShort(key);
}
public static ushort GetUShort(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetUShort(key);
}
public static void SetInt(string key, int data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetInt(key, data);
}
public static void SetUInt(string key, uint data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetUInt(key, data);
}
public static int GetInt(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetInt(key);
}
public static uint GetUInt(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetUInt(key);
}
public static void SetLong(string key, long data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetLong(key, data);
}
public static long GetLong(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetLong(key);
}
public static void SetULong(string key, ulong data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetULong(key, data);
}
public static ulong GetULong(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetULong(key);
}
public static void SetFloat(string key, float data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetFloat(key, data);
}
public static float GetFloat(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetFloat(key);
}
public static void SetDouble(string key, double data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetDouble(key, data);
}
public static double GetDouble(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetDouble(key);
}
public static void SetVector2(string key, Vector2 data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetVector2(key, data);
}
public static Vector2 GetVector2(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetVector2(key);
}
public static void SetVector3(string key, Vector3 data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetVector3(key, data);
}
public static Vector3 GetVector3(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetVector3(key);
}
public static void SetVector4(string key, Vector4 data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetVector4(key, data);
}
public static Vector4 GetVector4(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetVector4(key);
}
public static void SetQuaternion(string key, Quaternion data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetQuaternion(key, data);
}
public static Quaternion GetQuaternion(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetQuaternion(key);
}
public static void SetColor(string key, UnityEngine.Color data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetColor(key, data);
}
public static UnityEngine.Color GetColor(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetColor(key);
}
public static void SetColor32(string key, UnityEngine.Color32 data)
{
if (!FindStorage(Networking.LocalPlayer, out ClientSimPlayerDataStorage storage)) return;
storage.SetColor32(key, data);
}
public static UnityEngine.Color32 GetColor32(VRCPlayerApi playerApi, string key)
{
if (!FindStorage(playerApi, out ClientSimPlayerDataStorage storage)) return default;
return storage.GetColor32(key);
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cf5156727fe54ec1b396e496f91f03d4
timeCreated: 1708639223

View File

@ -0,0 +1,198 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using VRC.SDK3.ClientSim.Interfaces;
using VRC.SDK3.Data;
using VRC.SDKBase;
using VRC.Udon;
namespace VRC.SDK3.ClientSim.Persistence
{
[AddComponentMenu("")] // hides component in Add Component menu
public class ClientSimPlayerObjectStorage : ClientSimBehaviour
{
#if VRC_ENABLE_PLAYER_PERSISTENCE
public static string PlayerObjectsFolder => Path.Combine("ClientSimStorage", "PlayerObjects");
internal static string ActiveSceneName;
internal static string PlayerDataFilePath(VRCPlayerApi player)
{
string root = Path.GetDirectoryName(Application.dataPath);
string path = Path.Combine(root, PlayerObjectsFolder);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path + "/PlayerObject_" + $"{player.playerId}" + $"_{ActiveSceneName}" + ".json";
}
private VRCPlayerApi _player;
private IClientSimUdonEventSender _udonEventSender;
private IClientSimEventDispatcher _eventDispatcher;
private Dictionary<int,IClientSimNetworkView> _persistentObjects = new Dictionary<int, IClientSimNetworkView>();
private DataDictionary _persistentObjectData = new DataDictionary();
private bool _isInitialized = false;
private bool _HasJoined = false;
private Coroutine _ContinuousUpdate;
private const float _updateInterval = 1 / 4f;
private bool hadUpdate = false;
public int Size =>
VRCJson.TrySerializeToJson(_persistentObjectData, JsonExportType.Beautify, out DataToken json)
? System.Text.Encoding.UTF8.GetByteCount(json.String)
: 0;
public void Init(VRCPlayerApi player, IClientSimUdonEventSender udonEventSender, IClientSimEventDispatcher eventDispatcher)
{
_player = player;
_udonEventSender = udonEventSender;
_eventDispatcher = eventDispatcher;
ActiveSceneName = SceneManager.GetActiveScene().name;
_eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
UdonBehaviour.RequestSerializationHook += RequestSerializationHook;
_isInitialized = true;
_ContinuousUpdate = StartCoroutine(UpdateContinuous());
}
private void OnDestroy()
{
if(_eventDispatcher != null)
_eventDispatcher.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
if(_ContinuousUpdate != null)
StopCoroutine(_ContinuousUpdate);
}
public IEnumerator UpdateContinuous()
{
while (true)
{
if (_HasJoined && _isInitialized)
{
Encode();
}
yield return new WaitForSeconds(_updateInterval);
}
}
public void RequestSerializationHook(UdonBehaviour udonBehaviour)
{
ClientSimPersistenceEventSending.Instance.QueueRequest(udonBehaviour, this);
}
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent payload)
{
if (payload.player.playerId == _player.playerId)
{
_HasJoined = true;
Decode();
}
}
public void Encode(GameObject gameObject = null)
{
if (!_isInitialized || !_HasJoined) return;
if(_persistentObjectData == null)
_persistentObjectData = new DataDictionary();
foreach (var keyValuePersistentObject in _persistentObjects)
{
DataToken key = keyValuePersistentObject.Key.ToString();
if (!_persistentObjectData.ContainsKey(key))
_persistentObjectData.Add(key, new DataList());
_persistentObjectData[key] = keyValuePersistentObject.Value.Encode(gameObject);
}
hadUpdate = true;
}
private async UniTask SaveToFile(string data)
{
await UniTask.SwitchToTaskPool();
try{
await File.WriteAllTextAsync(PlayerDataFilePath(_player), data);
}
catch (Exception e)
{
this.LogError($"Error saving PlayerObjects: {e.Message}");
}
}
private void Decode()
{
string path = PlayerDataFilePath(_player);
if (!File.Exists(path))
{
File.WriteAllText(path, "{}");
}
string json = File.ReadAllText(path);
if (!VRCJson.TryDeserializeFromJson(json, out DataToken token))
{
this.LogError($"Error initializing PlayerObjects: {token.Error}");
return;
}
_persistentObjectData = token.DataDictionary;
ClientSimPlayer player = _player.GetClientSimPlayer();
foreach (GameObject persistantObject in player.PlayerPersistenceObjects)
{
IClientSimNetworkId networkId = persistantObject.GetComponent<IClientSimNetworkId>();
if (networkId == null) continue;
int id = networkId.GetNetworkId();
IClientSimNetworkView serializer = persistantObject.GetComponent<IClientSimNetworkView>();
_persistentObjects.TryAdd(id, serializer);
if (_persistentObjectData.TryGetValue(id.ToString(), out var data))
{
_persistentObjects[id].Decode(data.DataList);
}
}
_eventDispatcher.SendEvent(new ClientSimOnPlayerObjectsDecodedEvent { player = _player });
}
private float lastCheckTime = 0;
public void LateUpdate()
{
if (hadUpdate)
{
hadUpdate = false;
_eventDispatcher.SendEvent(new ClientSimOnPlayerObjectUpdateEndedEvent());
VRCJson.TrySerializeToJson(_persistentObjectData, JsonExportType.Beautify, out DataToken json);
SaveToFile(json.String).Forget();
}
if (Time.realtimeSinceStartup - lastCheckTime > 30f)
{
lastCheckTime = Time.realtimeSinceStartup;
float objLimit = ClientSimMain.TryGetInstance(out var instance) ? instance.GetPlayerObjectsUsageLimit() : 0;
float sz = Size;
if (sz >= objLimit)
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYEROBJECTSTORAGEEXCEEDED, ("player", _player));
else if (sz >= objLimit * 0.7f)
UdonManager.Instance.RunEvent(UdonManager.UDON_EVENT_ONPLAYERDATASTORAGEWARNING, ("player", _player));
}
}
#endif
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 06c904c1b8134763a58e319723ec67ad
timeCreated: 1709650412