diff options
author | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
---|---|---|
committer | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
commit | eb84bb298d2b95aec7b2ae12cbf25ac64f25379a (patch) | |
tree | efd616a157df06ab661c6d56651853431ac6b08b /VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts | |
download | unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.gz unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.bz2 unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.zip |
move to self host
Diffstat (limited to 'VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts')
78 files changed, 7126 insertions, 0 deletions
diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ApiFileHelper.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ApiFileHelper.cs new file mode 100644 index 00000000..0dedf888 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ApiFileHelper.cs @@ -0,0 +1,1903 @@ +#if UNITY_EDITOR +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System; +using System.IO; +using Debug = UnityEngine.Debug; +using System.Text.RegularExpressions; + +namespace VRC.Core +{ + public class ApiFileHelper : MonoBehaviour + { + private readonly int kMultipartUploadChunkSize = 100 * 1024 * 1024; // 100 MB + private readonly int SERVER_PROCESSING_WAIT_TIMEOUT_CHUNK_SIZE = 50 * 1024 * 1024; + private readonly float SERVER_PROCESSING_WAIT_TIMEOUT_PER_CHUNK_SIZE = 120.0f; + private readonly float SERVER_PROCESSING_MAX_WAIT_TIMEOUT = 600.0f; + private readonly float SERVER_PROCESSING_INITIAL_RETRY_TIME = 2.0f; + private readonly float SERVER_PROCESSING_MAX_RETRY_TIME = 10.0f; + + private static bool EnableDeltaCompression = false; + + private readonly Regex[] kUnityPackageAssetNameFilters = new Regex[] + { + new Regex(@"/LightingData\.asset$"), // lightmap base asset + new Regex(@"/Lightmap-.*(\.png|\.exr)$"), // lightmaps + new Regex(@"/ReflectionProbe-.*(\.exr|\.png)$"), // reflection probes + new Regex(@"/Editor/Data/UnityExtensions/") // anything that looks like part of the Unity installation + }; + + public delegate void OnFileOpSuccess(ApiFile apiFile, string message); + public delegate void OnFileOpError(ApiFile apiFile, string error); + public delegate void OnFileOpProgress(ApiFile apiFile, string status, string subStatus, float pct); + public delegate bool FileOpCancelQuery(ApiFile apiFile); + + public static ApiFileHelper Instance + { + get + { + CheckInstance(); + return mInstance; + } + } + + private static ApiFileHelper mInstance = null; + const float kPostWriteDelay = 0.75f; + + public enum FileOpResult + { + Success, + Unchanged + } + + public static string GetMimeTypeFromExtension(string extension) + { + if (extension == ".vrcw") + return "application/x-world"; + if (extension == ".vrca") + return "application/x-avatar"; + if (extension == ".dll") + return "application/x-msdownload"; + if (extension == ".unitypackage") + return "application/gzip"; + if (extension == ".gz") + return "application/gzip"; + if (extension == ".jpg") + return "image/jpg"; + if (extension == ".png") + return "image/png"; + if (extension == ".sig") + return "application/x-rsync-signature"; + if (extension == ".delta") + return "application/x-rsync-delta"; + + Debug.LogWarning("Unknown file extension for mime-type: " + extension); + return "application/octet-stream"; + } + + public static bool IsGZipCompressed(string filename) + { + return GetMimeTypeFromExtension(Path.GetExtension(filename)) == "application/gzip"; + } + + public IEnumerator UploadFile(string filename, string existingFileId, string friendlyName, + OnFileOpSuccess onSuccess, OnFileOpError onError, OnFileOpProgress onProgress, FileOpCancelQuery cancelQuery) + { + VRC.Core.Logger.Log("UploadFile: filename: " + filename + ", file id: " + + (!string.IsNullOrEmpty(existingFileId) ? existingFileId : "<new>") + ", name: " + friendlyName, DebugLevel.All); + + // init remote config + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + bool done = false; + ConfigManager.RemoteConfig.Init( + delegate () { done = true; }, + delegate () { done = true; } + ); + + while (!done) + yield return null; + + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + Error(onError, null, "Failed to fetch configuration."); + yield break; + } + } + + // configure delta compression + { + EnableDeltaCompression = ConfigManager.RemoteConfig.GetBool("sdkEnableDeltaCompression", false); + } + + // validate input file + Progress(onProgress, null, "Checking file..."); + + if (string.IsNullOrEmpty(filename)) + { + Error(onError, null, "Upload filename is empty!"); + yield break; + } + + if (!System.IO.Path.HasExtension(filename)) + { + Error(onError, null, "Upload filename must have an extension: " + filename); + yield break; + } + + string whyNot; + if (!VRC.Tools.FileCanRead(filename, out whyNot)) + { + Error(onError, null, "Could not read file to upload!", filename + "\n" + whyNot); + yield break; + } + + // get or create ApiFile + Progress(onProgress, null, string.IsNullOrEmpty(existingFileId) ? "Creating file record..." : "Getting file record..."); + + bool wait = true; + bool wasError = false; + bool worthRetry = false; + string errorStr = ""; + + if (string.IsNullOrEmpty(friendlyName)) + friendlyName = filename; + + string extension = System.IO.Path.GetExtension(filename); + string mimeType = GetMimeTypeFromExtension(extension); + + ApiFile apiFile = null; + + System.Action<ApiContainer> fileSuccess = (ApiContainer c) => + { + apiFile = c.Model as ApiFile; + wait = false; + }; + + System.Action<ApiContainer> fileFailure = (ApiContainer c) => + { + errorStr = c.Error; + wait = false; + + if (c.Code == 400) + worthRetry = true; + }; + + while (true) + { + apiFile = null; + wait = true; + worthRetry = false; + errorStr = ""; + + if (string.IsNullOrEmpty(existingFileId)) + ApiFile.Create(friendlyName, mimeType, extension, fileSuccess, fileFailure); + else + API.Fetch<ApiFile>(existingFileId, fileSuccess, fileFailure); + + while (wait) + { + if (apiFile != null && CheckCancelled(cancelQuery, onError, apiFile)) + yield break; + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (errorStr.Contains("File not found")) + { + Debug.LogError("Couldn't find file record: " + existingFileId + ", creating new file record"); + + existingFileId = ""; + continue; + } + + string msg = string.IsNullOrEmpty(existingFileId) ? "Failed to create file record." : "Failed to get file record."; + Error(onError, null, msg, errorStr); + + if (!worthRetry) + yield break; + } + + if (!worthRetry) + break; + else + yield return new WaitForSecondsRealtime(kPostWriteDelay); + } + + if (apiFile == null) + yield break; + + LogApiFileStatus(apiFile, false, true); + + while (apiFile.HasQueuedOperation(EnableDeltaCompression)) + { + wait = true; + + apiFile.DeleteLatestVersion((c) => wait = false, (c) => wait = false); + + while (wait) + { + if (apiFile != null && CheckCancelled(cancelQuery, onError, apiFile)) + yield break; + + yield return null; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + LogApiFileStatus(apiFile, false); + + // check for server side errors from last upload + if (apiFile.IsInErrorState()) + { + Debug.LogWarning("ApiFile: " + apiFile.id + ": server failed to process last uploaded, deleting failed version"); + + while (true) + { + // delete previous failed version + Progress(onProgress, apiFile, "Preparing file for upload...", "Cleaning up previous version"); + + wait = true; + errorStr = ""; + worthRetry = false; + + apiFile.DeleteLatestVersion(fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, null)) + { + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to delete previous failed version!", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + if (worthRetry) + yield return new WaitForSecondsRealtime(kPostWriteDelay); + else + break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + LogApiFileStatus(apiFile, false); + + // verify previous file op is complete + if (apiFile.HasQueuedOperation(EnableDeltaCompression)) + { + Error(onError, apiFile, "A previous upload is still being processed. Please try again later."); + yield break; + } + + if (wasError) + yield break; + + LogApiFileStatus(apiFile, false); + + // generate md5 and check if file has changed + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating file hash"); + + string fileMD5Base64 = ""; + wait = true; + errorStr = ""; + VRC.Tools.FileMD5(filename, + delegate (byte[] md5Bytes) + { + fileMD5Base64 = Convert.ToBase64String(md5Bytes); + wait = false; + }, + delegate (string error) + { + errorStr = filename + "\n" + error; + wait = false; + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to generate MD5 hash for upload file.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + LogApiFileStatus(apiFile, false); + + // check if file has been changed + Progress(onProgress, apiFile, "Preparing file for upload...", "Checking for changes"); + + bool isPreviousUploadRetry = false; + if (apiFile.HasExistingOrPendingVersion()) + { + // uploading the same file? + if (string.Compare(fileMD5Base64, apiFile.GetFileMD5(apiFile.GetLatestVersionNumber())) == 0) + { + // the previous operation completed successfully? + if (!apiFile.IsWaitingForUpload()) + { + Success(onSuccess, apiFile, "The file to upload is unchanged."); + CleanupTempFiles(apiFile.id); + yield break; + } + else + { + isPreviousUploadRetry = true; + + Debug.Log("Retrying previous upload"); + } + } + else + { + // the file has been modified + if (apiFile.IsWaitingForUpload()) + { + // previous upload failed, and the file is changed + while (true) + { + // delete previous failed version + Progress(onProgress, apiFile, "Preparing file for upload...", "Cleaning up previous version"); + + wait = true; + worthRetry = false; + errorStr = ""; + + apiFile.DeleteLatestVersion(fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to delete previous incomplete version!", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + } + } + } + + LogApiFileStatus(apiFile, false); + + // generate signature for new file + + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating signature"); + + string signatureFilename = VRC.Tools.GetTempFileName(".sig", out errorStr, apiFile.id); + if (string.IsNullOrEmpty(signatureFilename)) + { + Error(onError, apiFile, "Failed to generate file signature!", "Failed to create temp file: \n" + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + wasError = false; + yield return StartCoroutine(CreateFileSignatureInternal(filename, signatureFilename, + delegate () + { + // success! + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to generate file signature!", error); + CleanupTempFiles(apiFile.id); + wasError = true; + }) + ); + + if (wasError) + yield break; + + LogApiFileStatus(apiFile, false); + + // generate signature md5 and file size + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating signature hash"); + + string sigMD5Base64 = ""; + wait = true; + errorStr = ""; + VRC.Tools.FileMD5(signatureFilename, + delegate (byte[] md5Bytes) + { + sigMD5Base64 = Convert.ToBase64String(md5Bytes); + wait = false; + }, + delegate (string error) + { + errorStr = signatureFilename + "\n" + error; + wait = false; + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to generate MD5 hash for signature file.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + long sigFileSize = 0; + if (!VRC.Tools.GetFileSize(signatureFilename, out sigFileSize, out errorStr)) + { + Error(onError, apiFile, "Failed to generate file signature!", "Couldn't get file size:\n" + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + LogApiFileStatus(apiFile, false); + + // download previous version signature (if exists) + string existingFileSignaturePath = null; + if (EnableDeltaCompression && apiFile.HasExistingVersion()) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Downloading previous version signature"); + + wait = true; + errorStr = ""; + apiFile.DownloadSignature( + delegate (byte[] data) + { + // save to temp file + existingFileSignaturePath = VRC.Tools.GetTempFileName(".sig", out errorStr, apiFile.id); + if (string.IsNullOrEmpty(existingFileSignaturePath)) + { + errorStr = "Failed to create temp file: \n" + errorStr; + wait = false; + } + else + { + try + { + File.WriteAllBytes(existingFileSignaturePath, data); + } + catch (Exception e) + { + existingFileSignaturePath = null; + errorStr = "Failed to write signature temp file:\n" + e.Message; + } + wait = false; + } + }, + delegate (string error) + { + errorStr = error; + wait = false; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Downloading previous version signature", Tools.DivideSafe(downloaded, length)); + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to download previous file version signature.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + } + + LogApiFileStatus(apiFile, false); + + // create delta if needed + string deltaFilename = null; + + if (EnableDeltaCompression && !string.IsNullOrEmpty(existingFileSignaturePath)) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Creating file delta"); + + deltaFilename = VRC.Tools.GetTempFileName(".delta", out errorStr, apiFile.id); + if (string.IsNullOrEmpty(deltaFilename)) + { + Error(onError, apiFile, "Failed to create file delta for upload.", "Failed to create temp file: \n" + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + wasError = false; + yield return StartCoroutine(CreateFileDeltaInternal(filename, existingFileSignaturePath, deltaFilename, + delegate () + { + // success! + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to create file delta for upload.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + }) + ); + + if (wasError) + yield break; + } + + // upload smaller of delta and new file + long fullFileSize = 0; + long deltaFileSize = 0; + if (!VRC.Tools.GetFileSize(filename, out fullFileSize, out errorStr) || + (!string.IsNullOrEmpty(deltaFilename) && !VRC.Tools.GetFileSize(deltaFilename, out deltaFileSize, out errorStr))) + { + Error(onError, apiFile, "Failed to create file delta for upload.", "Couldn't get file size: " + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + bool uploadDeltaFile = EnableDeltaCompression && deltaFileSize > 0 && deltaFileSize < fullFileSize; + if (EnableDeltaCompression) + VRC.Core.Logger.Log("Delta size " + deltaFileSize + " (" + ((float)deltaFileSize / (float)fullFileSize) + " %), full file size " + fullFileSize + ", uploading " + (uploadDeltaFile ? " DELTA" : " FULL FILE"), DebugLevel.All); + else + VRC.Core.Logger.Log("Delta compression disabled, uploading FULL FILE, size " + fullFileSize, DebugLevel.All); + + LogApiFileStatus(apiFile, uploadDeltaFile); + + string deltaMD5Base64 = ""; + if (uploadDeltaFile) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating file delta hash"); + + wait = true; + errorStr = ""; + VRC.Tools.FileMD5(deltaFilename, + delegate (byte[] md5Bytes) + { + deltaMD5Base64 = Convert.ToBase64String(md5Bytes); + wait = false; + }, + delegate (string error) + { + errorStr = error; + wait = false; + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to generate file delta hash.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // validate existing pending version info, if this is a retry + bool versionAlreadyExists = false; + + LogApiFileStatus(apiFile, uploadDeltaFile); + + if (isPreviousUploadRetry) + { + bool isValid = true; + + ApiFile.Version v = apiFile.GetVersion(apiFile.GetLatestVersionNumber()); + if (v != null) + { + if (uploadDeltaFile) + { + isValid = deltaFileSize == v.delta.sizeInBytes && + deltaMD5Base64.CompareTo(v.delta.md5) == 0 && + sigFileSize == v.signature.sizeInBytes && + sigMD5Base64.CompareTo(v.signature.md5) == 0; + } + else + { + isValid = fullFileSize == v.file.sizeInBytes && + fileMD5Base64.CompareTo(v.file.md5) == 0 && + sigFileSize == v.signature.sizeInBytes && + sigMD5Base64.CompareTo(v.signature.md5) == 0; + } + } + else + { + isValid = false; + } + + if (isValid) + { + versionAlreadyExists = true; + + Debug.Log("Using existing version record"); + } + else + { + // delete previous invalid version + Progress(onProgress, apiFile, "Preparing file for upload...", "Cleaning up previous version"); + + while (true) + { + wait = true; + errorStr = ""; + worthRetry = false; + + apiFile.DeleteLatestVersion(fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, null)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to delete previous incomplete version!", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + } + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // create new version of file + if (!versionAlreadyExists) + { + while (true) + { + Progress(onProgress, apiFile, "Creating file version record..."); + + wait = true; + errorStr = ""; + worthRetry = false; + + if (uploadDeltaFile) + // delta file + apiFile.CreateNewVersion(ApiFile.Version.FileType.Delta, deltaMD5Base64, deltaFileSize, sigMD5Base64, sigFileSize, fileSuccess, fileFailure); + else + // full file + apiFile.CreateNewVersion(ApiFile.Version.FileType.Full, fileMD5Base64, fullFileSize, sigMD5Base64, sigFileSize, fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to create file version record.", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + } + + // upload components + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // upload delta + if (uploadDeltaFile) + { + if (apiFile.GetLatestVersion().delta.status == ApiFile.Status.Waiting) + { + Progress(onProgress, apiFile, "Uploading file delta..."); + + wasError = false; + yield return StartCoroutine(UploadFileComponentInternal(apiFile, + ApiFile.Version.FileDescriptor.Type.delta, deltaFilename, deltaMD5Base64, deltaFileSize, + delegate (ApiFile file) + { + Debug.Log("Successfully uploaded file delta."); + apiFile = file; + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to upload file delta.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Uploading file delta...", "", Tools.DivideSafe(downloaded, length)); + }, + cancelQuery) + ); + + if (wasError) + yield break; + } + } + // upload file + else + { + if (apiFile.GetLatestVersion().file.status == ApiFile.Status.Waiting) + { + Progress(onProgress, apiFile, "Uploading file..."); + + wasError = false; + yield return StartCoroutine(UploadFileComponentInternal(apiFile, + ApiFile.Version.FileDescriptor.Type.file, filename, fileMD5Base64, fullFileSize, + delegate (ApiFile file) + { + VRC.Core.Logger.Log("Successfully uploaded file.", DebugLevel.All); + apiFile = file; + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to upload file.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Uploading file...", "", Tools.DivideSafe(downloaded, length)); + }, + cancelQuery) + ); + + if (wasError) + yield break; + } + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // upload signature + if (apiFile.GetLatestVersion().signature.status == ApiFile.Status.Waiting) + { + Progress(onProgress, apiFile, "Uploading file signature..."); + + wasError = false; + yield return StartCoroutine(UploadFileComponentInternal(apiFile, + ApiFile.Version.FileDescriptor.Type.signature, signatureFilename, sigMD5Base64, sigFileSize, + delegate (ApiFile file) + { + VRC.Core.Logger.Log("Successfully uploaded file signature.", DebugLevel.All); + apiFile = file; + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to upload file signature.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Uploading file signature...", "", Tools.DivideSafe(downloaded, length)); + }, + cancelQuery) + ); + + if (wasError) + yield break; + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // Validate file records queued or complete + Progress(onProgress, apiFile, "Validating upload..."); + + bool isUploadComplete = (uploadDeltaFile + ? apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.delta).status == ApiFile.Status.Complete + : apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.file).status == ApiFile.Status.Complete); + isUploadComplete = isUploadComplete && + apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.signature).status == ApiFile.Status.Complete; + + if (!isUploadComplete) + { + Error(onError, apiFile, "Failed to upload file.", "Record status is not 'complete'"); + CleanupTempFiles(apiFile.id); + yield break; + } + + bool isServerOpQueuedOrComplete = (uploadDeltaFile + ? apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.file).status != ApiFile.Status.Waiting + : apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.delta).status != ApiFile.Status.Waiting); + + if (!isServerOpQueuedOrComplete) + { + Error(onError, apiFile, "Failed to upload file.", "Record is still in 'waiting' status"); + CleanupTempFiles(apiFile.id); + yield break; + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // wait for server processing to complete + Progress(onProgress, apiFile, "Processing upload..."); + float checkDelay = SERVER_PROCESSING_INITIAL_RETRY_TIME; + float maxDelay = SERVER_PROCESSING_MAX_RETRY_TIME; + float timeout = GetServerProcessingWaitTimeoutForDataSize(apiFile.GetLatestVersion().file.sizeInBytes); + double initialStartTime = Time.realtimeSinceStartup; + double startTime = initialStartTime; + while (apiFile.HasQueuedOperation(uploadDeltaFile)) + { + // wait before polling again + Progress(onProgress, apiFile, "Processing upload...", "Checking status in " + Mathf.CeilToInt(checkDelay) + " seconds"); + + while (Time.realtimeSinceStartup - startTime < checkDelay) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + + if (Time.realtimeSinceStartup - initialStartTime > timeout) + { + LogApiFileStatus(apiFile, uploadDeltaFile); + + Error(onError, apiFile, "Timed out waiting for upload processing to complete."); + CleanupTempFiles(apiFile.id); + yield break; + } + + yield return null; + } + + while (true) + { + // check status + Progress(onProgress, apiFile, "Processing upload...", "Checking status..."); + + wait = true; + worthRetry = false; + errorStr = ""; + API.Fetch<ApiFile>(apiFile.id, fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Checking upload status failed.", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + if (!worthRetry) + break; + } + + checkDelay = Mathf.Min(checkDelay * 2, maxDelay); + startTime = Time.realtimeSinceStartup; + } + + // cleanup and wait for it to finish + yield return StartCoroutine(CleanupTempFilesInternal(apiFile.id)); + + Success(onSuccess, apiFile, "Upload complete!"); + } + + private static void LogApiFileStatus(ApiFile apiFile, bool checkDelta, bool logSuccess = false) + { + if (apiFile == null || !apiFile.IsInitialized) + { + Debug.LogFormat("<color=yellow>apiFile not initialized</color>"); + } + else if (apiFile.IsInErrorState()) + { + Debug.LogFormat("<color=yellow>ApiFile {0} is in an error state.</color>", apiFile.name); + } + else if (logSuccess) + VRC.Core.Logger.Log("< color = yellow > Processing { 3}: { 0}, { 1}, { 2}</ color > " + + (apiFile.IsWaitingForUpload() ? "waiting for upload" : "upload complete") + + (apiFile.HasExistingOrPendingVersion() ? "has existing or pending version" : "no previous version") + + (apiFile.IsLatestVersionQueued(checkDelta) ? "latest version queued" : "latest version not queued") + + apiFile.name, DebugLevel.All); + + if (apiFile != null && apiFile.IsInitialized && logSuccess) + { + var apiFields = apiFile.ExtractApiFields(); + if (apiFields != null) + VRC.Core.Logger.Log("<color=yellow>{0}</color>" + VRC.Tools.JsonEncode(apiFields), DebugLevel.All); + } + } + + public IEnumerator CreateFileSignatureInternal(string filename, string outputSignatureFilename, Action onSuccess, Action<string> onError) + { + VRC.Core.Logger.Log("CreateFileSignature: " + filename + " => " + outputSignatureFilename, DebugLevel.All); + + yield return null; + + Stream inStream = null; + FileStream outStream = null; + byte[] buf = new byte[64 * 1024]; + IAsyncResult asyncRead = null; + IAsyncResult asyncWrite = null; + + try + { + inStream = librsync.net.Librsync.ComputeSignature(File.OpenRead(filename)); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't open input file: " + e.Message); + yield break; + } + + try + { + outStream = File.Open(outputSignatureFilename, FileMode.Create, FileAccess.Write); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't create output file: " + e.Message); + yield break; + } + + while (true) + { + try + { + asyncRead = inStream.BeginRead(buf, 0, buf.Length, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + while (!asyncRead.IsCompleted) + yield return null; + + int read = 0; + try + { + read = inStream.EndRead(asyncRead); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + if (read <= 0) + break; + + try + { + asyncWrite = outStream.BeginWrite(buf, 0, read, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + + while (!asyncWrite.IsCompleted) + yield return null; + + try + { + outStream.EndWrite(asyncWrite); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + } + + inStream.Close(); + outStream.Close(); + + yield return null; + + if (onSuccess != null) + onSuccess(); + } + + public IEnumerator CreateFileDeltaInternal(string newFilename, string existingFileSignaturePath, string outputDeltaFilename, Action onSuccess, Action<string> onError) + { + Debug.Log("CreateFileDelta: " + newFilename + " (delta) " + existingFileSignaturePath + " => " + outputDeltaFilename); + + yield return null; + + Stream inStream = null; + FileStream outStream = null; + byte[] buf = new byte[64 * 1024]; + IAsyncResult asyncRead = null; + IAsyncResult asyncWrite = null; + + try + { + inStream = librsync.net.Librsync.ComputeDelta(File.OpenRead(existingFileSignaturePath), File.OpenRead(newFilename)); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't open input file: " + e.Message); + yield break; + } + + try + { + outStream = File.Open(outputDeltaFilename, FileMode.Create, FileAccess.Write); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't create output file: " + e.Message); + yield break; + } + + while (true) + { + try + { + asyncRead = inStream.BeginRead(buf, 0, buf.Length, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + while (!asyncRead.IsCompleted) + yield return null; + + int read = 0; + try + { + read = inStream.EndRead(asyncRead); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + if (read <= 0) + break; + + try + { + asyncWrite = outStream.BeginWrite(buf, 0, read, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + + while (!asyncWrite.IsCompleted) + yield return null; + + try + { + outStream.EndWrite(asyncWrite); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + } + + inStream.Close(); + outStream.Close(); + + yield return null; + + if (onSuccess != null) + onSuccess(); + } + + protected static void Success(OnFileOpSuccess onSuccess, ApiFile apiFile, string message) + { + if (apiFile == null) + apiFile = new ApiFile(); + + VRC.Core.Logger.Log("ApiFile " + apiFile.ToStringBrief() + ": Operation Succeeded!", DebugLevel.All); + if (onSuccess != null) + onSuccess(apiFile, message); + } + + protected static void Error(OnFileOpError onError, ApiFile apiFile, string error, string moreInfo = "") + { + if (apiFile == null) + apiFile = new ApiFile(); + + Debug.LogError("ApiFile " + apiFile.ToStringBrief() + ": Error: " + error + "\n" + moreInfo); + if (onError != null) + onError(apiFile, error); + } + + protected static void Progress(OnFileOpProgress onProgress, ApiFile apiFile, string status, string subStatus = "", float pct = 0.0f) + { + if (apiFile == null) + apiFile = new ApiFile(); + + if (onProgress != null) + onProgress(apiFile, status, subStatus, pct); + } + + protected static bool CheckCancelled(FileOpCancelQuery cancelQuery, OnFileOpError onError, ApiFile apiFile) + { + if (apiFile == null) + { + Debug.LogError("apiFile was null"); + return true; + } + + if (cancelQuery != null && cancelQuery(apiFile)) + { + Debug.Log("ApiFile " + apiFile.ToStringBrief() + ": Operation cancelled"); + if (onError != null) + onError(apiFile, "Cancelled by user."); + return true; + } + + return false; + } + + protected static void CleanupTempFiles(string subFolderName) + { + Instance.StartCoroutine(Instance.CleanupTempFilesInternal(subFolderName)); + } + + protected IEnumerator CleanupTempFilesInternal(string subFolderName) + { + if (!string.IsNullOrEmpty(subFolderName)) + { + string folder = VRC.Tools.GetTempFolderPath(subFolderName); + + while (Directory.Exists(folder)) + { + try + { + if (Directory.Exists(folder)) + Directory.Delete(folder, true); + } + catch (System.Exception) + { + } + + yield return null; + } + } + } + + private static void CheckInstance() + { + if (mInstance == null) + { + GameObject go = new GameObject("ApiFileHelper"); + mInstance = go.AddComponent<ApiFileHelper>(); + + try + { + GameObject.DontDestroyOnLoad(go); + } + catch + { + } + } + } + + private float GetServerProcessingWaitTimeoutForDataSize(int size) + { + float timeoutMultiplier = Mathf.Ceil((float)size / (float)SERVER_PROCESSING_WAIT_TIMEOUT_CHUNK_SIZE); + return Mathf.Clamp(timeoutMultiplier * SERVER_PROCESSING_WAIT_TIMEOUT_PER_CHUNK_SIZE, SERVER_PROCESSING_WAIT_TIMEOUT_PER_CHUNK_SIZE, SERVER_PROCESSING_MAX_WAIT_TIMEOUT); + } + + private bool UploadFileComponentValidateFileDesc(ApiFile apiFile, string filename, string md5Base64, long fileSize, ApiFile.Version.FileDescriptor fileDesc, Action<ApiFile> onSuccess, Action<string> onError) + { + if (fileDesc.status != ApiFile.Status.Waiting) + { + // nothing to do (might be a retry) + Debug.Log("UploadFileComponent: (file record not in waiting status, done)"); + if (onSuccess != null) + onSuccess(apiFile); + return false; + } + + if (fileSize != fileDesc.sizeInBytes) + { + if (onError != null) + onError("File size does not match version descriptor"); + return false; + } + if (string.Compare(md5Base64, fileDesc.md5) != 0) + { + if (onError != null) + onError("File MD5 does not match version descriptor"); + return false; + } + + // make sure file is right size + long tempSize = 0; + string errorStr = ""; + if (!VRC.Tools.GetFileSize(filename, out tempSize, out errorStr)) + { + if (onError != null) + onError("Couldn't get file size"); + return false; + } + if (tempSize != fileSize) + { + if (onError != null) + onError("File size does not match input size"); + return false; + } + + return true; + } + + private IEnumerator UploadFileComponentDoSimpleUpload(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + Action<ApiFile> onSuccess, + Action<string> onError, + Action<long, long> onProgress, + FileOpCancelQuery cancelQuery) + { + OnFileOpError onCancelFunc = delegate (ApiFile file, string s) + { + if (onError != null) + onError(s); + }; + + string uploadUrl = ""; + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.StartSimpleUpload(fileDescriptorType, + (c) => + { + uploadUrl = (c as ApiDictContainer).ResponseDictionary["url"] as string; + wait = false; + }, + (c) => + { + errorStr = "Failed to start upload: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + // PUT file + { + bool wait = true; + string errorStr = ""; + + VRC.HttpRequest req = ApiFile.PutSimpleFileToURL(uploadUrl, filename, GetMimeTypeFromExtension(Path.GetExtension(filename)), md5Base64, true, + delegate () + { + wait = false; + }, + delegate (string error) + { + errorStr = "Failed to upload file: " + error; + wait = false; + }, + delegate (long uploaded, long length) + { + if (onProgress != null) + onProgress(uploaded, length); + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + if (req != null) + req.Abort(); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + yield break; + } + } + + // finish upload + while (true) + { + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.FinishUpload(fileDescriptorType, null, + (c) => + { + apiFile = c.Model as ApiFile; + wait = false; + }, + (c) => + { + errorStr = "Failed to finish upload: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = false; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + } + + private IEnumerator UploadFileComponentDoMultipartUpload(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + Action<ApiFile> onSuccess, + Action<string> onError, + Action<long, long> onProgress, + FileOpCancelQuery cancelQuery) + { + FileStream fs = null; + OnFileOpError onCancelFunc = delegate (ApiFile file, string s) + { + if (fs != null) + fs.Close(); + if (onError != null) + onError(s); + }; + + // query multipart upload status. + // we might be resuming a previous upload + ApiFile.UploadStatus uploadStatus = null; + { + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.GetUploadStatus(apiFile.GetLatestVersionNumber(), fileDescriptorType, + (c) => + { + uploadStatus = c.Model as ApiFile.UploadStatus; + wait = false; + + VRC.Core.Logger.Log("Found existing multipart upload status (next part = " + uploadStatus.nextPartNumber + ")", DebugLevel.All); + }, + (c) => + { + errorStr = "Failed to query multipart upload status: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + if (!worthRetry) + break; + } + } + + // split file into chunks + try + { + fs = File.OpenRead(filename); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't open file: " + e.Message); + yield break; + } + + byte[] buffer = new byte[kMultipartUploadChunkSize * 2]; + + long totalBytesUploaded = 0; + List<string> etags = new List<string>(); + if (uploadStatus != null) + etags = uploadStatus.etags.ToList(); + + int numParts = Mathf.Max(1, Mathf.FloorToInt((float)fs.Length / (float)kMultipartUploadChunkSize)); + for (int partNumber = 1; partNumber <= numParts; partNumber++) + { + // read chunk + int bytesToRead = partNumber < numParts ? kMultipartUploadChunkSize : (int)(fs.Length - fs.Position); + int bytesRead = 0; + try + { + bytesRead = fs.Read(buffer, 0, bytesToRead); + } + catch (Exception e) + { + fs.Close(); + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + if (bytesRead != bytesToRead) + { + fs.Close(); + if (onError != null) + onError("Couldn't read file: read incorrect number of bytes from stream"); + yield break; + } + + // check if this part has been upload already + // NOTE: uploadStatus.nextPartNumber == number of parts already uploaded + if (uploadStatus != null && partNumber <= uploadStatus.nextPartNumber) + { + totalBytesUploaded += bytesRead; + continue; + } + + // start upload + string uploadUrl = ""; + + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.StartMultipartUpload(fileDescriptorType, partNumber, + (c) => + { + uploadUrl = (c as ApiDictContainer).ResponseDictionary["url"] as string; + wait = false; + }, + (c) => + { + errorStr = "Failed to start part upload: " + c.Error; + wait = false; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + fs.Close(); + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + // PUT file part + { + bool wait = true; + string errorStr = ""; + + VRC.HttpRequest req = ApiFile.PutMultipartDataToURL(uploadUrl, buffer, bytesRead, GetMimeTypeFromExtension(Path.GetExtension(filename)), true, + delegate (string etag) + { + if (!string.IsNullOrEmpty(etag)) + etags.Add(etag); + totalBytesUploaded += bytesRead; + wait = false; + }, + delegate (string error) + { + errorStr = "Failed to upload data: " + error; + wait = false; + }, + delegate (long uploaded, long length) + { + if (onProgress != null) + onProgress(totalBytesUploaded + uploaded, fileSize); + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + if (req != null) + req.Abort(); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + fs.Close(); + if (onError != null) + onError(errorStr); + yield break; + } + } + } + + // finish upload + while (true) + { + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.FinishUpload(fileDescriptorType, etags, + (c) => + { + apiFile = c.Model as ApiFile; + wait = false; + }, + (c) => + { + errorStr = "Failed to finish upload: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + fs.Close(); + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + fs.Close(); + } + + private IEnumerator UploadFileComponentVerifyRecord(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + ApiFile.Version.FileDescriptor fileDesc, + Action<ApiFile> onSuccess, + Action<string> onError, + Action<long, long> onProgress, + FileOpCancelQuery cancelQuery) + { + OnFileOpError onCancelFunc = delegate (ApiFile file, string s) + { + if (onError != null) + onError(s); + }; + + float initialStartTime = Time.realtimeSinceStartup; + float startTime = initialStartTime; + float timeout = GetServerProcessingWaitTimeoutForDataSize(fileDesc.sizeInBytes); + float waitDelay = SERVER_PROCESSING_INITIAL_RETRY_TIME; + float maxDelay = SERVER_PROCESSING_MAX_RETRY_TIME; + + while (true) + { + if (apiFile == null) + { + if (onError != null) + onError("ApiFile is null"); + yield break; + } + + var desc = apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), fileDescriptorType); + if (desc == null) + { + if (onError != null) + onError("File descriptor is null ('" + fileDescriptorType + "')"); + yield break; + } + + if (desc.status != ApiFile.Status.Waiting) + { + // upload completed or is processing + break; + } + + // wait for next poll + while (Time.realtimeSinceStartup - startTime < waitDelay) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + + if (Time.realtimeSinceStartup - initialStartTime > timeout) + { + if (onError != null) + onError("Couldn't verify upload status: Timed out wait for server processing"); + yield break; + } + + yield return null; + } + + + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.Refresh( + (c) => + { + wait = false; + }, + (c) => + { + errorStr = "Couldn't verify upload status: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + if (!worthRetry) + break; + } + + waitDelay = Mathf.Min(waitDelay * 2, maxDelay); + startTime = Time.realtimeSinceStartup; + } + + if (onSuccess != null) + onSuccess(apiFile); + } + + private IEnumerator UploadFileComponentInternal(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + Action<ApiFile> onSuccess, + Action<string> onError, + Action<long, long> onProgress, + FileOpCancelQuery cancelQuery) + { + VRC.Core.Logger.Log("UploadFileComponent: " + fileDescriptorType + " (" + apiFile.id + "): " + filename, DebugLevel.All); + ApiFile.Version.FileDescriptor fileDesc = apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), fileDescriptorType); + + if (!UploadFileComponentValidateFileDesc(apiFile, filename, md5Base64, fileSize, fileDesc, onSuccess, onError)) + yield break; + + switch (fileDesc.category) + { + case ApiFile.Category.Simple: + yield return UploadFileComponentDoSimpleUpload(apiFile, fileDescriptorType, filename, md5Base64, fileSize, onSuccess, onError, onProgress, cancelQuery); + break; + case ApiFile.Category.Multipart: + yield return UploadFileComponentDoMultipartUpload(apiFile, fileDescriptorType, filename, md5Base64, fileSize, onSuccess, onError, onProgress, cancelQuery); + break; + default: + if (onError != null) + onError("Unknown file category type: " + fileDesc.category); + yield break; + } + + yield return UploadFileComponentVerifyRecord(apiFile, fileDescriptorType, filename, md5Base64, fileSize, fileDesc, onSuccess, onError, onProgress, cancelQuery); + } + } +} + +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ApiFileHelper.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ApiFileHelper.cs.meta new file mode 100644 index 00000000..29e6938a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ApiFileHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36c0d886a26373c46be857f2fc441071 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/AudioManagerSettings.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/AudioManagerSettings.cs new file mode 100644 index 00000000..108bd7e2 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/AudioManagerSettings.cs @@ -0,0 +1,20 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace VRC.SDKBase +{ + 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 + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/AudioManagerSettings.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/AudioManagerSettings.cs.meta new file mode 100644 index 00000000..91be94d7 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/AudioManagerSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: acadc6659c5ab3446ad0d5de2563f95f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/CommunityLabsConstants.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/CommunityLabsConstants.cs new file mode 100644 index 00000000..31ec162d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/CommunityLabsConstants.cs @@ -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."; + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/CommunityLabsConstants.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/CommunityLabsConstants.cs.meta new file mode 100644 index 00000000..5a616c22 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/CommunityLabsConstants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d047eaa3325d654aa62ccad6f73eb93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ContentUploadedDialog.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ContentUploadedDialog.cs new file mode 100644 index 00000000..2d136979 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ContentUploadedDialog.cs @@ -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 diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ContentUploadedDialog.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ContentUploadedDialog.cs.meta new file mode 100644 index 00000000..b589cde9 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/ContentUploadedDialog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e1c693656bf5d584b87df969efeb5536 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/GameViewMethods.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/GameViewMethods.cs new file mode 100644 index 00000000..50504090 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/GameViewMethods.cs @@ -0,0 +1,49 @@ +using System; +using System.Reflection; + +namespace VRC. SDKBase +{ + public static class GameViewMethods + { + private static readonly Type GameViewType = System.Type.GetType("UnityEditor.GameView,UnityEditor"); + private static readonly Type PlayModeViewType = System.Type.GetType("UnityEditor.PlayModeView, UnityEditor"); + + public static int GetSelectedSizeIndex() + { + return (int) GetSelectedSizeProperty().GetValue(GetPlayModeViewObject()); + } + + public static void SetSelectedSizeIndex(int value) + { + var selectedSizeIndexProp = GetSelectedSizeProperty(); + selectedSizeIndexProp.SetValue(GetPlayModeViewObject(), value, null); + } + + // Set it to something else just to force a refresh + public static void ResizeGameView() + { + int current = GetSelectedSizeIndex(); + SetSelectedSizeIndex(current == 0 ? 1 : 0); + } + + private static PropertyInfo GetSelectedSizeProperty() + { + return GameViewType.GetProperty("selectedSizeIndex", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + } + + private static Object GetPlayModeViewObject() + { + MethodInfo GetMainPlayModeView = PlayModeViewType.GetMethod("GetMainPlayModeView", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + return GetMainPlayModeView.Invoke(null, null); + } + + public static void Repaint() + { + MethodInfo RepaintAll = PlayModeViewType.GetMethod("RepaintAll", BindingFlags.NonPublic | BindingFlags.Static); + RepaintAll.Invoke(GetPlayModeViewObject(), null); + } + + } +}
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/GameViewMethods.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/GameViewMethods.cs.meta new file mode 100644 index 00000000..38eb991a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/GameViewMethods.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d0c461e358764cd1ab95544e34b0346c +timeCreated: 1627603592
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback.meta new file mode 100644 index 00000000..c943a0ce --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 546dc9c02d5c61d438c41f413d3cf866 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback/FallbackMaterialCache.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback/FallbackMaterialCache.cs new file mode 100644 index 00000000..5a65be9b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback/FallbackMaterialCache.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +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($"Attempted to add a duplicate fallback material '{fallbackMaterial.name}' for original material '{material.name}'."); + } + } + + public bool TryGetFallbackMaterial(Material material, out Material fallbackMaterial) + { + if(material != null) + { + return _fallbackMaterialCache.TryGetValue(material, out fallbackMaterial); + } + + fallbackMaterial = null; + return false; + } + + public void Clear() + { + Material[] cachedFallbackMaterials = _fallbackMaterialCache.Values.ToArray(); + for(int i = cachedFallbackMaterials.Length - 1; i >= 0; i--) + { + Object.Destroy(cachedFallbackMaterials[i]); + } + + _fallbackMaterialCache.Clear(); + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback/FallbackMaterialCache.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback/FallbackMaterialCache.cs.meta new file mode 100644 index 00000000..64637629 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/MaterialFallback/FallbackMaterialCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10121679f780956408f9a434a526f553 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeAPICreation.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeAPICreation.cs new file mode 100644 index 00000000..154fba15 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeAPICreation.cs @@ -0,0 +1,324 @@ +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 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 + +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeAPICreation.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeAPICreation.cs.meta new file mode 100644 index 00000000..a6fb7e10 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeAPICreation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3132e0ab7e16494a9d492087a1ca447 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeBlueprintCreation.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeBlueprintCreation.cs new file mode 100644 index 00000000..7017dbae --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeBlueprintCreation.cs @@ -0,0 +1,421 @@ +using UnityEngine; +using UnityEngine.UI; +using System.Collections; +using System.Collections.Generic; +using VRC.Core; +using VRC.SDKBase; + +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 Toggle tagFallback; + + 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.InitialFetchCurrentUser( + 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"); + + tagFallback.isOn = apiAvatar.tags.Contains("author_quest_fallback"); + tagFallback.transform.parent.gameObject.SetActive(true); + + switch (pipelineManager.fallbackStatus) + { + case PipelineManager.FallbackStatus.Valid: +#if UNITY_ANDROID + tagFallback.interactable = true; + tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback"; +#else + tagFallback.interactable = false; + tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (change only with Android upload)"; +#endif + break; + case PipelineManager.FallbackStatus.InvalidPerformance: + case PipelineManager.FallbackStatus.InvalidRig: + tagFallback.isOn = false; // need to remove tag on this upload, the updated version is not up-to-spec + tagFallback.interactable = false; + tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (avatar not valid, tag will be cleared)"; + break; + } + + blueprintDescription.text = apiAvatar.description; + shouldUpdateImageToggle.interactable = true; + shouldUpdateImageToggle.isOn = false; + liveBpImage.enabled = false; + bpImage.enabled = true; + + ImageDownloader.DownloadImage(apiAvatar.imageUrl, 0, (Texture2D obj) => bpImage.texture = obj, null); + } + 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; + tagFallback.isOn = false; + + // Janky fix for an avatar's blueprint image not showing up the very first time you press publish in a project until you resize the window + // can remove if we fix the underlying issue or move publishing out of Play Mode + string firstTimeResize = $"{Application.identifier}-firstTimeResize"; + if (!PlayerPrefs.HasKey(firstTimeResize)) + { + GameViewMethods.ResizeGameView(); + PlayerPrefs.SetInt(firstTimeResize, 1); + } + + tagFallback.transform.parent.gameObject.SetActive(true); + switch (pipelineManager.fallbackStatus) + { + case PipelineManager.FallbackStatus.Valid: +#if UNITY_ANDROID + tagFallback.interactable = true; + tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback"; +#else + tagFallback.interactable = false; + tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (change only with Android upload)"; +#endif + break; + case PipelineManager.FallbackStatus.InvalidPerformance: + case PipelineManager.FallbackStatus.InvalidRig: + tagFallback.transform.parent.gameObject.SetActive(true); + tagFallback.interactable = false; + tagFallback.GetComponentInChildren<Text>().text = "Use for Fallback (avatar not valid, tag will be cleared)"; + break; + } + } + } + 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"); + } + + if (tagFallback.isOn) + tags.Add("author_quest_fallback"); + + 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, obj => bpImage.texture = obj, null); + } + } + + void OnDestroy() + { + UnityEditor.EditorUtility.ClearProgressBar(); + UnityEditor.EditorPrefs.DeleteKey("currentBuildingAssetBundlePath"); + } + } +#endif +} + + diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeBlueprintCreation.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeBlueprintCreation.cs.meta new file mode 100644 index 00000000..cf873f35 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeBlueprintCreation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e5ebf65c5dceeb4c909aa7812bd2999 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeWorldCreation.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeWorldCreation.cs new file mode 100644 index 00000000..e002d2f8 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeWorldCreation.cs @@ -0,0 +1,681 @@ +#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.InitialFetchCurrentUser( + 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, + (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, obj => bpImage.texture = obj, null); + } + 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, obj => bpImage.texture = obj, null); + } + } + + 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 +} + + diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeWorldCreation.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeWorldCreation.cs.meta new file mode 100644 index 00000000..8121e7c2 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/RuntimeWorldCreation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2bd5ee5d69ee0f3449bf2f81fcb7f4e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/SceneSaver.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/SceneSaver.cs new file mode 100644 index 00000000..86d56a5d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/SceneSaver.cs @@ -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 + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/SceneSaver.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/SceneSaver.cs.meta new file mode 100644 index 00000000..454a9ab4 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/SceneSaver.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0d49300ad532d4ae6b569b28de5b7dac +timeCreated: 1446917779 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation.meta new file mode 100644 index 00000000..c172df80 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4d5dd0c5d2dfe12409684776a50ce130 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/AvatarValidation.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/AvatarValidation.cs new file mode 100644 index 00000000..39dd14a7 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/AvatarValidation.cs @@ -0,0 +1 @@ +/* Migration File: Intentionally Left Blank */
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/AvatarValidation.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/AvatarValidation.cs.meta new file mode 100644 index 00000000..f6e03bb1 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/AvatarValidation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d497915ac8463e048aeb2c934a36c299 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance.meta new file mode 100644 index 00000000..07fd6f1c --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16b48f3cc7015584c9d4157931f1c673 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformance.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformance.cs new file mode 100644 index 00000000..3269bc89 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformance.cs @@ -0,0 +1,165 @@ +using System.Collections; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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 + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformance.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformance.cs.meta new file mode 100644 index 00000000..8d06a338 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformance.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 15ecac6f7fdc1bc4fb723fee6f4635dd +timeCreated: 1540944864 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformanceCategory.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformanceCategory.cs new file mode 100644 index 00000000..847ac62c --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformanceCategory.cs @@ -0,0 +1,37 @@ +namespace VRC.SDKBase.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 + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformanceCategory.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformanceCategory.cs.meta new file mode 100644 index 00000000..449a3394 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/AvatarPerformanceCategory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f1ce994297384ff1bc330196df61b7ca +timeCreated: 1561267912
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters.meta new file mode 100644 index 00000000..b19c2f04 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bf5b2b0296bbecc4dba509652ba3eb24 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters/AbstractPerformanceFilter.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters/AbstractPerformanceFilter.cs new file mode 100644 index 00000000..9896fcc9 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters/AbstractPerformanceFilter.cs @@ -0,0 +1,109 @@ +using System.Collections; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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); + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters/AbstractPerformanceFilter.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters/AbstractPerformanceFilter.cs.meta new file mode 100644 index 00000000..6987057b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Filters/AbstractPerformanceFilter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: abda65e062e44213aa3e1f4c82b400a8 +timeCreated: 1563937347
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/MeshUtils.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/MeshUtils.cs new file mode 100644 index 00000000..e5acd99f --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/MeshUtils.cs @@ -0,0 +1,33 @@ +using UnityEngine; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/MeshUtils.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/MeshUtils.cs.meta new file mode 100644 index 00000000..4658d3db --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/MeshUtils.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f28c978154794266b38d686139c6b872 +timeCreated: 1561249515
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceFilterSet.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceFilterSet.cs new file mode 100644 index 00000000..b2637485 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceFilterSet.cs @@ -0,0 +1,46 @@ +using System.Collections; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Filters; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceFilterSet.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceFilterSet.cs.meta new file mode 100644 index 00000000..dce1427b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceFilterSet.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8cdca9d06d1b4732b9ccb82a38bb8d9c +timeCreated: 1563939583
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceInfoDisplayLevel.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceInfoDisplayLevel.cs new file mode 100644 index 00000000..d132fe4a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceInfoDisplayLevel.cs @@ -0,0 +1,12 @@ +namespace VRC.SDKBase.Validation.Performance +{ + public enum PerformanceInfoDisplayLevel + { + None, + + Verbose, + Info, + Warning, + Error + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceInfoDisplayLevel.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceInfoDisplayLevel.cs.meta new file mode 100644 index 00000000..c1ea78c5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceInfoDisplayLevel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a5ed7498cb1a46c78eab031f5f32448c +timeCreated: 1561267918
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceRating.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceRating.cs new file mode 100644 index 00000000..901d8494 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceRating.cs @@ -0,0 +1,12 @@ +namespace VRC.SDKBase.Validation.Performance +{ + public enum PerformanceRating + { + None = 0, + Excellent = 1, + Good = 2, + Medium = 3, + Poor = 4, + VeryPoor = 5 + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceRating.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceRating.cs.meta new file mode 100644 index 00000000..0175bfdb --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceRating.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5019a55ee9e2404c81bc61a39a010d8d +timeCreated: 1561267904
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceScannerSet.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceScannerSet.cs new file mode 100644 index 00000000..716c66f7 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceScannerSet.cs @@ -0,0 +1,44 @@ +using System.Collections; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Scanners; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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); + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceScannerSet.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceScannerSet.cs.meta new file mode 100644 index 00000000..7a3b582a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/PerformanceScannerSet.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4afb61f36d144fc381114cd7f78d13e7 +timeCreated: 1561259704
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners.meta new file mode 100644 index 00000000..3029e53c --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: edddcc1b6de3f554e8a13a0b94d47b96 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AbstractPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AbstractPerformanceScanner.cs new file mode 100644 index 00000000..9face97c --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AbstractPerformanceScanner.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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++; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AbstractPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AbstractPerformanceScanner.cs.meta new file mode 100644 index 00000000..357ef0f0 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AbstractPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0bd0691a021844f49444a04a959d6328 +timeCreated: 1561279121
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AnimatorPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AnimatorPerformanceScanner.cs new file mode 100644 index 00000000..6aa73010 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AnimatorPerformanceScanner.cs @@ -0,0 +1,44 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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<UnityEngine.Animation> animationBuffer = new List<UnityEngine.Animation>(); + yield return ScanAvatarForComponentsOfType(avatarObject, animationBuffer); + if(shouldIgnoreComponent != null) + { + animationBuffer.RemoveAll(c => shouldIgnoreComponent(c)); + } + + animatorCount += animationBuffer.Count; + + perfStats.animatorCount = animatorCount; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AnimatorPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AnimatorPerformanceScanner.cs.meta new file mode 100644 index 00000000..edec0a0f --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AnimatorPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 08c8e931d0544866a0f626855d9c1841 +timeCreated: 1561251363
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AudioPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AudioPerformanceScanner.cs new file mode 100644 index 00000000..a8a5944d --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AudioPerformanceScanner.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AudioPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AudioPerformanceScanner.cs.meta new file mode 100644 index 00000000..6cbd2a09 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/AudioPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b3a8bba736414d1aaa9e766da27b56b5 +timeCreated: 1561255618
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ClothPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ClothPerformanceScanner.cs new file mode 100644 index 00000000..d8332184 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ClothPerformanceScanner.cs @@ -0,0 +1,48 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ClothPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ClothPerformanceScanner.cs.meta new file mode 100644 index 00000000..b04d8aef --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ClothPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0cec88b5a46f459195f10a2f11fddb2f +timeCreated: 1561255843
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/DynamicBonePerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/DynamicBonePerformanceScanner.cs new file mode 100644 index 00000000..860df33a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/DynamicBonePerformanceScanner.cs @@ -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 VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/DynamicBonePerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/DynamicBonePerformanceScanner.cs.meta new file mode 100644 index 00000000..60f0db5a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/DynamicBonePerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a226df494ef04404a9a47c714822ab9f +timeCreated: 1561251560
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LightPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LightPerformanceScanner.cs new file mode 100644 index 00000000..8d86c6b5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LightPerformanceScanner.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LightPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LightPerformanceScanner.cs.meta new file mode 100644 index 00000000..9c346056 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LightPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 405778fdc32c44c1bb9fdd0476fb0007 +timeCreated: 1561256390
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LineRendererPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LineRendererPerformanceScanner.cs new file mode 100644 index 00000000..1d1faa85 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LineRendererPerformanceScanner.cs @@ -0,0 +1,31 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LineRendererPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LineRendererPerformanceScanner.cs.meta new file mode 100644 index 00000000..79875a56 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/LineRendererPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ec87392b85844f7bb526a48ec866a8f0 +timeCreated: 1561253047
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/MeshPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/MeshPerformanceScanner.cs new file mode 100644 index 00000000..9b5b1201 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/MeshPerformanceScanner.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/MeshPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/MeshPerformanceScanner.cs.meta new file mode 100644 index 00000000..ba7c1507 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/MeshPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 38bca10261df4ddfa10cff3b3bbb9428 +timeCreated: 1561249198
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ParticlePerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ParticlePerformanceScanner.cs new file mode 100644 index 00000000..3d7acea8 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ParticlePerformanceScanner.cs @@ -0,0 +1,114 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ParticlePerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ParticlePerformanceScanner.cs.meta new file mode 100644 index 00000000..54556038 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/ParticlePerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 10723e354ff14f98a49ab797b3f005e6 +timeCreated: 1561250267
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/PhysicsPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/PhysicsPerformanceScanner.cs new file mode 100644 index 00000000..c6b643da --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/PhysicsPerformanceScanner.cs @@ -0,0 +1,64 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/PhysicsPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/PhysicsPerformanceScanner.cs.meta new file mode 100644 index 00000000..ed550cf1 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/PhysicsPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6a94ecdeecd04f85824cc3244be5712a +timeCreated: 1561256481
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/TrailRendererPerformanceScanner.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/TrailRendererPerformanceScanner.cs new file mode 100644 index 00000000..c75e4cae --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/TrailRendererPerformanceScanner.cs @@ -0,0 +1,31 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using VRC.SDKBase.Validation.Performance.Stats; + +namespace VRC.SDKBase.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; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/TrailRendererPerformanceScanner.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/TrailRendererPerformanceScanner.cs.meta new file mode 100644 index 00000000..028dd032 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Scanners/TrailRendererPerformanceScanner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2efd714b564547b4be1ebd1f2700668b +timeCreated: 1561251810
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats.meta new file mode 100644 index 00000000..39e091dc --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6970bf241e7266748992480464b59687 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStats.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStats.cs new file mode 100644 index 00000000..44fd4041 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStats.cs @@ -0,0 +1,757 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using UnityEngine; + +namespace VRC.SDKBase.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 ImmutableArray<AvatarPerformanceCategory> _performanceCategories = Enum.GetValues(typeof(AvatarPerformanceCategory)) + .Cast<AvatarPerformanceCategory>() + .ToImmutableArray(); + + 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 Snapshot GetSnapshot() + { + return new Snapshot(this); + } + + 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 _performanceCategories) + { + 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 _performanceCategories) + { + 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(); + } + + // Mirror the AvatarPerformanceStats class even if some aren't used right now. + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + [SuppressMessage("ReSharper", "NotAccessedField.Global")] + public readonly struct Snapshot + { + public readonly string avatarName; + + // Stats + public readonly int? polyCount; + public readonly Bounds? aabb; + public readonly int? skinnedMeshCount; + public readonly int? meshCount; + public readonly int? materialCount; + public readonly int? animatorCount; + public readonly int? boneCount; + public readonly int? lightCount; + public readonly int? particleSystemCount; + public readonly int? particleTotalCount; + public readonly int? particleMaxMeshPolyCount; + public readonly bool? particleTrailsEnabled; + public readonly bool? particleCollisionEnabled; + public readonly int? trailRendererCount; + public readonly int? lineRendererCount; + public readonly int? dynamicBoneComponentCount; + public readonly int? dynamicBoneSimulatedBoneCount; + public readonly int? dynamicBoneColliderCount; + public readonly int? dynamicBoneCollisionCheckCount; // number of collider simulated bones excluding the root multiplied by the number of colliders + public readonly int? clothCount; + public readonly int? clothMaxVertices; + public readonly int? physicsColliderCount; + public readonly int? physicsRigidbodyCount; + public readonly int? audioSourceCount; + public readonly float? downloadSize; + + // Ratings + public readonly PerformanceRating overallRating; + public readonly PerformanceRating polyCountRating; + public readonly PerformanceRating aabbRating; + public readonly PerformanceRating skinnedMeshCountRating; + public readonly PerformanceRating meshCountRating; + public readonly PerformanceRating materialCountRating; + public readonly PerformanceRating animatorCountRating; + public readonly PerformanceRating boneCountRating; + public readonly PerformanceRating lightCountRating; + public readonly PerformanceRating particleSystemCountRating; + public readonly PerformanceRating particleTotalCountRating; + public readonly PerformanceRating particleMaxMeshPolyCountRating; + public readonly PerformanceRating particleTrailsEnabledRating; + public readonly PerformanceRating particleCollisionEnabledRating; + public readonly PerformanceRating trailRendererCountRating; + public readonly PerformanceRating lineRendererCountRating; + public readonly PerformanceRating dynamicBoneComponentCountRating; + public readonly PerformanceRating dynamicBoneSimulatedBoneCountRating; + public readonly PerformanceRating dynamicBoneColliderCountRating; + public readonly PerformanceRating dynamicBoneCollisionCheckCountRating; + public readonly PerformanceRating clothCountRating; + public readonly PerformanceRating clothMaxVerticesRating; + public readonly PerformanceRating physicsColliderCountRating; + public readonly PerformanceRating physicsRigidbodyCountRating; + public readonly PerformanceRating audioSourceCountRating; + public readonly PerformanceRating downloadSizeRating; + + public Snapshot(AvatarPerformanceStats avatarPerformanceStats) + { + avatarName = avatarPerformanceStats.avatarName; + polyCount = avatarPerformanceStats.polyCount; + aabb = avatarPerformanceStats.aabb; + skinnedMeshCount = avatarPerformanceStats.skinnedMeshCount; + meshCount = avatarPerformanceStats.meshCount; + materialCount = avatarPerformanceStats.materialCount; + animatorCount = avatarPerformanceStats.animatorCount; + boneCount = avatarPerformanceStats.boneCount; + lightCount = avatarPerformanceStats.lightCount; + particleSystemCount = avatarPerformanceStats.particleSystemCount; + particleTotalCount = avatarPerformanceStats.particleTotalCount; + particleMaxMeshPolyCount = avatarPerformanceStats.particleMaxMeshPolyCount; + particleTrailsEnabled = avatarPerformanceStats.particleTrailsEnabled; + particleCollisionEnabled = avatarPerformanceStats.particleCollisionEnabled; + trailRendererCount = avatarPerformanceStats.trailRendererCount; + lineRendererCount = avatarPerformanceStats.lineRendererCount; + dynamicBoneComponentCount = avatarPerformanceStats.dynamicBoneComponentCount; + dynamicBoneSimulatedBoneCount = avatarPerformanceStats.dynamicBoneSimulatedBoneCount; + dynamicBoneColliderCount = avatarPerformanceStats.dynamicBoneColliderCount; + dynamicBoneCollisionCheckCount = avatarPerformanceStats.dynamicBoneCollisionCheckCount; + clothCount = avatarPerformanceStats.clothCount; + clothMaxVertices = avatarPerformanceStats.clothMaxVertices; + physicsColliderCount = avatarPerformanceStats.physicsColliderCount; + physicsRigidbodyCount = avatarPerformanceStats.physicsRigidbodyCount; + audioSourceCount = avatarPerformanceStats.audioSourceCount; + downloadSize = avatarPerformanceStats.downloadSize; + + overallRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.Overall); + polyCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.PolyCount); + aabbRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.AABB); + skinnedMeshCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.SkinnedMeshCount); + meshCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.MeshCount); + materialCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.MaterialCount); + animatorCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.AnimatorCount); + boneCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.BoneCount); + lightCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.LightCount); + particleSystemCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.ParticleSystemCount); + particleTotalCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.ParticleTotalCount); + particleMaxMeshPolyCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.ParticleMaxMeshPolyCount); + particleTrailsEnabledRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.ParticleTrailsEnabled); + particleCollisionEnabledRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.ParticleCollisionEnabled); + trailRendererCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.TrailRendererCount); + lineRendererCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.LineRendererCount); + dynamicBoneComponentCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.DynamicBoneComponentCount); + dynamicBoneSimulatedBoneCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.DynamicBoneSimulatedBoneCount); + dynamicBoneColliderCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.DynamicBoneColliderCount); + dynamicBoneCollisionCheckCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.DynamicBoneCollisionCheckCount); + clothCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.ClothCount); + clothMaxVerticesRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.ClothMaxVertices); + physicsColliderCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.PhysicsColliderCount); + physicsRigidbodyCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.PhysicsRigidbodyCount); + audioSourceCountRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.AudioSourceCount); + downloadSizeRating = avatarPerformanceStats.GetPerformanceRatingForCategory(AvatarPerformanceCategory.DownloadSize); + } + + public override string ToString() + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(1024); + sb.AppendFormat("Avatar Name: {0}\n", avatarName); + sb.AppendFormat("Overall Performance: {0}\n", overallRating); + 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); + sb.AppendFormat("Download Size: {0} MB\n", downloadSize); + + return sb.ToString(); + } + } + + #endregion + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStats.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStats.cs.meta new file mode 100644 index 00000000..894716a6 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStats.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1bf4fb79a49d4b109c4dce6b38e5548e +timeCreated: 1561267926
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevel.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevel.cs new file mode 100644 index 00000000..c31b43a2 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevel.cs @@ -0,0 +1,32 @@ +using UnityEngine; + +namespace VRC.SDKBase.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; + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevel.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevel.cs.meta new file mode 100644 index 00000000..966fbdc0 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f742c36dce5730f4d96e37d82c330584 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevelSet.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevelSet.cs new file mode 100644 index 00000000..6490496b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevelSet.cs @@ -0,0 +1,15 @@ +using UnityEngine; +using UnityEngine.Serialization; + +namespace VRC.SDKBase.Validation.Performance.Stats +{ + public class AvatarPerformanceStatsLevelSet : ScriptableObject + { + [FormerlySerializedAs("veryGood")] + public AvatarPerformanceStatsLevel excellent; + public AvatarPerformanceStatsLevel good; + public AvatarPerformanceStatsLevel medium; + [FormerlySerializedAs("bad")] + public AvatarPerformanceStatsLevel poor; + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevelSet.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevelSet.cs.meta new file mode 100644 index 00000000..603c0a80 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/Performance/Stats/AvatarPerformanceStatsLevelSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 468554b1bfc447f41a20a2f5bae65d16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ShaderValidation.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ShaderValidation.cs new file mode 100644 index 00000000..0a69a1af --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ShaderValidation.cs @@ -0,0 +1,86 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace VRC.SDKBase.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(); + } + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ShaderValidation.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ShaderValidation.cs.meta new file mode 100644 index 00000000..1b9e8680 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ShaderValidation.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bef0a8d1d2c547119a62b7d7a5c512ea +timeCreated: 1563940360
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ValidationUtils.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ValidationUtils.cs new file mode 100644 index 00000000..e6dfef95 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ValidationUtils.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using UnityEngine; +using System.Reflection; + +namespace VRC.SDKBase.Validation +{ + public static class ValidationUtils + { + public static void RemoveIllegalComponents(GameObject target, HashSet<Type> whitelist, bool retry = true, bool onlySceneObjects = false, bool logStripping = true) + { + List<Component> foundComponents = FindIllegalComponents(target, whitelist); + foreach(Component component in foundComponents) + { + if(component == null) + { + continue; + } + + if(onlySceneObjects && component.GetInstanceID() < 0) + { + continue; + } + + if(logStripping) + { + Core.Logger.LogWarning($"Removing {component.GetType().Name} comp from {component.gameObject.name}"); + } + + RemoveComponent(component); + } + } + + public static List<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 readonly Dictionary<Type, ImmutableArray<RequireComponent>> _requireComponentsCache = new Dictionary<Type, ImmutableArray<RequireComponent>>(); + private static void RemoveDependencies(Component rootComponent) + { + if (rootComponent == null) + { + return; + } + + Component[] components = rootComponent.GetComponents<Component>(); + if (components == null || components.Length == 0) + { + return; + } + + Type compType = rootComponent.GetType(); + foreach (var siblingComponent in components) + { + if (siblingComponent == null) + { + continue; + } + + Type siblingComponentType = siblingComponent.GetType(); + if(!_requireComponentsCache.TryGetValue(siblingComponentType, out ImmutableArray<RequireComponent> requiredComponentAttributes)) + { + requiredComponentAttributes = siblingComponentType.GetCustomAttributes(typeof(RequireComponent), true).Cast<RequireComponent>().ToImmutableArray(); + _requireComponentsCache.Add(siblingComponentType, requiredComponentAttributes); + } + + bool deleteMe = false; + foreach (RequireComponent requireComponent in requiredComponentAttributes) + { + if (requireComponent == null) + { + continue; + } + + if(requireComponent.m_Type0 != compType && requireComponent.m_Type1 != compType && requireComponent.m_Type2 != compType) + { + continue; + } + + deleteMe = true; + break; + } + + if (deleteMe && siblingComponent != rootComponent) + { + RemoveComponent(siblingComponent); + } + } + } + + 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); + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ValidationUtils.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ValidationUtils.cs.meta new file mode 100644 index 00000000..adc2f728 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/ValidationUtils.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8a90ec11b51863c4cb2d8a8cee31c2fb +timeCreated: 1504829091 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/WorldValidation.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/WorldValidation.cs new file mode 100644 index 00000000..3dab42c2 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/WorldValidation.cs @@ -0,0 +1,682 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +#if TextMeshPro +using TMPro; +#endif +using UnityEngine; +using UnityEngine.Playables; +using UnityEngine.Timeline; +using UnityEngine.UI; + +namespace VRC.SDKBase.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", + #if !UNITY_2019_4_OR_NEWER + "UnityEngine.GUIText", + "UnityEngine.GUITexture", + #endif + "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", + #if !UNITY_2019_4_OR_NEWER + "UnityStandardAssets.Utility.SimpleActivatorMenu", + #endif + "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.Components.VRCObjectSync", + "VRC.SDK3.Components.VRCObjectPool", + "VRC.SDK3.Video.Components.VRCUnityVideoPlayer", + "VRC.SDK3.Video.Components.AVPro.VRCAVProVideoPlayer", + "VRC.SDK3.Video.Components.AVPro.VRCAVProVideoScreen", + "VRC.SDK3.Video.Components.AVPro.VRCAVProVideoSpeaker", + "VRC.SDK3.Midi.VRCMidiListener", + "VRC.Udon.UdonBehaviour", + "VRC.Udon.AbstractUdonBehaviourEventProxy", + "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(List<GameObject> targets, WhiteListConfiguration config, bool retry = true, HashSet<Type> tagWhitelistedTypes = null) + { + ConfigureWhiteList(config); + + HashSet<Type> whitelist = ValidationUtils.WhitelistedTypes($"world{config}", ComponentTypeWhiteList); + + // combine whitelist types from world tags with cached whitelist + if (tagWhitelistedTypes != null) + { + tagWhitelistedTypes.UnionWith(whitelist); + } + + foreach(GameObject target in targets) + { + ValidationUtils.RemoveIllegalComponents(target, (tagWhitelistedTypes == null) ? whitelist : tagWhitelistedTypes, retry, true, 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); + + // Must be called after AddScanned to avoid infinite recursion. + ScanDropdownTemplates(target, config); + } + + [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 ScanDropdownTemplates(GameObject target, WhiteListConfiguration config) + { + Dropdown[] dropdowns = target.GetComponentsInChildren<Dropdown>(true); + foreach(Dropdown dropdown in dropdowns) + { + if(dropdown == null) + { + continue; + } + + RectTransform dropdownTemplate = dropdown.template; + if(dropdownTemplate == null) + { + continue; + } + + ScanGameObject(dropdownTemplate.transform.root.gameObject, config); + } + + #if TextMeshPro + TMP_Dropdown[] tmpDropdowns = target.GetComponentsInChildren<TMP_Dropdown>(true); + foreach(TMP_Dropdown textMeshProDropdown in tmpDropdowns) + { + if(textMeshProDropdown == null) + { + continue; + } + + RectTransform dropdownTemplate = textMeshProDropdown.template; + if(dropdownTemplate == null) + { + continue; + } + + ScanGameObject(dropdownTemplate.transform.root.gameObject, config); + } + #endif + } + + 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); + } + } + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/WorldValidation.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/WorldValidation.cs.meta new file mode 100644 index 00000000..f84c2b09 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/Dependencies/VRChat/Scripts/Validation/WorldValidation.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9b03724cd556cb047b2da80492ea28a5 +timeCreated: 1504829091 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: |