├── CHANGELOG.md ├── CHANGELOG.md.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── PlayerLoopInterface.asmdef ├── PlayerLoopInterface.asmdef.meta ├── PlayerLoopInterface.cs └── PlayerLoopInterface.cs.meta ├── package.json └── package.json.meta /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.0.0] - not changelogged properly 8 | - Initial version 9 | 10 | ## [2.0.0] - 2021 October 10 11 | ### Removed 12 | - Removed the runInFixedUpdate parameter, as it didn't do anything. 13 | ### Added 14 | - Updated the docs to say how to actually run things in FixedUpdate. 15 | - Better overall documentation 16 | ### Changed 17 | - Replaced a bunch of different internal logging functions with one good one. 18 | 19 | ## [2.0.1] - 2021 October 11 20 | ### Added 21 | - Minor doc improvements 22 | - Update package.json to have documentation, changelog and license url's. 23 | 24 | ## [2.0.2] - 2022 January 06 25 | ### Fixed 26 | - Made PlayerLoopInterface only clean up the systems added through the interface, rather than all functions. This fixes an incompatibility with the Input System, and probably a bunch of other issues! 27 | 28 | ## [2.0.3] - 2022 June 09 29 | ### Fixed 30 | - Fixed systems added through InsertSystemBefore not getting cleaned up when playmode was exited. 31 | 32 | ## [2.0.4] - 2023 November 03 33 | ### Removed 34 | - Removed the PlayerLoopQuitChecker Monobehavivour, as all we needed was the Application.quitting event. 35 | ### Fixed 36 | - Fixed the PlayerLoopQuitChecker objects were never destroyed in the editor. So in addition to using the Application.quitting event, we will make sure to unsubscribe to the event every time it has been called. That way, no memory should be referred between runs of play mode. 37 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ce8d426e781d6dd40830f1124db7a37f 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Baste Nesse Buanes 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 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e9e985fe2885898408f675e3d687de09 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlayerLoopInterface 2 | A simple interface for interacting with Unity's player loop system 3 | 4 | ## About 5 | 6 | Unity exposes the PlayerLoop to allow you to insert your own "systems" to be run in similar ways to eg. Update or FixedUpate. 7 | The interface for that is a bit hairy, and there are bugs that needs workarounds, so this is a nice wrapper interface for interacting with that system. 8 | 9 | ## Installation 10 | 11 | Hit the "Add package from git URL..." button in the package manager, and enter https://github.com/Baste-RainGames/PlayerLoopInterface.git 12 | 13 | In older Unity versions, or if you don't want to open Unity, modify your Packages/Manifest.json to include this package: 14 | ``` 15 | { 16 | "dependencies": { 17 | "com.baste.playerloopinterface": "https://github.com/Baste-RainGames/PlayerLoopInterface.git", 18 | ... 19 | ``` 20 | 21 | ## Quick Use 22 | 23 | Use PlayerLoopInterface.InsertSystemBefore/After to have a callback be executed every frame, before or after some built-in system. 24 | The built-in systems can be found under UnityEngine.PlayerLoop. 25 | 26 | Here's an example that adds an entry point that will be called every frame, just before Unity's builtin Update function: 27 | 28 | ```cs 29 | public static class MyCustomSystem { 30 | 31 | [RuntimeInitializeOnLoadMethod] 32 | private static void Initialize() { 33 | PlayerLoopInterface.InsertSystemBefore(typeof(MyCustomSystem), UpdateSystem, typeof(UnityEngine.PlayerLoop.Update.ScriptRunBehaviourUpdate)); 34 | } 35 | 36 | private static void UpdateSystem() { 37 | Debug.Log("I get called once per frame!"); 38 | } 39 | } 40 | ``` 41 | 42 | If you want a function to run in the fixed timestep (FixedUpdate), you'll have to insert it as a subsystem of UnityEngine.PlayerLoop.FixedUpdate. 43 | A good alternative is to insert it before or after the UnityEngine.PlayerLoop.FixedUpdate.ScriptRunBehaviourFixedUpdate, which is the system that runs the FixedUpdate parts of MonoBehaviours: 44 | 45 | ```cs 46 | PlayerLoopInterface.InsertSystemBefore(typeof(MyFixedTimestepSystem), MyFixedTimestepMethod, typeof(UnityEngine.PlayerLoop.FixedUpdate.ScriptRunBehaviourFixedUpdate)); 47 | ``` 48 | 49 | ## Details, Misc 50 | 51 | Use `PlayerLoopInterface.CurrentLoopToString()` to get a string representation of the entire current player loop. This can be useful for understanding which systems exist, and for debugging your own systems. 52 | 53 | You can also insert a full PlayerLoopSystem instead of using the (Type, delegate) helper methods provided. This gives you access to all of the features of the PlayerLoopSystem. Here they are, with some details on what they are and do (the official docs at https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoopSystem.html) are a bit scarce) 54 | 55 | ```cs 56 | var mySystem = new PlayerLoopSystem { 57 | // The type seems to just be a marker. You can pass in pretty much whatever here. It's used to identify the system, 58 | // both by PlayerLoopSystem and (pressumably) some Unity internals. 59 | type = typeof(MyCustomSystem), 60 | 61 | // This is the C# method that gets called when the system runs. All of the builtin systems has a null delegate here. 62 | updateDelegate = MyMethod, 63 | 64 | /* This is a System.IntPtr. It's set for all of the built in leaf systems (see subSystemList). It's probably a pointer 65 | * to the c++ engine function that's run for those systems. Copying one could, in theory, allow you to eg. run the 66 | * builtin Update as your own thing, if you wanted? I haven't tested that! 67 | */ 68 | updateFunction = IntPtr.Zero, 69 | 70 | /* Red herring. Setting this doesn't seem to have any effect. For the builtin systems, only 71 | * UnityEngine.PlayerLoop.FixedUpdate has this set to a value, but copying that value doesn't seem to do anything */ 72 | loopConditionFunction = IntPtr.Zero, 73 | 74 | /* List of subsystems. Builtin systems uses a null value rather than an empty array for no subsystems. 75 | * Note that systems are structs, so managing the subsystem relations requires a bit of juggling to maintain the 76 | * subsystem tree. 77 | * 78 | * Systems are organized in a tree-like structure. The builtin has a root system (with type Null), which has a few 79 | * subsystems - like EarlyUpdate, FixedUpdate and Update. Those seem to be "folder" systems, as none of them have 80 | * an updateFunction set. The children of these again are leaf systems with no subsystems, but an updateFunction. 81 | * I recommend running PlayerLoopInterface.CurrentLoopToString() to get an overview. */ 82 | subSystemList = new PlayerLoopSystem[] { ... } 83 | }; 84 | 85 | PlayerLoopInterface.InsertSystemBefore(mySystem, typeof(UnityEngine.PlayerLoop.Update)); 86 | ``` 87 | 88 | ## Known Issues 89 | 90 | - When you make changes to the PlayerLoopSystem, they don't get changed back after exiting play mode. This causes systems you have added to be run at edit time (for some reason), which causes all kinds of problems. According to Unity, this is "by design", and it has been used internally (see https://github.com/Unity-Technologies/InputSystem/pull/1424/files#diff-9e93fa9ce6927588c3ffd63c65e117f9aa8879e106f379f0a1d1f19db79a40d0R125). Because of this, the PlayerLoopInterface makes a note of all the systems you have inserted, and removes them on exiting play mode. If you want systems to persist and between beyond play modes, you have to use the PlayerLoop manually 91 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c8848aa76f8a98e48bc30bc91958c251 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d0af1e20ccc8d994287bbb5eb8ec5645 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/PlayerLoopInterface.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PlayerLoopInterface", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false 8 | } -------------------------------------------------------------------------------- /Runtime/PlayerLoopInterface.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89dcb66bc38a0d640922fdcb803cf4bf 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/PlayerLoopInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | #if UNITY_2020_1_OR_NEWER 8 | using UnityEngine.LowLevel; 9 | #else 10 | using UnityEngine.Experimental.LowLevel; 11 | using UnityEngine.Experimental.PlayerLoop; 12 | #endif 13 | 14 | /// 15 | /// Unity exposes the PlayerLoop to allow you to insert your own "systems" to be run in similar ways to eg. Update or FixedUpate. 16 | /// The interface for that is a bit hairy, and there are bugs that needs workarounds, so this is a nice interface for interacting with that system. 17 | /// 18 | /// In essence, use PlayerLoopInterface.InsertSystemBefore/After to have a callback be executed every frame, before or after some built-in system. 19 | /// The built-in systems live in UnityEngine.Experimental.PlayerLoop, so if you want to insert a system to run just before Update, call: 20 | /// 21 | /// PlayerLoopInterface.InsertSystemBefore(typeof(MyType), MyMethod, typeof(UnityEngine.PlayerLoop.Update); 22 | /// 23 | /// If you want to run in the fixed timestep (FixedUpdate), you have to insert the system as a subsystem of UnityEngine.PlayerLoop.FixedUpdate. For example, use 24 | /// UnityEngine.PlayerLoop.FixedUpdate.ScriptRunBehaviourFixedUpdate: 25 | /// 26 | /// PlayerLoopInterface.InsertSystemBefore(typeof(MyType), MyMethod, typeof(UnityEngine.PlayerLoop.FixedUpdate.ScriptRunBehaviourFixedUpdate); 27 | /// 28 | public static class PlayerLoopInterface { 29 | 30 | private static List insertedSystems = new List(); 31 | 32 | [RuntimeInitializeOnLoadMethod] 33 | private static void Initialize() { 34 | // Systems are not automatically removed from the PlayerLoop, so we need to clean up the ones that have been added in play mode, as they'd otherwise 35 | // keep running when outside play mode, and in the next play mode if we don't have assembly reload turned on. 36 | 37 | Application.quitting += ClearInsertedSystems; 38 | } 39 | 40 | private static void ClearInsertedSystems () 41 | { 42 | foreach (var playerLoopSystem in insertedSystems) 43 | TryRemoveSystem(playerLoopSystem.type); 44 | 45 | insertedSystems.Clear(); 46 | 47 | Application.quitting -= ClearInsertedSystems; 48 | } 49 | 50 | private enum InsertType { 51 | Before, 52 | After 53 | } 54 | 55 | /// 56 | /// Inserts a new player loop system in the player loop, just after another system. 57 | /// 58 | /// Type marker for the new system. 59 | /// Callback that will be called each frame after insertAfter. 60 | /// The subsystem to insert the system after. 61 | public static void InsertSystemAfter(Type newSystemMarker, PlayerLoopSystem.UpdateFunction newSystemUpdate, Type insertAfter) { 62 | var playerLoopSystem = new PlayerLoopSystem {type = newSystemMarker, updateDelegate = newSystemUpdate}; 63 | InsertSystemAfter(playerLoopSystem, insertAfter); 64 | } 65 | 66 | /// 67 | /// Inserts a new player loop system in the player loop, just before another system. 68 | /// 69 | /// Type marker for the new system. 70 | /// Callback that will be called each frame before insertBefore. 71 | /// The subsystem to insert the system before. 72 | public static void InsertSystemBefore(Type newSystemMarker, PlayerLoopSystem.UpdateFunction newSystemUpdate, Type insertBefore) { 73 | var playerLoopSystem = new PlayerLoopSystem {type = newSystemMarker, updateDelegate = newSystemUpdate}; 74 | InsertSystemBefore(playerLoopSystem, insertBefore); 75 | } 76 | 77 | /// 78 | /// Inserts a new player loop system in the player loop, just after another system. 79 | /// 80 | /// System to insert. Needs to have updateDelegate and Type set. 81 | /// The subsystem to insert the system after 82 | public static void InsertSystemAfter(PlayerLoopSystem toInsert, Type insertAfter) { 83 | if (toInsert.type == null) 84 | throw new ArgumentException("The inserted player loop system must have a marker type!", nameof(toInsert.type)); 85 | if (toInsert.updateDelegate == null) 86 | throw new ArgumentException("The inserted player loop system must have an update delegate!", nameof(toInsert.updateDelegate)); 87 | if (insertAfter == null) 88 | throw new ArgumentNullException(nameof(insertAfter)); 89 | 90 | var rootSystem = PlayerLoop.GetCurrentPlayerLoop(); 91 | 92 | InsertSystem(ref rootSystem, toInsert, insertAfter, InsertType.After, out var couldInsert); 93 | if (!couldInsert) { 94 | throw new ArgumentException($"When trying to insert the type {toInsert.type.Name} into the player loop after {insertAfter.Name}, " + 95 | $"{insertAfter.Name} could not be found in the current player loop!"); 96 | } 97 | 98 | insertedSystems.Add(toInsert); 99 | PlayerLoop.SetPlayerLoop(rootSystem); 100 | } 101 | 102 | /// 103 | /// Inserts a new player loop system in the player loop, just before another system. 104 | /// 105 | /// System to insert. Needs to have updateDelegate and Type set. 106 | /// The subsystem to insert the system before 107 | public static void InsertSystemBefore(PlayerLoopSystem toInsert, Type insertBefore) { 108 | if (toInsert.type == null) 109 | throw new ArgumentException("The inserted player loop system must have a marker type!", nameof(toInsert.type)); 110 | if (toInsert.updateDelegate == null) 111 | throw new ArgumentException("The inserted player loop system must have an update delegate!", nameof(toInsert.updateDelegate)); 112 | if (insertBefore == null) 113 | throw new ArgumentNullException(nameof(insertBefore)); 114 | 115 | var rootSystem = PlayerLoop.GetCurrentPlayerLoop(); 116 | InsertSystem(ref rootSystem, toInsert, insertBefore, InsertType.Before, out var couldInsert); 117 | if (!couldInsert) { 118 | throw new ArgumentException($"When trying to insert the type {toInsert.type.Name} into the player loop before {insertBefore.Name}, " + 119 | $"{insertBefore.Name} could not be found in the current player loop!"); 120 | } 121 | 122 | insertedSystems.Add(toInsert); 123 | PlayerLoop.SetPlayerLoop(rootSystem); 124 | } 125 | 126 | /// 127 | /// Tries to remove a system from the PlayerLoop. The first system found that has the same type identifier as the supplied one will be removed. 128 | /// 129 | /// Type identifier of the system to remove 130 | /// 131 | public static bool TryRemoveSystem(Type type) { 132 | if (type == null) 133 | throw new ArgumentNullException(nameof(type), "Trying to remove a null type!"); 134 | 135 | var currentSystem = PlayerLoop.GetCurrentPlayerLoop(); 136 | var couldRemove = TryRemoveTypeFrom(ref currentSystem, type); 137 | PlayerLoop.SetPlayerLoop(currentSystem); 138 | return couldRemove; 139 | } 140 | 141 | private static bool TryRemoveTypeFrom(ref PlayerLoopSystem currentSystem, Type type) { 142 | var subSystems = currentSystem.subSystemList; 143 | if (subSystems == null) 144 | return false; 145 | 146 | for (int i = 0; i < subSystems.Length; i++) { 147 | if (subSystems[i].type == type) { 148 | var newSubSystems = new PlayerLoopSystem[subSystems.Length - 1]; 149 | 150 | Array.Copy(subSystems, newSubSystems, i); 151 | Array.Copy(subSystems, i + 1, newSubSystems, i, subSystems.Length - i - 1); 152 | 153 | currentSystem.subSystemList = newSubSystems; 154 | 155 | return true; 156 | } 157 | 158 | if (TryRemoveTypeFrom(ref subSystems[i], type)) 159 | return true; 160 | } 161 | 162 | return false; 163 | } 164 | 165 | public static PlayerLoopSystem CopySystem(PlayerLoopSystem system) { 166 | // PlayerLoopSystem is a struct. 167 | var copy = system; 168 | 169 | // but the sub system list is an array. 170 | if (system.subSystemList != null) { 171 | copy.subSystemList = new PlayerLoopSystem[system.subSystemList.Length]; 172 | for (int i = 0; i < copy.subSystemList.Length; i++) { 173 | copy.subSystemList[i] = CopySystem(system.subSystemList[i]); 174 | } 175 | } 176 | 177 | return copy; 178 | } 179 | 180 | private static void InsertSystem(ref PlayerLoopSystem currentLoopRecursive, PlayerLoopSystem toInsert, Type insertTarget, InsertType insertType, 181 | out bool couldInsert) { 182 | var currentSubSystems = currentLoopRecursive.subSystemList; 183 | if (currentSubSystems == null) { 184 | couldInsert = false; 185 | return; 186 | } 187 | 188 | int indexOfTarget = -1; 189 | for (int i = 0; i < currentSubSystems.Length; i++) { 190 | if (currentSubSystems[i].type == insertTarget) { 191 | indexOfTarget = i; 192 | break; 193 | } 194 | } 195 | 196 | if (indexOfTarget != -1) { 197 | var newSubSystems = new PlayerLoopSystem[currentSubSystems.Length + 1]; 198 | 199 | var insertIndex = insertType == InsertType.Before ? indexOfTarget : indexOfTarget + 1; 200 | 201 | for (int i = 0; i < newSubSystems.Length; i++) { 202 | if (i < insertIndex) 203 | newSubSystems[i] = currentSubSystems[i]; 204 | else if (i == insertIndex) { 205 | newSubSystems[i] = toInsert; 206 | } 207 | else { 208 | newSubSystems[i] = currentSubSystems[i - 1]; 209 | } 210 | } 211 | 212 | couldInsert = true; 213 | currentLoopRecursive.subSystemList = newSubSystems; 214 | } 215 | else { 216 | for (var i = 0; i < currentSubSystems.Length; i++) { 217 | var subSystem = currentSubSystems[i]; 218 | InsertSystem(ref subSystem, toInsert, insertTarget, insertType, out var couldInsertInInner); 219 | if (couldInsertInInner) { 220 | currentSubSystems[i] = subSystem; 221 | couldInsert = true; 222 | return; 223 | } 224 | } 225 | 226 | couldInsert = false; 227 | } 228 | } 229 | 230 | /// 231 | /// Utility to get a string representation of the current player loop. 232 | /// 233 | /// String representation of the current player loop system. 234 | public static string CurrentLoopToString() 235 | { 236 | return PrintSystemToString(PlayerLoop.GetCurrentPlayerLoop()); 237 | } 238 | 239 | private static string PrintSystemToString(PlayerLoopSystem s) { 240 | List<(PlayerLoopSystem, int)> systems = new List<(PlayerLoopSystem, int)>(); 241 | 242 | AddRecursively(s, 0); 243 | void AddRecursively(PlayerLoopSystem system, int depth) 244 | { 245 | systems.Add((system, depth)); 246 | if (system.subSystemList != null) 247 | foreach (var subsystem in system.subSystemList) 248 | AddRecursively(subsystem, depth + 1); 249 | } 250 | 251 | StringBuilder sb = new StringBuilder(); 252 | sb.AppendLine("Systems"); 253 | sb.AppendLine("======="); 254 | foreach (var (system, depth) in systems) 255 | { 256 | // root system has a null type, all others has a marker type. 257 | Append($"System Type: {system.type?.Name ?? "NULL"}"); 258 | 259 | // This is a C# delegate, so it's only set for functions created on the C# side. 260 | Append($"Delegate: {system.updateDelegate}"); 261 | 262 | // This is a pointer, probably to the function getting run internally. Has long values (like 140700263204024) for the builtin ones concrete ones, 263 | // while the builtin grouping functions has 0. So UnityEngine.PlayerLoop.Update has 0, while UnityEngine.PlayerLoop.Update.ScriptRunBehaviourUpdate 264 | // has a concrete value. 265 | Append($"Update Function: {system.updateFunction}"); 266 | 267 | // The loopConditionFunction seems to be a red herring. It's set to a value for only UnityEngine.PlayerLoop.FixedUpdate, but setting a different 268 | // system to have the same loop condition function doesn't seem to do anything 269 | Append($"Loop Condition Function: {system.loopConditionFunction}"); 270 | 271 | // null rather than an empty array when it's empty. 272 | Append($"{system.subSystemList?.Length ?? 0} subsystems"); 273 | 274 | void Append(string s) 275 | { 276 | for (int i = 0; i < depth; i++) 277 | sb.Append(" "); 278 | sb.AppendLine(s); 279 | } 280 | } 281 | 282 | return sb.ToString(); 283 | } 284 | 285 | // [MenuItem("Test/Output current state to file")] 286 | private static void OutputCurrentStateToFile() 287 | { 288 | var str = PrintSystemToString(PlayerLoop.GetCurrentPlayerLoop()); 289 | 290 | Debug.Log(str); 291 | File.WriteAllText("playerLoopInterfaceOutput.txt", str); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /Runtime/PlayerLoopInterface.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8ccc63a22010db24390e9d8a9f0bfb96 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.baste.playerloopinterface", 3 | "version": "2.0.4", 4 | "displayName": "Player Loop Interface", 5 | "description": "Interface for Unity's PlayerLoop System", 6 | "unity" : "2018.4", 7 | "author": { 8 | "name": "Baste Nesse Buanes", 9 | "email": "baste@rain-games.com", 10 | "url": "https://github.com/Baste-RainGames/PlayerLoopInterface" 11 | }, 12 | "documentationUrl": "https://github.com/Baste-RainGames/PlayerLoopInterface/blob/master/README.md", 13 | "changelogUrl": "https://github.com/Baste-RainGames/PlayerLoopInterface/blob/master/CHANGELOG.md", 14 | "licensesUrl": "https://github.com/Baste-RainGames/PlayerLoopInterface/blob/master/LICENSE.md" 15 | } 16 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 16c597cfe7b0f9642bc6c602529f0314 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------