├── .github ├── FUNDING.yml └── workflows │ ├── build-listing.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── Packages └── com.livedimensions.vrrefassist │ ├── Editor.meta │ ├── Editor │ ├── Automation.meta │ ├── Automation │ │ ├── BuildOrPlayModeCallback.cs │ │ ├── BuildOrPlayModeCallback.cs.meta │ │ ├── FieldAutomation.cs │ │ ├── FieldAutomation.cs.meta │ │ ├── OnBuildException.cs │ │ ├── OnBuildException.cs.meta │ │ ├── RunOnBuildAutomation.cs │ │ ├── RunOnBuildAutomation.cs.meta │ │ ├── RunOnBuildMethods.cs │ │ ├── RunOnBuildMethods.cs.meta │ │ ├── SingletonAutomation.cs │ │ └── SingletonAutomation.cs.meta │ ├── Extensions.meta │ ├── Extensions │ │ ├── UnityEditorExtensions.cs │ │ └── UnityEditorExtensions.cs.meta │ ├── LiveDimensions.VRRefAssist.Editor.asmdef │ ├── LiveDimensions.VRRefAssist.Editor.asmdef.meta │ ├── Utilities.meta │ └── Utilities │ │ ├── VRRADebugger.cs │ │ ├── VRRADebugger.cs.meta │ │ ├── VRRefAssistSettings.cs │ │ └── VRRefAssistSettings.cs.meta │ ├── LICENSE │ ├── LICENSE.meta │ ├── Runtime.meta │ ├── Runtime │ ├── CustomAttributes.meta │ ├── CustomAttributes │ │ ├── ClassAndMethodAttributes.cs │ │ ├── ClassAndMethodAttributes.cs.meta │ │ ├── FieldAttributes.cs │ │ └── FieldAttributes.cs.meta │ ├── LiveDimensions.VRRefAssist.Runtime.asmdef │ └── LiveDimensions.VRRefAssist.Runtime.asmdef.meta │ ├── package.json │ └── package.json.meta ├── README.md └── Website ├── app.js ├── banner.png ├── favicon.ico ├── index.html └── styles.css /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: LiveDimensions 2 | -------------------------------------------------------------------------------- /.github/workflows/build-listing.yml: -------------------------------------------------------------------------------- 1 | name: Build Repo Listing 2 | 3 | env: 4 | listPublishDirectory: Website 5 | pathToCi: ci 6 | 7 | on: 8 | workflow_dispatch: 9 | workflow_run: 10 | workflows: [Build Release] 11 | types: 12 | - completed 13 | release: 14 | types: [published, created, edited, unpublished, deleted, released] 15 | 16 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 17 | permissions: 18 | contents: read 19 | pages: write 20 | id-token: write 21 | 22 | # Allow one concurrent deployment 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: true 26 | 27 | jobs: 28 | 29 | # Build the VPM Listing Website and deploy to GitHub Pages 30 | build-listing: 31 | name: build-listing 32 | environment: 33 | name: github-pages 34 | url: ${{ steps.deployment.outputs.page_url }} 35 | runs-on: ubuntu-latest 36 | steps: 37 | 38 | # Checkout Local Repository 39 | - name: Checkout Local Repository 40 | uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac 41 | 42 | # Checkout Automation Repository without removing prior checkouts 43 | - name: Checkout Automation Repository 44 | uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac 45 | with: 46 | repository: vrchat-community/package-list-action 47 | path: ${{ env.pathToCi }} 48 | clean: false 49 | 50 | # Load cached data from previous runs 51 | - name: Restore Cache 52 | uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 53 | with: 54 | path: | 55 | ${{ env.pathToCi }}/.nuke/temp 56 | ~/.nuget/packages 57 | key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }} 58 | 59 | # Build Package Version Listing with Nuke 60 | - name: Build Package Version Listing 61 | run: ${{ env.pathToCi }}/build.cmd BuildRepoListing --root ${{ env.pathToCi }} --list-publish-directory $GITHUB_WORKSPACE/${{ env.listPublishDirectory }} --current-package-name ${{ vars.PACKAGE_NAME }} 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | 65 | # Prepare for GitHub Pages deployment 66 | - name: Setup Pages 67 | uses: actions/configure-pages@f156874f8191504dae5b037505266ed5dda6c382 68 | 69 | # Upload the VPM Listing Website to GitHub Pages artifacts 70 | - name: Upload Pages Artifact 71 | uses: actions/upload-pages-artifact@a753861a5debcf57bf8b404356158c8e1e33150c 72 | with: 73 | path: ${{ env.listPublishDirectory }} 74 | 75 | # Deploy the uploaded VPM Listing Website to GitHub Pages 76 | - name: Deploy to GitHub Pages 77 | id: deployment 78 | uses: actions/deploy-pages@9dbe3824824f8a1377b8e298bafde1a50ede43e5 -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | 8 | # Validate Repository Configuration 9 | config: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | config_package: ${{ steps.config_package.outputs.configPackage }} 13 | steps: 14 | 15 | # Ensure that required repository variable has been created for the Package 16 | - name: Validate Package Config 17 | id: config_package 18 | run: | 19 | if [ "${{ vars.PACKAGE_NAME }}" != "" ]; then 20 | echo "configPackage=true" >> $GITHUB_OUTPUT; 21 | else 22 | echo "configPackage=false" >> $GITHUB_OUTPUT; 23 | fi 24 | 25 | # Build and release the Package 26 | # If the repository is not configured properly, this job will be skipped 27 | build: 28 | needs: config 29 | runs-on: ubuntu-latest 30 | permissions: 31 | contents: write 32 | env: 33 | packagePath: Packages/${{ vars.PACKAGE_NAME }} 34 | if: needs.config.outputs.config_package == 'true' 35 | steps: 36 | 37 | # Checkout Local Repository 38 | - name: Checkout 39 | uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac 40 | 41 | # Get the Package version based on the package.json file 42 | - name: Get Version 43 | id: version 44 | uses: zoexx/github-action-json-file-properties@b9f36ce6ee6fe2680cd3c32b2c62e22eade7e590 45 | with: 46 | file_path: "${{ env.packagePath }}/package.json" 47 | prop_path: "version" 48 | 49 | # Configure the Environment Variables needed for releasing the Package 50 | - name: Set Environment Variables 51 | run: | 52 | echo "zipFile=${{ vars.PACKAGE_NAME }}-${{ steps.version.outputs.value }}".zip >> $GITHUB_ENV 53 | echo "unityPackage=${{ vars.PACKAGE_NAME }}-${{ steps.version.outputs.value }}.unitypackage" >> $GITHUB_ENV 54 | echo "version=${{ steps.version.outputs.value }}" >> $GITHUB_ENV 55 | 56 | # Zip the Package for release 57 | - name: Create Package Zip 58 | working-directory: "${{ env.packagePath }}" 59 | run: zip -r "${{ github.workspace }}/${{ env.zipFile }}" . 60 | 61 | # Build a list of .meta files for future use 62 | - name: Track Package Meta Files 63 | run: find "${{ env.packagePath }}/" -name \*.meta >> metaList 64 | 65 | # Make a UnityPackage version of the Package for release 66 | - name: Create UnityPackage 67 | uses: pCYSl5EDgo/create-unitypackage@cfcd3cf0391a5ef1306342794866a9897c32af0b 68 | with: 69 | package-path: ${{ env.unityPackage }} 70 | include-files: metaList 71 | 72 | # Make a release tag of the version from the package.json file 73 | - name: Create Tag 74 | id: tag_version 75 | uses: rickstaa/action-create-tag@88dbf7ff6fe2405f8e8f6c6fdfd78829bc631f83 76 | with: 77 | tag: "${{ env.version }}" 78 | 79 | # Publish the Release to GitHub 80 | - name: Make Release 81 | uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 82 | with: 83 | files: | 84 | ${{ env.zipFile }} 85 | ${{ env.unityPackage }} 86 | ${{ env.packagePath }}/package.json 87 | tag_name: ${{ env.version }} 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/.git 3 | !/.github 4 | !/.gitignore 5 | !/.LICENCE 6 | !/.README.md 7 | !/Packages 8 | /Packages/* 9 | !/Packages/com.livedimensions.vrrefassist 10 | /Packages/com.vrchat.core.vpm-resolver/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 LiveDimensions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5caa74e4d44111247b7d2f6a04a37f79 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 40e964f6aa6647ffa017f1ef60e8504d 3 | timeCreated: 1664660872 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/BuildOrPlayModeCallback.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEditor.Build; 3 | using UnityEditor.Build.Reporting; 4 | #if VRC_SDK_VRCSDK3 5 | using VRC.SDKBase.Editor.BuildPipeline; 6 | #endif 7 | 8 | namespace VRRefAssist.Editor.Automation 9 | { 10 | #if VRC_SDK_VRCSDK3 11 | internal class VRCBuildCallback : IVRCSDKBuildRequestedCallback 12 | { 13 | public int callbackOrder => -1; 14 | 15 | public bool OnBuildRequested(VRCSDKRequestedBuildType requestedBuildType) 16 | { 17 | if (requestedBuildType == VRCSDKRequestedBuildType.Avatar) return true; 18 | 19 | return BuildOrPlayModeCallback.ExecuteAutomation(true); 20 | } 21 | } 22 | #else 23 | internal class UnityBuildCallback : IPreprocessBuildWithReport 24 | { 25 | public int callbackOrder => -1; 26 | 27 | public void OnPreprocessBuild(BuildReport report) 28 | { 29 | if (!BuildOrPlayModeCallback.ExecuteAutomation(true)) 30 | { 31 | throw new BuildFailedException("Stopping build as requested by VRRefAssist!"); 32 | } 33 | } 34 | } 35 | #endif 36 | 37 | [InitializeOnLoad] 38 | public static class BuildOrPlayModeCallback 39 | { 40 | static BuildOrPlayModeCallback() 41 | { 42 | EditorApplication.playModeStateChanged += OnPlayModeChanged; 43 | } 44 | 45 | private static void OnPlayModeChanged(PlayModeStateChange state) 46 | { 47 | VRRADebugger.Log($"Play mode state changed: {state}"); 48 | if (state != PlayModeStateChange.ExitingEditMode) return; 49 | 50 | if(!ExecuteAutomation(true)) 51 | { 52 | VRRADebugger.LogError("Automation failed, stopping play mode!"); 53 | EditorApplication.isPlaying = false; 54 | } 55 | } 56 | 57 | public static bool ExecuteAutomation(bool buildRequested) 58 | { 59 | // 60 | //BUILD REQUESTED 61 | // 62 | if (buildRequested) 63 | { 64 | RunOnBuildMethods.CacheMonoInstances(); 65 | 66 | RunOnBuildAutomation.RunOnBuildMethodsWithExecuteOrderType(RunOnBuildAutomation.ExecuteOrderType.PreFieldAutomation, out bool cancelBuild); 67 | if (cancelBuild) 68 | { 69 | return false; 70 | } 71 | 72 | FieldAutomation.ExecuteAllFieldAutomation(out cancelBuild); 73 | if (cancelBuild) 74 | { 75 | return false; 76 | } 77 | 78 | SingletonAutomation.SetAllSingletonReferences(out cancelBuild); 79 | if (cancelBuild) 80 | { 81 | return false; 82 | } 83 | 84 | 85 | RunOnBuildAutomation.RunOnBuildMethodsWithExecuteOrderType(RunOnBuildAutomation.ExecuteOrderType.PostFieldAutomation, out cancelBuild); 86 | if (cancelBuild) 87 | { 88 | return false; 89 | } 90 | 91 | return true; 92 | } 93 | 94 | // 95 | //ENTERING PLAY MODE 96 | // 97 | bool executeRunOnBuildMethodsWhenEnteringPlayMode = VRRefAssistSettings.GetOrCreateSettings().executeRunOnBuildMethodsWhenEnteringPlayMode; 98 | 99 | if (executeRunOnBuildMethodsWhenEnteringPlayMode) 100 | { 101 | RunOnBuildMethods.CacheMonoInstances(); 102 | RunOnBuildAutomation.RunOnBuildMethodsWithExecuteOrderType(RunOnBuildAutomation.ExecuteOrderType.PreFieldAutomation); 103 | } 104 | 105 | if (VRRefAssistSettings.GetOrCreateSettings().executeFieldAutomationWhenEnteringPlayMode) 106 | { 107 | FieldAutomation.ExecuteAllFieldAutomation(); 108 | SingletonAutomation.SetAllSingletonReferences(); 109 | } 110 | 111 | if (executeRunOnBuildMethodsWhenEnteringPlayMode) 112 | { 113 | RunOnBuildAutomation.RunOnBuildMethodsWithExecuteOrderType(RunOnBuildAutomation.ExecuteOrderType.PostFieldAutomation); 114 | } 115 | 116 | return true; //There is no way of cancelling entering play mode 117 | } 118 | 119 | [MenuItem("VR RefAssist/Tools/Execute All Automation", priority = 100)] 120 | private static void ManuallyExecuteAllAutomation() 121 | { 122 | ExecuteAutomation(true); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/BuildOrPlayModeCallback.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 726176c7475e6fc4a8ae5a9b789ea547 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/FieldAutomation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using UnityEngine; 6 | using UnityEditor; 7 | using VRRefAssist.Editor.Extensions; 8 | 9 | namespace VRRefAssist.Editor.Automation 10 | { 11 | [InitializeOnLoad] 12 | public static class FieldAutomation 13 | { 14 | static FieldAutomation() 15 | { 16 | //Make list of all AutosetAttributes in the project by searching all assemblies and checking if is subclass of AutosetAttribute 17 | List autosetAttributes = TypeCache.GetTypesDerivedFrom().Where(t => !t.IsAbstract).ToList(); 18 | 19 | FieldAutomationTypeResults.Clear(); 20 | 21 | foreach (var autosetAttribute in autosetAttributes) 22 | { 23 | FieldAutomationTypeResults.Add(autosetAttribute, 0); 24 | } 25 | 26 | //Find all classes that inherit from MonoBehaviour 27 | List monoInheritors = TypeCache.GetTypesDerivedFrom().Where(t => !t.IsAbstract).ToList(); 28 | 29 | //Find all fields in all MonoBehaviour that have an AutosetAttribute and cache them 30 | foreach (var monoInheritor in monoInheritors) 31 | { 32 | FieldInfo[] fields = GetAllFields(monoInheritor).ToArray(); 33 | 34 | foreach (FieldInfo field in fields) 35 | { 36 | AutosetAttribute customAttribute = (AutosetAttribute)field.GetCustomAttribute(typeof(AutosetAttribute)); 37 | if (customAttribute == null) continue; 38 | if (!field.IsSerialized()) continue; 39 | 40 | if (cachedAutosetFields.ContainsKey(monoInheritor)) 41 | cachedAutosetFields[monoInheritor].Add(field); 42 | else 43 | cachedAutosetFields.Add(monoInheritor, new List { field }); 44 | } 45 | } 46 | } 47 | 48 | private static readonly Dictionary> cachedAutosetFields = new Dictionary>(); 49 | 50 | private static readonly Dictionary FieldAutomationTypeResults = new Dictionary(); 51 | 52 | private const BindingFlags FieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; 53 | 54 | private static bool showPopupWhenFieldAutomationFailed; 55 | 56 | [MenuItem("VR RefAssist/Tools/Execute Field Automation", priority = 202)] 57 | public static void ManuallyExecuteFieldAutomation() 58 | { 59 | ExecuteAllFieldAutomation(out _); 60 | } 61 | 62 | public static void ExecuteAllFieldAutomation() 63 | { 64 | ExecuteAllFieldAutomation(out _); 65 | } 66 | 67 | public static void ExecuteAllFieldAutomation(out bool cancelBuild) 68 | { 69 | FieldAutomationTypeResults.Clear(); 70 | 71 | showPopupWhenFieldAutomationFailed = VRRefAssistSettings.GetOrCreateSettings().showPopupWarnsForFailedFieldAutomation; 72 | 73 | cancelBuild = false; 74 | 75 | int count = 1; 76 | int total = cachedAutosetFields.Count; 77 | 78 | foreach (var cachedValuePair in cachedAutosetFields) 79 | { 80 | Type typeToFind = cachedValuePair.Key; 81 | 82 | if (UnityEditorExtensions.DisplaySmartUpdatingCancellableProgressBar("Running Field Automation...", count == total ? "Finishing..." : $"Progress: {count}/{total}.\tCurrent U# Behaviour: {typeToFind.Name}", count / (total - 1f))) 83 | { 84 | EditorUtility.ClearProgressBar(); 85 | 86 | cancelBuild = EditorUtility.DisplayDialog("Cancelled running Field Automation", "You have canceled field automation (Autoset fields)\nDo you want to cancel the build as well?", "Cancel", "Continue"); 87 | return; 88 | } 89 | 90 | count++; 91 | 92 | //When getting the monos, check for the ones that specifically are of the type, otherwise we will repeat classes that are inherited. 93 | #if UNITY_2020_1_OR_NEWER 94 | List monos = UnityEngine.Object.FindObjectsOfType(typeToFind, true).Where(x => x.GetType() == typeToFind).Select(x => (MonoBehaviour)x).ToList(); 95 | #else 96 | List monos = UnityEditorExtensions.FindObjectsOfTypeIncludeDisabled(typeToFind).Where(x => x.GetType() == typeToFind).Select(x => (MonoBehaviour)x).ToList(); 97 | #endif 98 | 99 | FieldInfo[] fields = cachedValuePair.Value.ToArray(); 100 | 101 | foreach (var sceneMono in monos) 102 | { 103 | foreach (var field in fields) 104 | { 105 | AutosetAttribute customAttribute = field.GetCustomAttribute(); 106 | 107 | if (customAttribute.dontOverride && field.GetValue(sceneMono) != null) 108 | { 109 | continue; 110 | } 111 | 112 | bool isArray = field.FieldType.IsArray; 113 | 114 | object[] components = customAttribute.GetObjectsLogic(sceneMono, isArray ? field.FieldType.GetElementType() : field.FieldType); 115 | 116 | bool failToSet; 117 | 118 | if (isArray) 119 | { 120 | failToSet = components.Length == 0; 121 | } 122 | else 123 | { 124 | failToSet = components.FirstOrDefault() == null; 125 | } 126 | 127 | if (failToSet) 128 | { 129 | //Don't log error if suppressErrors is true 130 | if (!customAttribute.suppressErrors) 131 | { 132 | VRRADebugger.LogError($"Failed to set \"[{customAttribute}] {field.Name}\" on ({sceneMono.GetType()}) {sceneMono.name}", sceneMono); 133 | } 134 | 135 | if (showPopupWhenFieldAutomationFailed) 136 | { 137 | bool cancel = EditorUtility.DisplayDialog("Failed Field Automation...", 138 | $"Failed to set \"[{customAttribute}] {field.Name}\" on ({sceneMono.GetType()}) {sceneMono.name}\nDo you want to cancel the build?", 139 | "Cancel", "Continue"); 140 | 141 | if (cancel) 142 | { 143 | cancelBuild = true; 144 | return; 145 | } 146 | } 147 | 148 | continue; 149 | } 150 | 151 | object obj; 152 | if (isArray) 153 | { 154 | var elementType = field.FieldType.GetElementType(); 155 | 156 | var actualValues = Array.CreateInstance(elementType, components.Length); 157 | 158 | Array.Copy(components, actualValues, components.Length); 159 | 160 | obj = actualValues; 161 | } 162 | else 163 | { 164 | obj = components.FirstOrDefault(); 165 | } 166 | 167 | field.SetValue(sceneMono, obj); 168 | 169 | Type customAttributeType = customAttribute.GetType(); 170 | 171 | if (FieldAutomationTypeResults.ContainsKey(customAttributeType)) 172 | FieldAutomationTypeResults[customAttributeType]++; 173 | else 174 | FieldAutomationTypeResults.Add(customAttributeType, 1); 175 | } 176 | 177 | UnityEditorExtensions.FullSetDirty(sceneMono); 178 | } 179 | } 180 | 181 | EditorUtility.ClearProgressBar(); 182 | 183 | foreach (var result in FieldAutomationTypeResults) 184 | { 185 | if (result.Value > 0) 186 | VRRADebugger.Log($"Successfully set ({result.Value}) {result.Key.Name} references"); 187 | } 188 | } 189 | 190 | public static IEnumerable GetAllFields(Type t) 191 | { 192 | if (t == null) return Enumerable.Empty(); 193 | 194 | return t.GetFields(FieldFlags).Concat(GetAllFields(t.BaseType)); 195 | } 196 | 197 | public static bool IsSerialized(this FieldInfo field) => !(field.GetCustomAttribute() != null || field.IsPrivate && field.GetCustomAttribute() == null); 198 | } 199 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/FieldAutomation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 924fc62064ac464db40915e044d981c1 3 | timeCreated: 1664660893 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/OnBuildException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VRRefAssist.Editor.Exceptions 4 | { 5 | public class OnBuildException : Exception 6 | { 7 | public bool logException { get; } 8 | 9 | internal bool showDialog; 10 | private OnBuildDialog _dialog; 11 | 12 | public OnBuildDialog dialog 13 | { 14 | set 15 | { 16 | _dialog = value; 17 | showDialog = _dialog != null; 18 | } 19 | get => _dialog; 20 | } 21 | 22 | public OnBuildException(string message, bool logException = false) : base(message) 23 | { 24 | this.logException = logException; 25 | } 26 | } 27 | 28 | public class OnBuildDialog 29 | { 30 | private readonly string title; 31 | private readonly string message; 32 | private readonly DialogType type; 33 | private DialogButton okButton; 34 | private DialogButton cancelButton; 35 | private DialogButton altButton; 36 | 37 | public OnBuildDialog(string title, string message, DialogButton okButton) 38 | { 39 | type = DialogType.Acknowledge; 40 | 41 | this.title = title; 42 | this.message = message; 43 | this.okButton = okButton; 44 | } 45 | 46 | public OnBuildDialog(string title, string message, DialogButton okButton, DialogButton cancelButton) 47 | { 48 | type = DialogType.Regular; 49 | 50 | this.title = title; 51 | this.message = message; 52 | this.okButton = okButton; 53 | this.cancelButton = cancelButton; 54 | } 55 | 56 | public OnBuildDialog(string title, string message, DialogButton okButton, DialogButton cancelButton, DialogButton altButton) 57 | { 58 | type = DialogType.Complex; 59 | 60 | this.title = title; 61 | this.message = message; 62 | this.okButton = okButton; 63 | this.cancelButton = cancelButton; 64 | this.altButton = altButton; 65 | } 66 | 67 | private int option; 68 | 69 | public Result ShowDialog() 70 | { 71 | switch (type) 72 | { 73 | default: 74 | case DialogType.Acknowledge: 75 | UnityEditor.EditorUtility.DisplayDialog(title, message, okButton.text); 76 | option = 0; 77 | break; 78 | case DialogType.Regular: 79 | option = UnityEditor.EditorUtility.DisplayDialog(title, message, okButton.text, cancelButton.text) ? 0 : 1; 80 | break; 81 | case DialogType.Complex: 82 | option = UnityEditor.EditorUtility.DisplayDialogComplex(title, message, okButton.text, cancelButton.text, altButton.text); 83 | break; 84 | } 85 | 86 | switch (option) 87 | { 88 | default: 89 | case 0: 90 | return okButton.GetResult(); 91 | case 1: 92 | return cancelButton.GetResult(); 93 | case 2: 94 | return altButton.GetResult(); 95 | } 96 | } 97 | 98 | private enum DialogType 99 | { 100 | Acknowledge, 101 | Regular, 102 | Complex 103 | } 104 | } 105 | 106 | public readonly struct DialogButton 107 | { 108 | public readonly string text; 109 | private readonly bool cancelBuild; 110 | private readonly Func action; 111 | 112 | public DialogButton(string text, bool cancelBuild = true, Func action = null) 113 | { 114 | this.text = text; 115 | this.cancelBuild = cancelBuild; 116 | this.action = action; 117 | } 118 | 119 | public Result GetResult() 120 | { 121 | if (action == null) 122 | { 123 | return cancelBuild ? Result.CancelBuild : Result.ContinueBuild; 124 | } 125 | 126 | return action.Invoke(); 127 | } 128 | } 129 | 130 | public enum Result 131 | { 132 | ContinueBuild, 133 | CancelBuild 134 | } 135 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/OnBuildException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67e99a83050e4f278b483c24bc5d184c 3 | timeCreated: 1709768852 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/RunOnBuildAutomation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using UnityEditor; 4 | using VRRefAssist.Editor.Exceptions; 5 | using VRRefAssist.Editor.Extensions; 6 | 7 | namespace VRRefAssist.Editor.Automation 8 | { 9 | public static class RunOnBuildAutomation 10 | { 11 | [MenuItem("VR RefAssist/Tools/Run OnBuild Methods", priority = 200)] 12 | private static void ManuallyRunOnBuildMethods() 13 | { 14 | RunOnBuildMethods.CacheMonoInstances(); 15 | 16 | RunOnBuildMethodsWithExecuteOrderType(ExecuteOrderType.PreFieldAutomation); 17 | RunOnBuildMethodsWithExecuteOrderType(ExecuteOrderType.PostFieldAutomation); 18 | } 19 | 20 | public static void RunOnBuildMethodsWithExecuteOrderType(ExecuteOrderType executeOrderType) 21 | { 22 | RunOnBuildMethodsWithExecuteOrderType(executeOrderType, out _); 23 | } 24 | 25 | public static void RunOnBuildMethodsWithExecuteOrderType(ExecuteOrderType executeOrderType, out bool cancelBuild) 26 | { 27 | Stopwatch stopwatch = Stopwatch.StartNew(); 28 | 29 | cancelBuild = false; 30 | 31 | var methods = executeOrderType == ExecuteOrderType.PreFieldAutomation ? RunOnBuildMethods.preFieldAutomationMethods : RunOnBuildMethods.postFieldAutomationMethods; 32 | 33 | string preOrPost = executeOrderType == ExecuteOrderType.PreFieldAutomation ? "pre" : "post"; 34 | 35 | int count = 0; 36 | int total = methods.Count; 37 | 38 | foreach (var method in methods) 39 | { 40 | if (UnityEditorExtensions.DisplaySmartUpdatingCancellableProgressBar($"Running {preOrPost}-field automation OnBuild Methods...", count == total ? "Finishing..." : $"Progress: {count}/{total}.\tCurrent: {method.MethodInfo.Name}", count / (total - 1f))) 41 | { 42 | EditorUtility.ClearProgressBar(); 43 | 44 | stopwatch.Stop(); 45 | 46 | cancelBuild = EditorUtility.DisplayDialog("Cancelled running instance OnBuild Methods", "You have canceled running instance OnBuild Methods\nDo you want to cancel the build as well?", "Cancel", "Continue"); 47 | 48 | if (!cancelBuild) 49 | { 50 | stopwatch.Start(); 51 | continue; 52 | } 53 | 54 | VRRADebugger.Log($"Ran {count}/{total} OnBuild Methods {preOrPost}-field automation in {stopwatch.Elapsed.TotalSeconds:F} seconds. Before it was cancelled"); 55 | EditorUtility.ClearProgressBar(); 56 | return; 57 | } 58 | 59 | if (!method.TryInvoke(out Exception e)) 60 | { 61 | stopwatch.Stop(); 62 | 63 | if (e is OnBuildException onBuildException) 64 | { 65 | if (onBuildException.logException) 66 | { 67 | UnityEngine.Debug.LogException(onBuildException); 68 | } 69 | 70 | if (onBuildException.showDialog) 71 | { 72 | switch (onBuildException.dialog.ShowDialog()) 73 | { 74 | case Result.ContinueBuild: 75 | cancelBuild = false; 76 | break; 77 | case Result.CancelBuild: 78 | cancelBuild = true; 79 | break; 80 | default: 81 | throw new ArgumentOutOfRangeException(); 82 | } 83 | } 84 | } 85 | else 86 | { 87 | var exception = e.InnerException ?? e; 88 | 89 | cancelBuild = EditorUtility.DisplayDialog("Error running OnBuild Methods...", 90 | $"An error occurred while running " + method.MethodInfo.Name + $".\n\n{exception.GetType()}: {exception.Message}\n\n{exception.StackTrace}\n\nDo you want to cancel the build?", 91 | "Cancel", "Continue"); 92 | } 93 | 94 | if (!cancelBuild) 95 | { 96 | stopwatch.Start(); 97 | continue; 98 | } 99 | 100 | VRRADebugger.Log($"Ran {count}/{total} OnBuild Methods {preOrPost}-field automation in {stopwatch.Elapsed.TotalSeconds:F} seconds. Before it was cancelled"); 101 | EditorUtility.ClearProgressBar(); 102 | return; 103 | } 104 | else 105 | { 106 | count++; 107 | } 108 | } 109 | 110 | EditorUtility.ClearProgressBar(); 111 | stopwatch.Stop(); 112 | 113 | if (count > 0) 114 | VRRADebugger.Log($"Finished running {count}/{total} OnBuild Methods {preOrPost}-field automation in {stopwatch.Elapsed.TotalSeconds:F} seconds."); 115 | } 116 | 117 | public enum ExecuteOrderType 118 | { 119 | PreFieldAutomation, 120 | PostFieldAutomation 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/RunOnBuildAutomation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 09ef2f5970284d649bb9b25d6cc05e22 3 | timeCreated: 1664661273 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/RunOnBuildMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading; 6 | using UnityEditor; 7 | using UnityEngine; 8 | using VRRefAssist.Editor.Exceptions; 9 | using VRRefAssist.Editor.Extensions; 10 | 11 | namespace VRRefAssist.Editor.Automation 12 | { 13 | [InitializeOnLoad] 14 | public static class RunOnBuildMethods 15 | { 16 | private static readonly Dictionary> monoTypeInstances = new Dictionary>(); 17 | 18 | public static List preFieldAutomationMethods = new List(); 19 | public static List postFieldAutomationMethods = new List(); 20 | 21 | public static bool refreshingOnBuildMethods; 22 | 23 | static RunOnBuildMethods() 24 | { 25 | refreshingOnBuildMethods = true; 26 | new Thread(RegisterRunOnBuildMethods).Start(); 27 | } 28 | 29 | private static void RegisterRunOnBuildMethods() 30 | { 31 | var allMethods = TypeCache.GetMethodsWithAttribute() 32 | .GroupBy(m => m.IsStatic) 33 | .ToDictionary(x => x.Key, z => z.ToList()); 34 | 35 | List staticMethods = new List(); 36 | List instanceMethods = new List(); 37 | 38 | if (allMethods.ContainsKey(true)) 39 | staticMethods = allMethods[true].Select(m => new StaticRunOnBuildMethod(m)).ToList(); 40 | 41 | if (allMethods.ContainsKey(false)) 42 | instanceMethods = allMethods[false].Select(m => new InstanceRunOnBuildMethod(m)).ToList(); 43 | 44 | monoTypeInstances.Clear(); 45 | 46 | foreach (var instanceMethod in instanceMethods) 47 | { 48 | if (!instanceMethod.declaringType.IsSubclassOf(typeof(MonoBehaviour))) 49 | { 50 | VRRADebugger.LogError($"RunOnBuild method {instanceMethod.MethodInfo.Name} in {instanceMethod.declaringType.Name} is not subclass of MonoBehaviour!"); 51 | continue; 52 | } 53 | 54 | if (!monoTypeInstances.ContainsKey(instanceMethod.declaringType)) 55 | monoTypeInstances.Add(instanceMethod.declaringType, new List()); 56 | 57 | var inheritedTypes = TypeCache.GetTypesDerivedFrom(instanceMethod.declaringType).Where(t => !t.IsAbstract).ToList(); 58 | 59 | foreach (var inheritedType in inheritedTypes) 60 | { 61 | if (monoTypeInstances.ContainsKey(inheritedType)) continue; 62 | 63 | monoTypeInstances.Add(inheritedType, new List()); 64 | } 65 | } 66 | 67 | var staticAndInstanceMethods = staticMethods.OfType().Concat(instanceMethods).ToList(); 68 | 69 | //Split into pre- and post-field automation methods (execution order <= 1000 is pre, > 1000 is post) 70 | var preAndPostSplit = staticAndInstanceMethods.GroupBy(m => m.attribute.executionOrder <= 1000).ToDictionary(x => x.Key, z => z.ToList()); 71 | 72 | if (preAndPostSplit.TryGetValue(true, out var preFieldMethods)) 73 | { 74 | preFieldAutomationMethods = preFieldMethods.OrderBy(m => m.attribute.executionOrder).ToList(); 75 | } 76 | 77 | if (preAndPostSplit.TryGetValue(false, out var postFieldMethods)) 78 | { 79 | postFieldAutomationMethods = postFieldMethods.OrderBy(m => m.attribute.executionOrder).ToList(); 80 | } 81 | 82 | refreshingOnBuildMethods = false; 83 | } 84 | 85 | public static void CacheMonoInstances() 86 | { 87 | if (refreshingOnBuildMethods) 88 | { 89 | VRRADebugger.LogError("Cannot cache MonoBehaviour instances while refreshing RunOnBuild methods!"); 90 | return; 91 | } 92 | 93 | foreach (var monoType in monoTypeInstances.Keys) 94 | { 95 | #if UNITY_2020_1_OR_NEWER 96 | MonoBehaviour[] monoInstances = UnityEngine.Object.FindObjectsOfType(monoType, true).Where(x => x.GetType() == monoType).Select(x => (MonoBehaviour)x).ToArray(); 97 | #else 98 | MonoBehaviour[] monoInstances = UnityEditorExtensions.FindObjectsOfTypeIncludeDisabled(monoType).Where(x => x.GetType() == monoType).Select(x => (MonoBehaviour)x).ToArray(); 99 | #endif 100 | 101 | monoTypeInstances[monoType].Clear(); 102 | monoTypeInstances[monoType].AddRange(monoInstances); 103 | } 104 | } 105 | 106 | public abstract class RunOnBuildMethod 107 | { 108 | protected RunOnBuildMethod(MethodInfo methodInfo) 109 | { 110 | this.methodInfo = methodInfo; 111 | attribute = methodInfo.GetCustomAttribute(); 112 | } 113 | 114 | public MethodInfo MethodInfo => methodInfo; 115 | protected readonly MethodInfo methodInfo; 116 | public readonly RunOnBuildAttribute attribute; 117 | 118 | public bool TryInvoke(out Exception exception) 119 | { 120 | exception = null; 121 | 122 | try 123 | { 124 | Invoke(); 125 | } 126 | catch (TargetInvocationException e) 127 | { 128 | if (e.InnerException is OnBuildException buildException) 129 | { 130 | exception = buildException; 131 | 132 | return false; 133 | } 134 | 135 | Debug.LogException(e); 136 | 137 | exception = e; 138 | 139 | return false; 140 | } 141 | catch (Exception e) 142 | { 143 | Debug.LogException(e); 144 | 145 | exception = e; 146 | 147 | return false; 148 | } 149 | 150 | return true; 151 | } 152 | 153 | protected abstract void Invoke(); 154 | } 155 | 156 | private class StaticRunOnBuildMethod : RunOnBuildMethod 157 | { 158 | protected override void Invoke() 159 | { 160 | methodInfo.Invoke(null, null); 161 | } 162 | 163 | public StaticRunOnBuildMethod(MethodInfo methodInfo) : base(methodInfo) 164 | { 165 | } 166 | } 167 | 168 | private class InstanceRunOnBuildMethod : RunOnBuildMethod 169 | { 170 | public readonly Type declaringType; 171 | 172 | protected override void Invoke() 173 | { 174 | var allTypesWithMethod = TypeCache.GetTypesDerivedFrom(declaringType).Where(t => !t.IsAbstract).ToList(); 175 | 176 | allTypesWithMethod.Add(declaringType); 177 | 178 | foreach (var type in allTypesWithMethod) 179 | { 180 | if (!monoTypeInstances.TryGetValue(type, out var instances)) 181 | { 182 | return; 183 | } 184 | 185 | foreach (var instance in instances) 186 | { 187 | methodInfo.Invoke(instance, null); 188 | } 189 | } 190 | } 191 | 192 | public InstanceRunOnBuildMethod(MethodInfo methodInfo) : base(methodInfo) 193 | { 194 | declaringType = methodInfo.DeclaringType; 195 | } 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/RunOnBuildMethods.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 35cc96b55e587bb4199218655dd639a3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/SingletonAutomation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using VRRefAssist.Editor.Extensions; 8 | 9 | namespace VRRefAssist.Editor.Automation 10 | { 11 | public static class SingletonAutomation 12 | { 13 | private static bool cachedSingletons; 14 | private static Type[] cachedSingletonTypes; 15 | 16 | private static readonly Dictionary> typesUsingSingletons = new Dictionary>(); 17 | 18 | static SingletonAutomation() 19 | { 20 | //Find all classes that inherit from MonoBehaviour 21 | List monoInheritors = TypeCache.GetTypesDerivedFrom().Where(t => !t.IsAbstract).ToList(); 22 | 23 | List singletonTypes = TypeCache.GetTypesWithAttribute().ToList(); 24 | 25 | //Find all fields that use Singletons 26 | foreach (Type monoInheritor in monoInheritors) 27 | { 28 | FieldInfo[] fields = FieldAutomation.GetAllFields(monoInheritor).ToArray(); 29 | foreach (FieldInfo field in fields) 30 | { 31 | if (!singletonTypes.Contains(field.FieldType)) continue; 32 | if (!field.IsSerialized()) continue; 33 | 34 | if (!typesUsingSingletons.ContainsKey(monoInheritor)) 35 | { 36 | typesUsingSingletons.Add(monoInheritor, new List { field }); 37 | } 38 | else 39 | { 40 | typesUsingSingletons[monoInheritor].Add(field); 41 | } 42 | } 43 | } 44 | } 45 | 46 | /// 47 | /// Returns all singletons found in assemblies 48 | /// 49 | public static Type[] Singletons 50 | { 51 | get 52 | { 53 | if (!cachedSingletons) 54 | { 55 | cachedSingletonTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes().Where(t => t.IsDefined(typeof(Singleton)))).ToArray(); 56 | cachedSingletons = true; 57 | } 58 | 59 | return cachedSingletonTypes; 60 | } 61 | } 62 | 63 | private static Dictionary sceneSingletonsDict = new Dictionary(); 64 | 65 | private static void RefreshSingletonsInScene() 66 | { 67 | var types = TypeCache.GetTypesWithAttribute(); 68 | 69 | List sceneMonoSingletons = new List(); 70 | 71 | foreach (var type in types) 72 | { 73 | #if UNITY_2020_1_OR_NEWER 74 | MonoBehaviour mono = UnityEngine.Object.FindObjectOfType(type, true) as MonoBehaviour; 75 | #else 76 | MonoBehaviour mono = UnityEditorExtensions.FindObjectOfTypeIncludeDisabled(type) as MonoBehaviour; 77 | #endif 78 | 79 | if (mono != null) 80 | { 81 | sceneMonoSingletons.Add(mono); 82 | } 83 | } 84 | 85 | /* 86 | #if UNITY_2020_1_OR_NEWER 87 | List sceneMonoSingletons = UnityEngine.Object.FindObjectsOfType(true).Where(x => x != null && x.GetType().GetCustomAttribute() != null).ToList(); 88 | #else 89 | List sceneMonoSingletons = UnityEditorExtensions.FindObjectsOfTypeIncludeDisabled().Where(x => x != null && x.GetType().GetCustomAttribute() != null).ToList(); 90 | #endif 91 | */ 92 | 93 | var repeatedSingletons = sceneMonoSingletons.GroupBy(u => u.GetType()).Where(r => r.Count() > 1); 94 | 95 | foreach (var repeatedSingleton in repeatedSingletons) 96 | { 97 | VRRADebugger.LogError($"There are multiple instances ({repeatedSingleton.Count()}) of the same singleton in the scene! (" + repeatedSingleton.Key + ")"); 98 | } 99 | 100 | sceneSingletonsDict = sceneMonoSingletons.GroupBy(u => u.GetType()).Select(u => u.First()).ToDictionary(x => x.GetType(), x => x); 101 | 102 | if(sceneMonoSingletons.Count > 0) 103 | VRRADebugger.Log($"Singleton refresh found {sceneMonoSingletons.Count} singletons in the scene"); 104 | } 105 | 106 | [MenuItem("VR RefAssist/Tools/Set Singleton References", priority = 201)] 107 | private static void ManuallySetAllSingletonReferences() 108 | { 109 | int count = SetAllSingletonReferences(); 110 | 111 | if (count > 0) SceneView.lastActiveSceneView.ShowNotification(new GUIContent($"Successfully set ({count}) singleton references")); 112 | } 113 | 114 | public static int SetAllSingletonReferences() 115 | { 116 | return SetAllSingletonReferences(out _); 117 | } 118 | 119 | public static int SetAllSingletonReferences(out bool cancelBuild) 120 | { 121 | RefreshSingletonsInScene(); 122 | 123 | bool showPopupWhenFieldAutomationFailed = VRRefAssistSettings.GetOrCreateSettings().showPopupWarnsForFailedFieldAutomation; 124 | 125 | cancelBuild = false; 126 | 127 | int count = 1; 128 | int total = typesUsingSingletons.Count; 129 | 130 | int resultCount = 0; 131 | 132 | foreach (var typeUsingSingleton in typesUsingSingletons) 133 | { 134 | Type typeToFind = typeUsingSingleton.Key; 135 | 136 | if (UnityEditorExtensions.DisplaySmartUpdatingCancellableProgressBar($"Setting Singleton References...", count == total ? "Finishing..." : $"Progress: {count}/{total}.\tCurrent U# Behaviour: {typeToFind.Name}", count / (total - 1f))) 137 | { 138 | EditorUtility.ClearProgressBar(); 139 | 140 | cancelBuild = EditorUtility.DisplayDialog("Cancelled setting Singleton References", "You have canceled setting Singleton References\nDo you want to cancel the build as well?", "Cancel", "Continue"); 141 | 142 | return resultCount; 143 | } 144 | 145 | count++; 146 | 147 | //When getting the monos, check for the ones that specifically are of the type, otherwise we will repeat classes that are inherited. 148 | #if UNITY_2020_1_OR_NEWER 149 | List monos = UnityEngine.Object.FindObjectsOfType(typeToFind, true).Where(x => x.GetType() == typeToFind).Select(x => (MonoBehaviour)x).ToList(); 150 | #else 151 | List monos = UnityEditorExtensions.FindObjectsOfTypeIncludeDisabled(typeToFind).Where(x => x.GetType() == typeToFind).Select(x => (MonoBehaviour)x).ToList(); 152 | #endif 153 | 154 | FieldInfo[] fields = typeUsingSingleton.Value.ToArray(); 155 | 156 | foreach (var sceneMono in monos) 157 | { 158 | foreach (var field in fields) 159 | { 160 | if (!sceneSingletonsDict.ContainsKey(field.FieldType)) 161 | { 162 | VRRADebugger.LogError($"Failed to set singleton \"{field.FieldType.Name}\" in {sceneMono.GetType().Name} ({sceneMono.name}), because the singleton was not found in the scene!", sceneMono.gameObject); 163 | 164 | if (showPopupWhenFieldAutomationFailed) 165 | { 166 | bool cancel = EditorUtility.DisplayDialog("Failed to set Singleton reference", $"Failed to set singleton \"{field.FieldType.Name}\" in {sceneMono.GetType().Name} ({sceneMono.name}), because the singleton was not found in the scene!\nDo you want to cancel the build?", "Cancel", "Continue"); 167 | if (cancel) 168 | { 169 | cancelBuild = true; 170 | return resultCount; 171 | } 172 | } 173 | 174 | continue; 175 | } 176 | 177 | resultCount++; 178 | 179 | field.SetValue(sceneMono, sceneSingletonsDict[field.FieldType]); 180 | } 181 | 182 | UnityEditorExtensions.FullSetDirty(sceneMono); 183 | } 184 | } 185 | 186 | if(resultCount > 0) 187 | VRRADebugger.Log($"Successfully set ({resultCount}) singleton references"); 188 | EditorUtility.ClearProgressBar(); 189 | 190 | return resultCount; 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Automation/SingletonAutomation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 61528cded05b76642b2b4ff02940d842 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Extensions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9413107aaa44e3a9999fe91fe5cb00d 3 | timeCreated: 1664661464 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Extensions/UnityEditorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEngine.SceneManagement; 7 | using Object = UnityEngine.Object; 8 | 9 | namespace VRRefAssist.Editor.Extensions 10 | { 11 | public static class UnityEditorExtensions 12 | { 13 | public static void FullSetDirty(Object obj) 14 | { 15 | if (obj == null) 16 | { 17 | return; 18 | } 19 | 20 | EditorUtility.SetDirty(obj); 21 | if (PrefabUtility.IsPartOfAnyPrefab(obj)) 22 | { 23 | PrefabUtility.RecordPrefabInstancePropertyModifications(obj); 24 | } 25 | } 26 | 27 | #if UNITY_2020_1_OR_NEWER 28 | [Obsolete("Use Object.FindObjectsOfType(bool includeInactive) instead")] 29 | #endif 30 | public static T[] FindObjectsOfTypeIncludeDisabled() where T : Object 31 | { 32 | //If T is a GameObject, get all Transforms and cast them to GameObjects 33 | if (typeof(T) == typeof(GameObject)) 34 | { 35 | return FindObjectsOfTypeIncludeDisabled().Select(t => t.gameObject).Cast().ToArray(); 36 | } 37 | 38 | GameObject[] rootGos = SceneManager.GetActiveScene().GetRootGameObjects(); 39 | 40 | List objs = new List(); 41 | 42 | foreach (GameObject root in rootGos) 43 | { 44 | objs.AddRange(root.GetComponentsInChildren(true)); 45 | } 46 | 47 | return objs.ToArray(); 48 | } 49 | 50 | #if UNITY_2020_1_OR_NEWER 51 | [Obsolete("Use Object.FindObjectsOfType(Type type,bool includeInactive) instead")] 52 | #endif 53 | public static Component[] FindObjectsOfTypeIncludeDisabled(Type type) 54 | { 55 | if (type == null) return Array.Empty(); 56 | 57 | GameObject[] rootGos = SceneManager.GetActiveScene().GetRootGameObjects(); 58 | 59 | List objs = new List(); 60 | 61 | foreach (GameObject root in rootGos) 62 | { 63 | objs.AddRange(root.GetComponentsInChildren(type, true)); 64 | } 65 | 66 | return objs.ToArray(); 67 | } 68 | 69 | #if UNITY_2020_1_OR_NEWER 70 | [Obsolete("Use Object.FindObjectOfType(bool includeInactive) instead")] 71 | #endif 72 | public static T FindObjectOfTypeIncludeDisabled() where T : Object 73 | { 74 | //If T is a GameObject, get all Transforms and cast them to GameObjects 75 | if (typeof(T) == typeof(GameObject)) 76 | { 77 | return FindObjectOfTypeIncludeDisabled().gameObject as T; 78 | } 79 | 80 | GameObject[] rootGos = SceneManager.GetActiveScene().GetRootGameObjects(); 81 | 82 | foreach (GameObject root in rootGos) 83 | { 84 | T obj = root.GetComponentInChildren(true); 85 | if (obj != null) 86 | { 87 | return obj; 88 | } 89 | } 90 | 91 | return null; 92 | } 93 | 94 | #if UNITY_2020_1_OR_NEWER 95 | [Obsolete("Use Object.FindObjectOfType(Type type, bool includeInactive) instead")] 96 | #endif 97 | public static Component FindObjectOfTypeIncludeDisabled(Type type) 98 | { 99 | if (type == null) return null; 100 | GameObject[] rootGos = SceneManager.GetActiveScene().GetRootGameObjects(); 101 | 102 | foreach (GameObject root in rootGos) 103 | { 104 | Component obj = root.GetComponentInChildren(type, true); 105 | if (obj != null) 106 | { 107 | return obj; 108 | } 109 | } 110 | 111 | return null; 112 | } 113 | 114 | private static System.Diagnostics.Stopwatch smartProgressBarWatch = System.Diagnostics.Stopwatch.StartNew(); 115 | private static int smartProgressBarDisplaysSinceLastUpdate = 0; 116 | 117 | public static bool DisplaySmartUpdatingCancellableProgressBar(string title, string details, float progress, int updateIntervalByMS = 200, int updateIntervalByCall = 50) 118 | { 119 | bool updateProgressBar = 120 | smartProgressBarWatch.ElapsedMilliseconds >= updateIntervalByMS 121 | || ++smartProgressBarDisplaysSinceLastUpdate >= updateIntervalByCall; 122 | 123 | if (updateProgressBar) 124 | { 125 | smartProgressBarWatch.Stop(); 126 | smartProgressBarWatch.Reset(); 127 | smartProgressBarWatch.Start(); 128 | 129 | smartProgressBarDisplaysSinceLastUpdate = 0; 130 | 131 | if (EditorUtility.DisplayCancelableProgressBar(title, details, progress)) 132 | { 133 | return true; 134 | } 135 | } 136 | 137 | return false; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Extensions/UnityEditorExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b7784fc491134bb892de2846c0b06528 3 | timeCreated: 1664661032 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/LiveDimensions.VRRefAssist.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LiveDimensions.VRRefAssist.Editor", 3 | "references": [ 4 | "UdonSharp.Runtime", 5 | "LiveDimensions.VRRefAssist.Runtime" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/LiveDimensions.VRRefAssist.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 260b6001a17165043991ed4c9643a306 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Utilities.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aff24a96283491f4ea24f11d8c1068b3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Utilities/VRRADebugger.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace VRRefAssist 4 | { 5 | public static class VRRADebugger 6 | { 7 | public static void Log(string message, Object obj = null) 8 | { 9 | Debug.Log("[VRRefAssist] " + message, obj); 10 | } 11 | 12 | public static void LogWarning(string message, Object obj = null) 13 | { 14 | Debug.LogWarning("[VRRefAssist] " + message, obj); 15 | } 16 | 17 | public static void LogError(string message, Object obj = null) 18 | { 19 | Debug.LogError("[VRRefAssist] " + message, obj); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Utilities/VRRADebugger.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c92bcb3ae094be98a89707e4de2a03a 3 | timeCreated: 1664661495 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Utilities/VRRefAssistSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using UnityEngine.UIElements; 8 | 9 | namespace VRRefAssist 10 | { 11 | internal class VRRefAssistSettings : ScriptableObject 12 | { 13 | private static string SettingsPath => "Assets/VR RefAssist/Settings/VR RefAssist Settings.asset"; 14 | 15 | public bool executeRunOnBuildMethodsWhenEnteringPlayMode = true; 16 | public bool executeFieldAutomationWhenEnteringPlayMode = true; 17 | 18 | public bool showPopupWarnsForFailedFieldAutomation = false; 19 | 20 | private static VRRefAssistSettings _settings; 21 | 22 | public static VRRefAssistSettings GetOrCreateSettings() 23 | { 24 | if(_settings != null) 25 | return _settings; 26 | 27 | string settingsPath = SettingsPath; 28 | VRRefAssistSettings settings = AssetDatabase.LoadAssetAtPath(settingsPath); 29 | if (settings == null) 30 | { 31 | if (!AssetDatabase.IsValidFolder(Path.GetDirectoryName(settingsPath))) 32 | Directory.CreateDirectory(Path.GetDirectoryName(settingsPath)); 33 | 34 | _settings = settings = CreateInstance(); 35 | AssetDatabase.CreateAsset(settings, settingsPath); 36 | AssetDatabase.SaveAssets(); 37 | } 38 | 39 | return settings; 40 | } 41 | } 42 | 43 | internal class VRRefAssistSettingsProvider : SettingsProvider 44 | { 45 | SerializedObject m_SerializedObject; 46 | SerializedProperty executeRunOnBuildMethodsWhenEnteringPlayModeProp; 47 | SerializedProperty executeFieldAutomationWhenEnteringPlayModeProp; 48 | SerializedProperty showPopupWarnsForFailedFieldAutomationProp; 49 | 50 | private class Styles 51 | { 52 | public static readonly GUIContent _executeRunOnBuildWhenEnteringPlayModeLabel = new GUIContent("Execute RunOnBuild Methods when entering Play Mode", "If enabled, RunOnBuild methods will be executed when entering Play Mode."); 53 | public static readonly GUIContent _runFieldAutomationWhenEnteringPlayModeLabel = new GUIContent("Run Field Automation when entering Play Mode", "If enabled, Field Automation will be executed when entering Play Mode."); 54 | 55 | public static readonly GUIContent _showPopupWarnsForFailedFieldAutomationLabel = new GUIContent("Show Popup Warns for Failed Field Automation", "If enabled, a popup will be shown when Field Automation fails asking if you want to abort a build.\nThis can be annoying if you have many fields and don't necessarily care if some reference is missing."); 56 | } 57 | 58 | [MenuItem("VR RefAssist/Settings", priority = 200)] 59 | public static void OpenSettings() 60 | { 61 | SettingsService.OpenProjectSettings("Project/VR RefAssist"); 62 | } 63 | 64 | public VRRefAssistSettingsProvider(string path, SettingsScope scopes, IEnumerable keywords = null) : base(path, scopes, keywords) 65 | { 66 | } 67 | 68 | public override void OnActivate(string searchContext, VisualElement rootElement) 69 | { 70 | m_SerializedObject = new SerializedObject(VRRefAssistSettings.GetOrCreateSettings()); 71 | executeRunOnBuildMethodsWhenEnteringPlayModeProp = m_SerializedObject.FindProperty(nameof(VRRefAssistSettings.executeRunOnBuildMethodsWhenEnteringPlayMode)); 72 | executeFieldAutomationWhenEnteringPlayModeProp = m_SerializedObject.FindProperty(nameof(VRRefAssistSettings.executeFieldAutomationWhenEnteringPlayMode)); 73 | 74 | showPopupWarnsForFailedFieldAutomationProp = m_SerializedObject.FindProperty(nameof(VRRefAssistSettings.showPopupWarnsForFailedFieldAutomation)); 75 | } 76 | 77 | [SettingsProvider] 78 | public static SettingsProvider CreateMySingletonProvider() 79 | { 80 | var provider = new VRRefAssistSettingsProvider("Project/VR RefAssist", SettingsScope.Project, GetSearchKeywordsFromGUIContentProperties()); 81 | return provider; 82 | } 83 | 84 | public override void OnGUI(string searchContext) 85 | { 86 | var settings = VRRefAssistSettings.GetOrCreateSettings(); 87 | 88 | using (CreateSettingsWindowGUIScope()) 89 | { 90 | m_SerializedObject.Update(); 91 | EditorGUI.BeginChangeCheck(); 92 | 93 | using (new GUILayout.VerticalScope(EditorStyles.helpBox)) 94 | { 95 | EditorGUILayout.LabelField("Entering Play Mode", EditorStyles.boldLabel); 96 | executeRunOnBuildMethodsWhenEnteringPlayModeProp.boolValue = EditorGUILayout.ToggleLeft(Styles._executeRunOnBuildWhenEnteringPlayModeLabel, executeRunOnBuildMethodsWhenEnteringPlayModeProp.boolValue); 97 | executeFieldAutomationWhenEnteringPlayModeProp.boolValue = EditorGUILayout.ToggleLeft(Styles._runFieldAutomationWhenEnteringPlayModeLabel, executeFieldAutomationWhenEnteringPlayModeProp.boolValue); 98 | } 99 | 100 | using (new GUILayout.VerticalScope(EditorStyles.helpBox)) 101 | { 102 | EditorGUILayout.LabelField("Aborting Builds", EditorStyles.boldLabel); 103 | showPopupWarnsForFailedFieldAutomationProp.boolValue = EditorGUILayout.ToggleLeft(Styles._showPopupWarnsForFailedFieldAutomationLabel, showPopupWarnsForFailedFieldAutomationProp.boolValue); 104 | } 105 | 106 | 107 | if (EditorGUI.EndChangeCheck()) 108 | { 109 | m_SerializedObject.ApplyModifiedProperties(); 110 | EditorUtility.SetDirty(settings); 111 | } 112 | } 113 | } 114 | 115 | private IDisposable CreateSettingsWindowGUIScope() 116 | { 117 | var unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow)); 118 | var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope"); 119 | return Activator.CreateInstance(type) as IDisposable; 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Editor/Utilities/VRRefAssistSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f0f837d96e17e254fb3f31f63dc82af9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 LiveDimensions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9777397690d6b22429cec0a2d82cfe94 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b3723bd579d8c94429f64df431106db0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime/CustomAttributes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 136c6de8cd434c459291ef5d1db216be 3 | timeCreated: 1664660831 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime/CustomAttributes/ClassAndMethodAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace VRRefAssist 5 | { 6 | /// 7 | /// Any class that has a serialized reference (SerializedField or public without NonSerialized) to the class using this attribute will be automatically set on build. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class Singleton : Attribute 11 | { 12 | } 13 | 14 | /// 15 | /// Any method with this attribute will be called on build. 16 | /// Static and instance methods are supported. 17 | /// 18 | [AttributeUsage(AttributeTargets.Method)] [MeansImplicitUse] 19 | public class RunOnBuildAttribute : Attribute 20 | { 21 | public readonly int executionOrder; 22 | 23 | /// Execution order for RunOnBuild methods, lower values execute first. Values higher than 1000 will execute after field-automation 24 | public RunOnBuildAttribute(int executionOrder = 0) 25 | { 26 | this.executionOrder = executionOrder; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime/CustomAttributes/ClassAndMethodAttributes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ccc2f5eecd3e814aa202e00165cc5ee 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime/CustomAttributes/FieldAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.SceneManagement; 5 | using System.Linq; 6 | // ReSharper disable CoVariantArrayConversion 7 | 8 | namespace VRRefAssist 9 | { 10 | /// 11 | /// You can create your own AutosetAttribute by inheriting this class and overriding GetObjectsLogic to return the objects you want to set the field to. 12 | /// 13 | [AttributeUsage(AttributeTargets.Field)] 14 | public abstract class AutosetAttribute : Attribute 15 | { 16 | public readonly bool dontOverride; 17 | public readonly bool suppressErrors; 18 | 19 | protected AutosetAttribute(bool dontOverride = false, bool suppressErrors = false) 20 | { 21 | this.dontOverride = dontOverride; 22 | this.suppressErrors = suppressErrors; 23 | } 24 | public abstract object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type); 25 | } 26 | 27 | /// 28 | /// This will run GetComponent(type) on the object this is attached to and set the field to the result. 29 | /// 30 | public class GetComponent : AutosetAttribute 31 | { 32 | /// If the field value is not null, it won't be set again. You can use this to override references 33 | /// If the reference fails to be set, the console error will be suppressed. 34 | public GetComponent(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 35 | { 36 | } 37 | 38 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type) 39 | { 40 | return monoBehaviour.GetComponents(type); 41 | } 42 | } 43 | 44 | public class GetComponents : GetComponent 45 | { 46 | public GetComponents(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 47 | { 48 | } 49 | } 50 | 51 | /// 52 | /// This will run GetComponentInChildren(type) on the object this is attached to and set the field to the result. 53 | /// 54 | public class GetComponentInChildren : AutosetAttribute 55 | { 56 | /// If the field value is not null, it won't be set again. You can use this to override references 57 | /// If the reference fails to be set, the console error will be suppressed. 58 | public GetComponentInChildren(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 59 | { 60 | } 61 | 62 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type) 63 | { 64 | return monoBehaviour.GetComponentsInChildren(type, true); 65 | } 66 | } 67 | 68 | public class GetComponentsInChildren : GetComponentInChildren 69 | { 70 | public GetComponentsInChildren(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 71 | { 72 | } 73 | } 74 | 75 | /// 76 | /// This will run GetComponentInParent(type) on the object this is attached to and set the field to the result. 77 | /// 78 | public class GetComponentInParent : AutosetAttribute 79 | { 80 | /// If the field value is not null, it won't be set again. You can use this to override references 81 | /// If the reference fails to be set, the console error will be suppressed. 82 | public GetComponentInParent(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 83 | { 84 | } 85 | 86 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type) 87 | { 88 | return monoBehaviour.GetComponentsInParent(type, true); 89 | } 90 | } 91 | 92 | public class GetComponentsInParent : GetComponentInParent 93 | { 94 | public GetComponentsInParent(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 95 | { 96 | } 97 | } 98 | 99 | /// 100 | /// This is will run transform.parent.GetComponent(type) on the object this is attached to and set the field to the result. 101 | /// 102 | public class GetComponentInDirectParent : AutosetAttribute 103 | { 104 | /// If the field value is not null, it won't be set again. You can use this to override references 105 | /// If the reference fails to be set, the console error will be suppressed. 106 | public GetComponentInDirectParent(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 107 | { 108 | } 109 | 110 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type) 111 | { 112 | return monoBehaviour.transform.parent == null ? Array.Empty() : monoBehaviour.transform.parent.GetComponents(type); 113 | } 114 | } 115 | 116 | public class GetComponentsInDirectParent : GetComponentInDirectParent 117 | { 118 | public GetComponentsInDirectParent(bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 119 | { 120 | } 121 | } 122 | 123 | /// 124 | /// This will run FindObjectsOfType(type) and set the field to the result, if the field is not an array it will use the first value. 125 | /// 126 | public class FindObjectOfType : AutosetAttribute 127 | { 128 | public readonly bool includeDisabled; 129 | 130 | /// Include components in disabled GameObjects? 131 | /// If the field value is not null, it won't be set again. You can use this to override references 132 | /// If the reference fails to be set, the console error will be suppressed. 133 | public FindObjectOfType(bool includeDisabled = true, bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 134 | { 135 | this.includeDisabled = includeDisabled; 136 | } 137 | 138 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type) 139 | { 140 | #if UNITY_2020_1_OR_NEWER 141 | return UnityEngine.Object.FindObjectsOfType(type, includeDisabled); 142 | #else 143 | return includeDisabled ? FindObjectsOfTypeIncludeDisabled(type) : UnityEngine.Object.FindObjectsOfType(type); 144 | #endif 145 | } 146 | 147 | private static Component[] FindObjectsOfTypeIncludeDisabled(Type type) 148 | { 149 | if (type == null) return Array.Empty(); 150 | 151 | GameObject[] rootGos = SceneManager.GetActiveScene().GetRootGameObjects(); 152 | 153 | List objs = new List(); 154 | 155 | foreach (GameObject root in rootGos) 156 | { 157 | objs.AddRange(root.GetComponentsInChildren(type, true)); 158 | } 159 | 160 | return objs.ToArray(); 161 | } 162 | } 163 | 164 | /// 165 | /// Exactly the same as FindObjectOfType, as it already works with array fields. 166 | /// 167 | public class FindObjectsOfType : FindObjectOfType 168 | { 169 | public FindObjectsOfType(bool includeDisabled = true, bool dontOverride = false, bool suppressErrors = false) : base(includeDisabled, dontOverride, suppressErrors) 170 | { 171 | } 172 | } 173 | 174 | /// 175 | /// This will run Find(searchName) and set the field to the result, it also works for type of GameObject. 176 | /// 177 | public class Find : AutosetAttribute 178 | { 179 | public readonly string searchName; 180 | 181 | /// The name of the object to find 182 | /// If the field value is not null, it won't be set again. You can use this to override references 183 | /// If the reference fails to be set, the console error will be suppressed. 184 | public Find(string searchName, bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 185 | { 186 | this.searchName = searchName; 187 | } 188 | 189 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type) 190 | { 191 | GameObject findGo = GameObject.Find(searchName); 192 | 193 | if (type == typeof(GameObject)) return new object[] {findGo}; 194 | 195 | return findGo == null ? Array.Empty() : findGo.GetComponents(type); 196 | } 197 | } 198 | 199 | /// 200 | /// This will run transform.Find(searchName) and set the field to the result, it also works for type of GameObject. 201 | /// 202 | public class FindInChildren : AutosetAttribute 203 | { 204 | public readonly string searchName; 205 | 206 | /// The name of the object to find 207 | /// If the field value is not null, it won't be set again. You can use this to override references 208 | /// If the reference fails to be set, the console error will be suppressed. 209 | public FindInChildren(string searchName, bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 210 | { 211 | this.searchName = searchName; 212 | } 213 | 214 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, Type type) 215 | { 216 | GameObject findInChildrenGo = monoBehaviour.transform.Find(searchName).gameObject; 217 | 218 | if (type == typeof(GameObject)) return new object[] {findInChildrenGo}; 219 | 220 | return findInChildrenGo == null ? Array.Empty() : findInChildrenGo.GetComponents(type); 221 | } 222 | } 223 | 224 | /// 225 | /// This will run GameObject.FindGameObjectsWithTag(tag) and GetComponents(type) on each result. Also works for GameObjects and Transforms. 226 | /// By default, this will include disabled gameObjects, but this can be changed with 'includeDisabledGameObjects'. Disabled *components* are always included. 227 | /// 228 | public class FindObjectWithTag : AutosetAttribute 229 | { 230 | public readonly string tag; 231 | public bool includeDisabledGameObjects; 232 | 233 | /// The tag to search for 234 | /// Include disabled GameObjects? 235 | /// If the field value is not null, it won't be set again. You can use this to override references 236 | /// If the reference fails to be set, the console error will be suppressed. 237 | public FindObjectWithTag(string tag, bool includeDisabledGameObjects = true, bool dontOverride = false, bool suppressErrors = false) : base(dontOverride, suppressErrors) 238 | { 239 | this.tag = tag; 240 | this.includeDisabledGameObjects = includeDisabledGameObjects; 241 | } 242 | 243 | public override object[] GetObjectsLogic(MonoBehaviour monoBehaviour, System.Type type) 244 | { 245 | List results; 246 | 247 | if (includeDisabledGameObjects) 248 | { 249 | //Unity 2020 and newer has a method to find objects of type including disabled ones, and then we can filter by tag. 250 | #if UNITY_2020_1_OR_NEWER 251 | results = UnityEngine.Object.FindObjectsOfType(true).Where(go => go.CompareTag(tag)).ToList(); 252 | #else 253 | //2019 and older doesn't, so we use a manual method. 254 | results = FindGameObjectsWithTagIncludeDisabled(tag).ToList(); 255 | #endif 256 | } 257 | else 258 | { 259 | //Just use the normal method if we don't want disabled gameObjects 260 | results = GameObject.FindGameObjectsWithTag(tag).ToList(); 261 | } 262 | 263 | results = results.OrderBy(g => g.name).ToList(); 264 | 265 | if (type == typeof(GameObject)) return results.ToArray(); 266 | if (type == typeof(Transform)) return results.Select(g => g.transform).ToArray(); 267 | 268 | List components = new List(); 269 | 270 | foreach(GameObject go in results) { 271 | components.AddRange(go.GetComponents(type)); 272 | } 273 | 274 | return components.ToArray(); 275 | } 276 | 277 | //Iterate over all root gameObjects and get all gameObjects with the tag, including disabled ones. 278 | //This uses the same method in UnityEditorExtensions.FindObjectsOfTypeIncludeDisabled but needs to be available outside of Editor since these attributes are runtime. 279 | private static GameObject[] FindGameObjectsWithTagIncludeDisabled(string tag) 280 | { 281 | GameObject[] rootGos = SceneManager.GetActiveScene().GetRootGameObjects(); 282 | 283 | List objs = new List(); 284 | 285 | foreach (GameObject root in rootGos) 286 | { 287 | objs.AddRange(root.GetComponentsInChildren(true).Select(t => t.gameObject).Where(go => go.CompareTag(tag))); 288 | } 289 | 290 | return objs.ToArray(); 291 | } 292 | } 293 | 294 | /// 295 | /// This will run GameObject.FindGameObjectsWithTag(tag) and GetComponents(type) on each result. Also works for GameObjects and Transforms. 296 | /// By default, this will include disabled gameObjects, but this can be changed with 'includeDisabledGameObjects'. Disabled *components* are always included. 297 | /// 298 | public class FindObjectsWithTag : FindObjectWithTag { 299 | public FindObjectsWithTag(string tag, bool includeDisabledGameObjects = true, bool dontOverride = false, bool suppressErrors = false) : base(tag, includeDisabledGameObjects, dontOverride, suppressErrors) 300 | { 301 | } 302 | } 303 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime/CustomAttributes/FieldAttributes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f035915fded444dba5a4dbeb3dd2814e 3 | timeCreated: 1664660708 -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime/LiveDimensions.VRRefAssist.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LiveDimensions.VRRefAssist.Runtime", 3 | "references": [ 4 | "GUID:99835874ee819da44948776e0df4ff1d" 5 | ], 6 | "includePlatforms": [], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/Runtime/LiveDimensions.VRRefAssist.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d668a3d28cde37a459b73d0662b3c95e 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.livedimensions.vrrefassist", 3 | "version": "1.0.0", 4 | "description": "Set of custom attributes to automate time consuming references and repetitive tasks.", 5 | "displayName": "VR RefAssist", 6 | "author": { 7 | "name": "LiveDimensions", 8 | "email": "livedimensions@gmail.com", 9 | "url": "https://github.com/LiveDimensions" 10 | }, 11 | "keywords": [ 12 | "vrchat", 13 | "vrc", 14 | "automation", 15 | "scripting" 16 | ], 17 | "documentationUrl": "https://github.com/LiveDimensions/VRRefAssist", 18 | "license": "MIT", 19 | "type": "tool", 20 | "unity": "2019.4", 21 | "unityRelease": "31f1" 22 | } -------------------------------------------------------------------------------- /Packages/com.livedimensions.vrrefassist/package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d13f52f1e8691541add2c729b29d5df 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VR RefAssist 2 | A set of custom attributes for Unity to automate usually time consuming references and repetitive tasks. 3 | 4 | 5 | 6 | ## Features 7 | - Auto-set Singleton references 8 | - Auto-set usually tedious references on MonoBehaviours 9 | - Run code any time a build is requested 10 | - All from within editor mode, no runtime code required or executed! 11 | 12 | ## How to install 13 | ### Unity Package Manager 14 | In your Unity project, go to `Window > Package Manager` then click the top left `+`, click on `Add package from git URL` and paste this link: 15 | 16 | 17 | 18 | ### Unity Package 19 | Download the latest package from the [latest release](https://github.com/LiveDimensions/VRRefAssist/releases/latest) 20 | 21 | ### VRChat Package Manager 22 | 23 | 24 | ## Requirements: 25 | - Git (to install through UnityPackage Manager) 26 | 27 | ## Notes 28 | By default all field and method automations (RunOnBuild and FieldAutomation) will execute when building **but also when entering play mode**, if you wish to change this go to `VR RefAssist > Settings`. 29 | 30 | At this moment there is no 'customization' options for which automations are run **per scene** this means that your console might get spammed a little bit if you change scenes and don't have the same RunOnBuild scene setup, you can of course manually detect which scene is active and go from there. 31 | 32 | VR RefAssist highly leans towards "baking" references within a scene before building/entering play mode, this means there's no need to use `Find` methods on `Awake()` for whatever scripts you have in your scene. This of course does not work with cross-scene references. If you are instantiating prefabs however, you can use an in-scene (inactive in hierarchy) instance of that prefab so that references are populated and instantiate that instead. Here's an example of how that looks in the hierarchy (Pre-warmed Prefabs is disabled, Enemy is enabled). 33 | 34 | ![image](https://github.com/user-attachments/assets/9a598539-9d63-47a8-b5dd-20f46bc876df) 35 | 36 | 37 | ## Singleton References 38 | `[Singleton]` 39 | 40 | This class attribute marks a MonoBehaviour as a singleton and means that any other classes with serialized references to the singleton class will be automatically set (if it is present in the scene). 41 | 42 | #### Example 43 | ```cs 44 | [Singleton] 45 | public class MySingleton : MonoBehaviour 46 | { 47 | 48 | } 49 | 50 | public class TestClass : MonoBehaviour 51 | { 52 | [SerializeField] private MySingleton mySingleton; //This reference will be automatically set by VR RefAssist 53 | } 54 | ``` 55 | 56 | ## On Build Requested 57 | ### RunOnBuild 58 | `[RunOnBuild]` 59 | 60 | This attribute is supported on both static and instance methods! These methods will be run whenever a VRChat build is requested. 61 | 62 | There is also an optional `executionOrder` parameter, values higher than `1000` will execute **after** field automation. Default value is `0` (Runs before any field automation). 63 | 64 | #### Example 65 | ```cs 66 | public class BuildDate : MonoBehaviour 67 | { 68 | //VR RefAssist field attributes are set before any RunOnBuild methods are executed. 69 | [SerializeField, GetComponent] public TextMeshPro buildDateText; 70 | 71 | 72 | //UpdateBuildText will be run when a build is requested and will set the TextMeshPro text to the build date. 73 | #if UNITY_EDITOR && !COMPILER_UDONSHARP //Optional 74 | [RunOnBuild] 75 | private void UpdateBuildText() 76 | { 77 | buildDateText.text = "This world was built " + DateTime.Now; 78 | } 79 | #endif 80 | } 81 | ``` 82 | 83 | ## Field Automation 84 | The following **field** attributes implement different functionality to automatically set any references on **serialized** fields, this means that public or private fields with `[SerializeField]` will work. 85 | All fields have an optional bool parameter `dontOverride` which means that if a field already has a value, it will not attempt to set it again, useful if you want to override a specific field. 86 | 87 | **NOTE:** Even though the names of the methods are not plural, they all support array references and will populate accordingly. 88 | 89 | ### GetComponent 90 | `[GetComponent]` 91 | 92 | Will run `GetComponent()` on it's MonoBehaviour to set that reference. 93 | #### Example 94 | ```cs 95 | [SerializeField, GetComponent] private Renderer myRenderer; 96 | ``` 97 | 98 | ### GetComponentInChildren 99 | `[GetComponentInChildren]` 100 | 101 | Will run `GetComponentInChildren()` on it's MonoBehaviour to set that reference. 102 | #### Example 103 | ```cs 104 | [SerializeField, GetComponentInChildren] private Renderer myRenderer; 105 | ``` 106 | 107 | ### GetComponentInParent 108 | `[GetComponentInParent]` 109 | 110 | Will run `GetComponentInParent()` on it's MonoBehaviour to set that reference. 111 | #### Example 112 | ```cs 113 | [SerializeField, GetComponentInParent] private Renderer myRenderer; 114 | ``` 115 | 116 | ### GetComponentInDirectParent 117 | `[GetComponentInDirectParent]` 118 | 119 | Will run `transform.parent.GetComponent()` on it's MonoBehaviour to set that reference. This is one of the few attributes that does not directly translate into a Unity method, but it is still useful in some cases. 120 | #### Example 121 | ```cs 122 | [SerializeField, GetComponentInDirectParent] private Renderer myRenderer; 123 | ``` 124 | 125 | ### FindObjectOfType 126 | `[FindObjectOfType]` 127 | 128 | Will run `FindObjectOfType()` on it's MonoBehaviour to set that reference. Optionally, you can specify if you want to include disabled GameObjects when running the method. **The default value is true for includeDisabled.** 129 | #### Example 130 | ```cs 131 | [SerializeField, FindObjectOfType] private Renderer myRenderer; 132 | or 133 | [SerializeField, FindObjectOfType(false)] private Renderer myRenderer; //Will not Find disabled Renderers 134 | ``` 135 | 136 | ### FindObjectWithTag 137 | `[FindObjectWithTag("Tag")]` 138 | 139 | Will run `GameObject.FindGameObjectWithTag().GetComponent()` on it's MonoBehaviour to set that reference. If the field is an array, GetComponents is used to get all valid components on a GameObject. Optionally, you can specify if you want to include disabled GameObjects when running the method. **By default, it will include disabled GameObjects.** This will always include disabled *components.* 140 | 141 | #### Example 142 | ```cs 143 | //Will grab all Transforms on each GameObject with the tag "PossibleItemSpawnPoint". 144 | [SerializeField, FindObjectsWithTag("PossibleItemSpawnPoint")] private Transform[] allPossibleItemSpawnPoints; 145 | or 146 | //Will grab all UdonBehaviours on each GameObject with the tag "PuzzleUdons", even if multiple UdonBehaviours are on one object or if the GameObject or UdonBehaviour is disabled. 147 | [SerializeField, FindObjectsWithTag("PuzzleUdons")] private UdonBehaviour[] allPuzzleUdons; 148 | or 149 | //The same as above, but will exclude disabled GameObjects. 150 | [SerializeField, FindObjectsWithTag("PuzzleUdons", false)] private UdonBehaviour[] allPuzzleUdons; 151 | ``` 152 | 153 | ### Find 154 | `[Find("Search")]` 155 | 156 | Will run `Find("Search").GetComponent()` on it's MonoBehaviour to set that reference. This is one of the few attributes that does not directly translates into a Unity method as it runs `GetComponent` after using `Find`. 157 | 158 | **NOTE:** `Find` does not currently support arrays. 159 | 160 | #### Example 161 | ```cs 162 | [SerializeField, Find("My Renderer")] private Renderer myRenderer; 163 | ``` 164 | 165 | 166 | ### FindInChildren 167 | `[FindInChildren("Search")]` 168 | 169 | Will run `transform.Find("Search").GetComponent()` on it's MonoBehaviour to set that reference. This is one of the few attributes that does not directly translates into a Unity method as it runs `GetComponent` after using `transform.Find`. 170 | 171 | **NOTE:** `FindInChildren` does not currently support arrays. 172 | 173 | #### Example 174 | ```cs 175 | [SerializeField, Find("My Renderer")] private Renderer myRenderer; 176 | ``` 177 | 178 | ## Miscellaneous Editor Methods 179 | ### FindObjectOfTypeIncludeDisabled 180 | These methods are to be used in editor scripting only. These were implemented because as of Unity 2019.4.31f1 (Current VRChat version) Unitys' FindObjectOfType method does not include disabled GameObjects. 181 | 182 | - `UnityEditorExtensions.FindObjectOfTypeIncludeDisabled()` 183 | - `UnityEditorExtensions.FindObjectOfTypeIncludeDisabled(Type type)` 184 | - `UnityEditorExtensions.FindObjectsOfTypeIncludeDisabled()` 185 | - `UnityEditorExtensions.FindObjectsOfTypeIncludeDisabled(Type type)` 186 | 187 | ### FullSetDirty 188 | `FullSetDirty(Object obj)` 189 | Simply runs 190 | ```cs 191 | EditorUtility.SetDirty(obj); 192 | 193 | if (PrefabUtility.IsPartOfAnyPrefab(obj)) 194 | { 195 | PrefabUtility.RecordPrefabInstancePropertyModifications(obj); 196 | } 197 | ``` 198 | -------------------------------------------------------------------------------- /Website/app.js: -------------------------------------------------------------------------------- 1 | import { baseLayerLuminance, StandardLuminance } from 'https://unpkg.com/@fluentui/web-components'; 2 | 3 | const LISTING_URL = "{{ listingInfo.Url }}"; 4 | 5 | const PACKAGES = { 6 | {{~ for package in packages ~}} 7 | "{{ package.Name }}": { 8 | name: "{{ package.Name }}", 9 | displayName: "{{ if package.DisplayName; package.DisplayName; end; }}", 10 | description: "{{ if package.Description; package.Description; end; }}", 11 | version: "{{ package.Version }}", 12 | author: { 13 | name: "{{ if package.Author.Name; package.Author.Name; end; }}", 14 | url: "{{ if package.Author.Url; package.Author.Url; end; }}", 15 | }, 16 | dependencies: { 17 | {{~ for dependency in package.Dependencies ~}} 18 | "{{ dependency.Name }}": "{{ dependency.Version }}", 19 | {{~ end ~}} 20 | }, 21 | keywords: [ 22 | {{~ for keyword in package.Keywords ~}} 23 | "{{ keyword }}", 24 | {{~ end ~}} 25 | ], 26 | license: "{{ package.License }}", 27 | licensesUrl: "{{ package.LicensesUrl }}", 28 | }, 29 | {{~ end ~}} 30 | }; 31 | 32 | const setTheme = () => { 33 | const isDarkTheme = () => window.matchMedia("(prefers-color-scheme: dark)").matches; 34 | if (isDarkTheme()) { 35 | baseLayerLuminance.setValueFor(document.documentElement, StandardLuminance.DarkMode); 36 | } else { 37 | baseLayerLuminance.setValueFor(document.documentElement, StandardLuminance.LightMode); 38 | } 39 | } 40 | 41 | (() => { 42 | setTheme(); 43 | 44 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { 45 | setTheme(); 46 | }); 47 | 48 | const packageGrid = document.getElementById('packageGrid'); 49 | 50 | const searchInput = document.getElementById('searchInput'); 51 | searchInput.addEventListener('input', ({ target: { value = '' }}) => { 52 | const items = packageGrid.querySelectorAll('fluent-data-grid-row[row-type="default"]'); 53 | items.forEach(item => { 54 | if (value === '') { 55 | item.style.display = 'grid'; 56 | return; 57 | } 58 | if ( 59 | item.dataset?.packageName?.toLowerCase()?.includes(value.toLowerCase()) || 60 | item.dataset?.packageId?.toLowerCase()?.includes(value.toLowerCase()) 61 | ) { 62 | item.style.display = 'grid'; 63 | } else { 64 | item.style.display = 'none'; 65 | } 66 | }); 67 | }); 68 | 69 | const urlBarHelpButton = document.getElementById('urlBarHelp'); 70 | const addListingToVccHelp = document.getElementById('addListingToVccHelp'); 71 | urlBarHelpButton.addEventListener('click', () => { 72 | addListingToVccHelp.hidden = false; 73 | }); 74 | const addListingToVccHelpClose = document.getElementById('addListingToVccHelpClose'); 75 | addListingToVccHelpClose.addEventListener('click', () => { 76 | addListingToVccHelp.hidden = true; 77 | }); 78 | 79 | const vccListingInfoUrlFieldCopy = document.getElementById('vccListingInfoUrlFieldCopy'); 80 | vccListingInfoUrlFieldCopy.addEventListener('click', () => { 81 | const vccUrlField = document.getElementById('vccListingInfoUrlField'); 82 | vccUrlField.select(); 83 | navigator.clipboard.writeText(vccUrlField.value); 84 | vccUrlFieldCopy.appearance = 'accent'; 85 | setTimeout(() => { 86 | vccUrlFieldCopy.appearance = 'neutral'; 87 | }, 1000); 88 | }); 89 | 90 | const vccAddRepoButton = document.getElementById('vccAddRepoButton'); 91 | vccAddRepoButton.addEventListener('click', () => window.location.assign(`vcc://vpm/addRepo?url=${encodeURIComponent(LISTING_URL)}`)); 92 | 93 | const vccUrlFieldCopy = document.getElementById('vccUrlFieldCopy'); 94 | vccUrlFieldCopy.addEventListener('click', () => { 95 | const vccUrlField = document.getElementById('vccUrlField'); 96 | vccUrlField.select(); 97 | navigator.clipboard.writeText(vccUrlField.value); 98 | vccUrlFieldCopy.appearance = 'accent'; 99 | setTimeout(() => { 100 | vccUrlFieldCopy.appearance = 'neutral'; 101 | }, 1000); 102 | }); 103 | 104 | const rowMoreMenu = document.getElementById('rowMoreMenu'); 105 | const hideRowMoreMenu = e => { 106 | if (rowMoreMenu.contains(e.target)) return; 107 | document.removeEventListener('click', hideRowMoreMenu); 108 | rowMoreMenu.hidden = true; 109 | } 110 | 111 | const rowMenuButtons = document.querySelectorAll('.rowMenuButton'); 112 | rowMenuButtons.forEach(button => { 113 | button.addEventListener('click', e => { 114 | if (rowMoreMenu?.hidden) { 115 | rowMoreMenu.style.top = `${e.clientY + e.target.clientHeight}px`; 116 | rowMoreMenu.style.left = `${e.clientX - 120}px`; 117 | rowMoreMenu.hidden = false; 118 | 119 | const downloadLink = rowMoreMenu.querySelector('#rowMoreMenuDownload'); 120 | const downloadListener = () => { 121 | window.open(e?.target?.dataset?.packageUrl, '_blank'); 122 | } 123 | downloadLink.addEventListener('change', () => { 124 | downloadListener(); 125 | downloadLink.removeEventListener('change', downloadListener); 126 | }); 127 | 128 | setTimeout(() => { 129 | document.addEventListener('click', hideRowMoreMenu); 130 | }, 1); 131 | } 132 | }); 133 | }); 134 | 135 | const packageInfoModal = document.getElementById('packageInfoModal'); 136 | const packageInfoModalClose = document.getElementById('packageInfoModalClose'); 137 | packageInfoModalClose.addEventListener('click', () => { 138 | packageInfoModal.hidden = true; 139 | }); 140 | 141 | // Fluent dialogs use nested shadow-rooted elements, so we need to use JS to style them 142 | const modalControl = packageInfoModal.shadowRoot.querySelector('.control'); 143 | modalControl.style.maxHeight = "90%"; 144 | modalControl.style.transition = 'height 0.2s ease-in-out'; 145 | modalControl.style.overflowY = 'hidden'; 146 | 147 | const packageInfoName = document.getElementById('packageInfoName'); 148 | const packageInfoId = document.getElementById('packageInfoId'); 149 | const packageInfoVersion = document.getElementById('packageInfoVersion'); 150 | const packageInfoDescription = document.getElementById('packageInfoDescription'); 151 | const packageInfoAuthor = document.getElementById('packageInfoAuthor'); 152 | const packageInfoDependencies = document.getElementById('packageInfoDependencies'); 153 | const packageInfoKeywords = document.getElementById('packageInfoKeywords'); 154 | const packageInfoLicense = document.getElementById('packageInfoLicense'); 155 | 156 | const rowAddToVccButtons = document.querySelectorAll('.rowAddToVccButton'); 157 | rowAddToVccButtons.forEach((button) => { 158 | button.addEventListener('click', () => window.location.assign(`vcc://vpm/addRepo?url=${encodeURIComponent(LISTING_URL)}`)); 159 | }); 160 | 161 | const rowPackageInfoButton = document.querySelectorAll('.rowPackageInfoButton'); 162 | rowPackageInfoButton.forEach((button) => { 163 | button.addEventListener('click', e => { 164 | const packageId = e.target.dataset?.packageId; 165 | const packageInfo = PACKAGES?.[packageId]; 166 | if (!packageInfo) { 167 | console.error(`Did not find package ${packageId}. Packages available:`, PACKAGES); 168 | return; 169 | } 170 | 171 | packageInfoName.textContent = packageInfo.displayName; 172 | packageInfoId.textContent = packageId; 173 | packageInfoVersion.textContent = `v${packageInfo.version}`; 174 | packageInfoDescription.textContent = packageInfo.description; 175 | packageInfoAuthor.textContent = packageInfo.author.name; 176 | packageInfoAuthor.href = packageInfo.author.url; 177 | 178 | if ((packageInfo.keywords?.length ?? 0) === 0) { 179 | packageInfoKeywords.parentElement.classList.add('hidden'); 180 | } else { 181 | packageInfoKeywords.parentElement.classList.remove('hidden'); 182 | packageInfoKeywords.innerHTML = null; 183 | packageInfo.keywords.forEach(keyword => { 184 | const keywordDiv = document.createElement('div'); 185 | keywordDiv.classList.add('me-2', 'mb-2', 'badge'); 186 | keywordDiv.textContent = keyword; 187 | packageInfoKeywords.appendChild(keywordDiv); 188 | }); 189 | } 190 | 191 | if (!packageInfo.license?.length && !packageInfo.licensesUrl?.length) { 192 | packageInfoLicense.parentElement.classList.add('hidden'); 193 | } else { 194 | packageInfoLicense.parentElement.classList.remove('hidden'); 195 | packageInfoLicense.textContent = packageInfo.license ?? 'See License'; 196 | packageInfoLicense.href = packageInfo.licensesUrl ?? '#'; 197 | } 198 | 199 | packageInfoDependencies.innerHTML = null; 200 | Object.entries(packageInfo.dependencies).forEach(([name, version]) => { 201 | const depRow = document.createElement('li'); 202 | depRow.classList.add('mb-2'); 203 | depRow.textContent = `${name} @ v${version}`; 204 | packageInfoDependencies.appendChild(depRow); 205 | }); 206 | 207 | packageInfoModal.hidden = false; 208 | 209 | setTimeout(() => { 210 | const height = packageInfoModal.querySelector('.col').clientHeight; 211 | modalControl.style.setProperty('--dialog-height', `${height + 14}px`); 212 | }, 1); 213 | }); 214 | }); 215 | 216 | const packageInfoVccUrlFieldCopy = document.getElementById('packageInfoVccUrlFieldCopy'); 217 | packageInfoVccUrlFieldCopy.addEventListener('click', () => { 218 | const vccUrlField = document.getElementById('packageInfoVccUrlField'); 219 | vccUrlField.select(); 220 | navigator.clipboard.writeText(vccUrlField.value); 221 | vccUrlFieldCopy.appearance = 'accent'; 222 | setTimeout(() => { 223 | vccUrlFieldCopy.appearance = 'neutral'; 224 | }, 1000); 225 | }); 226 | 227 | const packageInfoListingHelp = document.getElementById('packageInfoListingHelp'); 228 | packageInfoListingHelp.addEventListener('click', () => { 229 | addListingToVccHelp.hidden = false; 230 | }); 231 | })(); -------------------------------------------------------------------------------- /Website/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiveDimensions/VRRefAssist/c5c56b8d2b5af1c44296d2a882fb285a957cdd86/Website/banner.png -------------------------------------------------------------------------------- /Website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiveDimensions/VRRefAssist/c5c56b8d2b5af1c44296d2a882fb285a957cdd86/Website/favicon.ico -------------------------------------------------------------------------------- /Website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | VCC Listing 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | {{~ if listingInfo.BannerImage; ~}} 15 |
16 | {{~ end; ~}} 17 |

18 | {{~ listingInfo.Name ~}} 19 |

20 | {{~ if listingInfo.Description; ~}} 21 |
{{ listingInfo.Description }}
22 | {{~ end; ~}} 23 |
24 | {{~ if listingInfo.Author.Email; ~}} 25 | 26 | {{ listingInfo.Author.Email }} 27 | 28 | {{~ end; ~}} 29 | 30 | {{~ if listingInfo.InfoLink.Url ~}} 31 | 34 | {{~ end; ~}} 35 |
36 |
37 |
38 | 39 | 40 | Add to VCC 41 | 42 | 43 | 44 | 45 | Copy 46 | 47 | 48 | How to add a listing to your VCC 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 97 |
98 | 99 |
100 | 110 | 172 | 173 | 174 | 175 | 176 | Name 177 | 178 | 179 | Type 180 | 181 | 182 | 183 | 184 | {{~ for package in packages ~}} 185 | 186 | 187 |
188 |
{{ package.DisplayName }}
189 |
{{ package.Description }}
190 |
{{ package.Name }}
191 |
192 |
193 | 194 | {{ package.Type }} 195 | 196 | 197 | Add to VCC 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
210 | {{~ end ~}} 211 |
212 |
213 | {{~ if listingInfo.InfoLink.Url ~}} 214 | 217 | {{~ end; ~}} 218 |
219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /Website/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: light dark; 3 | } 4 | 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | body { 10 | padding: 0; 11 | margin: 0; 12 | min-width: 100vw; 13 | min-height: 100vh; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 18 | color: var(--neutral-foreground-rest); 19 | } 20 | 21 | .hidden { 22 | display: none !important; 23 | } 24 | 25 | .row { 26 | display: flex; 27 | flex-direction: row; 28 | } 29 | 30 | .col { 31 | display: flex; 32 | flex-direction: column; 33 | } 34 | 35 | .content { 36 | max-width: 1000px; 37 | width: 100%; 38 | margin: 0 auto; 39 | } 40 | 41 | .align-items-center { 42 | align-items: center; 43 | } 44 | 45 | .justify-content-between { 46 | justify-content: space-between; 47 | } 48 | 49 | .justify-content-end { 50 | justify-content: flex-end; 51 | } 52 | 53 | h1 { 54 | margin-bottom: 0.5rem; 55 | } 56 | 57 | .caption1 { 58 | font-size: 1rem; 59 | color: var(--neutral-foreground-hover); 60 | } 61 | 62 | .caption2 { 63 | font-size: 0.8rem; 64 | margin-top: 0.25rem; 65 | color: var(--neutral-foreground-rest); 66 | } 67 | 68 | .packages { 69 | margin: 0.5rem 0 1rem 0; 70 | max-width: 90%; 71 | padding: 0.25rem; 72 | display: flex; 73 | flex: 1; 74 | } 75 | 76 | #packageGrid { 77 | overflow-y: auto; 78 | width: 100%; 79 | max-height: 40rem; 80 | } 81 | 82 | .packages .packageName { 83 | font-size: 1.1rem; 84 | font-weight: 600; 85 | margin: 0.25rem 0; 86 | } 87 | 88 | .searchBlock { 89 | margin-top: 1rem; 90 | width: 100%; 91 | max-width: 90%; 92 | } 93 | 94 | .searchBlock .root { 95 | width: 100%; 96 | } 97 | 98 | #searchInput { 99 | width: 100%; 100 | } 101 | 102 | .vccUrlField { 103 | min-width: 450px; 104 | max-width: 90%; 105 | flex-grow:1; 106 | } 107 | 108 | #addListingToVccHelp { 109 | z-index: 11; 110 | } 111 | 112 | #packageInfoModal { 113 | z-index: 10; 114 | } 115 | 116 | #rowMoreMenu { 117 | top: 0; 118 | left: 0; 119 | position: absolute; 120 | z-index: 10; 121 | } 122 | 123 | #rowMoreMenu a { 124 | display: block; 125 | text-decoration: none; 126 | color: var(--neutral-foreground-rest); 127 | } 128 | 129 | .bannerImage { 130 | aspect-ratio: 5 / 1; 131 | border-radius: 6px; 132 | max-width: 90%; 133 | width: 100%; 134 | background-size: cover; 135 | background-position: center; 136 | background-repeat: no-repeat; 137 | margin-bottom: 0.25rem; 138 | } 139 | 140 | .badge { 141 | border-radius: 4px; 142 | padding: 0.25rem 0.5rem; 143 | background-color: var(--neutral-fill-hover); 144 | } 145 | 146 | .m-0 { 147 | margin: 0; 148 | } 149 | 150 | .m-1 { 151 | margin: 0.25rem; 152 | } 153 | 154 | .m-2 { 155 | margin: 0.5rem; 156 | } 157 | 158 | .m-3 { 159 | margin: 0.75rem; 160 | } 161 | 162 | .m-4 { 163 | margin: 1rem; 164 | } 165 | 166 | .m-5 { 167 | margin: 2rem; 168 | } 169 | 170 | .mt-1 { 171 | margin-top: 0.25rem; 172 | } 173 | 174 | .mt-2 { 175 | margin-top: 0.5rem; 176 | } 177 | 178 | .mt-3 { 179 | margin-top: 0.75rem; 180 | } 181 | 182 | .mt-4 { 183 | margin-top: 1rem; 184 | } 185 | 186 | .mt-5 { 187 | margin-top: 2rem; 188 | } 189 | 190 | .mb-1 { 191 | margin-bottom: 0.25rem; 192 | } 193 | 194 | .mb-2 { 195 | margin-bottom: 0.5rem; 196 | } 197 | 198 | .mb-3 { 199 | margin-bottom: 0.75rem; 200 | } 201 | 202 | .mb-4 { 203 | margin-bottom: 1rem; 204 | } 205 | 206 | .mb-5 { 207 | margin-bottom: 2rem; 208 | } 209 | 210 | .ms-1 { 211 | margin-left: 0.25rem; 212 | } 213 | 214 | .ms-2 { 215 | margin-left: 0.5rem; 216 | } 217 | 218 | .ms-3 { 219 | margin-left: 0.75rem; 220 | } 221 | 222 | .ms-4 { 223 | margin-left: 1rem; 224 | } 225 | 226 | .ms-5 { 227 | margin-left: 2rem; 228 | } 229 | 230 | .me-1 { 231 | margin-right: 0.25rem; 232 | } 233 | 234 | .me-2 { 235 | margin-right: 0.5rem; 236 | } 237 | 238 | .me-3 { 239 | margin-right: 0.75rem; 240 | } 241 | 242 | .me-4 { 243 | margin-right: 1rem; 244 | } 245 | 246 | .me-5 { 247 | margin-right: 2rem; 248 | } 249 | 250 | .p-1 { 251 | padding: 0.25rem; 252 | } 253 | 254 | .p-2 { 255 | padding: 0.5rem; 256 | } 257 | 258 | .p-3 { 259 | padding: 0.75rem; 260 | } 261 | 262 | .p-4 { 263 | padding: 1rem; 264 | } 265 | 266 | .p-5 { 267 | padding: 2rem; 268 | } 269 | 270 | .pt-1 { 271 | padding-top: 0.25rem; 272 | } 273 | 274 | .pt-2 { 275 | padding-top: 0.5rem; 276 | } 277 | 278 | .pt-3 { 279 | padding-top: 0.75rem; 280 | } 281 | 282 | .pt-4 { 283 | padding-top: 1rem; 284 | } 285 | 286 | .pt-5 { 287 | padding-top: 2rem; 288 | } 289 | 290 | .pb-1 { 291 | padding-bottom: 0.25rem; 292 | } 293 | 294 | .pb-2 { 295 | padding-bottom: 0.5rem; 296 | } 297 | 298 | .pb-3 { 299 | padding-bottom: 0.75rem; 300 | } 301 | 302 | .pb-4 { 303 | padding-bottom: 1rem; 304 | } 305 | 306 | .pb-5 { 307 | padding-bottom: 2rem; 308 | } 309 | 310 | .ps-1 { 311 | padding-left: 0.25rem; 312 | } 313 | 314 | .ps-2 { 315 | padding-left: 0.5rem; 316 | } 317 | 318 | .ps-3 { 319 | padding-left: 0.75rem; 320 | } 321 | 322 | .ps-4 { 323 | padding-left: 1rem; 324 | } 325 | 326 | .ps-5 { 327 | padding-left: 2rem; 328 | } 329 | 330 | .pe-1 { 331 | padding-right: 0.25rem; 332 | } 333 | 334 | .pe-2 { 335 | padding-right: 0.5rem; 336 | } 337 | 338 | .pe-3 { 339 | padding-right: 0.75rem; 340 | } 341 | 342 | .pe-4 { 343 | padding-right: 1rem; 344 | } 345 | 346 | .pe-5 { 347 | padding-right: 2rem; 348 | } 349 | 350 | .w-100 { 351 | width: 100%; 352 | } 353 | 354 | .flex-1 { 355 | flex: 1; 356 | } --------------------------------------------------------------------------------