Add VRChat and GitHub packages to Unity project
This commit is contained in:
1 parent
0334a52b21
commit
85538aaccc
3297 files changed
+847877
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36c0d886a26373c46be857f2fc441071
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRCSDK2
|
||||
{
|
||||
public class AudioManagerSettings
|
||||
{
|
||||
public const float MinVoiceSendDistance = 25.0f; // meters
|
||||
public const float MaxVoiceSendDistancePctOfFarRange = 0.5f;
|
||||
public const float VoiceFadeOutDistancePctOfFarRange = 0.25f;
|
||||
public const float RoomAudioGain = 10f; // dB
|
||||
public const float RoomAudioMaxRange = 80f; // meters
|
||||
public const float VoiceGain = 15f; // dB
|
||||
public const float VoiceMaxRange = 25f; // meters, this is half the oculus inv sq max range
|
||||
public const float LipsyncGain = 1f; // multiplier, not dB!
|
||||
public const float AvatarAudioMaxGain = 10f; // dB
|
||||
public const float AvatarAudioMaxRange = 40f; // meters
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acadc6659c5ab3446ad0d5de2563f95f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRCSDK2
|
||||
{
|
||||
public class CommunityLabsConstants
|
||||
{
|
||||
public const string COMMUNITY_LABS_DOCUMENTATION_URL = "https://docs.vrchat.com/docs/vrchat-community-labs";
|
||||
|
||||
public const string MANAGE_WORLD_IN_BROWSER_STRING = "Manage World in Browser";
|
||||
public const string READ_COMMUNITY_LABS_DOCS_STRING = "Read Community Labs Docs";
|
||||
|
||||
public const string UPLOADED_CONTENT_SUCCESSFULLY_MESSAGE = "Content Successfully Uploaded!";
|
||||
|
||||
public const string UPLOADED_NEW_PRIVATE_WORLD_CONFIRMATION_MESSAGE = "You've uploaded a private world. You can find it on the \"Mine\" row in your worlds menu.";
|
||||
public const string UPDATED_PRIVATE_WORLD_CONFIRMATION_MESSAGE = "You've updated your private world. You can find it on the \"Mine\" row in your worlds menu.";
|
||||
public const string PUBLISHED_WORLD_TO_COMMUNITY_LABS_CONFIRMATION_MESSAGE = "You've uploaded and published a world to Community Labs.";
|
||||
public const string UPDATED_COMMUNITY_LABS_WORLD_CONFIRMATION_MESSAGE = "You've updated your Community Labs world.";
|
||||
public const string UPDATED_PUBLIC_WORLD_CONFIRMATION_MESSAGE = "You've updated your public world.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d047eaa3325d654aa62ccad6f73eb93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,133 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VRC.Core;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
|
||||
namespace VRCSDK2
|
||||
{
|
||||
public class ContentUploadedDialog : EditorWindow
|
||||
{
|
||||
private Texture2D clIconGraphic = null;
|
||||
private Color dialogTextColor = Color.white;
|
||||
private string contentUrl = null;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if(EditorGUIUtility.isProSkin)
|
||||
dialogTextColor = Color.white;
|
||||
else
|
||||
dialogTextColor = Color.black;
|
||||
|
||||
clIconGraphic = Resources.Load("vrcSdkClDialogNewIcon") as Texture2D;
|
||||
}
|
||||
|
||||
public void setContentURL(string url = null)
|
||||
{
|
||||
contentUrl = url;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
const int CONTENT_UPLOADED_BORDER_SIZE = 20;
|
||||
const int CONTENT_UPLOADED_HORIZONTAL_SPACING = 10;
|
||||
|
||||
const int CONTENT_UPLOADED_BUTTON_WIDTH = 260;
|
||||
const int CONTENT_UPLOADED_BUTTON_HEIGHT = 40;
|
||||
const int CONTENT_CL_VERTICAL_HEADER_SPACING = 40;
|
||||
|
||||
const int CONTENT_CL_TEXT_REGION_HEIGHT = 120;
|
||||
|
||||
const int CONTENT_MIN_WINDOW_WIDTH = (CONTENT_UPLOADED_BUTTON_WIDTH * 2) + CONTENT_UPLOADED_HORIZONTAL_SPACING + (CONTENT_UPLOADED_BORDER_SIZE * 2);
|
||||
const int CONTENT_MIN_WINDOW_HEIGHT = CONTENT_UPLOADED_BUTTON_HEIGHT + CONTENT_CL_VERTICAL_HEADER_SPACING + CONTENT_CL_TEXT_REGION_HEIGHT + (CONTENT_UPLOADED_BORDER_SIZE * 2);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(CONTENT_UPLOADED_BORDER_SIZE);
|
||||
|
||||
// Community Labs graphic
|
||||
if (RuntimeWorldCreation.IsCurrentWorldInCommunityLabs && (null != clIconGraphic))
|
||||
{
|
||||
GUILayout.Label(new GUIContent(clIconGraphic), GUIStyle.none);
|
||||
}
|
||||
|
||||
this.minSize = new Vector2(CONTENT_MIN_WINDOW_WIDTH, CONTENT_MIN_WINDOW_HEIGHT);
|
||||
|
||||
GUILayout.BeginVertical();
|
||||
if (RuntimeWorldCreation.IsCurrentWorldInCommunityLabs && (null != clIconGraphic))
|
||||
GUILayout.Space(CONTENT_CL_VERTICAL_HEADER_SPACING);
|
||||
GUIStyle uploadedTitleStyle = new GUIStyle(EditorStyles.boldLabel);
|
||||
uploadedTitleStyle.normal.textColor = dialogTextColor;
|
||||
uploadedTitleStyle.fontSize = 15;
|
||||
GUILayout.Label(CommunityLabsConstants.UPLOADED_CONTENT_SUCCESSFULLY_MESSAGE, uploadedTitleStyle);
|
||||
|
||||
string uploadedMessage = CommunityLabsConstants.UPLOADED_NEW_PRIVATE_WORLD_CONFIRMATION_MESSAGE;
|
||||
|
||||
if (!RuntimeWorldCreation.IsCurrentWorldUploaded)
|
||||
{
|
||||
if (RuntimeWorldCreation.IsCurrentWorldInCommunityLabs)
|
||||
uploadedMessage = CommunityLabsConstants.PUBLISHED_WORLD_TO_COMMUNITY_LABS_CONFIRMATION_MESSAGE;
|
||||
else
|
||||
uploadedMessage = CommunityLabsConstants.UPLOADED_NEW_PRIVATE_WORLD_CONFIRMATION_MESSAGE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RuntimeWorldCreation.IsCurrentWorldInCommunityLabs)
|
||||
{
|
||||
uploadedMessage = CommunityLabsConstants.UPDATED_COMMUNITY_LABS_WORLD_CONFIRMATION_MESSAGE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RuntimeWorldCreation.IsCurrentWorldPubliclyPublished)
|
||||
uploadedMessage = CommunityLabsConstants.UPDATED_PUBLIC_WORLD_CONFIRMATION_MESSAGE;
|
||||
else
|
||||
uploadedMessage = CommunityLabsConstants.UPDATED_PRIVATE_WORLD_CONFIRMATION_MESSAGE;
|
||||
}
|
||||
}
|
||||
|
||||
GUIStyle uploadedMessageStyle = new GUIStyle(EditorStyles.label);
|
||||
uploadedMessageStyle.normal.textColor = dialogTextColor;
|
||||
uploadedMessageStyle.fontSize = 13;
|
||||
uploadedMessageStyle.wordWrap = true;
|
||||
GUILayout.Label(uploadedMessage, uploadedMessageStyle);
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
GUILayout.Space(CONTENT_UPLOADED_BORDER_SIZE);
|
||||
|
||||
if (RuntimeWorldCreation.IsCurrentWorldInCommunityLabs)
|
||||
{
|
||||
if (GUILayout.Button(CommunityLabsConstants.READ_COMMUNITY_LABS_DOCS_STRING, GUILayout.Width(CONTENT_UPLOADED_BUTTON_WIDTH), GUILayout.Height(CONTENT_UPLOADED_BUTTON_HEIGHT)))
|
||||
{
|
||||
Application.OpenURL(CommunityLabsConstants.COMMUNITY_LABS_DOCUMENTATION_URL);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button(CommunityLabsConstants.MANAGE_WORLD_IN_BROWSER_STRING, GUILayout.Width(CONTENT_UPLOADED_BUTTON_WIDTH), GUILayout.Height(CONTENT_UPLOADED_BUTTON_HEIGHT)))
|
||||
{
|
||||
Application.OpenURL(contentUrl);
|
||||
}
|
||||
|
||||
if (RuntimeWorldCreation.IsCurrentWorldInCommunityLabs)
|
||||
GUILayout.Space(CONTENT_UPLOADED_BORDER_SIZE);
|
||||
else
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(CONTENT_UPLOADED_BORDER_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1c693656bf5d584b87df969efeb5536
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5bbb34800f8c22438096902b3040447
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using VRC.Core;
|
||||
|
||||
public class FallbackMaterialCache
|
||||
{
|
||||
private readonly Dictionary<Material, Material> _fallbackMaterialCache = new Dictionary<Material, Material>();
|
||||
|
||||
public void AddFallbackMaterial(Material material, Material fallbackMaterial)
|
||||
{
|
||||
if(!_fallbackMaterialCache.ContainsKey(material))
|
||||
{
|
||||
_fallbackMaterialCache.Add(material, fallbackMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(string.Format("Attempted to add a duplicate fallback material '{0}' for original material '{1}'.", fallbackMaterial.name, material.name));
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasFallbackMaterial(Material material)
|
||||
{
|
||||
return _fallbackMaterialCache.ContainsKey(material);
|
||||
}
|
||||
|
||||
public Material GetFallBackMaterial(Material material)
|
||||
{
|
||||
return _fallbackMaterialCache[material];
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Material[] cachedFallbackMaterials = _fallbackMaterialCache.Values.ToArray();
|
||||
for(int i = cachedFallbackMaterials.Length - 1; i >= 0; i--)
|
||||
{
|
||||
UnityEngine.Object.Destroy(cachedFallbackMaterials[i]);
|
||||
}
|
||||
|
||||
_fallbackMaterialCache.Clear();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10121679f780956408f9a434a526f553
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,329 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VRC.Core;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace VRCSDK2
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
public class RuntimeAPICreation : MonoBehaviour
|
||||
{
|
||||
public VRC.Core.PipelineManager pipelineManager;
|
||||
|
||||
protected bool forceNewFileCreation = false;
|
||||
protected bool useFileApi = false;
|
||||
|
||||
protected bool isUploading = false;
|
||||
protected float uploadProgress = 0f;
|
||||
protected string uploadMessage;
|
||||
protected string uploadTitle;
|
||||
|
||||
protected string uploadVrcPath;
|
||||
protected string uploadUnityPackagePath;
|
||||
|
||||
protected string cloudFrontAssetUrl;
|
||||
protected string cloudFrontImageUrl;
|
||||
protected string cloudFrontUnityPackageUrl;
|
||||
|
||||
protected CameraImageCapture imageCapture;
|
||||
|
||||
private bool cancelRequested = false;
|
||||
|
||||
public static bool publishingToCommunityLabs = false;
|
||||
|
||||
private Dictionary<string, string> mRetryState = new Dictionary<string, string>();
|
||||
|
||||
protected bool isUpdate { get { return pipelineManager.completedSDKPipeline; } }
|
||||
|
||||
protected void Start()
|
||||
{
|
||||
if (!Application.isEditor || !Application.isPlaying)
|
||||
return;
|
||||
|
||||
PipelineSaver ps = GameObject.FindObjectOfType<PipelineSaver>();
|
||||
pipelineManager = ps.gameObject.GetComponent<PipelineManager>();
|
||||
|
||||
imageCapture = GetComponent<CameraImageCapture>();
|
||||
imageCapture.shotCamera = GameObject.Find("VRCCam").GetComponent<Camera>();
|
||||
|
||||
LoadUploadRetryStateFromCache();
|
||||
|
||||
forceNewFileCreation = UnityEditor.EditorPrefs.GetBool("forceNewFileCreation", true);
|
||||
useFileApi = UnityEditor.EditorPrefs.GetBool("useFileApi", false);
|
||||
|
||||
API.SetOnlineMode(true);
|
||||
}
|
||||
|
||||
protected void Update()
|
||||
{
|
||||
if (isUploading)
|
||||
{
|
||||
bool cancelled = UnityEditor.EditorUtility.DisplayCancelableProgressBar(uploadTitle, uploadMessage, uploadProgress);
|
||||
if (cancelled)
|
||||
{
|
||||
cancelRequested = true;
|
||||
}
|
||||
|
||||
if (EditorApplication.isPaused)
|
||||
EditorApplication.isPaused = false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected void LoadUploadRetryStateFromCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(GetUploadRetryStateFilePath());
|
||||
mRetryState = VRC.Tools.ObjDictToStringDict(VRC.Tools.JsonDecode(json) as Dictionary<string, object>);
|
||||
|
||||
Debug.LogFormat("<color=yellow> loaded retry state: {0}</color>", json);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// normal case
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("Loaded upload retry state from: " + GetUploadRetryStateFilePath());
|
||||
}
|
||||
|
||||
protected void SaveUploadRetryState(string key, string val)
|
||||
{
|
||||
if (string.IsNullOrEmpty(val))
|
||||
return;
|
||||
mRetryState[key] = val;
|
||||
SaveUploadRetryState();
|
||||
}
|
||||
|
||||
protected void SaveUploadRetryState()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(GetUploadRetryStateFilePath()));
|
||||
string json = VRC.Tools.JsonEncode(mRetryState);
|
||||
File.WriteAllText(GetUploadRetryStateFilePath(), json);
|
||||
|
||||
Debug.LogFormat("<color=yellow> wrote retry state: {0}</color>", json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("Couldn't save upload retry state: " + GetUploadRetryStateFilePath() + "\n" + e.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("Saved upload retry state to: " + GetUploadRetryStateFilePath());
|
||||
}
|
||||
|
||||
protected void ClearUploadRetryState()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(GetUploadRetryStateFilePath()))
|
||||
return;
|
||||
|
||||
File.Delete(GetUploadRetryStateFilePath());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("Couldn't delete upload retry state: " + GetUploadRetryStateFilePath() + "\n" + e.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("Cleared upload retry state at: " + GetUploadRetryStateFilePath());
|
||||
}
|
||||
|
||||
protected string GetUploadRetryStateFilePath()
|
||||
{
|
||||
string id = UnityEditor.AssetDatabase.AssetPathToGUID(SceneManager.GetActiveScene().path);
|
||||
return Path.Combine(VRC.Tools.GetTempFolderPath(id), "upload_retry.dat");
|
||||
}
|
||||
|
||||
protected string GetUploadRetryStateValue(string key)
|
||||
{
|
||||
return mRetryState.ContainsKey(key) ? mRetryState[key] : "";
|
||||
}
|
||||
|
||||
protected virtual void DisplayUpdateCompletedDialog(string contentUrl=null)
|
||||
{
|
||||
if (UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "Update Complete! Launch VRChat to see your uploaded content." + (null==contentUrl ? "" : "\n\nManage content at: " + contentUrl ), (null == contentUrl) ? "Okay" : CommunityLabsConstants.MANAGE_WORLD_IN_BROWSER_STRING, (null == contentUrl) ? "" : "Done" ))
|
||||
{
|
||||
if (null!=contentUrl)
|
||||
{
|
||||
Application.OpenURL(contentUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnSDKPipelineComplete(string contentUrl=null)
|
||||
{
|
||||
VRC.Core.Logger.Log("OnSDKPipelineComplete", DebugLevel.All);
|
||||
isUploading = false;
|
||||
pipelineManager.completedSDKPipeline = true;
|
||||
ClearUploadRetryState();
|
||||
UnityEditor.EditorPrefs.SetBool("forceNewFileCreation", false);
|
||||
UnityEditor.EditorApplication.isPaused = false;
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
UnityEditor.EditorUtility.ClearProgressBar();
|
||||
DisplayUpdateCompletedDialog(contentUrl);
|
||||
}
|
||||
|
||||
protected void OnSDKPipelineError(string error, string details)
|
||||
{
|
||||
VRC.Core.Logger.Log("OnSDKPipelineError: " + error + " - " + details, DebugLevel.All);
|
||||
isUploading = false;
|
||||
pipelineManager.completedSDKPipeline = true;
|
||||
UnityEditor.EditorApplication.isPaused = false;
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
UnityEditor.EditorUtility.ClearProgressBar();
|
||||
if (cancelRequested)
|
||||
UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "The update was cancelled.", "Okay");
|
||||
else
|
||||
UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "Error updating content. " + error + "\n" + details, "Okay");
|
||||
}
|
||||
|
||||
protected void SetUploadProgress(string title, string message, float progress)
|
||||
{
|
||||
uploadTitle = title;
|
||||
uploadMessage = message;
|
||||
uploadProgress = progress;
|
||||
}
|
||||
|
||||
protected void OnUploadProgess(object obj, Amazon.Runtime.StreamTransferProgressArgs args)
|
||||
{
|
||||
uploadProgress = args.PercentDone / 100.0f;
|
||||
}
|
||||
|
||||
protected bool WasCancelRequested(ApiFile apiFile)
|
||||
{
|
||||
return cancelRequested;
|
||||
}
|
||||
|
||||
protected void PrepareUnityPackageForS3(string packagePath, string blueprintId, int version, AssetVersion assetVersion)
|
||||
{
|
||||
uploadUnityPackagePath = Application.temporaryCachePath + "/" + blueprintId + "_" + version.ToString() + "_" + Application.unityVersion + "_" + assetVersion.ApiVersion + "_" + VRC.Tools.Platform +
|
||||
"_" + API.GetServerEnvironmentForApiUrl() + ".unitypackage";
|
||||
uploadUnityPackagePath.Trim();
|
||||
uploadUnityPackagePath.Replace(' ', '_');
|
||||
|
||||
if (System.IO.File.Exists(uploadUnityPackagePath))
|
||||
System.IO.File.Delete(uploadUnityPackagePath);
|
||||
|
||||
System.IO.File.Copy(packagePath, uploadUnityPackagePath);
|
||||
}
|
||||
|
||||
protected void PrepareVRCPathForS3(string abPath, string blueprintId, int version, AssetVersion assetVersion)
|
||||
{
|
||||
uploadVrcPath = Application.temporaryCachePath + "/" + blueprintId + "_" + version.ToString() + "_" + Application.unityVersion + "_" + assetVersion.ApiVersion + "_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl() + System.IO.Path.GetExtension(abPath);
|
||||
uploadVrcPath.Trim();
|
||||
uploadVrcPath.Replace(' ', '_');
|
||||
|
||||
if (System.IO.File.Exists(uploadVrcPath))
|
||||
System.IO.File.Delete(uploadVrcPath);
|
||||
|
||||
System.IO.File.Copy(abPath, uploadVrcPath);
|
||||
}
|
||||
|
||||
protected IEnumerator UploadFile(string filename, string existingFileUrl, string friendlyFilename, string fileType, Action<string> onSuccess)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
yield break;
|
||||
VRC.Core.Logger.Log("Uploading " + fileType + "(" + filename + ") ...", DebugLevel.All);
|
||||
SetUploadProgress("Uploading " + fileType + "...", "", 0.0f);
|
||||
|
||||
string fileId = GetUploadRetryStateValue(filename);
|
||||
if (string.IsNullOrEmpty(fileId))
|
||||
fileId = isUpdate ? ApiFile.ParseFileIdFromFileAPIUrl(existingFileUrl) : "";
|
||||
string errorStr = "";
|
||||
string newFileUrl = "";
|
||||
|
||||
|
||||
yield return StartCoroutine(ApiFileHelper.Instance.UploadFile(filename, forceNewFileCreation ? "" : fileId, friendlyFilename,
|
||||
delegate (ApiFile apiFile, string message)
|
||||
{
|
||||
newFileUrl = apiFile.GetFileURL();
|
||||
if (VRC.Core.Logger.DebugLevelIsEnabled(DebugLevel.API))
|
||||
VRC.Core.Logger.Log(fileType + " upload succeeded: " + message + " (" + filename +
|
||||
") => " + apiFile.ToString(), DebugLevel.API);
|
||||
else
|
||||
VRC.Core.Logger.Log(fileType + " upload succeeded ", DebugLevel.Always);
|
||||
},
|
||||
delegate (ApiFile apiFile, string error)
|
||||
{
|
||||
SaveUploadRetryState(filename, apiFile.id);
|
||||
|
||||
errorStr = error;
|
||||
Debug.LogError(fileType + " upload failed: " + error + " (" + filename +
|
||||
") => " + apiFile.ToString());
|
||||
},
|
||||
delegate (ApiFile apiFile, string status, string subStatus, float pct)
|
||||
{
|
||||
SetUploadProgress("Uploading " + fileType + "...", status + (!string.IsNullOrEmpty(subStatus) ? " (" + subStatus + ")" : ""), pct);
|
||||
},
|
||||
WasCancelRequested
|
||||
));
|
||||
|
||||
if (!string.IsNullOrEmpty(errorStr))
|
||||
{
|
||||
OnSDKPipelineError(fileType + " upload failed.", errorStr);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (onSuccess != null)
|
||||
onSuccess(newFileUrl);
|
||||
}
|
||||
|
||||
protected IEnumerator UpdateImage(string existingFileUrl, string friendlyFileName)
|
||||
{
|
||||
string imagePath = imageCapture.TakePicture();
|
||||
|
||||
if (!string.IsNullOrEmpty(imagePath))
|
||||
{
|
||||
yield return StartCoroutine(UploadFile(imagePath, existingFileUrl, friendlyFileName, "Image",
|
||||
delegate (string fileUrl)
|
||||
{
|
||||
cloudFrontImageUrl = fileUrl;
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerator CreateBlueprint()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual IEnumerator UpdateBlueprint()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected bool ValidateNameInput(InputField nameInput)
|
||||
{
|
||||
bool isValid = true;
|
||||
if (string.IsNullOrEmpty(nameInput.text))
|
||||
{
|
||||
isUploading = false;
|
||||
UnityEditor.EditorUtility.DisplayDialog("Invalid Input", "Cannot leave the name field empty.", "OK");
|
||||
isValid = false;
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
protected bool ValidateAssetBundleBlueprintID(string blueprintID)
|
||||
{
|
||||
string lastBuiltID = UnityEditor.EditorPrefs.GetString("lastBuiltAssetBundleBlueprintID", "");
|
||||
return !string.IsNullOrEmpty(lastBuiltID) && lastBuiltID == blueprintID;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3132e0ab7e16494a9d492087a1ca447
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,369 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using VRC.Core;
|
||||
|
||||
namespace VRCSDK2
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
public class RuntimeBlueprintCreation : RuntimeAPICreation
|
||||
{
|
||||
public GameObject waitingPanel;
|
||||
public GameObject blueprintPanel;
|
||||
public GameObject errorPanel;
|
||||
|
||||
public Text titleText;
|
||||
public InputField blueprintName;
|
||||
public InputField blueprintDescription;
|
||||
public RawImage bpImage;
|
||||
public Image liveBpImage;
|
||||
public Toggle shouldUpdateImageToggle;
|
||||
public Toggle contentSex;
|
||||
public Toggle contentViolence;
|
||||
public Toggle contentGore;
|
||||
public Toggle contentOther;
|
||||
public Toggle developerAvatar;
|
||||
public Toggle sharePrivate;
|
||||
public Toggle sharePublic;
|
||||
|
||||
public UnityEngine.UI.Button uploadButton;
|
||||
|
||||
private ApiAvatar apiAvatar;
|
||||
|
||||
new void Start()
|
||||
{
|
||||
if (!Application.isEditor || !Application.isPlaying)
|
||||
return;
|
||||
|
||||
base.Start();
|
||||
|
||||
var desc = pipelineManager.GetComponent<VRC.SDKBase.VRC_AvatarDescriptor>();
|
||||
desc.PositionPortraitCamera(imageCapture.shotCamera.transform);
|
||||
|
||||
Application.runInBackground = true;
|
||||
UnityEngine.XR.XRSettings.enabled = false;
|
||||
|
||||
uploadButton.onClick.AddListener(SetupUpload);
|
||||
|
||||
shouldUpdateImageToggle.onValueChanged.AddListener(ToggleUpdateImage);
|
||||
|
||||
Login();
|
||||
}
|
||||
|
||||
void LoginErrorCallback(string obj)
|
||||
{
|
||||
VRC.Core.Logger.LogError("Could not log in - " + obj, DebugLevel.Always);
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(true);
|
||||
}
|
||||
|
||||
void Login()
|
||||
{
|
||||
if (!ApiCredentials.Load())
|
||||
LoginErrorCallback("Not logged in");
|
||||
else
|
||||
APIUser.FetchCurrentUser(
|
||||
delegate (ApiModelContainer<APIUser> c)
|
||||
{
|
||||
pipelineManager.user = c.Model as APIUser;
|
||||
|
||||
ApiAvatar av = new ApiAvatar() { id = pipelineManager.blueprintId };
|
||||
av.Get(false,
|
||||
(c2) =>
|
||||
{
|
||||
VRC.Core.Logger.Log("<color=magenta>Updating an existing avatar.</color>", DebugLevel.API);
|
||||
apiAvatar = c2.Model as ApiAvatar;
|
||||
pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(apiAvatar.authorId);
|
||||
SetupUI();
|
||||
},
|
||||
(c2) =>
|
||||
{
|
||||
VRC.Core.Logger.Log("<color=magenta>Creating a new avatar.</color>", DebugLevel.API);
|
||||
apiAvatar = new ApiAvatar();
|
||||
apiAvatar.id = pipelineManager.blueprintId;
|
||||
pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(apiAvatar.authorId);
|
||||
SetupUI();
|
||||
});
|
||||
}, (c) => {
|
||||
LoginErrorCallback(c.Error);
|
||||
});
|
||||
}
|
||||
|
||||
void SetupUI()
|
||||
{
|
||||
if (!ValidateAssetBundleBlueprintID(apiAvatar.id))
|
||||
{
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(true);
|
||||
OnSDKPipelineError("The asset bundle is out of date. Please rebuild the scene using 'New Build'.", "The blueprint ID in the scene does not match the id in the asset bundle.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (APIUser.Exists(pipelineManager.user))
|
||||
{
|
||||
waitingPanel.SetActive(false);
|
||||
blueprintPanel.SetActive(true);
|
||||
errorPanel.SetActive(false);
|
||||
|
||||
if (isUpdate)
|
||||
{
|
||||
// bp update
|
||||
if (apiAvatar.authorId == pipelineManager.user.id)
|
||||
{
|
||||
titleText.text = "Update Avatar";
|
||||
// apiAvatar = pipelineManager.user.GetBlueprint(pipelineManager.blueprintId) as ApiAvatar;
|
||||
blueprintName.text = apiAvatar.name;
|
||||
contentSex.isOn = apiAvatar.tags.Contains("content_sex");
|
||||
contentViolence.isOn = apiAvatar.tags.Contains("content_violence");
|
||||
contentGore.isOn = apiAvatar.tags.Contains("content_gore");
|
||||
contentOther.isOn = apiAvatar.tags.Contains("content_other");
|
||||
developerAvatar.isOn = apiAvatar.tags.Contains("developer");
|
||||
sharePrivate.isOn = apiAvatar.releaseStatus.Contains("private");
|
||||
sharePublic.isOn = apiAvatar.releaseStatus.Contains("public");
|
||||
blueprintDescription.text = apiAvatar.description;
|
||||
shouldUpdateImageToggle.interactable = true;
|
||||
shouldUpdateImageToggle.isOn = false;
|
||||
liveBpImage.enabled = false;
|
||||
bpImage.enabled = true;
|
||||
|
||||
ImageDownloader.DownloadImage(apiAvatar.imageUrl, 0, delegate (Texture2D obj) {
|
||||
bpImage.texture = obj;
|
||||
});
|
||||
}
|
||||
else // user does not own apiAvatar id associated with descriptor
|
||||
{
|
||||
Debug.LogErrorFormat("{0} is not an owner of {1}", apiAvatar.authorId, pipelineManager.user.id);
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
titleText.text = "New Avatar";
|
||||
shouldUpdateImageToggle.interactable = false;
|
||||
shouldUpdateImageToggle.isOn = true;
|
||||
liveBpImage.enabled = true;
|
||||
bpImage.enabled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
waitingPanel.SetActive(true);
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(false);
|
||||
}
|
||||
|
||||
if (APIUser.CurrentUser != null && APIUser.CurrentUser.hasSuperPowers)
|
||||
developerAvatar.gameObject.SetActive(true);
|
||||
else
|
||||
developerAvatar.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void SetupUpload()
|
||||
{
|
||||
uploadTitle = "Preparing For Upload";
|
||||
isUploading = true;
|
||||
|
||||
string abPath = UnityEditor.EditorPrefs.GetString("currentBuildingAssetBundlePath");
|
||||
|
||||
string unityPackagePath = UnityEditor.EditorPrefs.GetString("VRC_exportedUnityPackagePath");
|
||||
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_scene_changed", true);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_sex", contentSex.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_violence", contentViolence.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_gore", contentGore.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_other", contentOther.isOn);
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(apiAvatar.id))
|
||||
{
|
||||
pipelineManager.AssignId();
|
||||
apiAvatar.id = pipelineManager.blueprintId;
|
||||
}
|
||||
|
||||
string avatarId = apiAvatar.id;
|
||||
int version = isUpdate ? apiAvatar.version + 1 : 1;
|
||||
PrepareVRCPathForS3(abPath, avatarId, version, ApiAvatar.VERSION);
|
||||
|
||||
if (!string.IsNullOrEmpty(unityPackagePath) && System.IO.File.Exists(unityPackagePath))
|
||||
{
|
||||
VRC.Core.Logger.Log("Found unity package path. Preparing to upload!", DebugLevel.All);
|
||||
PrepareUnityPackageForS3(unityPackagePath, avatarId, version, ApiAvatar.VERSION);
|
||||
}
|
||||
|
||||
StartCoroutine(UploadNew());
|
||||
}
|
||||
|
||||
IEnumerator UploadNew()
|
||||
{
|
||||
bool caughtInvalidInput = false;
|
||||
if (!ValidateNameInput(blueprintName))
|
||||
caughtInvalidInput = true;
|
||||
|
||||
if (caughtInvalidInput)
|
||||
yield break;
|
||||
|
||||
VRC.Core.Logger.Log("Starting upload", DebugLevel.Always);
|
||||
|
||||
// upload unity package
|
||||
if (!string.IsNullOrEmpty(uploadUnityPackagePath))
|
||||
{
|
||||
yield return StartCoroutine(UploadFile(uploadUnityPackagePath, isUpdate ? apiAvatar.unityPackageUrl : "", GetFriendlyAvatarFileName("Unity package"), "Unity package",
|
||||
delegate (string fileUrl)
|
||||
{
|
||||
cloudFrontUnityPackageUrl = fileUrl;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// upload asset bundle
|
||||
if (!string.IsNullOrEmpty(uploadVrcPath))
|
||||
{
|
||||
yield return StartCoroutine(UploadFile(uploadVrcPath, isUpdate ? apiAvatar.assetUrl : "", GetFriendlyAvatarFileName("Asset bundle"), "Asset bundle",
|
||||
delegate (string fileUrl)
|
||||
{
|
||||
cloudFrontAssetUrl = fileUrl;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
if (isUpdate)
|
||||
yield return StartCoroutine(UpdateBlueprint());
|
||||
else
|
||||
yield return StartCoroutine(CreateBlueprint());
|
||||
|
||||
OnSDKPipelineComplete();
|
||||
}
|
||||
|
||||
private string GetFriendlyAvatarFileName(string type)
|
||||
{
|
||||
return "Avatar - " + blueprintName.text + " - " + type + " - " + Application.unityVersion + "_" + ApiWorld.VERSION.ApiVersion +
|
||||
"_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl();
|
||||
}
|
||||
|
||||
List<string> BuildTags()
|
||||
{
|
||||
var tags = new List<string>();
|
||||
if (contentSex.isOn)
|
||||
tags.Add("content_sex");
|
||||
if (contentViolence.isOn)
|
||||
tags.Add("content_violence");
|
||||
if (contentGore.isOn)
|
||||
tags.Add("content_gore");
|
||||
if (contentOther.isOn)
|
||||
tags.Add("content_other");
|
||||
|
||||
if (APIUser.CurrentUser.hasSuperPowers)
|
||||
{
|
||||
if (developerAvatar.isOn)
|
||||
tags.Add("developer");
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
protected override IEnumerator CreateBlueprint()
|
||||
{
|
||||
yield return StartCoroutine(UpdateImage(isUpdate ? apiAvatar.imageUrl : "", GetFriendlyAvatarFileName("Image")));
|
||||
|
||||
ApiAvatar avatar = new ApiAvatar
|
||||
{
|
||||
id = pipelineManager.blueprintId,
|
||||
authorName = pipelineManager.user.displayName,
|
||||
authorId = pipelineManager.user.id,
|
||||
name = blueprintName.text,
|
||||
imageUrl = cloudFrontImageUrl,
|
||||
assetUrl = cloudFrontAssetUrl,
|
||||
description = blueprintDescription.text,
|
||||
tags = BuildTags(),
|
||||
unityPackageUrl = cloudFrontUnityPackageUrl,
|
||||
releaseStatus = sharePublic.isOn ? "public" : "private"
|
||||
};
|
||||
|
||||
bool doneUploading = false;
|
||||
bool wasError = false;
|
||||
|
||||
avatar.Post(
|
||||
(c) =>
|
||||
{
|
||||
ApiAvatar savedBP = (ApiAvatar)c.Model;
|
||||
pipelineManager.blueprintId = savedBP.id;
|
||||
UnityEditor.EditorPrefs.SetString("blueprintID-" + pipelineManager.GetInstanceID().ToString(), savedBP.id);
|
||||
|
||||
AnalyticsSDK.AvatarUploaded(savedBP, false);
|
||||
doneUploading = true;
|
||||
},
|
||||
(c) =>
|
||||
{
|
||||
Debug.LogError(c.Error);
|
||||
SetUploadProgress("Saving Avatar", "Error saving blueprint.", 0.0f);
|
||||
doneUploading = true;
|
||||
wasError = true;
|
||||
});
|
||||
|
||||
while (!doneUploading)
|
||||
yield return null;
|
||||
|
||||
if (wasError)
|
||||
yield return new WaitUntil(() => UnityEditor.EditorUtility.DisplayDialog("VRChat SDK", "Error saving blueprint.", "Okay"));
|
||||
}
|
||||
|
||||
protected override IEnumerator UpdateBlueprint()
|
||||
{
|
||||
bool doneUploading = false;
|
||||
|
||||
apiAvatar.name = blueprintName.text;
|
||||
apiAvatar.description = blueprintDescription.text;
|
||||
apiAvatar.assetUrl = cloudFrontAssetUrl;
|
||||
apiAvatar.releaseStatus = sharePublic.isOn ? "public" : "private";
|
||||
apiAvatar.tags = BuildTags();
|
||||
apiAvatar.unityPackageUrl = cloudFrontUnityPackageUrl;
|
||||
|
||||
if (shouldUpdateImageToggle.isOn)
|
||||
{
|
||||
yield return StartCoroutine(UpdateImage(isUpdate ? apiAvatar.imageUrl : "", GetFriendlyAvatarFileName("Image")));
|
||||
apiAvatar.imageUrl = cloudFrontImageUrl;
|
||||
}
|
||||
|
||||
SetUploadProgress("Saving Avatar", "Almost finished!!", 0.8f);
|
||||
apiAvatar.Save(
|
||||
(c) => { AnalyticsSDK.AvatarUploaded(apiAvatar, true); doneUploading = true; },
|
||||
(c) => {
|
||||
Debug.LogError(c.Error);
|
||||
SetUploadProgress("Saving Avatar", "Error saving blueprint.", 0.0f);
|
||||
doneUploading = true;
|
||||
});
|
||||
|
||||
while (!doneUploading)
|
||||
yield return null;
|
||||
}
|
||||
|
||||
void ToggleUpdateImage(bool isOn)
|
||||
{
|
||||
if (isOn)
|
||||
{
|
||||
bpImage.enabled = false;
|
||||
liveBpImage.enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bpImage.enabled = true;
|
||||
liveBpImage.enabled = false;
|
||||
ImageDownloader.DownloadImage(apiAvatar.imageUrl, 0, delegate (Texture2D obj) {
|
||||
bpImage.texture = obj;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
UnityEditor.EditorUtility.ClearProgressBar();
|
||||
UnityEditor.EditorPrefs.DeleteKey("currentBuildingAssetBundlePath");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e5ebf65c5dceeb4c909aa7812bd2999
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,685 @@
|
||||
#define COMMUNITY_LABS_SDK
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Resources;
|
||||
using VRC.Core;
|
||||
using System;
|
||||
using System.IO;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace VRCSDK2
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
public class RuntimeWorldCreation : RuntimeAPICreation
|
||||
{
|
||||
public GameObject waitingPanel;
|
||||
public GameObject blueprintPanel;
|
||||
public GameObject errorPanel;
|
||||
|
||||
public Text titleText;
|
||||
public InputField blueprintName;
|
||||
public InputField blueprintDescription;
|
||||
public InputField worldCapacity;
|
||||
public RawImage bpImage;
|
||||
public Image liveBpImage;
|
||||
public Toggle shouldUpdateImageToggle;
|
||||
public Toggle releasePublic;
|
||||
public Toggle contentNsfw;
|
||||
|
||||
public Toggle contentSex;
|
||||
public Toggle contentViolence;
|
||||
public Toggle contentGore;
|
||||
public Toggle contentOther;
|
||||
|
||||
public Toggle contentFeatured;
|
||||
public Toggle contentSDKExample;
|
||||
|
||||
public Image showInWorldsMenuGroup;
|
||||
public Toggle showInActiveWorlds;
|
||||
public Toggle showInPopularWorlds;
|
||||
public Toggle showInNewWorlds;
|
||||
|
||||
public InputField userTags;
|
||||
|
||||
public UnityEngine.UI.Button uploadButton;
|
||||
|
||||
public UnityEngine.UI.Button openCommunityLabsDocsButton;
|
||||
|
||||
public GameObject publishToCommunityLabsPanel;
|
||||
|
||||
private Toggle publishToCommLabsToggle;
|
||||
|
||||
private ApiWorld worldRecord;
|
||||
|
||||
private const int MAX_USER_TAGS_FOR_WORLD = 5;
|
||||
private const int MAX_CHARACTERS_ALLOWED_IN_USER_TAG = 20;
|
||||
List<String> customTags;
|
||||
|
||||
public static bool IsCurrentWorldInCommunityLabs = false;
|
||||
public static bool IsCurrentWorldUploaded = false;
|
||||
public static bool IsCurrentWorldPubliclyPublished = false;
|
||||
public static bool HasExceededPublishLimit = false;
|
||||
|
||||
new void Start()
|
||||
{
|
||||
if (!Application.isEditor || !Application.isPlaying)
|
||||
return;
|
||||
|
||||
base.Start();
|
||||
|
||||
IsCurrentWorldInCommunityLabs = false;
|
||||
IsCurrentWorldUploaded = false;
|
||||
IsCurrentWorldPubliclyPublished = false;
|
||||
|
||||
|
||||
var desc = pipelineManager.GetComponent<VRC.SDKBase.VRC_SceneDescriptor>();
|
||||
desc.PositionPortraitCamera(imageCapture.shotCamera.transform);
|
||||
|
||||
Application.runInBackground = true;
|
||||
UnityEngine.XR.XRSettings.enabled = false;
|
||||
|
||||
uploadButton.onClick.AddListener(SetupUpload);
|
||||
|
||||
openCommunityLabsDocsButton.onClick.AddListener(OpenCommunityLabsDocumentation);
|
||||
|
||||
shouldUpdateImageToggle.onValueChanged.AddListener(ToggleUpdateImage);
|
||||
|
||||
releasePublic.gameObject.SetActive(false);
|
||||
|
||||
System.Action<string> onError = (err) => {
|
||||
VRC.Core.Logger.LogError("Could not authenticate - " + err, DebugLevel.Always);
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(true);
|
||||
};
|
||||
|
||||
if (!ApiCredentials.Load())
|
||||
onError("Not logged in");
|
||||
else
|
||||
APIUser.FetchCurrentUser(
|
||||
delegate (ApiModelContainer<APIUser> c)
|
||||
{
|
||||
UserLoggedInCallback(c.Model as APIUser);
|
||||
},
|
||||
delegate (ApiModelContainer<APIUser> c)
|
||||
{
|
||||
onError(c.Error);
|
||||
}
|
||||
);
|
||||
|
||||
#if !COMMUNITY_LABS_SDK
|
||||
publishToCommunityLabsPanel.gameObject.SetActive(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UserLoggedInCallback(APIUser user)
|
||||
{
|
||||
pipelineManager.user = user;
|
||||
|
||||
ApiWorld model = new ApiWorld();
|
||||
model.id = pipelineManager.blueprintId;
|
||||
model.Fetch(null, null,
|
||||
(c) =>
|
||||
{
|
||||
VRC.Core.Logger.Log("<color=magenta>Updating an existing world.</color>", DebugLevel.All);
|
||||
worldRecord = c.Model as ApiWorld;
|
||||
pipelineManager.completedSDKPipeline = !string.IsNullOrEmpty(worldRecord.authorId);
|
||||
GetUserUploadInformationAndSetupUI(model.id);
|
||||
},
|
||||
(c) =>
|
||||
{
|
||||
VRC.Core.Logger.Log("<color=magenta>World record not found, creating a new world.</color>", DebugLevel.All);
|
||||
worldRecord = new ApiWorld { capacity = 16 };
|
||||
pipelineManager.completedSDKPipeline = false;
|
||||
worldRecord.id = pipelineManager.blueprintId;
|
||||
GetUserUploadInformationAndSetupUI(model.id);
|
||||
});
|
||||
}
|
||||
|
||||
void CheckWorldStatus(string worldId, Action onCheckComplete)
|
||||
{
|
||||
// check if world has been previously uploaded, and if world is in community labs
|
||||
ApiWorld.FetchUploadedWorlds(
|
||||
delegate (IEnumerable<ApiWorld> worlds)
|
||||
{
|
||||
ApiWorld selectedWorld = worlds.FirstOrDefault(w => w.id == worldId);
|
||||
if (null!=selectedWorld)
|
||||
{
|
||||
IsCurrentWorldInCommunityLabs = selectedWorld.IsCommunityLabsWorld;
|
||||
IsCurrentWorldPubliclyPublished = selectedWorld.IsPublicPublishedWorld;
|
||||
IsCurrentWorldUploaded = true;
|
||||
}
|
||||
if (onCheckComplete != null) onCheckComplete();
|
||||
|
||||
},
|
||||
delegate (string err)
|
||||
{
|
||||
IsCurrentWorldInCommunityLabs = false;
|
||||
IsCurrentWorldUploaded = false;
|
||||
IsCurrentWorldPubliclyPublished = false;
|
||||
Debug.Log("CheckWorldStatus error:" + err);
|
||||
if (onCheckComplete != null) onCheckComplete();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void GetUserUploadInformationAndSetupUI(string worldId)
|
||||
{
|
||||
CheckWorldStatus(worldId, delegate()
|
||||
{
|
||||
bool hasSufficientTrustLevelToPublishToCommunityLabs = APIUser.CurrentUser.hasKnownTrustLevel;
|
||||
APIUser.FetchPublishWorldsInformation(
|
||||
(c) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> publish = c as Dictionary<string, object>;
|
||||
if (publish["canPublish"] is bool)
|
||||
{
|
||||
HasExceededPublishLimit = !(bool)(publish["canPublish"]);
|
||||
}
|
||||
else
|
||||
HasExceededPublishLimit = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
HasExceededPublishLimit = true;
|
||||
}
|
||||
|
||||
if(Application.isPlaying)
|
||||
{
|
||||
SetupUI(hasSufficientTrustLevelToPublishToCommunityLabs, HasExceededPublishLimit);
|
||||
}
|
||||
},
|
||||
(c) =>
|
||||
{
|
||||
if(Application.isPlaying)
|
||||
{
|
||||
SetupUI(hasSufficientTrustLevelToPublishToCommunityLabs, HasExceededPublishLimit);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SetupUI(bool hasEnoughTrustToPublishToCL = false, bool hasExceededWeeklyPublishLimit = false)
|
||||
{
|
||||
#if COMMUNITY_LABS_SDK
|
||||
// do not display community labs panel if updating an existing CL world or updating a public world
|
||||
publishToCommunityLabsPanel.gameObject.SetActive(!IsCurrentWorldUploaded);
|
||||
#endif
|
||||
|
||||
if (!ValidateAssetBundleBlueprintID(worldRecord.id))
|
||||
{
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(true);
|
||||
OnSDKPipelineError("The asset bundle is out of date. Please rebuild the scene using 'New Build'.", "The blueprint ID in the scene does not match the id in the asset bundle.");
|
||||
return;
|
||||
}
|
||||
|
||||
contentFeatured.gameObject.SetActive(APIUser.CurrentUser.hasSuperPowers);
|
||||
contentSDKExample.gameObject.SetActive(APIUser.CurrentUser.hasSuperPowers);
|
||||
|
||||
if (APIUser.Exists(pipelineManager.user))
|
||||
{
|
||||
waitingPanel.SetActive(false);
|
||||
blueprintPanel.SetActive(true);
|
||||
errorPanel.SetActive(false);
|
||||
|
||||
if (string.IsNullOrEmpty(worldRecord.authorId) || worldRecord.authorId == pipelineManager.user.id)
|
||||
{
|
||||
titleText.text = "Configure World";
|
||||
blueprintName.text = worldRecord.name;
|
||||
worldCapacity.text = worldRecord.capacity.ToString();
|
||||
contentSex.isOn = worldRecord.tags.Contains("content_sex");
|
||||
contentViolence.isOn = worldRecord.tags.Contains("content_violence");
|
||||
contentGore.isOn = worldRecord.tags.Contains("content_gore");
|
||||
contentOther.isOn = worldRecord.tags.Contains("content_other");
|
||||
shouldUpdateImageToggle.interactable = isUpdate;
|
||||
shouldUpdateImageToggle.isOn = !isUpdate;
|
||||
liveBpImage.enabled = !isUpdate;
|
||||
bpImage.enabled = isUpdate;
|
||||
|
||||
if (!APIUser.CurrentUser.hasSuperPowers)
|
||||
{
|
||||
releasePublic.gameObject.SetActive(false);
|
||||
releasePublic.isOn = false;
|
||||
releasePublic.interactable = false;
|
||||
|
||||
contentFeatured.isOn = contentSDKExample.isOn = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentFeatured.isOn = worldRecord.tags.Contains("content_featured");
|
||||
contentSDKExample.isOn = worldRecord.tags.Contains("content_sdk_example");
|
||||
|
||||
releasePublic.isOn = worldRecord.releaseStatus == "public";
|
||||
releasePublic.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// "show in worlds menu"
|
||||
if (APIUser.CurrentUser.hasSuperPowers)
|
||||
{
|
||||
showInWorldsMenuGroup.gameObject.SetActive(true);
|
||||
showInActiveWorlds.isOn = !worldRecord.tags.Contains("admin_hide_active");
|
||||
showInPopularWorlds.isOn = !worldRecord.tags.Contains("admin_hide_popular");
|
||||
showInNewWorlds.isOn = !worldRecord.tags.Contains("admin_hide_new");
|
||||
}
|
||||
else
|
||||
{
|
||||
showInWorldsMenuGroup.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
blueprintDescription.text = worldRecord.description;
|
||||
|
||||
userTags.text = "";
|
||||
foreach (var tag in worldRecord.publicTags)
|
||||
{
|
||||
userTags.text = userTags.text + tag.Replace("author_tag_", "");
|
||||
userTags.text = userTags.text + " ";
|
||||
}
|
||||
|
||||
ImageDownloader.DownloadImage(worldRecord.imageUrl, 0, delegate (Texture2D obj) {
|
||||
bpImage.texture = obj;
|
||||
});
|
||||
}
|
||||
else // user does not own world id associated with descriptor
|
||||
{
|
||||
Debug.LogErrorFormat("{0} is not an owner of {1}", worldRecord.authorId, pipelineManager.user.id);
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
waitingPanel.SetActive(true);
|
||||
blueprintPanel.SetActive(false);
|
||||
errorPanel.SetActive(false);
|
||||
|
||||
if (!APIUser.CurrentUser.hasSuperPowers)
|
||||
{
|
||||
releasePublic.gameObject.SetActive(false);
|
||||
releasePublic.isOn = false;
|
||||
releasePublic.interactable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
releasePublic.gameObject.SetActive(true);
|
||||
releasePublic.isOn = false;
|
||||
}
|
||||
}
|
||||
|
||||
// set up publish to Community Labs checkbox and text
|
||||
int worldsPublishedThisWeek = hasExceededWeeklyPublishLimit ? 1 : 0;
|
||||
int maximumWorldsAllowedToPublishPerWeek = 1;
|
||||
publishToCommLabsToggle = publishToCommunityLabsPanel.GetComponentInChildren<Toggle>();
|
||||
|
||||
if (null != publishToCommLabsToggle)
|
||||
{
|
||||
// disable publishing to CL checkbox if not enough trust or exceeded publish limit
|
||||
publishToCommLabsToggle.interactable = hasEnoughTrustToPublishToCL && !hasExceededWeeklyPublishLimit;
|
||||
|
||||
Text publishText = publishToCommLabsToggle.gameObject.GetComponentInChildren<Text>();
|
||||
if (null != publishText)
|
||||
{
|
||||
if (!hasEnoughTrustToPublishToCL)
|
||||
{
|
||||
publishText.text = "Not enough Trust to Publish to Community Labs";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasExceededWeeklyPublishLimit)
|
||||
{
|
||||
publishText.text = "Publish limit for Community Labs Exceeded\n" + "(" + worldsPublishedThisWeek + "/" + maximumWorldsAllowedToPublishPerWeek + " Published this week)";
|
||||
}
|
||||
else
|
||||
{
|
||||
publishText.text = "Publish to Community Labs\n" + "(" + worldsPublishedThisWeek + "/" + maximumWorldsAllowedToPublishPerWeek + " Published this week)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetupUpload()
|
||||
{
|
||||
if (!ParseUserTags())
|
||||
return;
|
||||
|
||||
publishingToCommunityLabs = (publishToCommLabsToggle != null) && (publishToCommLabsToggle.isActiveAndEnabled) && (publishToCommLabsToggle.isOn);
|
||||
|
||||
uploadTitle = "Preparing For Upload";
|
||||
isUploading = true;
|
||||
|
||||
string abPath = UnityEditor.EditorPrefs.GetString("currentBuildingAssetBundlePath");
|
||||
|
||||
|
||||
string unityPackagePath = UnityEditor.EditorPrefs.GetString("VRC_exportedUnityPackagePath");
|
||||
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_scene_changed", true);
|
||||
UnityEditor.EditorPrefs.SetInt("VRCSDK2_capacity", System.Convert.ToInt16(worldCapacity.text));
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_sex", contentSex.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_violence", contentViolence.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_gore", contentGore.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_other", contentOther.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_release_public", releasePublic.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_featured", contentFeatured.isOn);
|
||||
UnityEditor.EditorPrefs.SetBool("VRCSDK2_content_sdk_example", contentSDKExample.isOn);
|
||||
|
||||
if (string.IsNullOrEmpty(worldRecord.id))
|
||||
{
|
||||
pipelineManager.AssignId();
|
||||
worldRecord.id = pipelineManager.blueprintId;
|
||||
}
|
||||
|
||||
string blueprintId = worldRecord.id;
|
||||
int version = Mathf.Max(1, worldRecord.version + 1);
|
||||
PrepareVRCPathForS3(abPath, blueprintId, version, ApiWorld.VERSION);
|
||||
|
||||
if (!string.IsNullOrEmpty(unityPackagePath) && System.IO.File.Exists(unityPackagePath))
|
||||
{
|
||||
VRC.Core.Logger.Log("Found unity package path. Preparing to upload!", DebugLevel.All);
|
||||
PrepareUnityPackageForS3(unityPackagePath, blueprintId, version, ApiWorld.VERSION);
|
||||
}
|
||||
|
||||
StartCoroutine(UploadNew());
|
||||
}
|
||||
|
||||
void OnUploadedWorld()
|
||||
{
|
||||
const string devUrl = "https://dev-api.vrchat.cloud";
|
||||
const string releaseUrl = "https://vrchat.com";
|
||||
|
||||
string uploadedWorldURL = (API.IsDevApi() ? devUrl : releaseUrl) + "/home/world/" + pipelineManager.blueprintId;
|
||||
OnSDKPipelineComplete(uploadedWorldURL);
|
||||
}
|
||||
|
||||
IEnumerator UploadNew()
|
||||
{
|
||||
bool caughtInvalidInput = false;
|
||||
if (!ValidateNameInput(blueprintName))
|
||||
caughtInvalidInput = true;
|
||||
|
||||
if (caughtInvalidInput)
|
||||
yield break;
|
||||
|
||||
VRC.Core.Logger.Log("Starting upload", DebugLevel.Always);
|
||||
|
||||
// upload unity package
|
||||
if (!string.IsNullOrEmpty(uploadUnityPackagePath))
|
||||
{
|
||||
yield return StartCoroutine(UploadFile(uploadUnityPackagePath, isUpdate ? worldRecord.unityPackageUrl : "", GetFriendlyWorldFileName("Unity package"), "Unity package",
|
||||
delegate (string fileUrl)
|
||||
{
|
||||
cloudFrontUnityPackageUrl = fileUrl;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// upload asset bundle
|
||||
if (!string.IsNullOrEmpty(uploadVrcPath))
|
||||
{
|
||||
yield return StartCoroutine(UploadFile(uploadVrcPath, isUpdate ? worldRecord.assetUrl : "", GetFriendlyWorldFileName("Asset bundle"), "Asset bundle",
|
||||
delegate (string fileUrl)
|
||||
{
|
||||
cloudFrontAssetUrl = fileUrl;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
if (isUpdate)
|
||||
yield return StartCoroutine(UpdateBlueprint());
|
||||
else
|
||||
yield return StartCoroutine(CreateBlueprint());
|
||||
|
||||
if (publishingToCommunityLabs)
|
||||
{
|
||||
ApiWorld.PublishWorldToCommunityLabs(pipelineManager.blueprintId,
|
||||
(world) => OnUploadedWorld(),
|
||||
(err) =>
|
||||
{
|
||||
Debug.LogError("PublishWorldToCommunityLabs error:" + err);
|
||||
OnUploadedWorld();
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnUploadedWorld();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFriendlyWorldFileName(string type)
|
||||
{
|
||||
return "World - " + blueprintName.text + " - " + type + " - " + Application.unityVersion + "_" + ApiWorld.VERSION.ApiVersion +
|
||||
"_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl();
|
||||
}
|
||||
|
||||
List<string> BuildTags()
|
||||
{
|
||||
var tags = new List<string>();
|
||||
if (contentSex.isOn)
|
||||
tags.Add("content_sex");
|
||||
if (contentViolence.isOn)
|
||||
tags.Add("content_violence");
|
||||
if (contentGore.isOn)
|
||||
tags.Add("content_gore");
|
||||
if (contentOther.isOn)
|
||||
tags.Add("content_other");
|
||||
|
||||
if (APIUser.CurrentUser.hasSuperPowers)
|
||||
{
|
||||
if (contentFeatured.isOn)
|
||||
tags.Add("content_featured");
|
||||
if (contentSDKExample.isOn)
|
||||
tags.Add("content_sdk_example");
|
||||
if(releasePublic.isOn)
|
||||
tags.Add("admin_approved");
|
||||
}
|
||||
|
||||
// "show in worlds menu"
|
||||
if (APIUser.CurrentUser.hasSuperPowers)
|
||||
{
|
||||
if (!showInActiveWorlds.isOn)
|
||||
tags.Add("admin_hide_active");
|
||||
if (!showInPopularWorlds.isOn)
|
||||
tags.Add("admin_hide_popular");
|
||||
if (!showInNewWorlds.isOn)
|
||||
tags.Add("admin_hide_new");
|
||||
}
|
||||
|
||||
// add any author tags
|
||||
foreach (var word in customTags)
|
||||
{
|
||||
// add all custom tags with "author_tag_" prefix
|
||||
tags.Add("author_tag_" + word);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
bool ParseUserTags()
|
||||
{
|
||||
bool validTags = true;
|
||||
customTags = new List<string>();
|
||||
char[] delimiterChars = { ' ', ',', '.', ':', '\t', '\n', '"', '#' };
|
||||
|
||||
// split user tags into individual words
|
||||
string[] words = userTags.text.Split(delimiterChars, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var word in words)
|
||||
{
|
||||
customTags.Add(word.ToLower());
|
||||
}
|
||||
|
||||
// check that number of tags is within tag limit
|
||||
if (words.Count() > MAX_USER_TAGS_FOR_WORLD)
|
||||
{
|
||||
validTags = false;
|
||||
UnityEditor.EditorUtility.DisplayDialog("Tags are limited to a maximum of " + MAX_USER_TAGS_FOR_WORLD + " per world.", "Please remove excess tags before uploading!", "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
// check that no tags exceed maximum tag length
|
||||
int maximumTagLength = 0;
|
||||
foreach (string item in words)
|
||||
{
|
||||
if (item.Length > maximumTagLength)
|
||||
{
|
||||
maximumTagLength = item.Length;
|
||||
}
|
||||
}
|
||||
|
||||
if (maximumTagLength > MAX_CHARACTERS_ALLOWED_IN_USER_TAG)
|
||||
{
|
||||
validTags = false;
|
||||
UnityEditor.EditorUtility.DisplayDialog("Tags are limited to a maximum of " + MAX_CHARACTERS_ALLOWED_IN_USER_TAG + " characters per tag.", "One or more of your tags exceeds the maximum " + MAX_CHARACTERS_ALLOWED_IN_USER_TAG + " character limit.\n\n" + "Please shorten tags before uploading!", "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
// make sure tags are all alphanumeric
|
||||
foreach (var word in words)
|
||||
{
|
||||
if (!word.All(char.IsLetterOrDigit))
|
||||
{
|
||||
validTags = false;
|
||||
UnityEditor.EditorUtility.DisplayDialog("Tags should consist of alphanumeric characters only.", "Please remove any non-alphanumeric characters from tags before uploading!", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validTags;
|
||||
}
|
||||
|
||||
protected override IEnumerator CreateBlueprint()
|
||||
{
|
||||
yield return StartCoroutine(UpdateImage(isUpdate ? worldRecord.imageUrl : "", GetFriendlyWorldFileName("Image")));
|
||||
|
||||
SetUploadProgress("Saving Blueprint to user", "Almost finished!!", 0.0f);
|
||||
ApiWorld world = new ApiWorld
|
||||
{
|
||||
id = worldRecord.id,
|
||||
authorName = pipelineManager.user.displayName,
|
||||
authorId = pipelineManager.user.id,
|
||||
name = blueprintName.text,
|
||||
imageUrl = cloudFrontImageUrl,
|
||||
assetUrl = cloudFrontAssetUrl,
|
||||
unityPackageUrl = cloudFrontUnityPackageUrl,
|
||||
description = blueprintDescription.text,
|
||||
tags = BuildTags(),
|
||||
releaseStatus = (releasePublic.isOn) ? ("public") : ("private"),
|
||||
capacity = System.Convert.ToInt16(worldCapacity.text),
|
||||
occupants = 0,
|
||||
shouldAddToAuthor = true
|
||||
};
|
||||
|
||||
if (APIUser.CurrentUser.hasSuperPowers)
|
||||
world.isCurated = contentFeatured.isOn || contentSDKExample.isOn;
|
||||
else
|
||||
world.isCurated = false;
|
||||
|
||||
bool doneUploading = false;
|
||||
world.Post(
|
||||
(c) =>
|
||||
{
|
||||
ApiWorld savedBP = (ApiWorld)c.Model;
|
||||
pipelineManager.blueprintId = savedBP.id;
|
||||
UnityEditor.EditorPrefs.SetString("blueprintID-" + pipelineManager.GetInstanceID().ToString(), savedBP.id);
|
||||
VRC.Core.Logger.Log("Setting blueprintID on pipeline manager and editor prefs", DebugLevel.All);
|
||||
doneUploading = true;
|
||||
},
|
||||
(c) => { doneUploading = true; Debug.LogError(c.Error); });
|
||||
|
||||
while (!doneUploading)
|
||||
yield return null;
|
||||
}
|
||||
|
||||
protected override IEnumerator UpdateBlueprint()
|
||||
{
|
||||
bool doneUploading = false;
|
||||
|
||||
worldRecord.name = blueprintName.text;
|
||||
worldRecord.description = blueprintDescription.text;
|
||||
worldRecord.capacity = System.Convert.ToInt16(worldCapacity.text);
|
||||
worldRecord.assetUrl = cloudFrontAssetUrl;
|
||||
worldRecord.tags = BuildTags();
|
||||
worldRecord.releaseStatus = (releasePublic.isOn) ? ("public") : ("private");
|
||||
worldRecord.unityPackageUrl = cloudFrontUnityPackageUrl;
|
||||
worldRecord.isCurated = contentFeatured.isOn || contentSDKExample.isOn;
|
||||
|
||||
if (shouldUpdateImageToggle.isOn)
|
||||
{
|
||||
yield return StartCoroutine(UpdateImage(isUpdate ? worldRecord.imageUrl : "", GetFriendlyWorldFileName("Image")));
|
||||
|
||||
worldRecord.imageUrl = cloudFrontImageUrl;
|
||||
}
|
||||
|
||||
SetUploadProgress("Saving Blueprint", "Almost finished!!", 0.0f);
|
||||
worldRecord.Save((c) => doneUploading = true, (c) => { doneUploading = true; Debug.LogError(c.Error); });
|
||||
|
||||
while (!doneUploading)
|
||||
yield return null;
|
||||
}
|
||||
|
||||
void ToggleUpdateImage(bool isOn)
|
||||
{
|
||||
if (isOn)
|
||||
{
|
||||
bpImage.enabled = false;
|
||||
liveBpImage.enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bpImage.enabled = true;
|
||||
liveBpImage.enabled = false;
|
||||
ImageDownloader.DownloadImage(worldRecord.imageUrl, 0, delegate (Texture2D obj) {
|
||||
bpImage.texture = obj;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisplayUpdateCompletedDialog(string contentUrl = null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
#if COMMUNITY_LABS_SDK
|
||||
if (null != contentUrl)
|
||||
{
|
||||
CheckWorldStatus(pipelineManager.blueprintId, delegate ()
|
||||
{
|
||||
ContentUploadedDialog window = (ContentUploadedDialog)EditorWindow.GetWindow(typeof(ContentUploadedDialog), true, "VRCSDK", true);
|
||||
window.setContentURL(contentUrl);
|
||||
window.Show();
|
||||
// refresh UI based on uploaded world
|
||||
GetUserUploadInformationAndSetupUI(pipelineManager.blueprintId);
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
base.DisplayUpdateCompletedDialog(contentUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OpenCommunityLabsDocumentation()
|
||||
{
|
||||
Application.OpenURL(CommunityLabsConstants.COMMUNITY_LABS_DOCUMENTATION_URL);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
UnityEditor.EditorUtility.ClearProgressBar();
|
||||
UnityEditor.EditorPrefs.DeleteKey("currentBuildingAssetBundlePath");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bd5ee5d69ee0f3449bf2f81fcb7f4e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class SceneSaver
|
||||
{
|
||||
static public void SaveScene()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var activeScene = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene();
|
||||
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene);
|
||||
|
||||
UnityEditor.EditorApplication.isPaused = false;
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
|
||||
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(activeScene.name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d49300ad532d4ae6b569b28de5b7dac
|
||||
timeCreated: 1446917779
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb0a95b6898009f4a9217f9c357ee80c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Rendering;
|
||||
using UnityEngine;
|
||||
|
||||
public class StripPostProcessing : IPreprocessShaders
|
||||
{
|
||||
public int callbackOrder => 0;
|
||||
|
||||
public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
|
||||
{
|
||||
if(EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string shaderName = shader.name;
|
||||
if(string.IsNullOrEmpty(shaderName) || !shaderName.Contains("PostProcessing"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c18570190ea21fa4babc80af77d4d766
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "VRCSDK.ShaderStripping",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": []
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1e8486f7c7c81a4ea45be9776971082
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44e6970d7fcd57945a927579f210b401
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38bda3f3d7ca8b9448473cf4c8efd08d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance
|
||||
{
|
||||
public static class AvatarPerformance
|
||||
{
|
||||
#region Public Constants
|
||||
|
||||
public const int DEFAULT_DYNAMIC_BONE_MAX_SIMULATED_BONE_LIMIT = 32;
|
||||
public const int DEFAULT_DYNAMIC_BONE_MAX_COLLIDER_CHECK_LIMIT = 8;
|
||||
|
||||
#if UNITY_ANDROID || UNITY_IOS
|
||||
internal const PerformanceRating AVATAR_PERFORMANCE_RATING_MINIMUM_TO_DISPLAY_DEFAULT = PerformanceRating.Medium;
|
||||
internal const PerformanceRating AVATAR_PERFORMANCE_RATING_MINIMUM_TO_DISPLAY_MIN = PerformanceRating.Medium;
|
||||
internal const PerformanceRating AVATAR_PERFORMANCE_RATING_MINIMUM_TO_DISPLAY_MAX = PerformanceRating.Poor;
|
||||
#else
|
||||
internal const PerformanceRating AVATAR_PERFORMANCE_RATING_MINIMUM_TO_DISPLAY_DEFAULT = PerformanceRating.VeryPoor;
|
||||
internal const PerformanceRating AVATAR_PERFORMANCE_RATING_MINIMUM_TO_DISPLAY_MIN = PerformanceRating.Medium;
|
||||
internal const PerformanceRating AVATAR_PERFORMANCE_RATING_MINIMUM_TO_DISPLAY_MAX = PerformanceRating.VeryPoor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Delegates
|
||||
|
||||
public delegate bool IgnoreDelegate(Component component);
|
||||
|
||||
public delegate void FilterBlockCallback();
|
||||
|
||||
public static IgnoreDelegate ShouldIgnoreComponent { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public static void CalculatePerformanceStats(string avatarName, GameObject avatarObject, AvatarPerformanceStats perfStats)
|
||||
{
|
||||
perfStats.Reset();
|
||||
perfStats.avatarName = avatarName;
|
||||
|
||||
PerformanceScannerSet performanceScannerSet = GetPerformanceScannerSet();
|
||||
if(performanceScannerSet != null)
|
||||
{
|
||||
performanceScannerSet.RunPerformanceScan(avatarObject, perfStats, ShouldIgnoreComponentInternal);
|
||||
}
|
||||
|
||||
// cache performance ratings
|
||||
perfStats.CalculateAllPerformanceRatings();
|
||||
}
|
||||
|
||||
public static IEnumerator CalculatePerformanceStatsEnumerator(string avatarName, GameObject avatarObject, AvatarPerformanceStats perfStats)
|
||||
{
|
||||
perfStats.Reset();
|
||||
perfStats.avatarName = avatarName;
|
||||
|
||||
PerformanceScannerSet performanceScannerSet = GetPerformanceScannerSet();
|
||||
if(performanceScannerSet != null)
|
||||
{
|
||||
yield return performanceScannerSet.RunPerformanceScanEnumerator(avatarObject, perfStats, ShouldIgnoreComponentInternal);
|
||||
}
|
||||
|
||||
// cache performance ratings
|
||||
perfStats.CalculateAllPerformanceRatings();
|
||||
}
|
||||
|
||||
public static IEnumerator ApplyPerformanceFiltersEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, PerformanceRating minPerfRating, FilterBlockCallback onBlock)
|
||||
{
|
||||
// Performance Filtering is disabled.
|
||||
if(minPerfRating == PerformanceRating.None)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
PerformanceFilterSet performanceFilterSet = GetPerformanceFilterSet();
|
||||
if(performanceFilterSet == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
bool avatarBlocked = false;
|
||||
yield return performanceFilterSet.ApplyPerformanceFilters(
|
||||
avatarObject,
|
||||
perfStats,
|
||||
minPerfRating,
|
||||
ShouldIgnoreComponentInternal,
|
||||
() => { avatarBlocked = true; }
|
||||
);
|
||||
|
||||
if(!avatarBlocked)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
VRC.Core.Logger.LogFormat(
|
||||
"Avatar hidden due to low performance rating: [{0}] {1} - minimum setting: {2}",
|
||||
perfStats.avatarName,
|
||||
perfStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.Overall),
|
||||
minPerfRating
|
||||
);
|
||||
|
||||
onBlock();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static PerformanceScannerSet GetPerformanceScannerSet()
|
||||
{
|
||||
PerformanceScannerSet performanceScannerSet;
|
||||
if(VRC.ValidationHelpers.IsStandalonePlatform())
|
||||
{
|
||||
performanceScannerSet = Resources.Load<PerformanceScannerSet>("Validation/Performance/ScannerSets/PerformanceScannerSet_Windows");
|
||||
}
|
||||
else
|
||||
{
|
||||
performanceScannerSet = Resources.Load<PerformanceScannerSet>("Validation/Performance/ScannerSets/PerformanceScannerSet_Quest");
|
||||
}
|
||||
|
||||
return performanceScannerSet;
|
||||
}
|
||||
|
||||
private static PerformanceFilterSet GetPerformanceFilterSet()
|
||||
{
|
||||
PerformanceFilterSet performanceFilterSet;
|
||||
if(VRC.ValidationHelpers.IsStandalonePlatform())
|
||||
{
|
||||
performanceFilterSet = Resources.Load<PerformanceFilterSet>("Validation/Performance/FilterSets/PerformanceFilterSet_Windows");
|
||||
}
|
||||
else
|
||||
{
|
||||
performanceFilterSet = Resources.Load<PerformanceFilterSet>("Validation/Performance/FilterSets/PerformanceFilterSet_Quest");
|
||||
}
|
||||
|
||||
return performanceFilterSet;
|
||||
}
|
||||
|
||||
private static bool ShouldIgnoreComponentInternal(Component component)
|
||||
{
|
||||
if(Application.isEditor)
|
||||
{
|
||||
if(component == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(component.CompareTag("EditorOnly"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(ShouldIgnoreComponent != null)
|
||||
{
|
||||
return ShouldIgnoreComponent(component);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15ecac6f7fdc1bc4fb723fee6f4635dd
|
||||
timeCreated: 1540944864
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
namespace VRCSDK2.Validation.Performance
|
||||
{
|
||||
public enum AvatarPerformanceCategory
|
||||
{
|
||||
None,
|
||||
|
||||
Overall,
|
||||
|
||||
DownloadSize,
|
||||
PolyCount,
|
||||
AABB,
|
||||
SkinnedMeshCount,
|
||||
MeshCount,
|
||||
MaterialCount,
|
||||
DynamicBoneComponentCount,
|
||||
DynamicBoneSimulatedBoneCount,
|
||||
DynamicBoneColliderCount,
|
||||
DynamicBoneCollisionCheckCount,
|
||||
AnimatorCount,
|
||||
BoneCount,
|
||||
LightCount,
|
||||
ParticleSystemCount,
|
||||
ParticleTotalCount,
|
||||
ParticleMaxMeshPolyCount,
|
||||
ParticleTrailsEnabled,
|
||||
ParticleCollisionEnabled,
|
||||
TrailRendererCount,
|
||||
LineRendererCount,
|
||||
ClothCount,
|
||||
ClothMaxVertices,
|
||||
PhysicsColliderCount,
|
||||
PhysicsRigidbodyCount,
|
||||
AudioSourceCount,
|
||||
|
||||
AvatarPerformanceCategoryCount
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1ce994297384ff1bc330196df61b7ca
|
||||
timeCreated: 1561267912
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b99e0ada644703542a2c3f854643609f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Filters
|
||||
{
|
||||
public abstract class AbstractPerformanceFilter : ScriptableObject
|
||||
{
|
||||
public abstract IEnumerator ApplyPerformanceFilter(
|
||||
GameObject avatarObject,
|
||||
AvatarPerformanceStats perfStats,
|
||||
PerformanceRating ratingLimit,
|
||||
AvatarPerformance.IgnoreDelegate shouldIgnoreComponent,
|
||||
AvatarPerformance.FilterBlockCallback onBlock
|
||||
);
|
||||
|
||||
protected static IEnumerator RemoveComponentsOfTypeEnumerator<T>(GameObject target) where T : Component
|
||||
{
|
||||
if(target == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach(T targetComponent in target.GetComponentsInChildren<T>(true))
|
||||
{
|
||||
if(targetComponent == null || targetComponent.gameObject == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#if VERBOSE_COMPONENT_REMOVAL
|
||||
Debug.LogWarningFormat("Removing {0} comp from {1}", targetComponent.GetType().Name, targetComponent.gameObject.name);
|
||||
#endif
|
||||
|
||||
yield return RemoveComponent(targetComponent);
|
||||
}
|
||||
}
|
||||
|
||||
protected static IEnumerator RemoveComponent(Component targetComponent)
|
||||
{
|
||||
yield return RemoveDependencies(targetComponent);
|
||||
|
||||
Destroy(targetComponent);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
protected static IEnumerator RemoveDependencies(Component targetComponent)
|
||||
{
|
||||
if(targetComponent == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
Component[] siblingComponents = targetComponent.GetComponents<Component>();
|
||||
if(siblingComponents == null || siblingComponents.Length == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
System.Type componentType = targetComponent.GetType();
|
||||
foreach(Component siblingComponent in siblingComponents)
|
||||
{
|
||||
if(siblingComponent == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool deleteMe = false;
|
||||
object[] requireComponentAttributes = siblingComponent.GetType().GetCustomAttributes(typeof(RequireComponent), true);
|
||||
if(requireComponentAttributes.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach(var requireComponentObject in requireComponentAttributes)
|
||||
{
|
||||
RequireComponent requireComponentAttribute = requireComponentObject as RequireComponent;
|
||||
if(requireComponentAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(
|
||||
requireComponentAttribute.m_Type0 != componentType &&
|
||||
requireComponentAttribute.m_Type1 != componentType &&
|
||||
requireComponentAttribute.m_Type2 != componentType
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!deleteMe)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#if VERBOSE_COMPONENT_REMOVAL
|
||||
Debug.LogWarningFormat("Deleting component dependency {0} found on {1}", siblingComponent.GetType().Name, targetComponent.gameObject.name);
|
||||
#endif
|
||||
|
||||
yield return RemoveComponent(siblingComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abda65e062e44213aa3e1f4c82b400a8
|
||||
timeCreated: 1563937347
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance
|
||||
{
|
||||
public static class MeshUtils
|
||||
{
|
||||
private const uint INDICES_PER_TRIANGLE = 3U;
|
||||
|
||||
public static uint GetMeshTriangleCount(Mesh sourceMesh)
|
||||
{
|
||||
if(sourceMesh == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We can't use GetIndexCount if the mesh isn't readable so just return a huge number.
|
||||
// The SDK Control Panel should show a warning in this case.
|
||||
if(!sourceMesh.isReadable)
|
||||
{
|
||||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
for(int i = 0; i < sourceMesh.subMeshCount; i++)
|
||||
{
|
||||
uint indexCount = sourceMesh.GetIndexCount(i);
|
||||
count += indexCount / INDICES_PER_TRIANGLE;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f28c978154794266b38d686139c6b872
|
||||
timeCreated: 1561249515
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using VRCSDK2.Validation.Performance.Filters;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New PerformanceFilterSet",
|
||||
menuName = "VRC Scriptable Objects/Performance/PerformanceFilterSet"
|
||||
)]
|
||||
#endif
|
||||
public class PerformanceFilterSet : ScriptableObject
|
||||
{
|
||||
public AbstractPerformanceFilter[] performanceFilters;
|
||||
|
||||
public IEnumerator ApplyPerformanceFilters(
|
||||
GameObject avatarObject,
|
||||
AvatarPerformanceStats perfStats,
|
||||
PerformanceRating ratingLimit,
|
||||
AvatarPerformance.IgnoreDelegate shouldIgnoreComponent,
|
||||
AvatarPerformance.FilterBlockCallback onBlock
|
||||
)
|
||||
{
|
||||
foreach(AbstractPerformanceFilter performanceFilter in performanceFilters)
|
||||
{
|
||||
if(performanceFilter == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool avatarBlocked = false;
|
||||
yield return performanceFilter.ApplyPerformanceFilter(avatarObject, perfStats, ratingLimit, shouldIgnoreComponent, () => { avatarBlocked = true; });
|
||||
|
||||
if(!avatarBlocked)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
onBlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cdca9d06d1b4732b9ccb82a38bb8d9c
|
||||
timeCreated: 1563939583
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace VRCSDK2.Validation.Performance
|
||||
{
|
||||
public enum PerformanceInfoDisplayLevel
|
||||
{
|
||||
None,
|
||||
|
||||
Verbose,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5ed7498cb1a46c78eab031f5f32448c
|
||||
timeCreated: 1561267918
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace VRCSDK2.Validation.Performance
|
||||
{
|
||||
public enum PerformanceRating
|
||||
{
|
||||
None = 0,
|
||||
Excellent = 1,
|
||||
Good = 2,
|
||||
Medium = 3,
|
||||
Poor = 4,
|
||||
VeryPoor = 5
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5019a55ee9e2404c81bc61a39a010d8d
|
||||
timeCreated: 1561267904
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using VRCSDK2.Validation.Performance.Scanners;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New PerformanceScannerSet",
|
||||
menuName = "VRC Scriptable Objects/Performance/PerformanceScannerSet"
|
||||
)]
|
||||
#endif
|
||||
public class PerformanceScannerSet : ScriptableObject
|
||||
{
|
||||
public AbstractPerformanceScanner[] performanceScanners;
|
||||
|
||||
public void RunPerformanceScan(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
foreach(AbstractPerformanceScanner performanceScanner in performanceScanners)
|
||||
{
|
||||
if(performanceScanner == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
performanceScanner.RunPerformanceScan(avatarObject, perfStats, shouldIgnoreComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
foreach(AbstractPerformanceScanner performanceScanner in performanceScanners)
|
||||
{
|
||||
if(performanceScanner == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return performanceScanner.RunPerformanceScanEnumerator(avatarObject, perfStats, shouldIgnoreComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4afb61f36d144fc381114cd7f78d13e7
|
||||
timeCreated: 1561259704
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67bd9cc73f0a3e64e99dac8128cdf4b2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
public abstract class AbstractPerformanceScanner : ScriptableObject
|
||||
{
|
||||
private const int MAXIMUM_COMPONENT_SCANS_PER_FRAME = 10;
|
||||
private static int _componentScansThisFrame = 0;
|
||||
private static int _componentScansFrameNumber = 0;
|
||||
|
||||
private readonly Stack<IEnumerator> _coroutines = new Stack<IEnumerator>();
|
||||
|
||||
private bool _limitComponentScansPerFrame = true;
|
||||
|
||||
public abstract IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent);
|
||||
|
||||
public void RunPerformanceScan(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
_limitComponentScansPerFrame = false;
|
||||
|
||||
try
|
||||
{
|
||||
_coroutines.Push(RunPerformanceScanEnumerator(avatarObject, perfStats, shouldIgnoreComponent));
|
||||
while(_coroutines.Count > 0)
|
||||
{
|
||||
IEnumerator currentCoroutine = _coroutines.Peek();
|
||||
if(currentCoroutine.MoveNext())
|
||||
{
|
||||
IEnumerator nestedCoroutine = currentCoroutine.Current as IEnumerator;
|
||||
if(nestedCoroutine != null)
|
||||
{
|
||||
_coroutines.Push(nestedCoroutine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_coroutines.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
_coroutines.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_limitComponentScansPerFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerator ScanAvatarForComponentsOfType(Type componentType, GameObject avatarObject, List<Component> destinationBuffer)
|
||||
{
|
||||
yield return HandleComponentScansPerFrameLimit();
|
||||
|
||||
Profiler.BeginSample("Component Scan");
|
||||
destinationBuffer.Clear();
|
||||
destinationBuffer.AddRange(avatarObject.GetComponentsInChildren(componentType, true));
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
protected IEnumerator ScanAvatarForComponentsOfType<T>(GameObject avatarObject, List<T> destinationBuffer)
|
||||
{
|
||||
yield return HandleComponentScansPerFrameLimit();
|
||||
|
||||
Profiler.BeginSample("Component Scan");
|
||||
destinationBuffer.Clear();
|
||||
avatarObject.GetComponentsInChildren(true, destinationBuffer);
|
||||
Profiler.EndSample();
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private IEnumerator HandleComponentScansPerFrameLimit()
|
||||
{
|
||||
if(!_limitComponentScansPerFrame)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
while(_componentScansThisFrame >= MAXIMUM_COMPONENT_SCANS_PER_FRAME)
|
||||
{
|
||||
if(Time.frameCount > _componentScansFrameNumber)
|
||||
{
|
||||
_componentScansFrameNumber = Time.frameCount;
|
||||
_componentScansThisFrame = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
_componentScansThisFrame++;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bd0691a021844f49444a04a959d6328
|
||||
timeCreated: 1561279121
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New AnimatorPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/AnimatorPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class AnimatorPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
int animatorCount = 0;
|
||||
|
||||
// Animators
|
||||
List<Animator> animatorBuffer = new List<Animator>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, animatorBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
animatorBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
// ReSharper disable once UselessBinaryOperation
|
||||
animatorCount += animatorBuffer.Count;
|
||||
|
||||
// Animations
|
||||
List<Animation> animationBuffer = new List<Animation>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, animationBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
animationBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
animatorCount += animationBuffer.Count;
|
||||
|
||||
perfStats.animatorCount = animatorCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08c8e931d0544866a0f626855d9c1841
|
||||
timeCreated: 1561251363
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New AudioPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/AudioPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class AudioPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Audio Sources
|
||||
List<AudioSource> audioSourceBuffer = new List<AudioSource>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, audioSourceBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
audioSourceBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
perfStats.audioSourceCount = audioSourceBuffer.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3a8bba736414d1aaa9e766da27b56b5
|
||||
timeCreated: 1561255618
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New ClothPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/ClothPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class ClothPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Cloth
|
||||
List<Cloth> clothBuffer = new List<Cloth>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, clothBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
clothBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
int totalClothVertices = 0;
|
||||
foreach(Cloth cloth in clothBuffer)
|
||||
{
|
||||
if(cloth == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3[] clothVertices = cloth.vertices;
|
||||
if(clothVertices == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
totalClothVertices += clothVertices.Length;
|
||||
}
|
||||
|
||||
perfStats.clothCount = clothBuffer.Count;
|
||||
perfStats.clothMaxVertices = totalClothVertices;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cec88b5a46f459195f10a2f11fddb2f
|
||||
timeCreated: 1561255843
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New DynamicBonePerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/DynamicBonePerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class DynamicBonePerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
private Type _dynamicBoneType;
|
||||
private FieldInfo _dynamicBoneRootFieldInfo;
|
||||
private FieldInfo _dynamicBoneExclusionsFieldInfo;
|
||||
private FieldInfo _dynamicBoneCollidersFieldInfo;
|
||||
private FieldInfo _dynamicBoneEndLengthFieldInfo;
|
||||
private FieldInfo _dynamicBoneEndOffsetFieldInfo;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
FindDynamicBoneTypes();
|
||||
}
|
||||
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
if(_dynamicBoneType == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Dynamic Bone as Component
|
||||
List<Component> dynamicBoneComponentBuffer = new List<Component>();
|
||||
List<object> dynamicBoneColliderObjectBuffer = new List<object>();
|
||||
yield return ScanAvatarForComponentsOfType(_dynamicBoneType, avatarObject, dynamicBoneComponentBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
dynamicBoneComponentBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
int totalSimulatedBoneCount = 0;
|
||||
int totalCollisionChecks = 0;
|
||||
|
||||
Profiler.BeginSample("Analyze Dynamic Bones");
|
||||
foreach(Component dynamicBone in dynamicBoneComponentBuffer)
|
||||
{
|
||||
Profiler.BeginSample("Single Dynamic Bone Component");
|
||||
int simulatedBones = 0;
|
||||
|
||||
// Add extra bones to the end of each chain if end bones are being used.
|
||||
float endLength = (float)_dynamicBoneEndLengthFieldInfo.GetValue(dynamicBone);
|
||||
Vector3 endOffset = (Vector3)_dynamicBoneEndOffsetFieldInfo.GetValue(dynamicBone);
|
||||
bool hasEndBones = endLength > 0 || endOffset != Vector3.zero;
|
||||
|
||||
Transform root = (Transform)_dynamicBoneRootFieldInfo.GetValue(dynamicBone);
|
||||
if(root != null)
|
||||
{
|
||||
List<Transform> exclusions = (List<Transform>)_dynamicBoneExclusionsFieldInfo.GetValue(dynamicBone);
|
||||
|
||||
// Calculate number of simulated bones for the hierarchy
|
||||
simulatedBones = CountTransformsRecursively(root, exclusions, hasEndBones);
|
||||
totalSimulatedBoneCount += simulatedBones;
|
||||
}
|
||||
|
||||
int colliderListEntryCount = 0;
|
||||
IList colliderList = (IList)_dynamicBoneCollidersFieldInfo.GetValue(dynamicBone);
|
||||
if(colliderList != null)
|
||||
{
|
||||
foreach(object collider in colliderList)
|
||||
{
|
||||
colliderListEntryCount += 1;
|
||||
if(collider != null && !dynamicBoneColliderObjectBuffer.Contains(collider))
|
||||
{
|
||||
dynamicBoneColliderObjectBuffer.Add(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The root bone is skipped in collision checks.
|
||||
totalCollisionChecks += (simulatedBones - 1) * colliderListEntryCount;
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
|
||||
yield return null;
|
||||
|
||||
perfStats.dynamicBoneComponentCount = dynamicBoneComponentBuffer.Count;
|
||||
perfStats.dynamicBoneSimulatedBoneCount = totalSimulatedBoneCount;
|
||||
perfStats.dynamicBoneColliderCount = dynamicBoneColliderObjectBuffer.Count;
|
||||
perfStats.dynamicBoneCollisionCheckCount = totalCollisionChecks;
|
||||
}
|
||||
|
||||
private void FindDynamicBoneTypes()
|
||||
{
|
||||
if(_dynamicBoneType != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Type dyBoneType = ValidationUtils.GetTypeFromName("DynamicBone");
|
||||
if(dyBoneType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Type dyBoneColliderType = ValidationUtils.GetTypeFromName("DynamicBoneColliderBase") ?? ValidationUtils.GetTypeFromName("DynamicBoneCollider");
|
||||
if(dyBoneColliderType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldInfo rootFieldInfo = dyBoneType.GetField("m_Root", BindingFlags.Public | BindingFlags.Instance);
|
||||
if(rootFieldInfo == null || rootFieldInfo.FieldType != typeof(Transform))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldInfo exclusionsFieldInfo = dyBoneType.GetField("m_Exclusions", BindingFlags.Public | BindingFlags.Instance);
|
||||
if(exclusionsFieldInfo == null || exclusionsFieldInfo.FieldType != typeof(List<Transform>))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldInfo collidersFieldInfo = dyBoneType.GetField("m_Colliders", BindingFlags.Public | BindingFlags.Instance);
|
||||
if(collidersFieldInfo == null || collidersFieldInfo.FieldType.GetGenericTypeDefinition() != typeof(List<>) ||
|
||||
collidersFieldInfo.FieldType.GetGenericArguments().Single() != dyBoneColliderType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldInfo endLengthFieldInfo = dyBoneType.GetField("m_EndLength", BindingFlags.Public | BindingFlags.Instance);
|
||||
if(endLengthFieldInfo == null || endLengthFieldInfo.FieldType != typeof(float))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldInfo endOffsetFieldInfo = dyBoneType.GetField("m_EndOffset", BindingFlags.Public | BindingFlags.Instance);
|
||||
if(endOffsetFieldInfo == null || endOffsetFieldInfo.FieldType != typeof(Vector3))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dynamicBoneType = dyBoneType;
|
||||
_dynamicBoneRootFieldInfo = rootFieldInfo;
|
||||
_dynamicBoneExclusionsFieldInfo = exclusionsFieldInfo;
|
||||
_dynamicBoneCollidersFieldInfo = collidersFieldInfo;
|
||||
_dynamicBoneEndLengthFieldInfo = endLengthFieldInfo;
|
||||
_dynamicBoneEndOffsetFieldInfo = endOffsetFieldInfo;
|
||||
}
|
||||
|
||||
// Like DynamicBone itself exclusions only apply to children of the current bone.
|
||||
// This means the root bone itself never excluded.
|
||||
private static int CountTransformsRecursively(Transform transform, List<Transform> exclusions, bool addEndBones)
|
||||
{
|
||||
if(transform == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 1;
|
||||
int childCount = transform.childCount;
|
||||
if(childCount > 0)
|
||||
{
|
||||
foreach(Transform child in transform)
|
||||
{
|
||||
if(exclusions == null || !exclusions.Contains(child))
|
||||
{
|
||||
count += CountTransformsRecursively(child, exclusions, addEndBones);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(addEndBones)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a226df494ef04404a9a47c714822ab9f
|
||||
timeCreated: 1561251560
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New LightPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/LightPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class LightPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Lights
|
||||
List<Light> lightBuffer = new List<Light>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, lightBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
lightBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
perfStats.lightCount = lightBuffer.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 405778fdc32c44c1bb9fdd0476fb0007
|
||||
timeCreated: 1561256390
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New LineRendererPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/LineRendererPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class LineRendererPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Line Renderers
|
||||
List<LineRenderer> lineRendererBuffer = new List<LineRenderer>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, lineRendererBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
lineRendererBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
int numLineRenderers = lineRendererBuffer.Count;
|
||||
perfStats.lineRendererCount = numLineRenderers;
|
||||
perfStats.materialCount = perfStats.materialCount.GetValueOrDefault() + numLineRenderers;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec87392b85844f7bb526a48ec866a8f0
|
||||
timeCreated: 1561253047
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New MeshPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/MeshPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class MeshPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Renderers
|
||||
List<Renderer> rendererBuffer = new List<Renderer>(16);
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, rendererBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
rendererBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
yield return AnalyzeGeometry(avatarObject, rendererBuffer, perfStats);
|
||||
AnalyzeMeshRenderers(rendererBuffer, perfStats);
|
||||
AnalyzeSkinnedMeshRenderers(rendererBuffer, perfStats);
|
||||
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private static uint CalculateRendererPolyCount(Renderer renderer)
|
||||
{
|
||||
Mesh sharedMesh = null;
|
||||
SkinnedMeshRenderer skinnedMeshRenderer = renderer as SkinnedMeshRenderer;
|
||||
if(skinnedMeshRenderer != null)
|
||||
{
|
||||
sharedMesh = skinnedMeshRenderer.sharedMesh;
|
||||
}
|
||||
|
||||
if(sharedMesh == null)
|
||||
{
|
||||
MeshRenderer meshRenderer = renderer as MeshRenderer;
|
||||
if(meshRenderer != null)
|
||||
{
|
||||
MeshFilter meshFilter = meshRenderer.GetComponent<MeshFilter>();
|
||||
if(meshFilter != null)
|
||||
{
|
||||
sharedMesh = meshFilter.sharedMesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sharedMesh == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return MeshUtils.GetMeshTriangleCount(sharedMesh);
|
||||
}
|
||||
|
||||
private static bool RendererHasMesh(Renderer renderer)
|
||||
{
|
||||
MeshRenderer meshRenderer = renderer as MeshRenderer;
|
||||
if(meshRenderer != null)
|
||||
{
|
||||
MeshFilter meshFilter = meshRenderer.GetComponent<MeshFilter>();
|
||||
if(meshFilter == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return meshFilter.sharedMesh != null;
|
||||
}
|
||||
|
||||
SkinnedMeshRenderer skinnedMeshRenderer = renderer as SkinnedMeshRenderer;
|
||||
if(skinnedMeshRenderer != null)
|
||||
{
|
||||
return skinnedMeshRenderer.sharedMesh != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerator AnalyzeGeometry(GameObject avatarObject, IEnumerable<Renderer> renderers, AvatarPerformanceStats perfStats)
|
||||
{
|
||||
List<Renderer> lodGroupRendererIgnoreBuffer = new List<Renderer>(16);
|
||||
List<LODGroup> lodBuffer = new List<LODGroup>(16);
|
||||
|
||||
ulong polyCount = 0;
|
||||
Bounds bounds = new Bounds(avatarObject.transform.position, Vector3.zero);
|
||||
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, lodBuffer);
|
||||
try
|
||||
{
|
||||
foreach(LODGroup lodGroup in lodBuffer)
|
||||
{
|
||||
LOD[] lodLevels = lodGroup.GetLODs();
|
||||
|
||||
ulong highestLodPolyCount = 0;
|
||||
foreach(LOD lod in lodLevels)
|
||||
{
|
||||
uint thisLodPolyCount = 0;
|
||||
foreach(Renderer renderer in lod.renderers)
|
||||
{
|
||||
lodGroupRendererIgnoreBuffer.Add(renderer);
|
||||
checked
|
||||
{
|
||||
thisLodPolyCount += CalculateRendererPolyCount(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
if(thisLodPolyCount > highestLodPolyCount)
|
||||
{
|
||||
highestLodPolyCount = thisLodPolyCount;
|
||||
}
|
||||
}
|
||||
|
||||
checked
|
||||
{
|
||||
polyCount += highestLodPolyCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(OverflowException e)
|
||||
{
|
||||
VRC.Core.Logger.Log("Overflow exception while analyzing geometry, assuming max value:" + e.ToString(), VRC.Core.DebugLevel.All);
|
||||
polyCount = uint.MaxValue;
|
||||
}
|
||||
|
||||
Profiler.BeginSample("Calculate Total Polygon Count and Bounds");
|
||||
foreach(Renderer renderer in renderers)
|
||||
{
|
||||
Profiler.BeginSample("Single Renderer");
|
||||
if(renderer is MeshRenderer || renderer is SkinnedMeshRenderer)
|
||||
{
|
||||
if(!RendererHasMesh(renderer))
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
bounds.Encapsulate(renderer.bounds);
|
||||
}
|
||||
|
||||
if(lodGroupRendererIgnoreBuffer.Contains(renderer))
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
polyCount += CalculateRendererPolyCount(renderer);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
|
||||
bounds.center -= avatarObject.transform.position;
|
||||
|
||||
lodGroupRendererIgnoreBuffer.Clear();
|
||||
lodBuffer.Clear();
|
||||
|
||||
perfStats.polyCount = polyCount > int.MaxValue ? int.MaxValue : (int)polyCount;
|
||||
perfStats.aabb = bounds;
|
||||
}
|
||||
|
||||
private static void AnalyzeSkinnedMeshRenderers(IEnumerable<Renderer> renderers, AvatarPerformanceStats perfStats)
|
||||
{
|
||||
Profiler.BeginSample("AnalyzeSkinnedMeshRenderers");
|
||||
int count = 0;
|
||||
int materialSlots = 0;
|
||||
int skinnedBoneCount = 0;
|
||||
HashSet<Transform> transformIgnoreBuffer = new HashSet<Transform>();
|
||||
|
||||
foreach(Renderer renderer in renderers)
|
||||
{
|
||||
Profiler.BeginSample("Analyze SkinnedMeshRenderer");
|
||||
SkinnedMeshRenderer skinnedMeshRenderer = renderer as SkinnedMeshRenderer;
|
||||
if(skinnedMeshRenderer == null)
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
Mesh sharedMesh = skinnedMeshRenderer.sharedMesh;
|
||||
if(sharedMesh != null)
|
||||
{
|
||||
materialSlots += sharedMesh.subMeshCount;
|
||||
}
|
||||
|
||||
// bone count
|
||||
Profiler.BeginSample("Count Bones");
|
||||
Transform[] bones = skinnedMeshRenderer.bones;
|
||||
foreach(Transform bone in bones)
|
||||
{
|
||||
Profiler.BeginSample("Count Bone");
|
||||
if(bone == null || transformIgnoreBuffer.Contains(bone))
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
transformIgnoreBuffer.Add(bone);
|
||||
skinnedBoneCount++;
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
transformIgnoreBuffer.Clear();
|
||||
Profiler.EndSample();
|
||||
|
||||
perfStats.skinnedMeshCount = count;
|
||||
perfStats.boneCount = skinnedBoneCount;
|
||||
perfStats.materialCount = perfStats.materialCount.GetValueOrDefault() + materialSlots;
|
||||
}
|
||||
|
||||
private static void AnalyzeMeshRenderers(IEnumerable<Renderer> renderers, AvatarPerformanceStats perfStats)
|
||||
{
|
||||
Profiler.BeginSample("AnalyzeMeshRenderers");
|
||||
int count = 0;
|
||||
int materialSlots = 0;
|
||||
foreach(Renderer renderer in renderers)
|
||||
{
|
||||
Profiler.BeginSample("Analyze MeshRenderer");
|
||||
MeshRenderer meshRenderer = renderer as MeshRenderer;
|
||||
if(meshRenderer == null)
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
Profiler.BeginSample("Get MeshFilter");
|
||||
MeshFilter meshFilter = meshRenderer.GetComponent<MeshFilter>();
|
||||
Profiler.EndSample();
|
||||
if(meshFilter == null)
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
Mesh sharedMesh = meshFilter.sharedMesh;
|
||||
if(sharedMesh != null)
|
||||
{
|
||||
materialSlots += sharedMesh.subMeshCount;
|
||||
}
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
|
||||
perfStats.meshCount = count;
|
||||
perfStats.materialCount = perfStats.materialCount.GetValueOrDefault() + materialSlots;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38bca10261df4ddfa10cff3b3bbb9428
|
||||
timeCreated: 1561249198
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New ParticlePerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/ParticlePerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class ParticlePerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Particle Systems
|
||||
List<ParticleSystem> particleSystemBuffer = new List<ParticleSystem>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, particleSystemBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
particleSystemBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
AnalyzeParticleSystemRenderers(particleSystemBuffer, perfStats);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private static void AnalyzeParticleSystemRenderers(IEnumerable<ParticleSystem> particleSystems, AvatarPerformanceStats perfStats)
|
||||
{
|
||||
int particleSystemCount = 0;
|
||||
ulong particleTotalCount = 0;
|
||||
ulong particleTotalMaxMeshPolyCount = 0;
|
||||
bool particleTrailsEnabled = false;
|
||||
bool particleCollisionEnabled = false;
|
||||
int materialSlots = 0;
|
||||
|
||||
Profiler.BeginSample("AnalyzeParticleSystemRenderers");
|
||||
foreach(ParticleSystem particleSystem in particleSystems)
|
||||
{
|
||||
Profiler.BeginSample("Single Particle System");
|
||||
int particleCount = particleSystem.main.maxParticles;
|
||||
if(particleCount <= 0)
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
particleSystemCount++;
|
||||
particleTotalCount += (uint)particleCount;
|
||||
|
||||
ParticleSystemRenderer particleSystemRenderer = particleSystem.GetComponent<ParticleSystemRenderer>();
|
||||
if(particleSystemRenderer == null)
|
||||
{
|
||||
Profiler.EndSample();
|
||||
continue;
|
||||
}
|
||||
|
||||
materialSlots++;
|
||||
|
||||
// mesh particles
|
||||
if(particleSystemRenderer.renderMode == ParticleSystemRenderMode.Mesh && particleSystemRenderer.meshCount > 0)
|
||||
{
|
||||
uint highestPolyCount = 0;
|
||||
|
||||
Mesh[] meshes = new Mesh[particleSystemRenderer.meshCount];
|
||||
int particleRendererMeshCount = particleSystemRenderer.GetMeshes(meshes);
|
||||
for(int meshIndex = 0; meshIndex < particleRendererMeshCount; meshIndex++)
|
||||
{
|
||||
Mesh mesh = meshes[meshIndex];
|
||||
if(mesh == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint polyCount = MeshUtils.GetMeshTriangleCount(mesh);
|
||||
if(polyCount > highestPolyCount)
|
||||
{
|
||||
highestPolyCount = polyCount;
|
||||
}
|
||||
}
|
||||
|
||||
ulong maxMeshParticlePolyCount = (uint)particleCount * highestPolyCount;
|
||||
particleTotalMaxMeshPolyCount += maxMeshParticlePolyCount;
|
||||
}
|
||||
|
||||
if(particleSystem.trails.enabled)
|
||||
{
|
||||
particleTrailsEnabled = true;
|
||||
materialSlots++;
|
||||
}
|
||||
|
||||
if(particleSystem.collision.enabled)
|
||||
{
|
||||
particleCollisionEnabled = true;
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
|
||||
perfStats.particleSystemCount = particleSystemCount;
|
||||
perfStats.particleTotalCount = particleTotalCount > int.MaxValue ? int.MaxValue : (int)particleTotalCount;
|
||||
perfStats.particleMaxMeshPolyCount = particleTotalMaxMeshPolyCount > int.MaxValue ? int.MaxValue : (int)particleTotalMaxMeshPolyCount;
|
||||
perfStats.particleTrailsEnabled = particleTrailsEnabled;
|
||||
perfStats.particleCollisionEnabled = particleCollisionEnabled;
|
||||
perfStats.materialCount = perfStats.materialCount.GetValueOrDefault() + materialSlots;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10723e354ff14f98a49ab797b3f005e6
|
||||
timeCreated: 1561250267
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New PhysicsPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/PhysicsPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class PhysicsPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Colliders
|
||||
List<Collider> colliderBuffer = new List<Collider>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, colliderBuffer);
|
||||
colliderBuffer.RemoveAll(
|
||||
o =>
|
||||
{
|
||||
if(shouldIgnoreComponent != null && shouldIgnoreComponent(o))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o.GetComponent<VRC.SDKBase.VRCStation>() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
perfStats.physicsColliderCount = colliderBuffer.Count;
|
||||
|
||||
// Rigidbodies
|
||||
List<Rigidbody> rigidbodyBuffer = new List<Rigidbody>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, rigidbodyBuffer);
|
||||
rigidbodyBuffer.RemoveAll(
|
||||
o =>
|
||||
{
|
||||
if(shouldIgnoreComponent != null && shouldIgnoreComponent(o))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o.GetComponent<VRC.SDKBase.VRCStation>() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
perfStats.physicsRigidbodyCount = rigidbodyBuffer.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a94ecdeecd04f85824cc3244be5712a
|
||||
timeCreated: 1561256481
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using VRCSDK2.Validation.Performance.Stats;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Scanners
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
[CreateAssetMenu(
|
||||
fileName = "New TrailRendererPerformanceScanner",
|
||||
menuName = "VRC Scriptable Objects/Performance/Avatar/Scanners/TrailRendererPerformanceScanner"
|
||||
)]
|
||||
#endif
|
||||
public sealed class TrailRendererPerformanceScanner : AbstractPerformanceScanner
|
||||
{
|
||||
public override IEnumerator RunPerformanceScanEnumerator(GameObject avatarObject, AvatarPerformanceStats perfStats, AvatarPerformance.IgnoreDelegate shouldIgnoreComponent)
|
||||
{
|
||||
// Trail Renderers
|
||||
List<TrailRenderer> trailRendererBuffer = new List<TrailRenderer>();
|
||||
yield return ScanAvatarForComponentsOfType(avatarObject, trailRendererBuffer);
|
||||
if(shouldIgnoreComponent != null)
|
||||
{
|
||||
trailRendererBuffer.RemoveAll(c => shouldIgnoreComponent(c));
|
||||
}
|
||||
|
||||
int numTrailRenderers = trailRendererBuffer.Count;
|
||||
perfStats.trailRendererCount = numTrailRenderers;
|
||||
perfStats.materialCount = perfStats.materialCount.GetValueOrDefault() + numTrailRenderers;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2efd714b564547b4be1ebd1f2700668b
|
||||
timeCreated: 1561251810
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68702a268e708b043a0dca66980158fa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+589
@@ -0,0 +1,589 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Stats
|
||||
{
|
||||
public class AvatarPerformanceStats
|
||||
{
|
||||
private delegate int ComparePerformanceStatsDelegate(AvatarPerformanceStats stats, AvatarPerformanceStatsLevel statsLevel);
|
||||
|
||||
#region Public Fields
|
||||
|
||||
public string avatarName;
|
||||
|
||||
public int? polyCount;
|
||||
public Bounds? aabb;
|
||||
public int? skinnedMeshCount;
|
||||
public int? meshCount;
|
||||
public int? materialCount;
|
||||
public int? animatorCount;
|
||||
public int? boneCount;
|
||||
public int? lightCount;
|
||||
public int? particleSystemCount;
|
||||
public int? particleTotalCount;
|
||||
public int? particleMaxMeshPolyCount;
|
||||
public bool? particleTrailsEnabled;
|
||||
public bool? particleCollisionEnabled;
|
||||
public int? trailRendererCount;
|
||||
public int? lineRendererCount;
|
||||
public int? dynamicBoneComponentCount;
|
||||
public int? dynamicBoneSimulatedBoneCount;
|
||||
public int? dynamicBoneColliderCount;
|
||||
public int? dynamicBoneCollisionCheckCount; // number of collider simulated bones excluding the root multiplied by the number of colliders
|
||||
public int? clothCount;
|
||||
public int? clothMaxVertices;
|
||||
public int? physicsColliderCount;
|
||||
public int? physicsRigidbodyCount;
|
||||
public int? audioSourceCount;
|
||||
public float? downloadSize;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private readonly PerformanceRating[] _performanceRatingCache;
|
||||
private static readonly Dictionary<AvatarPerformanceCategory, string> _performanceCategoryDisplayNames = new Dictionary<AvatarPerformanceCategory, string>
|
||||
{
|
||||
{AvatarPerformanceCategory.PolyCount, "Polygons"},
|
||||
{AvatarPerformanceCategory.AABB, "Bounds"},
|
||||
{AvatarPerformanceCategory.SkinnedMeshCount, "Skinned Meshes"},
|
||||
{AvatarPerformanceCategory.MeshCount, "Meshes"},
|
||||
{AvatarPerformanceCategory.MaterialCount, "Material Slots"},
|
||||
{AvatarPerformanceCategory.AnimatorCount, "Animators"},
|
||||
{AvatarPerformanceCategory.BoneCount, "Bones"},
|
||||
{AvatarPerformanceCategory.LightCount, "Lights"},
|
||||
{AvatarPerformanceCategory.ParticleSystemCount, "Particle Systems"},
|
||||
{AvatarPerformanceCategory.ParticleTotalCount, "Total Max Particles"},
|
||||
{AvatarPerformanceCategory.ParticleMaxMeshPolyCount, "Mesh Particle Max Polygons"},
|
||||
{AvatarPerformanceCategory.ParticleTrailsEnabled, "Particle Trails Enabled"},
|
||||
{AvatarPerformanceCategory.ParticleCollisionEnabled, "Particle Collision Enabled"},
|
||||
{AvatarPerformanceCategory.TrailRendererCount, "Trail Renderers"},
|
||||
{AvatarPerformanceCategory.LineRendererCount, "Line Renderers"},
|
||||
{AvatarPerformanceCategory.DynamicBoneComponentCount, "Dynamic Bone Components"},
|
||||
{AvatarPerformanceCategory.DynamicBoneSimulatedBoneCount, "Dynamic Bone Transforms"},
|
||||
{AvatarPerformanceCategory.DynamicBoneColliderCount, "Dynamic Bone Colliders"},
|
||||
{AvatarPerformanceCategory.DynamicBoneCollisionCheckCount, "Dynamic Bone Collision Check Count"},
|
||||
{AvatarPerformanceCategory.ClothCount, "Cloths"},
|
||||
{AvatarPerformanceCategory.ClothMaxVertices, "Total Cloth Vertices"},
|
||||
{AvatarPerformanceCategory.PhysicsColliderCount, "Physics Colliders"},
|
||||
{AvatarPerformanceCategory.PhysicsRigidbodyCount, "Physics Rigidbodies"},
|
||||
{AvatarPerformanceCategory.AudioSourceCount, "Audio Sources"},
|
||||
{AvatarPerformanceCategory.DownloadSize, "Download Size"},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<PerformanceRating, string> _performanceRatingDisplayNames = new Dictionary<PerformanceRating, string>
|
||||
{
|
||||
{PerformanceRating.None, "None"},
|
||||
{PerformanceRating.Excellent, "Excellent"},
|
||||
{PerformanceRating.Good, "Good"},
|
||||
{PerformanceRating.Medium, "Medium"},
|
||||
{PerformanceRating.Poor, "Poor"},
|
||||
{PerformanceRating.VeryPoor, "VeryPoor"}
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
private static AvatarPerformanceStatsLevelSet _performanceStatsLevelSet = null;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
public static void Initialize()
|
||||
{
|
||||
if(_performanceStatsLevelSet != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_performanceStatsLevelSet = Resources.Load<AvatarPerformanceStatsLevelSet>(GetPlatformPerformanceStatLevels());
|
||||
}
|
||||
|
||||
private static string GetPlatformPerformanceStatLevels()
|
||||
{
|
||||
#if UNITY_ANDROID
|
||||
return "Validation/Performance/StatsLevels/Quest/AvatarPerformanceStatLevels_Quest";
|
||||
#else
|
||||
return "Validation/Performance/StatsLevels/Windows/AvatarPerformanceStatLevels_Windows";
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public AvatarPerformanceStats()
|
||||
{
|
||||
_performanceRatingCache = new PerformanceRating[(int)AvatarPerformanceCategory.AvatarPerformanceCategoryCount];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
avatarName = null;
|
||||
polyCount = null;
|
||||
aabb = null;
|
||||
skinnedMeshCount = null;
|
||||
meshCount = null;
|
||||
materialCount = null;
|
||||
animatorCount = null;
|
||||
boneCount = null;
|
||||
lightCount = null;
|
||||
particleSystemCount = null;
|
||||
particleTotalCount = null;
|
||||
particleMaxMeshPolyCount = null;
|
||||
particleTrailsEnabled = null;
|
||||
particleCollisionEnabled = null;
|
||||
trailRendererCount = null;
|
||||
lineRendererCount = null;
|
||||
dynamicBoneComponentCount = null;
|
||||
dynamicBoneSimulatedBoneCount = null;
|
||||
dynamicBoneColliderCount = null;
|
||||
dynamicBoneCollisionCheckCount = null;
|
||||
clothCount = null;
|
||||
clothMaxVertices = null;
|
||||
physicsColliderCount = null;
|
||||
physicsRigidbodyCount = null;
|
||||
audioSourceCount = null;
|
||||
downloadSize = null;
|
||||
|
||||
for(int i = 0; i < (int)AvatarPerformanceCategory.AvatarPerformanceCategoryCount; i++)
|
||||
{
|
||||
_performanceRatingCache[i] = PerformanceRating.None;
|
||||
}
|
||||
}
|
||||
|
||||
public PerformanceRating GetPerformanceRatingForCategory(AvatarPerformanceCategory perfCategory)
|
||||
{
|
||||
if(_performanceRatingCache[(int)perfCategory] == PerformanceRating.None)
|
||||
{
|
||||
_performanceRatingCache[(int)perfCategory] = CalculatePerformanceRatingForCategory(perfCategory);
|
||||
}
|
||||
|
||||
return _performanceRatingCache[(int)perfCategory];
|
||||
}
|
||||
|
||||
public void CalculateAllPerformanceRatings()
|
||||
{
|
||||
for(int i = 0; i < _performanceRatingCache.Length; i++)
|
||||
{
|
||||
_performanceRatingCache[i] = PerformanceRating.None;
|
||||
}
|
||||
|
||||
foreach(AvatarPerformanceCategory perfCategory in Enum.GetValues(typeof(AvatarPerformanceCategory)))
|
||||
{
|
||||
if(perfCategory == AvatarPerformanceCategory.None ||
|
||||
perfCategory == AvatarPerformanceCategory.AvatarPerformanceCategoryCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(_performanceRatingCache[(int)perfCategory] == PerformanceRating.None)
|
||||
{
|
||||
_performanceRatingCache[(int)perfCategory] = CalculatePerformanceRatingForCategory(perfCategory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetPerformanceCategoryDisplayName(AvatarPerformanceCategory category)
|
||||
{
|
||||
return _performanceCategoryDisplayNames[category];
|
||||
}
|
||||
|
||||
public static string GetPerformanceRatingDisplayName(PerformanceRating rating)
|
||||
{
|
||||
return _performanceRatingDisplayNames[rating];
|
||||
}
|
||||
|
||||
public static AvatarPerformanceStatsLevel GetStatLevelForRating(PerformanceRating rating)
|
||||
{
|
||||
switch(rating)
|
||||
{
|
||||
case PerformanceRating.None:
|
||||
return _performanceStatsLevelSet.excellent;
|
||||
|
||||
case PerformanceRating.Excellent:
|
||||
return _performanceStatsLevelSet.excellent;
|
||||
|
||||
case PerformanceRating.Good:
|
||||
return _performanceStatsLevelSet.good;
|
||||
|
||||
case PerformanceRating.Medium:
|
||||
return _performanceStatsLevelSet.medium;
|
||||
|
||||
case PerformanceRating.Poor:
|
||||
return _performanceStatsLevelSet.poor;
|
||||
|
||||
case PerformanceRating.VeryPoor:
|
||||
return _performanceStatsLevelSet.poor;
|
||||
|
||||
default:
|
||||
return _performanceStatsLevelSet.excellent;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private PerformanceRating CalculatePerformanceRatingForCategory(AvatarPerformanceCategory perfCategory)
|
||||
{
|
||||
switch(perfCategory)
|
||||
{
|
||||
case AvatarPerformanceCategory.Overall:
|
||||
{
|
||||
PerformanceRating maxRating = PerformanceRating.None;
|
||||
|
||||
foreach(AvatarPerformanceCategory category in Enum.GetValues(typeof(AvatarPerformanceCategory)))
|
||||
{
|
||||
if(category == AvatarPerformanceCategory.None ||
|
||||
category == AvatarPerformanceCategory.Overall ||
|
||||
category == AvatarPerformanceCategory.AvatarPerformanceCategoryCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PerformanceRating rating = GetPerformanceRatingForCategory(category);
|
||||
if(rating > maxRating)
|
||||
{
|
||||
maxRating = rating;
|
||||
}
|
||||
}
|
||||
|
||||
return maxRating;
|
||||
}
|
||||
case AvatarPerformanceCategory.PolyCount:
|
||||
{
|
||||
if(!polyCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.polyCount.GetValueOrDefault() - y.polyCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.AABB:
|
||||
{
|
||||
if(!aabb.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating(
|
||||
(x, y) =>
|
||||
ApproxLessOrEqual(y.aabb.extents.x, 0.0f) || // -1 extents means "no AABB limit"
|
||||
(
|
||||
ApproxLessOrEqual(x.aabb.GetValueOrDefault().extents.x, y.aabb.extents.x) &&
|
||||
ApproxLessOrEqual(x.aabb.GetValueOrDefault().extents.y, y.aabb.extents.y) &&
|
||||
ApproxLessOrEqual(x.aabb.GetValueOrDefault().extents.z, y.aabb.extents.z))
|
||||
? -1
|
||||
: 1
|
||||
);
|
||||
}
|
||||
case AvatarPerformanceCategory.SkinnedMeshCount:
|
||||
{
|
||||
if(!skinnedMeshCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.skinnedMeshCount.GetValueOrDefault() - y.skinnedMeshCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.MeshCount:
|
||||
{
|
||||
if(!meshCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.meshCount.GetValueOrDefault() - y.meshCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.MaterialCount:
|
||||
{
|
||||
if(!materialCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.materialCount.GetValueOrDefault() - y.materialCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.AnimatorCount:
|
||||
{
|
||||
if(!animatorCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.animatorCount.GetValueOrDefault() - y.animatorCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.BoneCount:
|
||||
{
|
||||
if(!boneCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.boneCount.GetValueOrDefault() - y.boneCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.LightCount:
|
||||
{
|
||||
if(!lightCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.lightCount.GetValueOrDefault() - y.lightCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.ParticleSystemCount:
|
||||
{
|
||||
if(!particleSystemCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.particleSystemCount.GetValueOrDefault() - y.particleSystemCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.ParticleTotalCount:
|
||||
{
|
||||
if(!particleTotalCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.particleTotalCount.GetValueOrDefault() - y.particleTotalCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.ParticleMaxMeshPolyCount:
|
||||
{
|
||||
if(!particleMaxMeshPolyCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.particleMaxMeshPolyCount.GetValueOrDefault() - y.particleMaxMeshPolyCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.ParticleTrailsEnabled:
|
||||
{
|
||||
if(!particleTrailsEnabled.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating(
|
||||
(x, y) =>
|
||||
{
|
||||
if(x.particleTrailsEnabled == y.particleTrailsEnabled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return x.particleTrailsEnabled.GetValueOrDefault() ? 1 : -1;
|
||||
});
|
||||
}
|
||||
case AvatarPerformanceCategory.ParticleCollisionEnabled:
|
||||
{
|
||||
if(!particleCollisionEnabled.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating(
|
||||
(x, y) =>
|
||||
{
|
||||
if(x.particleCollisionEnabled == y.particleCollisionEnabled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return x.particleCollisionEnabled.GetValueOrDefault() ? 1 : -1;
|
||||
});
|
||||
}
|
||||
case AvatarPerformanceCategory.TrailRendererCount:
|
||||
{
|
||||
if(!trailRendererCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.trailRendererCount.GetValueOrDefault() - y.trailRendererCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.LineRendererCount:
|
||||
{
|
||||
if(!lineRendererCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.lineRendererCount.GetValueOrDefault() - y.lineRendererCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.DynamicBoneComponentCount:
|
||||
{
|
||||
if(!dynamicBoneComponentCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.dynamicBoneComponentCount.GetValueOrDefault() - y.dynamicBoneComponentCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.DynamicBoneSimulatedBoneCount:
|
||||
{
|
||||
if(!dynamicBoneSimulatedBoneCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.dynamicBoneSimulatedBoneCount.GetValueOrDefault() - y.dynamicBoneSimulatedBoneCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.DynamicBoneColliderCount:
|
||||
{
|
||||
if(!dynamicBoneColliderCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.dynamicBoneColliderCount.GetValueOrDefault() - y.dynamicBoneColliderCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.DynamicBoneCollisionCheckCount:
|
||||
{
|
||||
if(!dynamicBoneCollisionCheckCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.dynamicBoneCollisionCheckCount.GetValueOrDefault() - y.dynamicBoneCollisionCheckCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.ClothCount:
|
||||
{
|
||||
if(!clothCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
return CalculatePerformanceRating((x, y) => x.clothCount.GetValueOrDefault() - y.clothCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.ClothMaxVertices:
|
||||
{
|
||||
if(!clothMaxVertices.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
return CalculatePerformanceRating((x, y) => x.clothMaxVertices.GetValueOrDefault() - y.clothMaxVertices);
|
||||
}
|
||||
case AvatarPerformanceCategory.PhysicsColliderCount:
|
||||
{
|
||||
if(!physicsColliderCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.physicsColliderCount.GetValueOrDefault() - y.physicsColliderCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.PhysicsRigidbodyCount:
|
||||
{
|
||||
if(!physicsRigidbodyCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.physicsRigidbodyCount.GetValueOrDefault() - y.physicsRigidbodyCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.AudioSourceCount:
|
||||
{
|
||||
if(!audioSourceCount.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return CalculatePerformanceRating((x, y) => x.audioSourceCount.GetValueOrDefault() - y.audioSourceCount);
|
||||
}
|
||||
case AvatarPerformanceCategory.DownloadSize:
|
||||
{
|
||||
if(!downloadSize.HasValue)
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
|
||||
return PerformanceRating.Excellent;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return PerformanceRating.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PerformanceRating CalculatePerformanceRating(ComparePerformanceStatsDelegate compareFn)
|
||||
{
|
||||
if(compareFn(this, _performanceStatsLevelSet.excellent) <= 0)
|
||||
{
|
||||
return PerformanceRating.Excellent;
|
||||
}
|
||||
|
||||
if(compareFn(this, _performanceStatsLevelSet.good) <= 0)
|
||||
{
|
||||
return PerformanceRating.Good;
|
||||
}
|
||||
|
||||
if(compareFn(this, _performanceStatsLevelSet.medium) <= 0)
|
||||
{
|
||||
return PerformanceRating.Medium;
|
||||
}
|
||||
|
||||
if(compareFn(this, _performanceStatsLevelSet.poor) <= 0)
|
||||
{
|
||||
return PerformanceRating.Poor;
|
||||
}
|
||||
|
||||
return PerformanceRating.VeryPoor;
|
||||
}
|
||||
|
||||
private static bool ApproxLessOrEqual(float x1, float x2)
|
||||
{
|
||||
float r = x1 - x2;
|
||||
return r < 0.0f || Mathf.Approximately(r, 0.0f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
sb.AppendFormat("Avatar Name: {0}\n", avatarName);
|
||||
sb.AppendFormat("Overall Performance: {0}\n", GetPerformanceRatingForCategory(AvatarPerformanceCategory.Overall));
|
||||
sb.AppendFormat("Poly Count: {0}\n", polyCount);
|
||||
sb.AppendFormat("Bounds: {0}\n", aabb.ToString());
|
||||
sb.AppendFormat("Skinned Mesh Count: {0}\n", skinnedMeshCount);
|
||||
sb.AppendFormat("Mesh Count: {0}\n", meshCount);
|
||||
sb.AppendFormat("Material Count: {0}\n", materialCount);
|
||||
sb.AppendFormat("Animator Count: {0}\n", animatorCount);
|
||||
sb.AppendFormat("Bone Count: {0}\n", boneCount);
|
||||
sb.AppendFormat("Light Count: {0}\n", lightCount);
|
||||
sb.AppendFormat("Particle System Count: {0}\n", particleSystemCount);
|
||||
sb.AppendFormat("Particle Total Count: {0}\n", particleTotalCount);
|
||||
sb.AppendFormat("Particle Max Mesh Poly Count: {0}\n", particleMaxMeshPolyCount);
|
||||
sb.AppendFormat("Particle Trails Enabled: {0}\n", particleTrailsEnabled);
|
||||
sb.AppendFormat("Particle Collision Enabled: {0}\n", particleCollisionEnabled);
|
||||
sb.AppendFormat("Trail Renderer Count: {0}\n", trailRendererCount);
|
||||
sb.AppendFormat("Line Renderer Count: {0}\n", lineRendererCount);
|
||||
sb.AppendFormat("Dynamic Bone Component Count: {0}\n", dynamicBoneComponentCount);
|
||||
sb.AppendFormat("Dynamic Bone Simulated Bone Count: {0}\n", dynamicBoneSimulatedBoneCount);
|
||||
sb.AppendFormat("Dynamic Bone Collider Count: {0}\n", dynamicBoneColliderCount);
|
||||
sb.AppendFormat("Dynamic Bone Collision Check Count: {0}\n", dynamicBoneCollisionCheckCount);
|
||||
sb.AppendFormat("Cloth Count: {0}\n", clothCount);
|
||||
sb.AppendFormat("Cloth Max Vertices: {0}\n", clothMaxVertices);
|
||||
sb.AppendFormat("Physics Collider Count: {0}\n", physicsColliderCount);
|
||||
sb.AppendFormat("Physics Rigidbody Count: {0}\n", physicsRigidbodyCount);
|
||||
if(downloadSize > 0)
|
||||
{
|
||||
sb.AppendFormat("Download Size: {0} MB\n", downloadSize);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bf4fb79a49d4b109c4dce6b38e5548e
|
||||
timeCreated: 1561267926
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Stats
|
||||
{
|
||||
public class AvatarPerformanceStatsLevel : ScriptableObject
|
||||
{
|
||||
public int polyCount;
|
||||
public Bounds aabb;
|
||||
public int skinnedMeshCount;
|
||||
public int meshCount;
|
||||
public int materialCount;
|
||||
public int animatorCount;
|
||||
public int boneCount;
|
||||
public int lightCount;
|
||||
public int particleSystemCount;
|
||||
public int particleTotalCount;
|
||||
public int particleMaxMeshPolyCount;
|
||||
public bool particleTrailsEnabled;
|
||||
public bool particleCollisionEnabled;
|
||||
public int trailRendererCount;
|
||||
public int lineRendererCount;
|
||||
public int dynamicBoneComponentCount;
|
||||
public int dynamicBoneSimulatedBoneCount;
|
||||
public int dynamicBoneColliderCount;
|
||||
public int dynamicBoneCollisionCheckCount;
|
||||
public int clothCount;
|
||||
public int clothMaxVertices;
|
||||
public int physicsColliderCount;
|
||||
public int physicsRigidbodyCount;
|
||||
public int audioSourceCount;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f742c36dce5730f4d96e37d82c330584
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace VRCSDK2.Validation.Performance.Stats
|
||||
{
|
||||
public class AvatarPerformanceStatsLevelSet : ScriptableObject
|
||||
{
|
||||
[FormerlySerializedAs("veryGood")]
|
||||
public AvatarPerformanceStatsLevel excellent;
|
||||
public AvatarPerformanceStatsLevel good;
|
||||
public AvatarPerformanceStatsLevel medium;
|
||||
[FormerlySerializedAs("bad")]
|
||||
public AvatarPerformanceStatsLevel poor;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 468554b1bfc447f41a20a2f5bae65d16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRCSDK2.Validation
|
||||
{
|
||||
public static class ShaderValidation
|
||||
{
|
||||
public static IEnumerable<Shader> FindIllegalShaders(GameObject target, string[] whitelist)
|
||||
{
|
||||
List<Shader> illegalShaders = new List<Shader>();
|
||||
IEnumerator seeker = FindIllegalShadersEnumerator(target, whitelist, (c) => illegalShaders.Add(c));
|
||||
while(seeker.MoveNext())
|
||||
{
|
||||
}
|
||||
|
||||
return illegalShaders;
|
||||
}
|
||||
|
||||
private static IEnumerator FindIllegalShadersEnumerator(GameObject target, string[] whitelist, System.Action<Shader> onFound, bool useWatch = false)
|
||||
{
|
||||
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
|
||||
if(useWatch)
|
||||
{
|
||||
watch.Start();
|
||||
}
|
||||
|
||||
List<Material> materialCache = new List<Material>();
|
||||
Queue<GameObject> children = new Queue<GameObject>();
|
||||
children.Enqueue(target.gameObject);
|
||||
while(children.Count > 0)
|
||||
{
|
||||
GameObject child = children.Dequeue();
|
||||
if(child == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for(int idx = 0; idx < child.transform.childCount; ++idx)
|
||||
{
|
||||
children.Enqueue(child.transform.GetChild(idx).gameObject);
|
||||
}
|
||||
|
||||
foreach(Renderer childRenderers in child.transform.GetComponents<Renderer>())
|
||||
{
|
||||
if(childRenderers == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach(Material sharedMaterial in childRenderers.sharedMaterials)
|
||||
{
|
||||
if(materialCache.Any(cacheMtl => sharedMaterial == cacheMtl)) // did we already look at this one?
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty material slots, or materials without shaders.
|
||||
// Both will end up using the magenta error shader.
|
||||
if(sharedMaterial == null || sharedMaterial.shader == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(whitelist.All(okayShaderName => sharedMaterial.shader.name != okayShaderName))
|
||||
{
|
||||
onFound(sharedMaterial.shader);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
materialCache.Add(sharedMaterial);
|
||||
}
|
||||
|
||||
if(!useWatch || watch.ElapsedMilliseconds <= 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
watch.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bef0a8d1d2c547119a62b7d7a5c512ea
|
||||
timeCreated: 1563940360
|
||||
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
|
||||
namespace VRCSDK2.Validation
|
||||
{
|
||||
public static class ValidationUtils
|
||||
{
|
||||
public static void RemoveIllegalComponents(GameObject target, HashSet<Type> whitelist, bool retry = true, bool onlySceneObjects = false, bool logStripping = true)
|
||||
{
|
||||
IEnumerable<Component> foundComponents = FindIllegalComponents(target, whitelist);
|
||||
foreach(Component component in foundComponents)
|
||||
{
|
||||
if(component == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (onlySceneObjects && component.GetInstanceID() < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (logStripping)
|
||||
{
|
||||
VRC.Core.Logger.LogWarning(string.Format("Removing {0} comp from {1}", component.GetType().Name, component.gameObject.name));
|
||||
}
|
||||
|
||||
RemoveComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Component> FindIllegalComponents(GameObject target, HashSet<Type> whitelist)
|
||||
{
|
||||
List<Component> foundComponents = new List<Component>();
|
||||
Component[] allComponents = target.GetComponentsInChildren<Component>(true);
|
||||
foreach(Component component in allComponents)
|
||||
{
|
||||
if(component == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Type componentType = component.GetType();
|
||||
if(whitelist.Contains(componentType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foundComponents.Add(component);
|
||||
}
|
||||
|
||||
return foundComponents;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, Type> _typeCache = new Dictionary<string, Type>();
|
||||
private static readonly Dictionary<string, HashSet<Type>> _whitelistCache = new Dictionary<string, HashSet<Type>>();
|
||||
public static HashSet<Type> WhitelistedTypes(string whitelistName, IEnumerable<string> componentTypeWhitelist)
|
||||
{
|
||||
if (_whitelistCache.ContainsKey(whitelistName))
|
||||
{
|
||||
return _whitelistCache[whitelistName];
|
||||
}
|
||||
|
||||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
HashSet<Type> whitelist = new HashSet<Type>();
|
||||
foreach(string whitelistedTypeName in componentTypeWhitelist)
|
||||
{
|
||||
Type whitelistedType = GetTypeFromName(whitelistedTypeName, assemblies);
|
||||
if(whitelistedType == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(whitelist.Contains(whitelistedType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
whitelist.Add(whitelistedType);
|
||||
}
|
||||
|
||||
AddDerivedClasses(whitelist);
|
||||
|
||||
_whitelistCache[whitelistName] = whitelist;
|
||||
|
||||
return _whitelistCache[whitelistName];
|
||||
}
|
||||
|
||||
public static HashSet<Type> WhitelistedTypes(string whitelistName, IEnumerable<Type> componentTypeWhitelist)
|
||||
{
|
||||
if (_whitelistCache.ContainsKey(whitelistName))
|
||||
{
|
||||
return _whitelistCache[whitelistName];
|
||||
}
|
||||
|
||||
HashSet<Type> whitelist = new HashSet<Type>();
|
||||
whitelist.UnionWith(componentTypeWhitelist);
|
||||
|
||||
AddDerivedClasses(whitelist);
|
||||
|
||||
_whitelistCache[whitelistName] = whitelist;
|
||||
|
||||
return _whitelistCache[whitelistName];
|
||||
}
|
||||
|
||||
private static void AddDerivedClasses(HashSet<Type> whitelist)
|
||||
{
|
||||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach(Assembly assembly in assemblies)
|
||||
{
|
||||
foreach(Type type in assembly.GetTypes())
|
||||
{
|
||||
if(whitelist.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Type currentType = type;
|
||||
while(currentType != typeof(object) && currentType != null)
|
||||
{
|
||||
if(whitelist.Contains(currentType))
|
||||
{
|
||||
whitelist.Add(type);
|
||||
break;
|
||||
}
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetTypeFromName(string name, Assembly[] assemblies = null)
|
||||
{
|
||||
if (_typeCache.ContainsKey(name))
|
||||
{
|
||||
|
||||
return _typeCache[name];
|
||||
}
|
||||
|
||||
if (assemblies == null)
|
||||
{
|
||||
assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
}
|
||||
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
Type found = assembly.GetType(name);
|
||||
if(found == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_typeCache[name] = found;
|
||||
return found;
|
||||
}
|
||||
|
||||
//This is really verbose for some SDK scenes, eg.
|
||||
//If they don't have FinalIK installed
|
||||
#if VRC_CLIENT && UNITY_EDITOR
|
||||
Debug.LogWarningFormat("Could not find type {0}", name);
|
||||
#endif
|
||||
|
||||
_typeCache[name] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void RemoveDependencies(Component component)
|
||||
{
|
||||
if (component == null)
|
||||
return;
|
||||
|
||||
Component[] components = component.GetComponents<Component>();
|
||||
if (components == null || components.Length == 0)
|
||||
return;
|
||||
|
||||
System.Type compType = component.GetType();
|
||||
foreach (var c in components)
|
||||
{
|
||||
if (c == null)
|
||||
continue;
|
||||
|
||||
bool deleteMe = false;
|
||||
object[] requires = c.GetType().GetCustomAttributes(typeof(RequireComponent), true);
|
||||
if (requires == null || requires.Length == 0)
|
||||
continue;
|
||||
|
||||
foreach (var r in requires)
|
||||
{
|
||||
RequireComponent rc = r as RequireComponent;
|
||||
if (rc == null)
|
||||
continue;
|
||||
|
||||
if (rc.m_Type0 == compType ||
|
||||
rc.m_Type1 == compType ||
|
||||
rc.m_Type2 == compType)
|
||||
{
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteMe)
|
||||
RemoveComponent(c);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveComponent(Component comp)
|
||||
{
|
||||
if (comp == null)
|
||||
return;
|
||||
|
||||
RemoveDependencies(comp);
|
||||
|
||||
#if VRC_CLIENT
|
||||
UnityEngine.Object.DestroyImmediate(comp, true);
|
||||
#else
|
||||
UnityEngine.Object.DestroyImmediate(comp, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void RemoveComponentsOfType<T>(GameObject target) where T : Component
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
foreach (T comp in target.GetComponentsInChildren<T>(true))
|
||||
{
|
||||
if (comp == null || comp.gameObject == null)
|
||||
continue;
|
||||
|
||||
RemoveComponent(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a90ec11b51863c4cb2d8a8cee31c2fb
|
||||
timeCreated: 1504829091
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,612 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace VRCSDK2.Validation
|
||||
{
|
||||
public static class WorldValidation
|
||||
{
|
||||
private static readonly Lazy<int> _debugLevel = new Lazy<int>(InitializeLogging);
|
||||
private static int DebugLevel => _debugLevel.Value;
|
||||
private static int InitializeLogging()
|
||||
{
|
||||
int hashCode = typeof(WorldValidation).GetHashCode();
|
||||
VRC.Core.Logger.DescribeDebugLevel(hashCode, "WorldValidation", VRC.Core.Logger.Color.red);
|
||||
VRC.Core.Logger.AddDebugLevel(hashCode);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
static string[] ComponentTypeWhiteList = null;
|
||||
public enum WhiteListConfiguration
|
||||
{
|
||||
None,
|
||||
VRCSDK2,
|
||||
VRCSDK3,
|
||||
Unchanged
|
||||
}
|
||||
static WhiteListConfiguration ComponentTypeWhiteListConfiguration = WhiteListConfiguration.None;
|
||||
|
||||
static readonly string[] ComponentTypeWhiteListCommon = new string[]
|
||||
{
|
||||
#if UNITY_STANDALONE
|
||||
"UnityEngine.Rendering.PostProcessing.PostProcessDebug",
|
||||
"UnityEngine.Rendering.PostProcessing.PostProcessLayer",
|
||||
"UnityEngine.Rendering.PostProcessing.PostProcessVolume",
|
||||
#endif
|
||||
"VRC.Core.PipelineManager",
|
||||
"UiInputField",
|
||||
"VRCProjectSettings",
|
||||
"DynamicBone",
|
||||
"DynamicBoneCollider",
|
||||
"TMPro.TMP_Dropdown",
|
||||
"TMPro.TMP_InputField",
|
||||
"TMPro.TMP_ScrollbarEventHandler",
|
||||
"TMPro.TMP_SelectionCaret",
|
||||
"TMPro.TMP_SpriteAnimator",
|
||||
"TMPro.TMP_SubMesh",
|
||||
"TMPro.TMP_SubMeshUI",
|
||||
"TMPro.TMP_Text",
|
||||
"TMPro.TextMeshPro",
|
||||
"TMPro.TextMeshProUGUI",
|
||||
"TMPro.TextContainer",
|
||||
"TMPro.TMP_Dropdown+DropdownItem",
|
||||
"UnityEngine.EventSystems.EventSystem",
|
||||
"UnityEngine.EventSystems.EventTrigger",
|
||||
"UnityEngine.EventSystems.UIBehaviour",
|
||||
"UnityEngine.EventSystems.BaseInput",
|
||||
"UnityEngine.EventSystems.BaseInputModule",
|
||||
"UnityEngine.EventSystems.PointerInputModule",
|
||||
"UnityEngine.EventSystems.StandaloneInputModule",
|
||||
"UnityEngine.EventSystems.TouchInputModule",
|
||||
"UnityEngine.EventSystems.BaseRaycaster",
|
||||
"UnityEngine.EventSystems.PhysicsRaycaster",
|
||||
"UnityEngine.UI.Button",
|
||||
"UnityEngine.UI.Dropdown",
|
||||
"UnityEngine.UI.Dropdown+DropdownItem",
|
||||
"UnityEngine.UI.Graphic",
|
||||
"UnityEngine.UI.GraphicRaycaster",
|
||||
"UnityEngine.UI.Image",
|
||||
"UnityEngine.UI.InputField",
|
||||
"UnityEngine.UI.Mask",
|
||||
"UnityEngine.UI.MaskableGraphic",
|
||||
"UnityEngine.UI.RawImage",
|
||||
"UnityEngine.UI.RectMask2D",
|
||||
"UnityEngine.UI.Scrollbar",
|
||||
"UnityEngine.UI.ScrollRect",
|
||||
"UnityEngine.UI.Selectable",
|
||||
"UnityEngine.UI.Slider",
|
||||
"UnityEngine.UI.Text",
|
||||
"UnityEngine.UI.Toggle",
|
||||
"UnityEngine.UI.ToggleGroup",
|
||||
"UnityEngine.UI.AspectRatioFitter",
|
||||
"UnityEngine.UI.CanvasScaler",
|
||||
"UnityEngine.UI.ContentSizeFitter",
|
||||
"UnityEngine.UI.GridLayoutGroup",
|
||||
"UnityEngine.UI.HorizontalLayoutGroup",
|
||||
"UnityEngine.UI.HorizontalOrVerticalLayoutGroup",
|
||||
"UnityEngine.UI.LayoutElement",
|
||||
"UnityEngine.UI.LayoutGroup",
|
||||
"UnityEngine.UI.VerticalLayoutGroup",
|
||||
"UnityEngine.UI.BaseMeshEffect",
|
||||
"UnityEngine.UI.Outline",
|
||||
"UnityEngine.UI.PositionAsUV1",
|
||||
"UnityEngine.UI.Shadow",
|
||||
"OVRLipSync",
|
||||
"OVRLipSyncContext",
|
||||
"OVRLipSyncContextBase",
|
||||
"OVRLipSyncContextCanned",
|
||||
"OVRLipSyncContextMorphTarget",
|
||||
"OVRLipSyncContextTextureFlip",
|
||||
"ONSPReflectionZone",
|
||||
"OculusSpatializerUnity",
|
||||
"ONSPAmbisonicsNative",
|
||||
"ONSPAudioSource",
|
||||
"RootMotion.FinalIK.BipedIK",
|
||||
"RootMotion.FinalIK.FingerRig",
|
||||
"RootMotion.FinalIK.Grounder",
|
||||
"RootMotion.FinalIK.GrounderBipedIK",
|
||||
"RootMotion.FinalIK.GrounderFBBIK",
|
||||
"RootMotion.FinalIK.GrounderIK",
|
||||
"RootMotion.FinalIK.GrounderQuadruped",
|
||||
"RootMotion.FinalIK.GrounderVRIK",
|
||||
"RootMotion.FinalIK.AimIK",
|
||||
"RootMotion.FinalIK.CCDIK",
|
||||
"RootMotion.FinalIK.FABRIK",
|
||||
"RootMotion.FinalIK.FABRIKRoot",
|
||||
"RootMotion.FinalIK.FullBodyBipedIK",
|
||||
"RootMotion.FinalIK.IK",
|
||||
"RootMotion.FinalIK.IKExecutionOrder",
|
||||
"RootMotion.FinalIK.LegIK",
|
||||
"RootMotion.FinalIK.LimbIK",
|
||||
"RootMotion.FinalIK.LookAtIK",
|
||||
"RootMotion.FinalIK.TrigonometricIK",
|
||||
"RootMotion.FinalIK.VRIK",
|
||||
"RootMotion.FinalIK.FBBIKArmBending",
|
||||
"RootMotion.FinalIK.FBBIKHeadEffector",
|
||||
"RootMotion.FinalIK.TwistRelaxer",
|
||||
"RootMotion.FinalIK.InteractionObject",
|
||||
"RootMotion.FinalIK.InteractionSystem",
|
||||
"RootMotion.FinalIK.InteractionTarget",
|
||||
"RootMotion.FinalIK.InteractionTrigger",
|
||||
"RootMotion.FinalIK.GenericPoser",
|
||||
"RootMotion.FinalIK.HandPoser",
|
||||
"RootMotion.FinalIK.Poser",
|
||||
"RootMotion.FinalIK.RagdollUtility",
|
||||
"RootMotion.FinalIK.RotationLimit",
|
||||
"RootMotion.FinalIK.RotationLimitAngle",
|
||||
"RootMotion.FinalIK.RotationLimitHinge",
|
||||
"RootMotion.FinalIK.RotationLimitPolygonal",
|
||||
"RootMotion.FinalIK.RotationLimitSpline",
|
||||
"RootMotion.FinalIK.AimPoser",
|
||||
"RootMotion.FinalIK.Amplifier",
|
||||
"RootMotion.FinalIK.BodyTilt",
|
||||
"RootMotion.FinalIK.HitReaction",
|
||||
"RootMotion.FinalIK.HitReactionVRIK",
|
||||
"RootMotion.FinalIK.Inertia",
|
||||
"RootMotion.FinalIK.OffsetModifier",
|
||||
"RootMotion.FinalIK.OffsetModifierVRIK",
|
||||
"RootMotion.FinalIK.OffsetPose",
|
||||
"RootMotion.FinalIK.Recoil",
|
||||
"RootMotion.FinalIK.ShoulderRotator",
|
||||
"RootMotion.Dynamics.AnimationBlocker",
|
||||
"RootMotion.Dynamics.BehaviourBase",
|
||||
"RootMotion.Dynamics.BehaviourFall",
|
||||
"RootMotion.Dynamics.BehaviourPuppet",
|
||||
"RootMotion.Dynamics.JointBreakBroadcaster",
|
||||
"RootMotion.Dynamics.MuscleCollisionBroadcaster",
|
||||
"RootMotion.Dynamics.PressureSensor",
|
||||
"RootMotion.Dynamics.Prop",
|
||||
"RootMotion.Dynamics.PropRoot",
|
||||
"RootMotion.Dynamics.PuppetMaster",
|
||||
"RootMotion.Dynamics.PuppetMasterSettings",
|
||||
// TODO: remove these if they are only needed in editor
|
||||
"RootMotion.Dynamics.BipedRagdollCreator",
|
||||
"RootMotion.Dynamics.RagdollCreator",
|
||||
"RootMotion.Dynamics.RagdollEditor",
|
||||
//
|
||||
"RootMotion.SolverManager",
|
||||
"RootMotion.TriggerEventBroadcaster",
|
||||
"UnityEngine.WindZone",
|
||||
"UnityEngine.Tilemaps.Tilemap",
|
||||
"UnityEngine.Tilemaps.TilemapRenderer",
|
||||
"UnityEngine.Terrain",
|
||||
"UnityEngine.Tree",
|
||||
"UnityEngine.SpriteMask",
|
||||
"UnityEngine.Grid",
|
||||
"UnityEngine.GridLayout",
|
||||
"UnityEngine.AudioSource",
|
||||
"UnityEngine.AudioReverbZone",
|
||||
"UnityEngine.AudioLowPassFilter",
|
||||
"UnityEngine.AudioHighPassFilter",
|
||||
"UnityEngine.AudioDistortionFilter",
|
||||
"UnityEngine.AudioEchoFilter",
|
||||
"UnityEngine.AudioChorusFilter",
|
||||
"UnityEngine.AudioReverbFilter",
|
||||
"UnityEngine.Playables.PlayableDirector",
|
||||
"UnityEngine.TerrainCollider",
|
||||
"UnityEngine.Canvas",
|
||||
"UnityEngine.CanvasGroup",
|
||||
"UnityEngine.CanvasRenderer",
|
||||
"UnityEngine.TextMesh",
|
||||
"UnityEngine.Animator",
|
||||
"UnityEngine.AI.NavMeshAgent",
|
||||
"UnityEngine.AI.NavMeshObstacle",
|
||||
"UnityEngine.AI.OffMeshLink",
|
||||
"UnityEngine.Cloth",
|
||||
"UnityEngine.WheelCollider",
|
||||
"UnityEngine.Rigidbody",
|
||||
"UnityEngine.Joint",
|
||||
"UnityEngine.HingeJoint",
|
||||
"UnityEngine.SpringJoint",
|
||||
"UnityEngine.FixedJoint",
|
||||
"UnityEngine.CharacterJoint",
|
||||
"UnityEngine.ConfigurableJoint",
|
||||
"UnityEngine.ConstantForce",
|
||||
"UnityEngine.Collider",
|
||||
"UnityEngine.BoxCollider",
|
||||
"UnityEngine.SphereCollider",
|
||||
"UnityEngine.MeshCollider",
|
||||
"UnityEngine.CapsuleCollider",
|
||||
"UnityEngine.CharacterController",
|
||||
"UnityEngine.ParticleSystem",
|
||||
"UnityEngine.ParticleSystemRenderer",
|
||||
"UnityEngine.BillboardRenderer",
|
||||
"UnityEngine.Camera",
|
||||
"UnityEngine.FlareLayer",
|
||||
"UnityEngine.SkinnedMeshRenderer",
|
||||
"UnityEngine.Renderer",
|
||||
"UnityEngine.TrailRenderer",
|
||||
"UnityEngine.LineRenderer",
|
||||
"UnityEngine.GUIElement",
|
||||
"UnityEngine.GUILayer",
|
||||
"UnityEngine.Light",
|
||||
"UnityEngine.LightProbeGroup",
|
||||
"UnityEngine.LightProbeProxyVolume",
|
||||
"UnityEngine.LODGroup",
|
||||
"UnityEngine.ReflectionProbe",
|
||||
"UnityEngine.SpriteRenderer",
|
||||
"UnityEngine.Transform",
|
||||
"UnityEngine.RectTransform",
|
||||
"UnityEngine.Rendering.SortingGroup",
|
||||
"UnityEngine.Projector",
|
||||
"UnityEngine.OcclusionPortal",
|
||||
"UnityEngine.OcclusionArea",
|
||||
"UnityEngine.LensFlare",
|
||||
"UnityEngine.Skybox",
|
||||
"UnityEngine.MeshFilter",
|
||||
"UnityEngine.Halo",
|
||||
"UnityEngine.MeshRenderer",
|
||||
"UnityEngine.Collider2D",
|
||||
"UnityEngine.Rigidbody2D",
|
||||
"UnityEngine.CompositeCollider2D",
|
||||
"UnityEngine.ConstantForce2D",
|
||||
"UnityEngine.AreaEffector2D",
|
||||
"UnityEngine.CapsuleCollider2D",
|
||||
"UnityEngine.DistanceJoint2D",
|
||||
"UnityEngine.EdgeCollider2D",
|
||||
"UnityEngine.Effector2D",
|
||||
"UnityEngine.BoxCollider2D",
|
||||
"UnityEngine.CircleCollider2D",
|
||||
"UnityEngine.FixedJoint2D",
|
||||
"UnityEngine.HingeJoint2D",
|
||||
"UnityEngine.FrictionJoint2D",
|
||||
"UnityEngine.PlatformEffector2D",
|
||||
"UnityEngine.PointEffector2D",
|
||||
"UnityEngine.PolygonCollider2D",
|
||||
"UnityEngine.SliderJoint2D",
|
||||
"UnityEngine.SurfaceEffector2D",
|
||||
"UnityEngine.RelativeJoint2D",
|
||||
"UnityEngine.TargetJoint2D",
|
||||
"UnityEngine.WheelJoint2D",
|
||||
"UnityEngine.Joint2D",
|
||||
"UnityEngine.ParticleSystemForceField"
|
||||
};
|
||||
|
||||
static readonly string[] ComponentTypeWhiteListSdk2 = new string[]
|
||||
{
|
||||
#if UNITY_STANDALONE
|
||||
"VRCSDK2.VRC_CustomRendererBehaviour",
|
||||
"VRCSDK2.VRC_MidiNoteIn",
|
||||
"VRCSDK2.scripts.Scenes.VRC_Panorama",
|
||||
"VRCSDK2.VRC_Water",
|
||||
"UnityStandardAssets.Water.WaterBasic",
|
||||
"UnityStandardAssets.Water.Displace",
|
||||
"UnityStandardAssets.Water.GerstnerDisplace",
|
||||
"UnityStandardAssets.Water.PlanarReflection",
|
||||
"UnityStandardAssets.Water.SpecularLighting",
|
||||
"UnityStandardAssets.Water.Water",
|
||||
"UnityStandardAssets.Water.WaterBase",
|
||||
"UnityStandardAssets.Water.WaterTile",
|
||||
#endif
|
||||
"VRCSDK2.VRCTriggerRelay",
|
||||
"VRCSDK2.VRC_AudioBank",
|
||||
"VRCSDK2.VRC_DataStorage",
|
||||
"VRCSDK2.VRC_EventHandler",
|
||||
"VRCSDK2.VRC_IKFollower",
|
||||
"VRCSDK2.VRC_Label",
|
||||
"VRCSDK2.VRC_KeyEvents",
|
||||
"VRCSDK2.VRC_PhysicsRoot",
|
||||
"VRCSDK2.VRC_CombatSystem",
|
||||
"VRCSDK2.VRC_DestructibleStandard",
|
||||
"VRC_VisualDamage",
|
||||
"VRCSDK2.VRC_OscButtonIn",
|
||||
"VRCSDK2.VRC_GunStats",
|
||||
"VRCSDK2.VRC_JukeBox",
|
||||
"VRCSDK2.VRC_AddDamage",
|
||||
"VRCSDK2.VRC_AddHealth",
|
||||
"VRCSDK2.VRC_AvatarCalibrator",
|
||||
"VRCSDK2.VRC_AvatarPedestal",
|
||||
"VRCSDK2.VRC_NPCSpawn",
|
||||
"VRCSDK2.VRC_ObjectSpawn",
|
||||
"VRCSDK2.VRC_ObjectSync",
|
||||
"VRCSDK2.VRC_Pickup",
|
||||
"VRCSDK2.VRC_PortalMarker",
|
||||
"VRCSDK2.VRC_SlideShow",
|
||||
"VRCSDK2.VRC_SpatialAudioSource",
|
||||
"VRCSDK2.VRC_StationInput",
|
||||
"VRCSDK2.VRC_SyncAnimation",
|
||||
"VRCSDK2.VRC_SyncVideoPlayer",
|
||||
"VRCSDK2.VRC_SyncVideoStream",
|
||||
"VRCSDK2.VRC_VideoScreen",
|
||||
"VRCSDK2.VRC_VideoSpeaker",
|
||||
"VRCSDK2.VRC_PlayerAudioOverride",
|
||||
"VRCSDK2.VRC_MirrorReflection",
|
||||
"VRCSDK2.VRC_PlayerMods",
|
||||
"VRCSDK2.VRC_SceneDescriptor",
|
||||
"VRCSDK2.VRC_SceneResetPosition",
|
||||
"VRCSDK2.VRC_SceneSmoothShift",
|
||||
"VRCSDK2.VRC_SpecialLayer",
|
||||
"VRCSDK2.VRC_Station",
|
||||
"VRCSDK2.VRC_StereoObject",
|
||||
"VRCSDK2.VRC_TimedEvents",
|
||||
"VRCSDK2.VRC_Trigger",
|
||||
"VRCSDK2.VRC_TriggerColliderEventTrigger",
|
||||
"VRCSDK2.VRC_UseEvents",
|
||||
"VRCSDK2.VRC_UiShape",
|
||||
"UnityEngine.Animation",
|
||||
"UnityEngine.GUIText",
|
||||
"UnityEngine.GUITexture",
|
||||
"UnityEngine.Video.VideoPlayer",
|
||||
"PhysSound.PhysSoundBase",
|
||||
"PhysSound.PhysSoundObject",
|
||||
"PhysSound.PhysSoundTempAudio",
|
||||
"PhysSound.PhysSoundTempAudioPool",
|
||||
"PhysSound.PhysSoundTerrain",
|
||||
"RealisticEyeMovements.EyeAndHeadAnimator",
|
||||
"RealisticEyeMovements.LookTargetController",
|
||||
"UnityStandardAssets.Cameras.AbstractTargetFollower",
|
||||
"UnityStandardAssets.Cameras.AutoCam",
|
||||
"UnityStandardAssets.Cameras.FreeLookCam",
|
||||
"UnityStandardAssets.Cameras.HandHeldCam",
|
||||
"UnityStandardAssets.Cameras.LookatTarget",
|
||||
"UnityStandardAssets.Cameras.PivotBasedCameraRig",
|
||||
"UnityStandardAssets.Cameras.ProtectCameraFromWallClip",
|
||||
"UnityStandardAssets.Cameras.TargetFieldOfView",
|
||||
"UnityStandardAssets.Characters.FirstPerson.FirstPersonController",
|
||||
"UnityStandardAssets.Characters.FirstPerson.HeadBob",
|
||||
"UnityStandardAssets.Characters.FirstPerson.RigidbodyFirstPersonController",
|
||||
"UnityStandardAssets.Vehicles.Ball.Ball",
|
||||
"UnityStandardAssets.Vehicles.Ball.BallUserControl",
|
||||
"UnityStandardAssets.Characters.ThirdPerson.AICharacterControl",
|
||||
"UnityStandardAssets.Characters.ThirdPerson.ThirdPersonCharacter",
|
||||
"UnityStandardAssets.Characters.ThirdPerson.ThirdPersonUserControl",
|
||||
"UnityStandardAssets.CrossPlatformInput.AxisTouchButton",
|
||||
"UnityStandardAssets.CrossPlatformInput.ButtonHandler",
|
||||
"UnityStandardAssets.CrossPlatformInput.InputAxisScrollbar",
|
||||
"UnityStandardAssets.CrossPlatformInput.Joystick",
|
||||
"UnityStandardAssets.CrossPlatformInput.MobileControlRig",
|
||||
"UnityStandardAssets.CrossPlatformInput.TiltInput",
|
||||
"UnityStandardAssets.CrossPlatformInput.TouchPad",
|
||||
"UnityStandardAssets.Effects.AfterburnerPhysicsForce",
|
||||
"UnityStandardAssets.Effects.ExplosionFireAndDebris",
|
||||
"UnityStandardAssets.Effects.ExplosionPhysicsForce",
|
||||
"UnityStandardAssets.Effects.Explosive",
|
||||
"UnityStandardAssets.Effects.ExtinguishableParticleSystem",
|
||||
"UnityStandardAssets.Effects.FireLight",
|
||||
"UnityStandardAssets.Effects.Hose",
|
||||
"UnityStandardAssets.Effects.ParticleSystemMultiplier",
|
||||
"UnityStandardAssets.Effects.SmokeParticles",
|
||||
"UnityStandardAssets.Effects.WaterHoseParticles",
|
||||
"UnityStandardAssets.Utility.ActivateTrigger",
|
||||
"UnityStandardAssets.Utility.AutoMoveAndRotate",
|
||||
"UnityStandardAssets.Utility.DragRigidbody",
|
||||
"UnityStandardAssets.Utility.DynamicShadowSettings",
|
||||
"UnityStandardAssets.Utility.FollowTarget",
|
||||
"UnityStandardAssets.Utility.FPSCounter",
|
||||
"UnityStandardAssets.Utility.ObjectResetter",
|
||||
"UnityStandardAssets.Utility.ParticleSystemDestroyer",
|
||||
"UnityStandardAssets.Utility.SimpleActivatorMenu",
|
||||
"UnityStandardAssets.Utility.SimpleMouseRotator",
|
||||
"UnityStandardAssets.Utility.SmoothFollow",
|
||||
"UnityStandardAssets.Utility.TimedObjectActivator",
|
||||
"UnityStandardAssets.Utility.TimedObjectDestructor",
|
||||
"UnityStandardAssets.Utility.WaypointCircuit",
|
||||
"UnityStandardAssets.Utility.WaypointProgressTracker",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneAiControl",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneAudio",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneController",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneControlSurfaceAnimator",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.AeroplanePropellerAnimator",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneUserControl2Axis",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.AeroplaneUserControl4Axis",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.JetParticleEffect",
|
||||
"UnityStandardAssets.Vehicles.Aeroplane.LandingGear",
|
||||
"UnityStandardAssets.Vehicles.Car.BrakeLight",
|
||||
"UnityStandardAssets.Vehicles.Car.CarAIControl",
|
||||
"UnityStandardAssets.Vehicles.Car.CarAudio",
|
||||
"UnityStandardAssets.Vehicles.Car.CarController",
|
||||
"UnityStandardAssets.Vehicles.Car.CarSelfRighting",
|
||||
"UnityStandardAssets.Vehicles.Car.CarUserControl",
|
||||
"UnityStandardAssets.Vehicles.Car.Mudguard",
|
||||
"UnityStandardAssets.Vehicles.Car.SkidTrail",
|
||||
"UnityStandardAssets.Vehicles.Car.Suspension",
|
||||
"UnityStandardAssets.Vehicles.Car.WheelEffects",
|
||||
"RenderHeads.Media.AVProVideo.ApplyToMaterial",
|
||||
"RenderHeads.Media.AVProVideo.ApplyToMesh",
|
||||
"RenderHeads.Media.AVProVideo.AudioOutput",
|
||||
"RenderHeads.Media.AVProVideo.CubemapCube",
|
||||
"RenderHeads.Media.AVProVideo.DebugOverlay",
|
||||
"RenderHeads.Media.AVProVideo.DisplayBackground",
|
||||
"RenderHeads.Media.AVProVideo.DisplayIMGUI",
|
||||
"RenderHeads.Media.AVProVideo.DisplayUGUI",
|
||||
"RenderHeads.Media.AVProVideo.MediaPlayer",
|
||||
"RenderHeads.Media.AVProVideo.StreamParser",
|
||||
"RenderHeads.Media.AVProVideo.SubtitlesUGUI",
|
||||
"RenderHeads.Media.AVProVideo.UpdateStereoMaterial",
|
||||
"AlphaButtonClickMask",
|
||||
"EventSystemChecker",
|
||||
"VirtualMarketplaceItem",
|
||||
"SDK2UrlLauncher"
|
||||
};
|
||||
|
||||
static readonly string[] ComponentTypeWhiteListSdk3 = new string[]
|
||||
{
|
||||
"VRC.SDK3.VRCDestructibleStandard",
|
||||
"VRC.SDK3.Components.VRCVisualDamage",
|
||||
"VRC.SDK3.Components.VRCAvatarPedestal",
|
||||
"VRC.SDK3.Components.VRCPickup",
|
||||
"VRC.SDK3.Components.VRCPortalMarker",
|
||||
"VRC.SDK3.Components.VRCSpatialAudioSource",
|
||||
"VRC.SDK3.Components.VRCMirrorReflection",
|
||||
"VRC.SDK3.Components.VRCSceneDescriptor",
|
||||
"VRC.SDK3.Components.VRCStation",
|
||||
"VRC.SDK3.Components.VRCUiShape",
|
||||
"VRC.SDK3.Video.Components.VRCUnityVideoPlayer",
|
||||
"VRC.SDK3.Video.Components.AVPro.VRCAVProVideoPlayer",
|
||||
"VRC.SDK3.Video.Components.AVPro.VRCAVProVideoScreen",
|
||||
"VRC.SDK3.Video.Components.AVPro.VRCAVProVideoSpeaker",
|
||||
"VRC.Udon.UdonBehaviour",
|
||||
"UnityEngine.Animations.AimConstraint",
|
||||
"UnityEngine.Animations.LookAtConstraint",
|
||||
"UnityEngine.Animations.ParentConstraint",
|
||||
"UnityEngine.Animations.PositionConstraint",
|
||||
"UnityEngine.Animations.RotationConstraint",
|
||||
"UnityEngine.Animations.ScaleConstraint",
|
||||
"UnityEngine.ParticleSystemForceField",
|
||||
"Cinemachine.Cinemachine3rdPersonAim",
|
||||
"Cinemachine.CinemachineBlendListCamera",
|
||||
"Cinemachine.CinemachineBrain",
|
||||
"Cinemachine.CinemachineCameraOffset",
|
||||
"Cinemachine.CinemachineClearShot",
|
||||
"Cinemachine.CinemachineCollider",
|
||||
"Cinemachine.CinemachineConfiner",
|
||||
"Cinemachine.CinemachineDollyCart",
|
||||
"Cinemachine.CinemachineExternalCamera",
|
||||
"Cinemachine.CinemachineFollowZoom",
|
||||
"Cinemachine.CinemachineFreeLook",
|
||||
"Cinemachine.CinemachineMixingCamera",
|
||||
"Cinemachine.CinemachinePath",
|
||||
"Cinemachine.CinemachinePipeline",
|
||||
"Cinemachine.CinemachinePixelPerfect",
|
||||
"Cinemachine.CinemachineRecomposer",
|
||||
"Cinemachine.CinemachineSmoothPath",
|
||||
"Cinemachine.CinemachineStateDrivenCamera",
|
||||
"Cinemachine.CinemachineStoryboard",
|
||||
"Cinemachine.CinemachineTargetGroup",
|
||||
"Cinemachine.CinemachineVirtualCamera",
|
||||
"Cinemachine.Cinemachine3rdPersonFollow",
|
||||
"Cinemachine.CinemachineBasicMultiChannelPerlin",
|
||||
"Cinemachine.CinemachineComposer",
|
||||
"Cinemachine.CinemachineFramingTransposer",
|
||||
"Cinemachine.CinemachineGroupComposer",
|
||||
"Cinemachine.CinemachineHardLockToTarget",
|
||||
"Cinemachine.CinemachineHardLookAt",
|
||||
"Cinemachine.CinemachineOrbitalTransposer",
|
||||
"Cinemachine.CinemachinePOV",
|
||||
"Cinemachine.CinemachineSameAsFollowTarget",
|
||||
"Cinemachine.CinemachineTrackedDolly",
|
||||
"Cinemachine.CinemachineTransposer",
|
||||
"Cinemachine.CinemachineCore"
|
||||
};
|
||||
|
||||
public static readonly string[] ShaderWhiteList = new string[]
|
||||
{
|
||||
"VRChat/Mobile/Standard Lite",
|
||||
"VRChat/Mobile/Diffuse",
|
||||
"VRChat/Mobile/Bumped Diffuse",
|
||||
"VRChat/Mobile/Bumped Mapped Specular",
|
||||
"VRChat/Mobile/Toon Lit",
|
||||
"VRChat/Mobile/MatCap Lit",
|
||||
"VRChat/Mobile/Lightmapped",
|
||||
"VRChat/Mobile/Skybox",
|
||||
"VRChat/Mobile/Particles/Additive",
|
||||
"VRChat/Mobile/Particles/Multiply",
|
||||
"FX/MirrorReflection",
|
||||
"UI/Default",
|
||||
};
|
||||
|
||||
private static readonly HashSet<int> scannedObjects = new HashSet<int>();
|
||||
|
||||
private static void ConfigureWhiteList(WhiteListConfiguration config)
|
||||
{
|
||||
if (ComponentTypeWhiteListConfiguration == config ||
|
||||
config == WhiteListConfiguration.Unchanged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<string> concatenation = new List<string>();
|
||||
concatenation.AddRange(ComponentTypeWhiteListCommon);
|
||||
|
||||
switch(config)
|
||||
{
|
||||
case WhiteListConfiguration.VRCSDK2:
|
||||
concatenation.AddRange(ComponentTypeWhiteListSdk2);
|
||||
break;
|
||||
case WhiteListConfiguration.VRCSDK3:
|
||||
concatenation.AddRange(ComponentTypeWhiteListSdk3);
|
||||
break;
|
||||
}
|
||||
|
||||
ComponentTypeWhiteListConfiguration = config;
|
||||
ComponentTypeWhiteList = concatenation.ToArray();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void RemoveIllegalComponents(IEnumerable<GameObject> targets, WhiteListConfiguration config, bool retry = true)
|
||||
{
|
||||
ConfigureWhiteList(config);
|
||||
HashSet<Type> whitelist = ValidationUtils.WhitelistedTypes("world" + config, ComponentTypeWhiteList);
|
||||
foreach(GameObject target in targets)
|
||||
{
|
||||
ValidationUtils.RemoveIllegalComponents(target, whitelist, retry, true);
|
||||
SecurityScan(target);
|
||||
AddScanned(target);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddScanned(GameObject obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return;
|
||||
if (!scannedObjects.Contains(obj.GetInstanceID()))
|
||||
scannedObjects.Add(obj.GetInstanceID());
|
||||
for (int idx = 0; idx < obj.transform.childCount; ++idx)
|
||||
AddScanned(obj.transform.GetChild(idx)?.gameObject);
|
||||
}
|
||||
|
||||
private static bool WasScanned(GameObject obj)
|
||||
{
|
||||
return scannedObjects.Contains(obj.GetInstanceID());
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void ScanGameObject(GameObject target, WhiteListConfiguration config)
|
||||
{
|
||||
if (WasScanned(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigureWhiteList(config);
|
||||
HashSet<Type> whitelist = ValidationUtils.WhitelistedTypes("world" + config, ComponentTypeWhiteList);
|
||||
ValidationUtils.RemoveIllegalComponents(target, whitelist);
|
||||
SecurityScan(target);
|
||||
|
||||
AddScanned(target);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void ClearScannedGameObjectCache()
|
||||
{
|
||||
scannedObjects.Clear();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static IEnumerable<Shader> FindIllegalShaders(GameObject target)
|
||||
{
|
||||
return ShaderValidation.FindIllegalShaders(target, ShaderWhiteList);
|
||||
}
|
||||
|
||||
private static void SecurityScan(GameObject target)
|
||||
{
|
||||
PlayableDirector[] playabledirectors = target.GetComponentsInChildren<PlayableDirector>(true);
|
||||
foreach (PlayableDirector playableDirector in playabledirectors)
|
||||
StripPlayableDirectorWithPrefabs(playableDirector);
|
||||
}
|
||||
|
||||
private static void StripPlayableDirectorWithPrefabs(PlayableDirector playableDirector)
|
||||
{
|
||||
if (!(playableDirector.playableAsset is UnityEngine.Timeline.TimelineAsset timelineAsset))
|
||||
return;
|
||||
IEnumerable<TrackAsset> tracks = timelineAsset.GetOutputTracks();
|
||||
foreach (TrackAsset track in tracks)
|
||||
{
|
||||
if (!(track is ControlTrack))
|
||||
continue;
|
||||
IEnumerable<TimelineClip> clips = track.GetClips();
|
||||
foreach (TimelineClip clip in clips)
|
||||
{
|
||||
if (clip.asset is ControlPlayableAsset controlPlayableAsset && controlPlayableAsset.prefabGameObject != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(playableDirector);
|
||||
VRC.Core.Logger.LogWarning("PlayableDirector containing prefab removed", DebugLevel, playableDirector.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b03724cd556cb047b2da80492ea28a5
|
||||
timeCreated: 1504829091
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 178bc3d6d6cc3ca4897436bc22109f96
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+968
@@ -0,0 +1,968 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Video;
|
||||
#if VRC_SDK_VRCSDK3
|
||||
using VRC.SDK3.Components;
|
||||
using TMPro;
|
||||
#endif
|
||||
#if UDON
|
||||
using VRC.Udon;
|
||||
#endif
|
||||
#if !VRC_CLIENT && UNITY_EDITOR && VRC_SDK_VRCSDK3
|
||||
using UnityEditor;
|
||||
using UnityEngine.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace VRC.Core
|
||||
{
|
||||
public class UnityEventFilter
|
||||
{
|
||||
// These types are will always be prohibited even if they are derived from an allowed type.
|
||||
private static readonly HashSet<Type> _prohibitedUIEventTargetTypes = new HashSet<Type>
|
||||
{
|
||||
#if VRC_CLIENT
|
||||
typeof(RenderHeads.Media.AVProVideo.MediaPlayer),
|
||||
#endif
|
||||
#if VRC_SDK_VRCSDK3
|
||||
typeof(VRCUrlInputField),
|
||||
#endif
|
||||
typeof(VideoPlayer)
|
||||
};
|
||||
|
||||
private static readonly Lazy<Dictionary<Type, AllowedMethodFilter>> _allowedUnityEventTargetTypes =
|
||||
new Lazy<Dictionary<Type, AllowedMethodFilter>>(GetRuntimeUnityEventTargetAccessFilterDictionary);
|
||||
|
||||
private static Dictionary<Type, AllowedMethodFilter> AllowedUnityEventTargetTypes => _allowedUnityEventTargetTypes.Value;
|
||||
|
||||
private static readonly Lazy<int> _debugLevel = new Lazy<int>(InitializeLogging);
|
||||
private static int DebugLevel => _debugLevel.Value;
|
||||
|
||||
// Builds a HashSet of allowed types, and their derived types, and removes explicitly prohibited types.
|
||||
private static Dictionary<Type, AllowedMethodFilter> GetRuntimeUnityEventTargetAccessFilterDictionary()
|
||||
{
|
||||
Dictionary<Type, AllowedMethodFilter> accessFilterDictionary = new Dictionary<Type, AllowedMethodFilter>(_initialTargetAccessFilters);
|
||||
AddDerivedTypes(accessFilterDictionary);
|
||||
RemoveProhibitedTypes(accessFilterDictionary);
|
||||
|
||||
#if VERBOSE_EVENT_SANITIZATION_LOGGING
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
foreach(KeyValuePair<Type, AllowedMethodFilter> entry in accessFilterDictionary)
|
||||
{
|
||||
stringBuilder.AppendLine(entry.Key.FullName);
|
||||
AllowedMethodFilter targetMethodAccessFilter = entry.Value;
|
||||
foreach(string targetMethod in targetMethodAccessFilter.GetTargetMethodNames())
|
||||
{
|
||||
stringBuilder.AppendLine($" {targetMethod}");
|
||||
}
|
||||
|
||||
stringBuilder.AppendLine();
|
||||
}
|
||||
|
||||
VerboseLog(stringBuilder.ToString());
|
||||
#endif
|
||||
|
||||
return accessFilterDictionary;
|
||||
}
|
||||
|
||||
#if !VRC_CLIENT && UNITY_EDITOR && VRC_SDK_VRCSDK3
|
||||
[RuntimeInitializeOnLoadMethod]
|
||||
private static void SetupPlayMode()
|
||||
{
|
||||
EditorApplication.playModeStateChanged += RunFilteringOnPlayModeEntry;
|
||||
}
|
||||
|
||||
private static void RunFilteringOnPlayModeEntry(PlayModeStateChange playModeStateChange)
|
||||
{
|
||||
switch(playModeStateChange)
|
||||
{
|
||||
case PlayModeStateChange.EnteredPlayMode:
|
||||
{
|
||||
for(int sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++)
|
||||
{
|
||||
Scene currentScene = SceneManager.GetSceneAt(sceneIndex);
|
||||
List<GameObject> rootGameObjects = new List<GameObject>();
|
||||
currentScene.GetRootGameObjects(rootGameObjects);
|
||||
|
||||
FilterUIEvents(rootGameObjects);
|
||||
FilterAnimatorEvents(rootGameObjects);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PlayModeStateChange.EnteredEditMode:
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
case PlayModeStateChange.ExitingPlayMode:
|
||||
{
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(playModeStateChange), playModeStateChange, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static int InitializeLogging()
|
||||
{
|
||||
int hashCode = typeof(UnityEventFilter).GetHashCode();
|
||||
Logger.DescribeDebugLevel(hashCode, "UnityEventFilter", Logger.Color.red);
|
||||
Logger.AddDebugLevel(hashCode);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void FilterUIEvents(GameObject gameObject)
|
||||
{
|
||||
List<UIBehaviour> uiBehaviours = new List<UIBehaviour>();
|
||||
gameObject.GetComponentsInChildren(true, uiBehaviours);
|
||||
|
||||
FilterUIBehaviourEvents(uiBehaviours);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void FilterUIEvents(List<GameObject> gameObjects)
|
||||
{
|
||||
HashSet<UIBehaviour> uiBehaviours = new HashSet<UIBehaviour>();
|
||||
List<UIBehaviour> uiBehavioursWorkingList = new List<UIBehaviour>();
|
||||
foreach(GameObject gameObject in gameObjects)
|
||||
{
|
||||
gameObject.GetComponentsInChildren(true, uiBehavioursWorkingList);
|
||||
uiBehaviours.UnionWith(uiBehavioursWorkingList);
|
||||
}
|
||||
|
||||
FilterUIBehaviourEvents(uiBehaviours);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void FilterAnimatorEvents(GameObject gameObject)
|
||||
{
|
||||
List<Animator> animators = new List<Animator>();
|
||||
gameObject.GetComponentsInChildren(true, animators);
|
||||
|
||||
FilterAnimatorEvents(animators);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static void FilterAnimatorEvents(List<GameObject> gameObjects)
|
||||
{
|
||||
HashSet<Animator> animators = new HashSet<Animator>();
|
||||
List<Animator> animatorsWorkingList = new List<Animator>();
|
||||
foreach(GameObject gameObject in gameObjects)
|
||||
{
|
||||
gameObject.GetComponentsInChildren(true, animatorsWorkingList);
|
||||
animators.UnionWith(animatorsWorkingList);
|
||||
}
|
||||
|
||||
FilterAnimatorEvents(animators);
|
||||
}
|
||||
|
||||
private static void FilterUIBehaviourEvents(IEnumerable<UIBehaviour> uiBehaviours)
|
||||
{
|
||||
Dictionary<Type, List<UIBehaviour>> uiBehavioursByType = new Dictionary<Type, List<UIBehaviour>>();
|
||||
foreach(UIBehaviour uiBehaviour in uiBehaviours)
|
||||
{
|
||||
if(uiBehaviour == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Type uiBehaviourType = uiBehaviour.GetType();
|
||||
if(!uiBehavioursByType.TryGetValue(uiBehaviourType, out List<UIBehaviour> uiBehavioursOfType))
|
||||
{
|
||||
uiBehavioursByType.Add(uiBehaviourType, new List<UIBehaviour> {uiBehaviour});
|
||||
continue;
|
||||
}
|
||||
|
||||
uiBehavioursOfType.Add(uiBehaviour);
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<Type, List<UIBehaviour>> uiBehavioursOfTypeKvp in uiBehavioursByType)
|
||||
{
|
||||
Type uiBehaviourType = uiBehavioursOfTypeKvp.Key;
|
||||
FieldInfo[] fieldInfos = uiBehaviourType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
||||
List<FieldInfo> unityEventFieldInfos = new List<FieldInfo>();
|
||||
foreach(FieldInfo fieldInfo in fieldInfos)
|
||||
{
|
||||
if(typeof(UnityEventBase).IsAssignableFrom(fieldInfo.FieldType))
|
||||
{
|
||||
unityEventFieldInfos.Add(fieldInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if(unityEventFieldInfos.Count <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldInfo persistentCallsGroupFieldInfo = typeof(UnityEventBase).GetField("m_PersistentCalls", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if(persistentCallsGroupFieldInfo == null)
|
||||
{
|
||||
VerboseLog($"Could not find 'm_PersistentCalls' on UnityEventBase.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(UIBehaviour uiBehaviour in uiBehavioursOfTypeKvp.Value)
|
||||
{
|
||||
VerboseLog($"Checking '{uiBehaviour.name} for UI Events.", uiBehaviour);
|
||||
foreach(FieldInfo unityEventFieldInfo in unityEventFieldInfos)
|
||||
{
|
||||
VerboseLog($"Checking field '{unityEventFieldInfo.Name}' on '{uiBehaviour.name}.", uiBehaviour);
|
||||
UnityEventBase unityEventBase = unityEventFieldInfo.GetValue(uiBehaviour) as UnityEventBase;
|
||||
if(unityEventBase == null)
|
||||
{
|
||||
VerboseLog($"Null '{unityEventFieldInfo.Name}' UnityEvent on {uiBehaviour.name}.", uiBehaviour);
|
||||
continue;
|
||||
}
|
||||
|
||||
int numEventListeners = unityEventBase.GetPersistentEventCount();
|
||||
VerboseLog($"There are '{numEventListeners}' on event '{unityEventFieldInfo.Name}' on '{uiBehaviour.name}.", uiBehaviour);
|
||||
for(int index = 0; index < numEventListeners; index++)
|
||||
{
|
||||
string persistentMethodName = unityEventBase.GetPersistentMethodName(index);
|
||||
|
||||
UnityEngine.Object persistentTarget = unityEventBase.GetPersistentTarget(index);
|
||||
if(persistentTarget == null)
|
||||
{
|
||||
VerboseLog($"The target for listener '{index}' on event '{unityEventFieldInfo.Name}' on '{uiBehaviour.name} is null.", uiBehaviour);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(IsTargetPermitted(persistentTarget, persistentMethodName))
|
||||
{
|
||||
VerboseLog(
|
||||
$"Allowing event '{unityEventFieldInfo.Name}' on '{uiBehaviour.name}' to call '{persistentMethodName}' on target '{persistentTarget.name}'.",
|
||||
uiBehaviour);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
LogRemoval(
|
||||
$"Events on '{uiBehaviour.name}' were removed because one of them targeted a prohibited type '{persistentTarget.GetType().Name}', method '{persistentMethodName}' or object '{persistentTarget.name}'.",
|
||||
uiBehaviour);
|
||||
|
||||
unityEventFieldInfo.SetValue(uiBehaviour, Activator.CreateInstance(unityEventBase.GetType()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void FilterAnimatorEvents(IEnumerable<Animator> animators)
|
||||
{
|
||||
foreach(Animator animator in animators)
|
||||
{
|
||||
if(animator == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RuntimeAnimatorController animatorController = animator.runtimeAnimatorController;
|
||||
if(animatorController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(AnimationClip animationClip in animatorController.animationClips)
|
||||
{
|
||||
if(animationClip == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach(AnimationEvent animationEvent in animationClip.events)
|
||||
{
|
||||
if(animationEvent == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string animationEventFunctionName = animationEvent.functionName;
|
||||
if(_allowedAnimationEventFunctionNames.Contains(animationEventFunctionName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
animationClip.events = null;
|
||||
LogRemoval(
|
||||
$"Removed AnimationEvents from AnimationClip used by the Animator on '{animator.gameObject}' because the event targets '{animationEventFunctionName}' which is not allowed.");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("VERBOSE_EVENT_SANITIZATION_LOGGING")]
|
||||
private static void VerboseLog(string message, UnityEngine.Object target = null)
|
||||
{
|
||||
Logger.LogWarning(message, DebugLevel, target);
|
||||
}
|
||||
|
||||
private static void LogRemoval(string message, UnityEngine.Object target = null)
|
||||
{
|
||||
Logger.LogWarning(message, DebugLevel, target);
|
||||
}
|
||||
|
||||
private static bool IsTargetPermitted(UnityEngine.Object target, string targetMethod)
|
||||
{
|
||||
// Block anything blacklisted by Udon to prevent UnityEvents from being used to bypass the blacklist.
|
||||
// NOTE: This will only block events targeting objects that are blacklisted before the UnityEventSanitizer is run.
|
||||
// If objects are added to the blacklist after scene loading has finished it will be necessary to re-run the UnityEventSanitizer.
|
||||
#if UDON
|
||||
if(UdonManager.Instance.IsBlacklisted(target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
Type persistentTargetType = target.GetType();
|
||||
if(!AllowedUnityEventTargetTypes.TryGetValue(persistentTargetType, out AllowedMethodFilter accessFilter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return accessFilter.IsTargetMethodAllowed(targetMethod);
|
||||
}
|
||||
|
||||
// Adds types derived from whitelisted types.
|
||||
private static void AddDerivedTypes(Dictionary<Type, AllowedMethodFilter> accessFilterDictionary)
|
||||
{
|
||||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach(Assembly assembly in assemblies)
|
||||
{
|
||||
foreach(Type type in assembly.GetTypes())
|
||||
{
|
||||
if(accessFilterDictionary.ContainsKey(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Type currentType = type;
|
||||
while(currentType != typeof(object) && currentType != null)
|
||||
{
|
||||
if(accessFilterDictionary.TryGetValue(currentType, out AllowedMethodFilter accessFilter))
|
||||
{
|
||||
accessFilterDictionary.Add(type, accessFilter);
|
||||
break;
|
||||
}
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removes prohibited types and types derived from them.
|
||||
private static void RemoveProhibitedTypes(Dictionary<Type, AllowedMethodFilter> accessFilterDictionary)
|
||||
{
|
||||
foreach(Type prohibitedType in _prohibitedUIEventTargetTypes)
|
||||
{
|
||||
foreach(Type accessFilterType in accessFilterDictionary.Keys.ToArray())
|
||||
{
|
||||
if(prohibitedType.IsAssignableFrom(accessFilterType))
|
||||
{
|
||||
accessFilterDictionary.Remove(accessFilterType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, AllowedMethodFilter> _initialTargetAccessFilters = new Dictionary<Type, AllowedMethodFilter>
|
||||
{
|
||||
{
|
||||
typeof(GameObject), new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
nameof(GameObject.SetActive)
|
||||
},
|
||||
new List<string>())
|
||||
},
|
||||
{
|
||||
typeof(AudioSource),
|
||||
new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioSource.Pause),
|
||||
nameof(AudioSource.Play),
|
||||
nameof(AudioSource.PlayDelayed),
|
||||
nameof(AudioSource.PlayOneShot),
|
||||
nameof(AudioSource.Stop),
|
||||
nameof(AudioSource.UnPause)
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioSource.bypassEffects),
|
||||
nameof(AudioSource.bypassListenerEffects),
|
||||
nameof(AudioSource.bypassReverbZones),
|
||||
nameof(AudioSource.dopplerLevel),
|
||||
nameof(AudioSource.enabled),
|
||||
nameof(AudioSource.loop),
|
||||
nameof(AudioSource.maxDistance),
|
||||
nameof(AudioSource.rolloffMode),
|
||||
nameof(AudioSource.minDistance),
|
||||
nameof(AudioSource.mute),
|
||||
nameof(AudioSource.pitch),
|
||||
nameof(AudioSource.playOnAwake),
|
||||
nameof(AudioSource.priority),
|
||||
nameof(AudioSource.spatialize),
|
||||
nameof(AudioSource.spread),
|
||||
nameof(AudioSource.time),
|
||||
nameof(AudioSource.volume)
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
typeof(AudioDistortionFilter), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioDistortionFilter.distortionLevel),
|
||||
nameof(AudioDistortionFilter.enabled)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(AudioEchoFilter), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioEchoFilter.decayRatio),
|
||||
nameof(AudioEchoFilter.delay),
|
||||
nameof(AudioEchoFilter.dryMix),
|
||||
nameof(AudioEchoFilter.enabled),
|
||||
nameof(AudioEchoFilter.wetMix)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(AudioHighPassFilter), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioHighPassFilter.cutoffFrequency),
|
||||
nameof(AudioHighPassFilter.enabled),
|
||||
nameof(AudioHighPassFilter.highpassResonanceQ)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(AudioLowPassFilter), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioLowPassFilter.cutoffFrequency),
|
||||
nameof(AudioLowPassFilter.enabled),
|
||||
nameof(AudioLowPassFilter.lowpassResonanceQ)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(AudioReverbFilter), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioReverbFilter.decayHFRatio),
|
||||
nameof(AudioReverbFilter.decayTime),
|
||||
nameof(AudioReverbFilter.density),
|
||||
nameof(AudioReverbFilter.diffusion),
|
||||
nameof(AudioReverbFilter.dryLevel),
|
||||
nameof(AudioReverbFilter.enabled),
|
||||
nameof(AudioReverbFilter.hfReference),
|
||||
nameof(AudioReverbFilter.reflectionsDelay),
|
||||
nameof(AudioReverbFilter.reflectionsLevel),
|
||||
nameof(AudioReverbFilter.reverbDelay),
|
||||
nameof(AudioReverbFilter.reverbLevel),
|
||||
nameof(AudioReverbFilter.room),
|
||||
nameof(AudioReverbFilter.roomHF),
|
||||
nameof(AudioReverbFilter.roomLF)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(AudioReverbZone), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(AudioReverbZone.decayHFRatio),
|
||||
nameof(AudioReverbZone.decayTime),
|
||||
nameof(AudioReverbZone.density),
|
||||
nameof(AudioReverbZone.diffusion),
|
||||
nameof(AudioReverbZone.enabled),
|
||||
nameof(AudioReverbZone.HFReference),
|
||||
nameof(AudioReverbZone.LFReference),
|
||||
nameof(AudioReverbZone.maxDistance),
|
||||
nameof(AudioReverbZone.minDistance),
|
||||
nameof(AudioReverbZone.reflections),
|
||||
nameof(AudioReverbZone.reflectionsDelay),
|
||||
nameof(AudioReverbZone.room),
|
||||
nameof(AudioReverbZone.roomHF),
|
||||
nameof(AudioReverbZone.roomLF)
|
||||
})
|
||||
},
|
||||
#if UDON
|
||||
{
|
||||
typeof(UdonBehaviour), new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
nameof(UdonBehaviour.RunProgram),
|
||||
nameof(UdonBehaviour.SendCustomEvent),
|
||||
nameof(UdonBehaviour.Interact),
|
||||
},
|
||||
new List<string>()
|
||||
{
|
||||
nameof(UdonBehaviour.enabled)
|
||||
})
|
||||
},
|
||||
#endif
|
||||
{
|
||||
typeof(MeshRenderer), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(MeshRenderer.shadowCastingMode),
|
||||
nameof(MeshRenderer.enabled),
|
||||
nameof(MeshRenderer.probeAnchor),
|
||||
nameof(MeshRenderer.probeAnchor),
|
||||
nameof(MeshRenderer.receiveShadows),
|
||||
nameof(MeshRenderer.lightProbeUsage)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Collider), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Collider.enabled),
|
||||
nameof(Collider.isTrigger)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(SkinnedMeshRenderer), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(SkinnedMeshRenderer.allowOcclusionWhenDynamic),
|
||||
nameof(SkinnedMeshRenderer.shadowCastingMode),
|
||||
nameof(SkinnedMeshRenderer.enabled),
|
||||
nameof(SkinnedMeshRenderer.lightProbeProxyVolumeOverride),
|
||||
nameof(SkinnedMeshRenderer.motionVectorGenerationMode),
|
||||
nameof(SkinnedMeshRenderer.probeAnchor),
|
||||
nameof(SkinnedMeshRenderer.receiveShadows),
|
||||
nameof(SkinnedMeshRenderer.rootBone),
|
||||
nameof(SkinnedMeshRenderer.skinnedMotionVectors),
|
||||
nameof(SkinnedMeshRenderer.updateWhenOffscreen),
|
||||
nameof(SkinnedMeshRenderer.lightProbeUsage)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Light), new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
nameof(Light.Reset)
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
nameof(Light.bounceIntensity),
|
||||
nameof(Light.colorTemperature),
|
||||
nameof(Light.cookie),
|
||||
nameof(Light.enabled),
|
||||
nameof(Light.intensity),
|
||||
nameof(Light.range),
|
||||
nameof(Light.shadowBias),
|
||||
nameof(Light.shadowNearPlane),
|
||||
nameof(Light.shadowNormalBias),
|
||||
nameof(Light.shadowStrength),
|
||||
nameof(Light.spotAngle)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(ParticleSystem), new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
nameof(ParticleSystem.Clear),
|
||||
nameof(ParticleSystem.Emit),
|
||||
nameof(ParticleSystem.Pause),
|
||||
nameof(ParticleSystem.Pause),
|
||||
nameof(ParticleSystem.Play),
|
||||
nameof(ParticleSystem.Simulate),
|
||||
nameof(ParticleSystem.Stop),
|
||||
nameof(ParticleSystem.Stop),
|
||||
nameof(ParticleSystem.TriggerSubEmitter)
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
nameof(ParticleSystem.time),
|
||||
nameof(ParticleSystem.useAutoRandomSeed)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(ParticleSystemForceField), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(ParticleSystemForceField.endRange),
|
||||
nameof(ParticleSystemForceField.gravityFocus),
|
||||
nameof(ParticleSystemForceField.length),
|
||||
nameof(ParticleSystemForceField.multiplyDragByParticleSize),
|
||||
nameof(ParticleSystemForceField.multiplyDragByParticleVelocity),
|
||||
nameof(ParticleSystemForceField.startRange)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Projector), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Projector.aspectRatio),
|
||||
nameof(Projector.enabled),
|
||||
nameof(Projector.nearClipPlane),
|
||||
nameof(Projector.farClipPlane),
|
||||
nameof(Projector.fieldOfView),
|
||||
nameof(Projector.orthographic),
|
||||
nameof(Projector.orthographicSize)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(LineRenderer), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(LineRenderer.allowOcclusionWhenDynamic),
|
||||
nameof(LineRenderer.shadowCastingMode),
|
||||
nameof(LineRenderer.enabled),
|
||||
nameof(LineRenderer.endWidth),
|
||||
nameof(LineRenderer.loop),
|
||||
nameof(LineRenderer.motionVectorGenerationMode),
|
||||
nameof(LineRenderer.numCapVertices),
|
||||
nameof(LineRenderer.numCornerVertices),
|
||||
nameof(LineRenderer.probeAnchor),
|
||||
nameof(LineRenderer.receiveShadows),
|
||||
nameof(LineRenderer.shadowBias),
|
||||
nameof(LineRenderer.startWidth),
|
||||
nameof(LineRenderer.lightProbeUsage),
|
||||
nameof(LineRenderer.useWorldSpace),
|
||||
nameof(LineRenderer.widthMultiplier)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(TrailRenderer), new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
nameof(TrailRenderer.Clear)
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
nameof(TrailRenderer.allowOcclusionWhenDynamic),
|
||||
nameof(TrailRenderer.autodestruct),
|
||||
nameof(TrailRenderer.shadowCastingMode),
|
||||
nameof(TrailRenderer.enabled),
|
||||
nameof(TrailRenderer.emitting),
|
||||
nameof(TrailRenderer.endWidth),
|
||||
nameof(TrailRenderer.motionVectorGenerationMode),
|
||||
nameof(TrailRenderer.numCapVertices),
|
||||
nameof(TrailRenderer.numCornerVertices),
|
||||
nameof(TrailRenderer.probeAnchor),
|
||||
nameof(TrailRenderer.receiveShadows),
|
||||
nameof(TrailRenderer.shadowBias),
|
||||
nameof(TrailRenderer.startWidth),
|
||||
nameof(TrailRenderer.lightProbeUsage),
|
||||
nameof(TrailRenderer.widthMultiplier)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Animator), new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
nameof(Animator.Play),
|
||||
nameof(Animator.PlayInFixedTime),
|
||||
nameof(Animator.Rebind),
|
||||
nameof(Animator.SetBool),
|
||||
nameof(Animator.SetFloat),
|
||||
nameof(Animator.SetInteger),
|
||||
nameof(Animator.SetTrigger),
|
||||
nameof(Animator.ResetTrigger)
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
nameof(Animator.speed),
|
||||
nameof(Animator.enabled)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Text), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Text.alignByGeometry),
|
||||
nameof(Text.enabled),
|
||||
nameof(Text.fontSize),
|
||||
nameof(Text.lineSpacing),
|
||||
nameof(Text.maskable),
|
||||
nameof(Text.raycastTarget),
|
||||
nameof(Text.resizeTextForBestFit),
|
||||
nameof(Text.resizeTextMaxSize),
|
||||
nameof(Text.resizeTextMinSize),
|
||||
nameof(Text.supportRichText),
|
||||
nameof(Text.text)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Image), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Image.alphaHitTestMinimumThreshold),
|
||||
nameof(Image.enabled),
|
||||
nameof(Image.fillAmount),
|
||||
nameof(Image.fillCenter),
|
||||
nameof(Image.fillClockwise),
|
||||
nameof(Image.fillOrigin),
|
||||
nameof(Image.maskable),
|
||||
nameof(Image.preserveAspect),
|
||||
nameof(Image.raycastTarget),
|
||||
nameof(Image.useSpriteMesh)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(RawImage), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(RawImage.enabled),
|
||||
nameof(RawImage.maskable),
|
||||
nameof(RawImage.raycastTarget)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(InputField), new AllowedMethodFilter(
|
||||
new List<string>
|
||||
{
|
||||
"Append",
|
||||
nameof(InputField.ForceLabelUpdate)
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
nameof(InputField.caretBlinkRate),
|
||||
nameof(InputField.caretPosition),
|
||||
nameof(InputField.caretWidth),
|
||||
nameof(InputField.characterLimit),
|
||||
nameof(InputField.customCaretColor),
|
||||
nameof(InputField.enabled),
|
||||
nameof(InputField.interactable),
|
||||
nameof(InputField.readOnly),
|
||||
nameof(InputField.selectionAnchorPosition),
|
||||
nameof(InputField.text),
|
||||
nameof(InputField.textComponent),
|
||||
nameof(InputField.selectionFocusPosition)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Dropdown), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Dropdown.captionText),
|
||||
nameof(Dropdown.enabled),
|
||||
nameof(Dropdown.interactable),
|
||||
nameof(Dropdown.itemText),
|
||||
nameof(Dropdown.targetGraphic),
|
||||
nameof(Dropdown.template),
|
||||
nameof(Dropdown.value)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Slider), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Slider.enabled),
|
||||
nameof(Slider.fillRect),
|
||||
nameof(Slider.handleRect),
|
||||
nameof(Slider.interactable),
|
||||
nameof(Slider.maxValue),
|
||||
nameof(Slider.minValue),
|
||||
nameof(Slider.normalizedValue),
|
||||
nameof(Slider.targetGraphic),
|
||||
nameof(Slider.value),
|
||||
nameof(Slider.wholeNumbers)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Toggle), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Toggle.enabled),
|
||||
nameof(Toggle.group),
|
||||
nameof(Toggle.interactable),
|
||||
nameof(Toggle.isOn),
|
||||
nameof(Toggle.targetGraphic)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Scrollbar), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Scrollbar.enabled),
|
||||
nameof(Scrollbar.handleRect),
|
||||
nameof(Scrollbar.interactable),
|
||||
nameof(Scrollbar.numberOfSteps),
|
||||
nameof(Scrollbar.size),
|
||||
nameof(Scrollbar.targetGraphic),
|
||||
nameof(Scrollbar.value)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(ScrollRect), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(ScrollRect.content),
|
||||
nameof(ScrollRect.decelerationRate),
|
||||
nameof(ScrollRect.elasticity),
|
||||
nameof(ScrollRect.enabled),
|
||||
nameof(ScrollRect.horizontal),
|
||||
nameof(ScrollRect.horizontalNormalizedPosition),
|
||||
nameof(ScrollRect.horizontalScrollbar),
|
||||
nameof(ScrollRect.horizontalScrollbarSpacing),
|
||||
nameof(ScrollRect.inertia),
|
||||
nameof(ScrollRect.scrollSensitivity),
|
||||
nameof(ScrollRect.vertical),
|
||||
nameof(ScrollRect.verticalNormalizedPosition),
|
||||
nameof(ScrollRect.verticalScrollbar),
|
||||
nameof(ScrollRect.verticalScrollbarSpacing),
|
||||
nameof(ScrollRect.viewport)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Button), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Button.enabled),
|
||||
nameof(Button.interactable),
|
||||
nameof(Button.targetGraphic)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Mask), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Mask.enabled),
|
||||
nameof(Mask.showMaskGraphic)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(RectMask2D), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(RectMask2D.enabled)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Selectable), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(Selectable.enabled),
|
||||
nameof(Selectable.interactable),
|
||||
nameof(Selectable.targetGraphic)
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(ToggleGroup), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(ToggleGroup.allowSwitchOff),
|
||||
nameof(ToggleGroup.enabled)
|
||||
})
|
||||
},
|
||||
#if VRC_SDK_VRCSDK3 // only access Cinemachine and TMPro after install
|
||||
{
|
||||
typeof(TextMeshPro), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(TextMeshPro.text),
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(TextMeshProUGUI), new AllowedMethodFilter(
|
||||
new List<string>(),
|
||||
new List<string>
|
||||
{
|
||||
nameof(TextMeshProUGUI.text),
|
||||
})
|
||||
},
|
||||
{
|
||||
typeof(Cinemachine.CinemachineVirtualCamera), new AllowedMethodFilter(
|
||||
new List<string>()
|
||||
{
|
||||
nameof(Cinemachine.CinemachineVirtualCamera.Priority),
|
||||
},
|
||||
new List<string>())
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _allowedAnimationEventFunctionNames = new HashSet<string>
|
||||
{
|
||||
"RunProgram",
|
||||
"SendCustomEvent",
|
||||
"Play",
|
||||
"Pause",
|
||||
"Stop",
|
||||
"PlayInFixedTime",
|
||||
"Rebind",
|
||||
"SetBool",
|
||||
"SetFloat",
|
||||
"SetInteger",
|
||||
"SetTrigger",
|
||||
"ResetTrigger",
|
||||
"SetActive"
|
||||
};
|
||||
|
||||
private class AllowedMethodFilter
|
||||
{
|
||||
private readonly HashSet<string> _allowedTargets;
|
||||
|
||||
[PublicAPI]
|
||||
public AllowedMethodFilter(List<string> allowedTargetMethodNames, List<string> allowedTargetPropertyNames)
|
||||
{
|
||||
_allowedTargets = new HashSet<string>();
|
||||
_allowedTargets.UnionWith(allowedTargetMethodNames);
|
||||
foreach(string allowedTargetProperty in allowedTargetPropertyNames)
|
||||
{
|
||||
_allowedTargets.Add($"get_{allowedTargetProperty}");
|
||||
_allowedTargets.Add($"set_{allowedTargetProperty}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTargetMethodAllowed(string targetMethodName)
|
||||
{
|
||||
return _allowedTargets.Contains(targetMethodName);
|
||||
}
|
||||
|
||||
#if VERBOSE_EVENT_SANITIZATION_LOGGING
|
||||
public List<string> GetTargetMethodNames()
|
||||
{
|
||||
return _allowedTargets.ToList();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87f13d6a98e54fb78ca47650a29029a5
|
||||
timeCreated: 1594690552
|
||||
Reference in new issue
Block a user