Added Unity project files
This commit is contained in:
@ -0,0 +1,198 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Network;
|
||||
using VRC.SDK3.UdonNetworkCalling;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public static class ClientSimNetworkCalling
|
||||
{
|
||||
// this is fine (insert funny dog with fire meme)
|
||||
internal static int GetAllQueuedEventsProxy() => 0;
|
||||
internal static int GetQueuedEventsProxy(IUdonEventReceiver udonBehaviour, string eventName) => 0;
|
||||
|
||||
// magic goes here, though note we don't really encode/decode parameters here, just pass them through
|
||||
// in the real client, even local events are passed through binary serialization
|
||||
internal static void SendCustomNetworkEventProxy(IUdonEventReceiver udonBehaviour, NetworkEventTarget target, string eventName, Memory<object> parameters)
|
||||
{
|
||||
// "Others" is not relevant for ClientSim, we're forever alone :(
|
||||
// "All", "Owner" and "Self" pass through all the same
|
||||
if (target == NetworkEventTarget.Others)
|
||||
return;
|
||||
|
||||
if (udonBehaviour is not UdonBehaviour behaviour)
|
||||
{
|
||||
// ClientSim only warning
|
||||
Debug.LogWarning($"Attempted to send network event {eventName}, but the IUdonEventReceiver was null or the wrong type.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (behaviour.SyncMethod == Networking.SyncType.None)
|
||||
{
|
||||
Debug.LogError($"Unable to send network event to UdonBehaviour '{behaviour.name}' with SyncType '{behaviour.SyncMethod}'.", behaviour);
|
||||
return;
|
||||
}
|
||||
|
||||
var hasEventHash = behaviour.TryGetEntrypointHashFromName(eventName, out var _);
|
||||
if (!hasEventHash)
|
||||
{
|
||||
Debug.LogError($"Unable to send network event '{eventName}' as it does not exist on the target Udon behaviour '{behaviour.name}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
var metadata = behaviour.GetNetworkCallingMetadata(eventName);
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
// legacy event, match client checks and errors
|
||||
if (eventName.StartsWith("_"))
|
||||
{
|
||||
Debug.LogError($"Unable to send local event '{eventName}' as an RPC. Events starting with an underscore may not be run remotely.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters.Length > 0)
|
||||
{
|
||||
throw new ArgumentException($"Network call to '{eventName}' had {parameters.Length} parameters, but no metadata was found. Are you missing a [NetworkCallable] attribute?");
|
||||
}
|
||||
|
||||
// GameObject targeting for legacy events
|
||||
foreach (var component in behaviour.gameObject.GetComponents<UdonBehaviour>())
|
||||
{
|
||||
if (component == null || component.SyncMethod == SDKBase.Networking.SyncType.None)
|
||||
continue;
|
||||
|
||||
RunEventSafe(component, eventName, null, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: add size limits
|
||||
|
||||
// modern style, once again match client validation errors here
|
||||
metadata.ValidateOnce();
|
||||
|
||||
if (metadata.Parameters.Length != parameters.Length)
|
||||
{
|
||||
throw new ArgumentException($"Parameter count mismatch for network call to '{metadata.Name}'. Expected {metadata.Parameters.Length}, got {parameters.Length}.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters.Span[i];
|
||||
if (parameter == null)
|
||||
continue;
|
||||
var paramMetadata = metadata.Parameters[i];
|
||||
var parameterType = parameter.GetType();
|
||||
if (VRCUdonSyncTypeConverter.TypeToUdonType(parameterType) != paramMetadata.Type)
|
||||
{
|
||||
throw new ArgumentException($"Parameter '{paramMetadata.Name}' in network call to '{metadata.Name}' had incorrect type '{parameterType}'.");
|
||||
}
|
||||
}
|
||||
|
||||
// component targeting for modern events
|
||||
RunEventSafe(behaviour, eventName, metadata, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunEventSafe(UdonBehaviour udonBehaviour, string eventName, NetworkCallingEntrypointMetadata metadata, Memory<object> parameters)
|
||||
{
|
||||
// client does a try/catch, so we do it too
|
||||
try
|
||||
{
|
||||
NetworkCalling.WithNetworkCallingContext(Networking.LocalPlayer, () => {
|
||||
RunEvent(udonBehaviour, eventName, metadata, parameters.Span);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Error running Udon network event '{eventName}' on {udonBehaviour.name}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunEvent(UdonBehaviour udonBehaviour, string eventName, NetworkCallingEntrypointMetadata metadata, Span<object> parameters)
|
||||
{
|
||||
var parameterCount = metadata?.Parameters != null ? metadata.Parameters.Length : 0;
|
||||
switch (parameterCount)
|
||||
{
|
||||
case 0:
|
||||
// includes legacy events
|
||||
udonBehaviour.RunEventAdvanced(eventName, canRunBeforeStart: true);
|
||||
break;
|
||||
case 1:
|
||||
udonBehaviour.RunEventAdvanced<object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null)
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
udonBehaviour.RunEventAdvanced<object, object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null),
|
||||
(metadata.Parameters[1].Name, parameters.Length >= 2 ? parameters[1] : null)
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
udonBehaviour.RunEventAdvanced<object, object, object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null),
|
||||
(metadata.Parameters[1].Name, parameters.Length >= 2 ? parameters[1] : null),
|
||||
(metadata.Parameters[2].Name, parameters.Length >= 3 ? parameters[2] : null)
|
||||
);
|
||||
break;
|
||||
case 4:
|
||||
udonBehaviour.RunEventAdvanced<object, object, object, object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null),
|
||||
(metadata.Parameters[1].Name, parameters.Length >= 2 ? parameters[1] : null),
|
||||
(metadata.Parameters[2].Name, parameters.Length >= 3 ? parameters[2] : null),
|
||||
(metadata.Parameters[3].Name, parameters.Length >= 4 ? parameters[3] : null)
|
||||
);
|
||||
break;
|
||||
case 5:
|
||||
udonBehaviour.RunEventAdvanced<object, object, object, object, object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null),
|
||||
(metadata.Parameters[1].Name, parameters.Length >= 2 ? parameters[1] : null),
|
||||
(metadata.Parameters[2].Name, parameters.Length >= 3 ? parameters[2] : null),
|
||||
(metadata.Parameters[3].Name, parameters.Length >= 4 ? parameters[3] : null),
|
||||
(metadata.Parameters[4].Name, parameters.Length >= 5 ? parameters[4] : null)
|
||||
);
|
||||
break;
|
||||
// look I don't like "vibe-coding" and "AI programmers", but you'd be mistaken if you think I didn't just tell copilot to copy these at some point
|
||||
case 6:
|
||||
udonBehaviour.RunEventAdvanced<object, object, object, object, object, object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null),
|
||||
(metadata.Parameters[1].Name, parameters.Length >= 2 ? parameters[1] : null),
|
||||
(metadata.Parameters[2].Name, parameters.Length >= 3 ? parameters[2] : null),
|
||||
(metadata.Parameters[3].Name, parameters.Length >= 4 ? parameters[3] : null),
|
||||
(metadata.Parameters[4].Name, parameters.Length >= 5 ? parameters[4] : null),
|
||||
(metadata.Parameters[5].Name, parameters.Length >= 6 ? parameters[5] : null)
|
||||
);
|
||||
break;
|
||||
case 7:
|
||||
udonBehaviour.RunEventAdvanced<object, object, object, object, object, object, object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null),
|
||||
(metadata.Parameters[1].Name, parameters.Length >= 2 ? parameters[1] : null),
|
||||
(metadata.Parameters[2].Name, parameters.Length >= 3 ? parameters[2] : null),
|
||||
(metadata.Parameters[3].Name, parameters.Length >= 4 ? parameters[3] : null),
|
||||
(metadata.Parameters[4].Name, parameters.Length >= 5 ? parameters[4] : null),
|
||||
(metadata.Parameters[5].Name, parameters.Length >= 6 ? parameters[5] : null),
|
||||
(metadata.Parameters[6].Name, parameters.Length >= 7 ? parameters[6] : null)
|
||||
);
|
||||
break;
|
||||
case 8:
|
||||
udonBehaviour.RunEventAdvanced<object, object, object, object, object, object, object, object>(eventName, mangleParameterNames: false, canRunBeforeStart: true,
|
||||
(metadata.Parameters[0].Name, parameters.Length >= 1 ? parameters[0] : null),
|
||||
(metadata.Parameters[1].Name, parameters.Length >= 2 ? parameters[1] : null),
|
||||
(metadata.Parameters[2].Name, parameters.Length >= 3 ? parameters[2] : null),
|
||||
(metadata.Parameters[3].Name, parameters.Length >= 4 ? parameters[3] : null),
|
||||
(metadata.Parameters[4].Name, parameters.Length >= 5 ? parameters[4] : null),
|
||||
(metadata.Parameters[5].Name, parameters.Length >= 6 ? parameters[5] : null),
|
||||
(metadata.Parameters[6].Name, parameters.Length >= 7 ? parameters[6] : null),
|
||||
(metadata.Parameters[7].Name, parameters.Length >= 8 ? parameters[7] : null)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7aec0ce478dd45e3b123829428887c09
|
||||
timeCreated: 1712850919
|
||||
@ -0,0 +1,206 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.ClientSim.EncodeDecoders;
|
||||
using VRC.SDK3.ClientSim.Interfaces;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDK3.Data;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public class ClientSimNetworkIdHolder : ClientSimBehaviour, IClientSimNetworkSerializer
|
||||
{
|
||||
internal ClientSimNetworkingView _networkId;
|
||||
internal DataList _data = new DataList();
|
||||
private bool IsManual = false;
|
||||
|
||||
private bool _isInitialized;
|
||||
|
||||
internal List<MonoBehaviour> _components;
|
||||
internal static Dictionary<string,IClientSimEncodeDecoder> encodeDecoders = new Dictionary<string,IClientSimEncodeDecoder>
|
||||
{
|
||||
{ typeof(VRCObjectSync).FullName, new ClientSimObjectSyncEncodeDecoder() },
|
||||
{ typeof(UdonBehaviour).FullName, new ClientSimUdonEncodeDecode() },
|
||||
{ typeof(VRCObjectPool).FullName, new ClientSimObjectPoolEncodeDecode()}
|
||||
};
|
||||
|
||||
public void SetNetworkView(ClientSimNetworkingView networkId)
|
||||
{
|
||||
_networkId = networkId;
|
||||
}
|
||||
|
||||
public ClientSimNetworkingView GetNetworkView()
|
||||
{
|
||||
return _networkId;
|
||||
}
|
||||
|
||||
public int GetPlayerId()
|
||||
{
|
||||
return _networkId.GetPlayerId();
|
||||
}
|
||||
|
||||
public void SetNetworkComponents()
|
||||
{
|
||||
_components = new List<MonoBehaviour>();
|
||||
MonoBehaviour[] allComponents = GetComponents<MonoBehaviour>();
|
||||
IsManual = true;
|
||||
for (int i =0; i < allComponents.Length;i++)
|
||||
{
|
||||
if (encodeDecoders.ContainsKey(allComponents[i].GetType().FullName))
|
||||
{
|
||||
if (allComponents[i].GetType() == typeof(UdonBehaviour))
|
||||
{
|
||||
if (((UdonBehaviour)allComponents[i]).SyncMethod == Networking.SyncType.None)
|
||||
continue;
|
||||
}
|
||||
|
||||
IsManual = IsManual && encodeDecoders[allComponents[i].GetType().FullName].IsManualSynced(allComponents[i]);
|
||||
_components.Add(allComponents[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDirty(GameObject gameObject = null)
|
||||
{
|
||||
if(this == null)
|
||||
return false;
|
||||
|
||||
if(gameObject != null)
|
||||
if(gameObject != this.gameObject)
|
||||
return false;
|
||||
|
||||
if(IsManual && gameObject == null)
|
||||
return false;
|
||||
|
||||
if(_data.Count < _components.Count)
|
||||
return true;
|
||||
|
||||
for(int i = 0; i < _components.Count;i++)
|
||||
{
|
||||
string componentType = _components[i].GetType().FullName;
|
||||
if (encodeDecoders.TryGetValue(componentType, out var encodeDecoder))
|
||||
{
|
||||
if(encodeDecoder.IsDirty(_components[i], _data[i].DataDictionary))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public DataList Encode(GameObject gameObject = null)
|
||||
{
|
||||
if(gameObject != null)
|
||||
if (gameObject != this.gameObject)
|
||||
return _data;
|
||||
|
||||
if (IsManual && gameObject == null)
|
||||
return _data;
|
||||
|
||||
_data ??= new DataList();
|
||||
for(int i = 0; i < _components.Count;i++)
|
||||
{
|
||||
string componentType = _components[i].GetType().FullName;
|
||||
if (encodeDecoders.TryGetValue(componentType, out var encodeDecoder))
|
||||
{
|
||||
if(_data.Count > i)
|
||||
_data[i] = encodeDecoder.Encode(_components[i]);
|
||||
else
|
||||
{
|
||||
_data.Add(encodeDecoder.Encode(_components[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
if (ClientSimMain.TryGetInstance(out var instance))
|
||||
instance.GetEventDispatcher().SendEvent(new ClientSimOnPlayerObjectUpdatedEvent{Data = this});
|
||||
#endif
|
||||
return _data;
|
||||
}
|
||||
|
||||
public void PostEncode(GameObject gameObject = null)
|
||||
{
|
||||
if(this == null)
|
||||
return;
|
||||
|
||||
if(gameObject != null)
|
||||
if(gameObject != this.gameObject)
|
||||
return;
|
||||
|
||||
if(IsManual && gameObject == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _components.Count; i++)
|
||||
{
|
||||
string componentType = _components[i].GetType().FullName;
|
||||
if (encodeDecoders.TryGetValue(componentType, out var encodeDecoder))
|
||||
{
|
||||
encodeDecoder.PostEncode(_components[i], _data[i].DataDictionary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void PreEncode(GameObject gameObject = null)
|
||||
{
|
||||
if(this == null)
|
||||
return;
|
||||
|
||||
if(gameObject != null)
|
||||
if(gameObject != this.gameObject)
|
||||
return;
|
||||
|
||||
if(IsManual && gameObject == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _components.Count; i++)
|
||||
{
|
||||
string componentType = _components[i].GetType().FullName;
|
||||
if (encodeDecoders.TryGetValue(componentType, out var encodeDecoder))
|
||||
{
|
||||
encodeDecoder.PreEncode(_components[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Decode(DataList data)
|
||||
{
|
||||
_data = data;
|
||||
for(int i = 0; i < _components.Count;i++)
|
||||
{
|
||||
string componentType = _components[i].GetType().FullName;
|
||||
if (encodeDecoders.TryGetValue(componentType, out var encodeDecoder))
|
||||
{
|
||||
if(data.Count > i && !data[i].IsNull)
|
||||
encodeDecoder.Decode(_components[i], data[i].DataDictionary);
|
||||
}
|
||||
}
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
if (ClientSimMain.TryGetInstance(out var instance))
|
||||
instance.GetEventDispatcher().SendEvent(new ClientSimOnPlayerObjectUpdatedEvent{Data = this});
|
||||
#endif
|
||||
}
|
||||
|
||||
public int GetNetworkComponentCount()
|
||||
{
|
||||
return _components.Count;
|
||||
}
|
||||
|
||||
public DataList GetData()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
public List<MonoBehaviour> GetNetworkComponents()
|
||||
{
|
||||
return _components;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 197c19bb6430471bb97ef9982849c00a
|
||||
timeCreated: 1709653519
|
||||
@ -0,0 +1,437 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VRC.SDK3.ClientSim.Interfaces;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDK3.Data;
|
||||
using VRC.SDKBase;
|
||||
using VRC.SDKBase.Network;
|
||||
using VRC.Utility;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public static class ClientSimNetworkingUtilities
|
||||
{
|
||||
public const int MinID = 10;
|
||||
public const int MaxID = 100_000;
|
||||
public const int MinimumViewID = 10;
|
||||
|
||||
public const int PlayerDataObjectID = 4;
|
||||
public static int FirstPlayerPersistenceID => System.Math.Max(MinimumViewID, 100);
|
||||
public static int MaxPlayerPersistenceID => Mathf.CeilToInt(MaxID * 0.1f);
|
||||
|
||||
private static VRCPlayerObject[] _playerObjectList;
|
||||
|
||||
public enum OwnershipOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Ownership is fixed. Instantiated objects stick with their creator, scene objects always belong to the Master Client.
|
||||
/// </summary>
|
||||
Fixed,
|
||||
/// <summary>
|
||||
/// Ownership can be taken away from the current owner who can't object.
|
||||
/// </summary>
|
||||
Takeover,
|
||||
/// <summary>
|
||||
/// Ownership can be requested with PhotonView.RequestOwnership but the current owner has to agree to give up ownership.
|
||||
/// </summary>
|
||||
/// <remarks>The current owner has to implement IPunCallbacks.OnOwnershipRequest to react to the ownership request.</remarks>
|
||||
Request
|
||||
}
|
||||
|
||||
public static VRCPlayerObject[] GetPlayerObjectList()
|
||||
{
|
||||
if (_playerObjectList == null)
|
||||
{
|
||||
_playerObjectList = Object.FindObjectsByType<VRCPlayerObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
}
|
||||
return _playerObjectList;
|
||||
}
|
||||
|
||||
#region NetworkIdGeneration
|
||||
|
||||
public static int FlattenPlayerViewId(int viewID)
|
||||
=> viewID % MaxID;
|
||||
|
||||
public static void ConfigureNetworkOnScene(VRC_SceneDescriptor sceneDescriptor)
|
||||
{
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
VRCPlayerObject[] playerPersistence = GetPlayerObjectList();
|
||||
bool IsPlayerPersistence(GameObject obj)
|
||||
=> playerPersistence != null && playerPersistence.Any(pp => obj.transform.IsChildOf(pp.transform) || obj.transform == pp.transform);
|
||||
#endif
|
||||
|
||||
var (netIDs, _) = ConfigureNetworkIdsForClientSim(sceneDescriptor, out List<NetworkIDAssignment.SetErrorLocation> errors, NetworkIDAssignment.SetError.IncompatibleTypes);
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
foreach (var error in errors)
|
||||
Debug.LogError(error.ToString());
|
||||
Debug.LogErrorFormat("Found {0} errors while configuring network IDs!", errors.Count);
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.isPlaying = false;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (NetworkIDPair netID in netIDs)
|
||||
{
|
||||
GameObject obj = netID.gameObject;
|
||||
if (obj == null)
|
||||
continue;
|
||||
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
// This is initialized later, as players join
|
||||
if (IsPlayerPersistence(netID.gameObject))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
IClientSimNetworkId view = obj.GetComponent<IClientSimNetworkId>();
|
||||
if(view == null)
|
||||
view = obj.AddComponent<ClientSimNetworkingView>() as IClientSimNetworkId;
|
||||
view.SetNetworkId(netID.ID);
|
||||
view.OwnershipStyle(OwnershipOption.Takeover);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.isPlaying = false;
|
||||
#endif
|
||||
Debug.LogErrorFormat("Failed to configure network IDs on {0}", obj.name);
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static void DoSceneSetup()
|
||||
{
|
||||
//Find scene descriptor
|
||||
VRC_SceneDescriptor descriptor = VRC_SceneDescriptor.Instance;
|
||||
|
||||
if(descriptor == null)
|
||||
{
|
||||
VRC.Core.Logger.LogWarning("Unable to find scene descriptor in scene - Playing without ClientSim");
|
||||
return;
|
||||
}
|
||||
|
||||
var (_, newIDs) = NetworkIDAssignment.ConfigureNetworkIDs(
|
||||
descriptor,
|
||||
out List<NetworkIDAssignment.SetErrorLocation> errors,
|
||||
NetworkIDAssignment.SetError.InvalidObject);
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
// Try to fix the errors by removing duplicates
|
||||
List<NetworkIDPair> newIDsList = errors.FindAll(error => error.error == NetworkIDAssignment.SetError.IncompatibleTypes)
|
||||
.Select(error => new NetworkIDPair{ gameObject = error.location.gameObject, ID = error.location.ID, SerializedTypeNames = NetworkIDAssignment.GetSerializedTypes(error.location.gameObject) })
|
||||
.ToList();
|
||||
|
||||
descriptor.NetworkIDCollection = descriptor.NetworkIDCollection
|
||||
.Where(pair =>
|
||||
newIDsList.FindIndex(newID => newID.ID == pair.ID && pair.gameObject == newID.gameObject) == -1)
|
||||
.ToList();
|
||||
|
||||
descriptor.NetworkIDCollection.AddRange(newIDsList);
|
||||
|
||||
(_, newIDs) = NetworkIDAssignment.ConfigureNetworkIDs(
|
||||
descriptor,
|
||||
out errors,
|
||||
NetworkIDAssignment.SetError.InvalidObject);
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
VRC.Core.Logger.LogError($"Failed to assign network IDs, {errors.Count} errors encountered!\nTry using the Network ID Utility to resolve them.");
|
||||
EditorApplication.isPlaying = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (newIDs.Count() > 0)
|
||||
{
|
||||
descriptor.gameObject.MarkDirty();
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(descriptor);
|
||||
UnityEditor.AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static (IEnumerable<NetworkIDPair> allIDs, IEnumerable<NetworkIDPair> newIDs) ConfigureNetworkIdsForClientSim(INetworkIDContainer container, out List<NetworkIDAssignment.SetErrorLocation> errors, params NetworkIDAssignment.SetError[] errorsToIgnore)
|
||||
{
|
||||
errors = new List<NetworkIDAssignment.SetErrorLocation>();
|
||||
if(container.NetworkIDCollection == null)
|
||||
container.NetworkIDCollection = new List<NetworkIDPair>();
|
||||
|
||||
List<INetworkID> netIDs = new List<INetworkID>();
|
||||
List<NetworkIDPair> newIDs = new List<NetworkIDPair>();
|
||||
Dictionary<GameObject, NetworkIDPair> idMap = new Dictionary<GameObject, NetworkIDPair>();
|
||||
int id = MinID;
|
||||
Scene scene = SceneManager.GetActiveScene();
|
||||
|
||||
foreach (var pair in container.NetworkIDCollection)
|
||||
{
|
||||
if (!pair.gameObject)
|
||||
{
|
||||
errors.Add(new NetworkIDAssignment.SetErrorLocation { error = NetworkIDAssignment.SetError.InvalidObject, location = pair });
|
||||
}
|
||||
else if (pair.ID < MinID || pair.ID >= MaxID)
|
||||
{
|
||||
errors.Add(new NetworkIDAssignment.SetErrorLocation { error = NetworkIDAssignment.SetError.InvalidId, location = pair });
|
||||
}
|
||||
else
|
||||
{
|
||||
idMap[pair.gameObject] = pair;
|
||||
|
||||
if (pair.ID >= id)
|
||||
id = pair.ID + 1;
|
||||
}
|
||||
}
|
||||
|
||||
GameObject[] allObjects = GameObject.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
|
||||
foreach (var obj in allObjects)
|
||||
{
|
||||
netIDs.AddRange(obj.GetComponents<INetworkID>());
|
||||
}
|
||||
|
||||
netIDs = netIDs
|
||||
.Where(nid => (nid is Component cnid) && cnid.gameObject && scene == cnid.gameObject.scene)
|
||||
.Select(nid => (nid as Component).gameObject)
|
||||
.Distinct()
|
||||
.OrderBy(go => go.transform.Path(), StringComparer.InvariantCulture)
|
||||
.Select(go => go.GetComponent<INetworkID>())
|
||||
.ToList();
|
||||
|
||||
id = FindNextFreeID(id);
|
||||
foreach (INetworkID netID in netIDs)
|
||||
if (TrySetID(netID, ref id, ref errors, out NetworkIDPair newIDPair) && newIDPair != null)
|
||||
newIDs.Add(newIDPair);
|
||||
|
||||
errors.RemoveAll(err => errorsToIgnore.Contains(err.error));
|
||||
|
||||
container.NetworkIDCollection = idMap.Values.ToList();
|
||||
|
||||
return (netIDs.Select(nid => idMap[(nid as Component).gameObject]).Distinct(), newIDs);
|
||||
|
||||
bool TrySetID(INetworkID NetworkID, ref int refId, ref List<NetworkIDAssignment.SetErrorLocation> errors, out NetworkIDPair newIDPair)
|
||||
{
|
||||
GameObject source = (NetworkID as Component).gameObject;
|
||||
List<string> typeNames = GetSerializedTypes(NetworkID);
|
||||
int curId = refId;
|
||||
newIDPair = null;
|
||||
|
||||
if (curId < MinID || curId >= MaxID)
|
||||
{
|
||||
errors.Add(new NetworkIDAssignment.SetErrorLocation { error = NetworkIDAssignment.SetError.InvalidId, location = new NetworkIDPair { gameObject = source, ID = curId } });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idMap.Values.Any(pair => pair.ID == curId && pair.gameObject != source))
|
||||
{
|
||||
errors.Add(new NetworkIDAssignment.SetErrorLocation
|
||||
{
|
||||
error = NetworkIDAssignment.SetError.IdInUse,
|
||||
location = new NetworkIDPair { gameObject = source, ID = curId },
|
||||
relatedLocation = idMap.Values.First(pair => pair.ID == curId && pair.gameObject != source)
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idMap.TryGetValue(source, out NetworkIDPair matchInfo))
|
||||
{
|
||||
if (!DoTypesMatch(matchInfo, typeNames))
|
||||
errors.Add(new NetworkIDAssignment.SetErrorLocation { error = NetworkIDAssignment.SetError.IncompatibleTypes, location = matchInfo });
|
||||
}
|
||||
else
|
||||
{
|
||||
newIDPair = new SDKBase.Network.NetworkIDPair
|
||||
{
|
||||
gameObject = source,
|
||||
ID = curId,
|
||||
SerializedTypeNames = typeNames
|
||||
};
|
||||
|
||||
idMap.Add(source, newIDPair);
|
||||
|
||||
refId = FindNextFreeID(curId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DoTypesMatch(NetworkIDPair idInfo, List<string> typeNames)
|
||||
=> idInfo.SerializedTypeNames.Count == 0 // Won't exist for old scenes
|
||||
|| idInfo.SerializedTypeNames.SequenceEqual(typeNames);
|
||||
|
||||
int FindNextFreeID(int curId)
|
||||
{
|
||||
void findNextFree()
|
||||
{
|
||||
while (idMap.Values.Any(pair => pair.ID == curId) && curId < MaxID)
|
||||
curId++;
|
||||
}
|
||||
|
||||
findNextFree();
|
||||
|
||||
// Loop over once, in case there are gaps
|
||||
if (curId >= MaxID)
|
||||
{
|
||||
curId = MinID;
|
||||
findNextFree();
|
||||
}
|
||||
|
||||
if (curId >= MaxID)
|
||||
throw new System.ApplicationException("Ran out of Network IDs!");
|
||||
|
||||
return curId;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> GetSerializedTypes(INetworkID NetworkID)
|
||||
=> GetSerializedTypes((NetworkID as Component).gameObject);
|
||||
|
||||
public static List<string> GetSerializedTypes(GameObject NetworkID)
|
||||
=> NetworkID
|
||||
.GetComponents<VRCNetworkBehaviour>()
|
||||
.Select(nb => nb.GetType().FullName)
|
||||
.ToList();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Conversion To/From JToken
|
||||
|
||||
public static DataToken GetJTokenFromVector3(this Vector3 vector3)
|
||||
{
|
||||
DataDictionary jObject = new DataDictionary();
|
||||
jObject["T"] = "V3";
|
||||
jObject["x"] = vector3.x;
|
||||
jObject["y"] = vector3.y;
|
||||
jObject["z"] = vector3.z;
|
||||
return jObject;
|
||||
}
|
||||
|
||||
public static DataToken GetJTokenFromQuaternion(this Quaternion quaternion)
|
||||
{
|
||||
DataDictionary jObject = new DataDictionary();
|
||||
jObject["T"] = "Q";
|
||||
jObject["x"] = quaternion.x;
|
||||
jObject["y"] = quaternion.y;
|
||||
jObject["z"] = quaternion.z;
|
||||
jObject["w"] = quaternion.w;
|
||||
return jObject;
|
||||
}
|
||||
|
||||
public static DataToken GetJTokenFromVector2(this Vector2 vector2)
|
||||
{
|
||||
DataDictionary jObject = new DataDictionary();
|
||||
jObject["T"] = "V2";
|
||||
jObject["x"] = vector2.x;
|
||||
jObject["y"] = vector2.y;
|
||||
return jObject;
|
||||
}
|
||||
|
||||
public static DataToken GetJTokenFromColor(this Color color)
|
||||
{
|
||||
DataDictionary jObject = new DataDictionary();
|
||||
jObject["T"] = "C";
|
||||
jObject["r"] = color.r;
|
||||
jObject["g"] = color.g;
|
||||
jObject["b"] = color.b;
|
||||
jObject["a"] = color.a;
|
||||
return jObject;
|
||||
}
|
||||
|
||||
public static DataToken GetJTokenFromColor32(this Color32 color32)
|
||||
{
|
||||
DataDictionary jObject = new DataDictionary();
|
||||
jObject["T"] = "C32";
|
||||
jObject["r"] = color32.r;
|
||||
jObject["g"] = color32.g;
|
||||
jObject["b"] = color32.b;
|
||||
jObject["a"] = color32.a;
|
||||
return jObject;
|
||||
}
|
||||
|
||||
public static DataToken GetJTokenFromVector4(this Vector4 vector4)
|
||||
{
|
||||
DataDictionary jObject = new DataDictionary();
|
||||
jObject["T"] = "V4";
|
||||
jObject["x"] = vector4.x;
|
||||
jObject["y"] = vector4.y;
|
||||
jObject["z"] = vector4.z;
|
||||
jObject["w"] = vector4.w;
|
||||
return jObject;
|
||||
}
|
||||
|
||||
public static Vector3 GetVector3(this DataToken jToken)
|
||||
{
|
||||
DataDictionary dict = jToken.DataDictionary;
|
||||
if (dict != null)
|
||||
{
|
||||
return new Vector3((float)dict["x"].Double, (float)dict["y"].Double, (float)dict["z"].Double);
|
||||
}
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
public static Quaternion GetQuaternion(this DataToken jToken)
|
||||
{
|
||||
DataDictionary dict = jToken.DataDictionary;
|
||||
if (dict != null)
|
||||
{
|
||||
return new Quaternion((float)dict["x"].Double, (float)dict["y"].Double, (float)dict["z"].Double, (float)dict["w"].Double);
|
||||
}
|
||||
return Quaternion.identity;
|
||||
}
|
||||
|
||||
public static Vector2 GetVector2(this DataToken jToken)
|
||||
{
|
||||
DataDictionary dict = jToken.DataDictionary;
|
||||
if (dict != null)
|
||||
{
|
||||
return new Vector2((float)dict["x"].Double, (float)dict["y"].Double);
|
||||
}
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
public static Color GetColor(this DataToken jToken)
|
||||
{
|
||||
DataDictionary dict = jToken.DataDictionary;
|
||||
if (dict != null)
|
||||
{
|
||||
return new Color((float)dict["r"].Double, (float)dict["g"].Double, (float)dict["b"].Double, (float)dict["a"].Double);
|
||||
}
|
||||
return Color.black;
|
||||
}
|
||||
|
||||
public static Color32 GetColor32(this DataToken jToken)
|
||||
{
|
||||
DataDictionary dict = jToken.DataDictionary;
|
||||
if (dict != null)
|
||||
{
|
||||
return new Color32((byte)dict["r"].Double, (byte)dict["g"].Double, (byte)dict["b"].Double, (byte)dict["a"].Double);
|
||||
}
|
||||
return new Color32(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static Vector4 GetVector4(this DataToken jToken)
|
||||
{
|
||||
DataDictionary dict = jToken.DataDictionary;
|
||||
if (dict != null)
|
||||
{
|
||||
return new Vector4((float)dict["x"].Double, (float)dict["y"].Double, (float)dict["z"].Double, (float)dict["w"].Double);
|
||||
}
|
||||
return Vector4.zero;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91efea47f0e9471bb1167162bed3ea1d
|
||||
timeCreated: 1709653994
|
||||
@ -0,0 +1,99 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.ClientSim.Interfaces;
|
||||
using VRC.SDK3.Data;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public class ClientSimNetworkingView : ClientSimBehaviour, IClientSimNetworkId, IClientSimNetworkView
|
||||
{
|
||||
private int _networkId;
|
||||
private int _playerId;
|
||||
private ClientSimNetworkingUtilities.OwnershipOption _ownershipOption;
|
||||
private List<IClientSimNetworkSerializer> _networkedObjects = new List<IClientSimNetworkSerializer>();
|
||||
private bool _persist = true;
|
||||
private DataList _data;
|
||||
|
||||
public void SetNetworkId(int networkId)
|
||||
{
|
||||
_networkId = networkId;
|
||||
}
|
||||
|
||||
public void SetPersist(bool persist)
|
||||
{
|
||||
_persist = persist;
|
||||
}
|
||||
|
||||
public bool GetPersist()
|
||||
{
|
||||
return _persist;
|
||||
}
|
||||
|
||||
public int GetNetworkId()
|
||||
{
|
||||
return _networkId;
|
||||
}
|
||||
|
||||
public void SetPlayerId(int playerId)
|
||||
{
|
||||
_playerId = playerId;
|
||||
}
|
||||
|
||||
public int GetPlayerId()
|
||||
{
|
||||
return _playerId;
|
||||
}
|
||||
|
||||
public void OwnershipStyle(ClientSimNetworkingUtilities.OwnershipOption option)
|
||||
{
|
||||
_ownershipOption = option;
|
||||
}
|
||||
|
||||
public void AddNetworkedObject(IClientSimNetworkSerializer obj)
|
||||
{
|
||||
if(!_networkedObjects.Contains(obj))
|
||||
_networkedObjects.Add(obj);
|
||||
}
|
||||
|
||||
public DataList Encode(GameObject gameObject = null)
|
||||
{
|
||||
_data ??= new DataList();
|
||||
|
||||
if(!_persist) return _data;
|
||||
|
||||
for (int i = 0; i < _networkedObjects.Count; i++)
|
||||
{
|
||||
IClientSimNetworkSerializer obj = _networkedObjects[i];
|
||||
obj.PreEncode(gameObject);
|
||||
if(obj.IsDirty(gameObject))
|
||||
{
|
||||
DataList objData = obj.Encode(gameObject);
|
||||
if(objData != null)
|
||||
if(_data.Count > i)
|
||||
_data[i] = objData;
|
||||
else
|
||||
_data.Add(objData);
|
||||
}
|
||||
obj.PostEncode(gameObject);
|
||||
}
|
||||
|
||||
return _data;
|
||||
}
|
||||
|
||||
public void Decode(DataList data)
|
||||
{
|
||||
_data = data;
|
||||
// If the data is not the same size as the networked objects, we can't decode it. This is possible if PlayerObjects are added or removed.
|
||||
if(data.Count != _networkedObjects.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i =0; i < _networkedObjects.Count;i++)
|
||||
{
|
||||
_networkedObjects[i].Decode(data[i].DataList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6296660aa7ac42f8ab5a8a7c4439c53d
|
||||
timeCreated: 1710776534
|
||||
@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using VRC.SDK3.ClientSim.Persistence;
|
||||
using VRC.Udon;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public class ClientSimPersistenceEventSending
|
||||
{
|
||||
#if VRC_ENABLE_PLAYER_PERSISTENCE
|
||||
private static ClientSimPersistenceEventSending _instance;
|
||||
|
||||
public static ClientSimPersistenceEventSending Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new ClientSimPersistenceEventSending();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
private Dictionary<ClientSimPlayerObjectStorage,List<UdonBehaviour>> _RequestedObjects = new Dictionary<ClientSimPlayerObjectStorage,List<UdonBehaviour>>();
|
||||
|
||||
private const int TIME_BETWEEN_EVENTS = 50;
|
||||
|
||||
private ClientSimPersistenceEventSending()
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
SendNetworkEvents().Forget();
|
||||
}
|
||||
|
||||
~ClientSimPersistenceEventSending()
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
public void QueueRequest(UdonBehaviour udonBehaviour, ClientSimPlayerObjectStorage playerObjectStorage)
|
||||
{
|
||||
if(!_RequestedObjects.ContainsKey(playerObjectStorage)){
|
||||
List<UdonBehaviour> udonBehaviours = new List<UdonBehaviour>();
|
||||
udonBehaviours.Add(udonBehaviour);
|
||||
_RequestedObjects[playerObjectStorage] = udonBehaviours;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!_RequestedObjects[playerObjectStorage].Contains(udonBehaviour))
|
||||
_RequestedObjects[playerObjectStorage].Add(udonBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask SendNetworkEvents()
|
||||
{
|
||||
await UniTask.SwitchToMainThread();
|
||||
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
await UniTask.Delay(TIME_BETWEEN_EVENTS, cancellationToken: _cancellationTokenSource.Token);
|
||||
|
||||
List<ClientSimPlayerObjectStorage> keys = _RequestedObjects.Keys.ToList();
|
||||
|
||||
for (int i = keys.Count-1; i >= 0; i--)
|
||||
{
|
||||
ClientSimPlayerObjectStorage storage = keys[i];
|
||||
List<UdonBehaviour> udonBehaviours = _RequestedObjects[storage];
|
||||
for (int j = udonBehaviours.Count-1; j >= 0; j--)
|
||||
{
|
||||
storage.Encode(udonBehaviours[j].gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
_RequestedObjects.Clear();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bddf4921eeba80843963ee58000f76fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2dd6ec7ad394795bcbb866efc8840a7
|
||||
timeCreated: 1710765786
|
||||
@ -0,0 +1,90 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using VRC.SDK3.ClientSim.Interfaces;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDK3.Data;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.EncodeDecoders
|
||||
{
|
||||
public class ClientSimObjectPoolEncodeDecode : IClientSimEncodeDecoder
|
||||
{
|
||||
public DataDictionary Encode(MonoBehaviour component)
|
||||
{
|
||||
VRCObjectPool objectPool = (VRCObjectPool) component;
|
||||
|
||||
DataDictionary data = new DataDictionary();
|
||||
|
||||
data["Length"] = objectPool.Pool.Length;
|
||||
DataList values = new DataList();
|
||||
|
||||
for (int i = 0; i < objectPool.Pool.Length; i++)
|
||||
{
|
||||
values.Add( objectPool.Pool[i].activeSelf);
|
||||
}
|
||||
|
||||
data["Values"] = values;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void PreEncode(MonoBehaviour component)
|
||||
{
|
||||
}
|
||||
|
||||
public void PostEncode(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public void Decode(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
VRCObjectPool objectPool = (VRCObjectPool) component;
|
||||
|
||||
if (data.TryGetValue("Length", out DataToken lengthToken))
|
||||
{
|
||||
int length = (int)lengthToken.Double;
|
||||
|
||||
if (data.TryGetValue("Values", out DataToken valuesToken))
|
||||
{
|
||||
DataList values = valuesToken.DataList;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
objectPool.Pool[i].SetActive(values[i].Boolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsManualSynced(MonoBehaviour component)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsDirty(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
VRCObjectPool objectPool = (VRCObjectPool) component;
|
||||
|
||||
if (data.TryGetValue("Length", out DataToken lengthToken))
|
||||
{
|
||||
int length = (int)lengthToken.Double;
|
||||
|
||||
if (data.TryGetValue("Values", out DataToken valuesToken))
|
||||
{
|
||||
DataList values = valuesToken.DataList;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (objectPool.Pool[i].activeSelf != values[i].Boolean)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e271e685c8c4adfa9c6eacf4a0ae1ac
|
||||
timeCreated: 1710948667
|
||||
@ -0,0 +1,170 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using VRC.SDK3.ClientSim.Interfaces;
|
||||
using VRC.SDK3.Components;
|
||||
using VRC.SDK3.Data;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using BestHTTP.JSON;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace VRC.SDK3.ClientSim.EncodeDecoders
|
||||
{
|
||||
public class ClientSimObjectSyncEncodeDecoder : IClientSimEncodeDecoder
|
||||
{
|
||||
public DataDictionary Encode(MonoBehaviour component)
|
||||
{
|
||||
VRCObjectSync objectSync = (VRCObjectSync) component;
|
||||
Transform transform = objectSync.transform;
|
||||
Rigidbody rigidbody = objectSync.GetComponent<Rigidbody>();
|
||||
bool hasRigidbody = rigidbody != null;
|
||||
DataDictionary data = new DataDictionary();
|
||||
|
||||
data["Position"] = transform.position.GetJTokenFromVector3();
|
||||
data["Rotation"] = transform.rotation.GetJTokenFromQuaternion();
|
||||
data["Velocity"] = hasRigidbody ? rigidbody.velocity.GetJTokenFromVector3() : "not available";
|
||||
data["IsKinematic"] = !hasRigidbody || rigidbody.isKinematic;
|
||||
data["UseGravity"] = hasRigidbody && rigidbody.useGravity;
|
||||
data["Discontinuity"] = "not available";
|
||||
data["DiscontinuityCounter"] = "not available";
|
||||
data["WasSleeping"] = hasRigidbody && rigidbody.IsSleeping();
|
||||
data["Time"] = Time.realtimeSinceStartup;
|
||||
data["HeldInHand"] = "not available";
|
||||
return data;
|
||||
}
|
||||
public void PreEncode(MonoBehaviour component)
|
||||
{
|
||||
}
|
||||
|
||||
public void PostEncode(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool IsManualSynced(MonoBehaviour component)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsDirty(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
VRCObjectSync objectSync = (VRCObjectSync) component;
|
||||
if(objectSync == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Transform transform = objectSync.transform;
|
||||
Rigidbody rigidbody = objectSync.GetComponent<Rigidbody>();
|
||||
|
||||
if(data.TryGetValue("Position", out DataToken positionToken))
|
||||
{
|
||||
if (transform.position != positionToken.GetVector3())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(data.TryGetValue("Rotation", out DataToken rotationToken))
|
||||
{
|
||||
if (transform.rotation != rotationToken.GetQuaternion())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(data.TryGetValue("Velocity", out DataToken velocityToken))
|
||||
{
|
||||
if (rigidbody != null && rigidbody.velocity != velocityToken.GetVector3())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(data.TryGetValue("IsKinematic", out DataToken isKinematicToken))
|
||||
{
|
||||
if (rigidbody != null && rigidbody.isKinematic != isKinematicToken.Boolean)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(data.TryGetValue("UseGravity", out DataToken useGravityToken))
|
||||
{
|
||||
if (rigidbody != null && rigidbody.useGravity != useGravityToken.Boolean)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(data.TryGetValue("WasSleeping", out DataToken wasSleepingToken))
|
||||
{
|
||||
if (rigidbody != null && rigidbody.IsSleeping() != wasSleepingToken.Boolean)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Decode(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
VRCObjectSync objectSync = (VRCObjectSync) component;
|
||||
Transform transform = objectSync.transform;
|
||||
Rigidbody rigidbody = objectSync.GetComponent<Rigidbody>();
|
||||
|
||||
if (data.TryGetValue("Position", out DataToken positionToken))
|
||||
{
|
||||
transform.position = positionToken.GetVector3();
|
||||
}
|
||||
|
||||
if (data.TryGetValue("Rotation", out DataToken rotationToken))
|
||||
{
|
||||
transform.rotation = rotationToken.GetQuaternion();
|
||||
}
|
||||
|
||||
if (data.TryGetValue("Velocity", out DataToken velocityToken))
|
||||
{
|
||||
if (rigidbody != null)
|
||||
{
|
||||
rigidbody.velocity = velocityToken.GetVector3();
|
||||
}
|
||||
}
|
||||
|
||||
if (data.TryGetValue("IsKinematic", out DataToken isKinematicToken))
|
||||
{
|
||||
if (rigidbody != null)
|
||||
{
|
||||
rigidbody.isKinematic = isKinematicToken.Boolean;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.TryGetValue("UseGravity", out DataToken useGravityToken))
|
||||
{
|
||||
if (rigidbody != null)
|
||||
{
|
||||
rigidbody.useGravity = useGravityToken.Boolean;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.TryGetValue("WasSleeping", out DataToken wasSleepingToken))
|
||||
{
|
||||
if (rigidbody != null)
|
||||
{
|
||||
if (wasSleepingToken.Boolean)
|
||||
{
|
||||
rigidbody.Sleep();
|
||||
}
|
||||
else
|
||||
{
|
||||
rigidbody.WakeUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 616494e20f224f1095644c36acd3141e
|
||||
timeCreated: 1710765810
|
||||
@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using VRC.SDK3.ClientSim.Interfaces;
|
||||
using VRC.SDK3.Data;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon;
|
||||
using VRC.Udon.Common;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.EncodeDecoders
|
||||
{
|
||||
public class ClientSimUdonEncodeDecode : IClientSimEncodeDecoder
|
||||
{
|
||||
public void PreEncode(MonoBehaviour component)
|
||||
{
|
||||
UdonBehaviour udonBehaviour = component as UdonBehaviour;
|
||||
udonBehaviour.OnPreSerialization();
|
||||
}
|
||||
|
||||
public DataDictionary Encode(MonoBehaviour component)
|
||||
{
|
||||
UdonBehaviour udonBehaviour = component as UdonBehaviour;
|
||||
DataDictionary data = new DataDictionary();
|
||||
|
||||
IEnumerable<IUdonSyncMetadata> SyncMetadatas = udonBehaviour.SyncMetadataTable.GetAllSyncMetadata();
|
||||
|
||||
foreach (IUdonSyncMetadata syncMetadata in SyncMetadatas)
|
||||
{
|
||||
object obj = udonBehaviour.GetProgramVariable(syncMetadata.Name);
|
||||
data.Add(syncMetadata.Name, GetJTokenFromObject(obj));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void PostEncode(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
UdonBehaviour udonBehaviour = component as UdonBehaviour;
|
||||
udonBehaviour.OnPostSerialization(new SerializationResult(true,data.Count*4));
|
||||
}
|
||||
|
||||
public bool IsManualSynced(MonoBehaviour component)
|
||||
{
|
||||
UdonBehaviour udonBehaviour = component as UdonBehaviour;
|
||||
return udonBehaviour.SyncMethod == Networking.SyncType.Manual;
|
||||
}
|
||||
|
||||
public bool IsDirty(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
UdonBehaviour udonBehaviour = component as UdonBehaviour;
|
||||
|
||||
IUdonSyncMetadataTable syncMetadataTable = udonBehaviour.SyncMetadataTable;
|
||||
if(syncMetadataTable == null)
|
||||
return false;
|
||||
|
||||
IEnumerable<IUdonSyncMetadata> SyncMetadatas = syncMetadataTable.GetAllSyncMetadata();
|
||||
|
||||
foreach (IUdonSyncMetadata syncMetadata in SyncMetadatas)
|
||||
{
|
||||
if (data.ContainsKey(syncMetadata.Name))
|
||||
{
|
||||
if (!IsDataSame(data[syncMetadata.Name],GetJTokenFromObject(udonBehaviour.GetProgramVariable(syncMetadata.Name))))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static DataToken GetJTokenFromObject(object obj)
|
||||
{
|
||||
if(obj == null)
|
||||
return new DataToken();
|
||||
|
||||
if (obj.GetType().IsArray)
|
||||
{
|
||||
DataList jArray = new DataList();
|
||||
foreach (object arrayObj in (Array)obj)
|
||||
{
|
||||
jArray.Add(GetJTokenFromObject(arrayObj));
|
||||
}
|
||||
return jArray;
|
||||
}
|
||||
|
||||
return obj switch
|
||||
{
|
||||
bool bo => new DataToken(bo),
|
||||
char c => new DataToken(c),
|
||||
byte b => new DataToken(b),
|
||||
sbyte sb => new DataToken(sb),
|
||||
short s => new DataToken(s),
|
||||
ushort us => new DataToken(us),
|
||||
int i => new DataToken(i),
|
||||
uint u => new DataToken(u),
|
||||
long l => new DataToken(l),
|
||||
ulong ul => new DataToken(ul),
|
||||
float f => new DataToken(f),
|
||||
double d => new DataToken(d),
|
||||
Vector2 vector2 => vector2.GetJTokenFromVector2(),
|
||||
Vector3 vector3 => vector3.GetJTokenFromVector3(),
|
||||
Vector4 vector4 => vector4.GetJTokenFromVector4(),
|
||||
Quaternion quaternion => quaternion.GetJTokenFromQuaternion(),
|
||||
string str => new DataToken(str),
|
||||
VRCUrl url => new DataToken(url.Get()),
|
||||
Color color => color.GetJTokenFromColor(),
|
||||
Color32 color32 => color32.GetJTokenFromColor32(),
|
||||
_ => new DataToken()
|
||||
};
|
||||
}
|
||||
|
||||
public object Get(Type t, DataToken dataToken)
|
||||
{
|
||||
if (t.IsArray)
|
||||
{
|
||||
Array array = Array.CreateInstance(t.GetElementType(), dataToken.DataList.Count);
|
||||
for (int i = 0; i < dataToken.DataList.Count; i++)
|
||||
{
|
||||
array.SetValue(Get(t.GetElementType(), dataToken.DataList[i]), i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
return t switch
|
||||
{
|
||||
Type boolType when boolType == typeof(bool) => dataToken.Boolean,
|
||||
Type charType when charType == typeof(char) => dataToken.String[0],
|
||||
Type byteType when byteType == typeof(byte) => (byte)dataToken.Double,
|
||||
Type sbyteType when sbyteType == typeof(sbyte) => (sbyte)dataToken.Double,
|
||||
Type shortType when shortType == typeof(short) => (short)dataToken.Double,
|
||||
Type ushortType when ushortType == typeof(ushort) => (ushort)dataToken.Double,
|
||||
Type intType when intType == typeof(int) => (int)dataToken.Double,
|
||||
Type uintType when uintType == typeof(uint) => (uint)dataToken.Double,
|
||||
Type longType when longType == typeof(long) => (long)dataToken.Double,
|
||||
Type ulongType when ulongType == typeof(ulong) => (ulong)dataToken.Double,
|
||||
Type floatType when floatType == typeof(float) => (float)dataToken.Double,
|
||||
Type doubleType when doubleType == typeof(double) => dataToken.Double,
|
||||
Type vector2Type when vector2Type == typeof(Vector2) => dataToken.GetVector2(),
|
||||
Type vector3Type when vector3Type == typeof(Vector3) => dataToken.GetVector3(),
|
||||
Type vector4Type when vector4Type == typeof(Vector4) => dataToken.GetVector4(),
|
||||
Type quaternionType when quaternionType == typeof(Quaternion) => dataToken.GetQuaternion(),
|
||||
Type stringType when stringType == typeof(string) => dataToken.String,
|
||||
Type urlType when urlType == typeof(VRCUrl) => new VRCUrl(dataToken.String),
|
||||
Type colorType when colorType == typeof(Color) => dataToken.GetColor(),
|
||||
Type color32Type when color32Type == typeof(Color32) => dataToken.GetColor32(),
|
||||
_ => null
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public void Decode(MonoBehaviour component, DataDictionary data)
|
||||
{
|
||||
UdonBehaviour udonBehaviour = component as UdonBehaviour;
|
||||
|
||||
IEnumerable<IUdonSyncMetadata> SyncMetadatas = udonBehaviour.SyncMetadataTable.GetAllSyncMetadata();
|
||||
|
||||
foreach (IUdonSyncMetadata syncMetadata in SyncMetadatas)
|
||||
{
|
||||
if (data.ContainsKey(syncMetadata.Name))
|
||||
{
|
||||
Type type = udonBehaviour.GetProgramVariableType(syncMetadata.Name);
|
||||
udonBehaviour.SetProgramVariable(syncMetadata.Name,
|
||||
Get(type,data[syncMetadata.Name]));
|
||||
}
|
||||
}
|
||||
|
||||
udonBehaviour.OnDeserialization(new DeserializationResult(0, 0, true));
|
||||
|
||||
}
|
||||
|
||||
private bool IsDataSame(DataToken a, DataToken b)
|
||||
{
|
||||
if(a.TokenType != b.TokenType)
|
||||
return false;
|
||||
|
||||
switch (a.TokenType)
|
||||
{
|
||||
case TokenType.DataList when a.DataList.Count != b.DataList.Count:
|
||||
return false;
|
||||
case TokenType.DataList:
|
||||
{
|
||||
for (int i = 0; i < a.DataList.Count; i++)
|
||||
{
|
||||
if (!IsDataSame(a.DataList[i], b.DataList[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case TokenType.DataDictionary when a.DataDictionary.Count != b.DataDictionary.Count:
|
||||
return false;
|
||||
case TokenType.DataDictionary:
|
||||
{
|
||||
foreach (KeyValuePair<DataToken, DataToken> keyValuePair in a.DataDictionary)
|
||||
{
|
||||
if (!b.DataDictionary.ContainsKey(keyValuePair.Key))
|
||||
return false;
|
||||
|
||||
if (!IsDataSame(keyValuePair.Value, b.DataDictionary[keyValuePair.Key]))
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78783327b868484796bf6a9a824fc123
|
||||
timeCreated: 1710874470
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4325933c93df4022b3e4699f171f76f9
|
||||
timeCreated: 1709653577
|
||||
@ -0,0 +1,24 @@
|
||||
|
||||
using BestHTTP.JSON;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using VRC.SDK3.Data;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Interfaces
|
||||
{
|
||||
public interface IClientSimEncodeDecoder
|
||||
{
|
||||
DataDictionary Encode(MonoBehaviour component);
|
||||
|
||||
void Decode(MonoBehaviour component, DataDictionary data);
|
||||
|
||||
public bool IsManualSynced(MonoBehaviour component);
|
||||
|
||||
bool IsDirty(MonoBehaviour component, DataDictionary data);
|
||||
|
||||
void PostEncode(MonoBehaviour component, DataDictionary data);
|
||||
|
||||
void PreEncode(MonoBehaviour component);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2d1b3d741084cd081baf2ef556378fe
|
||||
timeCreated: 1710765826
|
||||
@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Interfaces
|
||||
{
|
||||
public interface IClientSimNetworkId
|
||||
{
|
||||
public void SetNetworkId(int networkId);
|
||||
public int GetNetworkId();
|
||||
|
||||
public void OwnershipStyle(ClientSimNetworkingUtilities.OwnershipOption option);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 653d0e2b7d2e4b11a6fe5160b5c52c08
|
||||
timeCreated: 1709290307
|
||||
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Data;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Interfaces
|
||||
{
|
||||
public interface IClientSimNetworkSerializer
|
||||
{
|
||||
DataList Encode(GameObject gameObject = null);
|
||||
void Decode(DataList data);
|
||||
DataList GetData();
|
||||
bool IsDirty(GameObject gameObject = null);
|
||||
int GetPlayerId();
|
||||
void SetNetworkComponents();
|
||||
int GetNetworkComponentCount();
|
||||
List<MonoBehaviour> GetNetworkComponents();
|
||||
void PostEncode(GameObject gameObject = null);
|
||||
void PreEncode(GameObject gameObject = null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4533b7b933a340b9b7d4ff9a99bfb0f5
|
||||
timeCreated: 1710436946
|
||||
@ -0,0 +1,13 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Data;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.Interfaces
|
||||
{
|
||||
public interface IClientSimNetworkView
|
||||
{
|
||||
DataList Encode(GameObject gameObject = null);
|
||||
|
||||
void Decode(DataList data);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 261ee1d33413443daecbfb1b7aac8583
|
||||
timeCreated: 1710777013
|
||||
Reference in New Issue
Block a user