|
| 1 | +This library helps managing MonoBehaviour lifetime by CancellationToken introduced in Unity 2022. |
| 2 | +Not only that but also has an ability to manage "Update" actions ordered and efficiently. |
| 3 | + |
| 4 | +And also! library provides missing `destroyCancellationToken` feature for Unity 2021 LTS!! |
| 5 | + |
| 6 | +**Feature Highlights** |
| 7 | +- [Class Instance Lifetime Management](#object-lifetime-management) |
| 8 | +- [Update Function Manager](#update-function-manager) |
| 9 | + - Designed to address consideration written in the following article |
| 10 | + - https://blog.unity.com/engine-platform/10000-update-calls |
| 11 | +- [API Reference](https://sator-imaging.github.io/Unity-LifecycleManager) |
| 12 | + |
| 13 | +**Installation** |
| 14 | +- In Unity 2021 or later, Enter the following URL in Unity Package Manager (UPM) |
| 15 | + - https://github.com/sator-imaging/Unity-LifecycleManager.git |
| 16 | + |
| 17 | + |
| 18 | +Object Lifetime Management |
| 19 | +========================== |
| 20 | +Here is example to bind instance lifetime to cancellation token or MonoBehaviour. |
| 21 | + |
| 22 | +```csharp |
| 23 | +using SatorImaging.LifecycleManager; |
| 24 | + |
| 25 | +// works on Unity 2021 or later |
| 26 | +disposable.DestroyWith(monoBehaviourOrCancellationToken); |
| 27 | +gameObject.DestroyWith(cancellationToken); |
| 28 | +unityObj.DestroyUnityObjectWith(tokenOrBehaviour); |
| 29 | + |
| 30 | +// bind to unity scene lifetime |
| 31 | +var sceneLifetime = SceneLifetime.Get(gameObject.scene); |
| 32 | +disposable.DestroyWith(sceneLifetime); |
| 33 | +sceneLifetime.Token.Register(() => DoSomethingOnSceneUnloading()); |
| 34 | + |
| 35 | +// lifecycle and its GameObject which will be destroyed on scene unloading |
| 36 | +var sceneLC = SceneLifecycle.Get(); |
| 37 | + |
| 38 | +// nesting lifecycles |
| 39 | +var root = LifecycleBehaviour.Create("Root Lifecycle"); |
| 40 | +var child = LifecycleBehaviour.Create("Child Lifecycle"); |
| 41 | +var grand = LifecycleBehaviour.Create("Grandchild Lifecycle"); |
| 42 | +child.DestroyWith(root); |
| 43 | +grand.DestroyWith(child); |
| 44 | + // --> child and grand will be marked as DontDestroyOnLoad automatically |
| 45 | +
|
| 46 | +// action for debugging purpose which will be invoked before binding (when not null) |
| 47 | +LifetimeExtensions.DebuggerAction = (obj, token, ticket, ownerOrNull) => |
| 48 | +{ |
| 49 | + Debug.Log($"Target Object: {obj}"); |
| 50 | + if (ownerOrNull != null) |
| 51 | + Debug.Log($"Lifetime Owner: {ownerOrNull}"); |
| 52 | + Debug.Log($"CancellationToken: {token}"); |
| 53 | + Debug.Log($"CancellationTokenRegistration: {ticket}"); |
| 54 | +}; |
| 55 | +``` |
| 56 | + |
| 57 | + |
| 58 | +Technical Note |
| 59 | +-------------- |
| 60 | + |
| 61 | +### Unity Object/Component Binding Notice |
| 62 | + |
| 63 | +You can bind `UnityEngine.Object` or component lifetime to cancellation token or MonoBehaviour by using |
| 64 | +`DestroyUnityObjectWith` extension method instead of `DestroyWith`. |
| 65 | + |
| 66 | +Note that when binding unity object lifetime to other, need to consider both situation that component is |
| 67 | +destroyed by scene unloading OR by lifetime owner. As a result, it makes thing really complex and scene |
| 68 | +will be spaghetti-ed. |
| 69 | + |
| 70 | +Strongly recommended that binding GameObject lifetime instead of component, or implement `IDisposable` |
| 71 | +on your unity engine object explicitly to preciously control behaviour. |
| 72 | + |
| 73 | + |
| 74 | +### Inter-Scene Binding Notice |
| 75 | + |
| 76 | +Lifetime binding across scenes is restricted. Nevertheless you want to bind lifetime to another |
| 77 | +scene object, use `DestroyWith(CancellationToken)` method with `monoBehaviour.destroyCancellationToken`. |
| 78 | + |
| 79 | +> [!WARNING] |
| 80 | +> When Unity object bound to another scene object, it will be destroyed by both when lifetime owner is |
| 81 | +> destroyed and scene which containing bound object is unloaded. |
| 82 | +
|
| 83 | + |
| 84 | +### Quick Tests |
| 85 | + |
| 86 | +Select menu commands in `Unity Editor > LifecycleManager > ...` to test the features. |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +Order of Destruction |
| 91 | +-------------------- |
| 92 | +Destroy actions will happen in LIFO order, that is, last lifetime-bound object is destroyed first. |
| 93 | +(of course lifetime owner is destroyed before) |
| 94 | + |
| 95 | +Note that C# class instances and GameObjects destruction order is stable whereas MonoBehaviours |
| 96 | +destruction order is NOT stable. For reference, MonoBehaviours (components) will be destroyed earlier |
| 97 | +when scene is unloaded otherwise destroyed based on binding order. |
| 98 | + |
| 99 | +> [!NOTE] |
| 100 | +> To make destruction order stable, extension method automatically mark lifetime bound GameObjects as |
| 101 | +> `DontDestroyOnLoad`. |
| 102 | +
|
| 103 | + |
| 104 | + |
| 105 | +Update Function Manager |
| 106 | +======================= |
| 107 | +In this feature, each "update" function has 5 stages, Initialize, Early, Normal, Late and Finalize. |
| 108 | +Initialize and Finalize is designed for system usage, other 3 stages are for casual usage. |
| 109 | + |
| 110 | +```csharp |
| 111 | +// create lifecycle behaviour |
| 112 | +var lifecycle = LifecycleBehaviour.Create("My Lifecycle!!"); |
| 113 | + |
| 114 | +// register action to lifecycle stages |
| 115 | +lifecycle.RegisterUpdateEarly(...); |
| 116 | +lifecycle.RegisterUpdate(...); |
| 117 | +lifecycle.RegisterUpdateLate(...); |
| 118 | + |
| 119 | +lifecycle.RegisterLateUpdateEarly(...); |
| 120 | +lifecycle.RegisterLateUpdate(...); |
| 121 | +lifecycle.RegisterLateUpdateLate(...); |
| 122 | + |
| 123 | +lifecycle.RegisterFixedUpdateEarly(...); |
| 124 | +lifecycle.RegisterFixedUpdate(...); |
| 125 | +lifecycle.RegisterFixedUpdateLate(...); |
| 126 | + |
| 127 | +// to remove action manually, store and use instance which returned from register method |
| 128 | +var entry = lifecycle.RegisterUpdateLate(...); |
| 129 | +lifecycle.RemoveUpdateLate(entry); |
| 130 | +``` |
| 131 | + |
| 132 | +> [!NOTE] |
| 133 | +> For performance optimization, removing registered action will swap items in list instead of reordering |
| 134 | +> whole items in list. |
| 135 | +> ie. Order of update stages (early, late, etc) are promised but registered action order is NOT promised. |
| 136 | +> (like Unity) |
| 137 | +
|
| 138 | + |
| 139 | +Automatic Unregistration |
| 140 | +------------------------ |
| 141 | +If action is depending on instance that will be destroyed with cancellation token, You have to specify |
| 142 | +same token to unregister action together when token is canceled. |
| 143 | + |
| 144 | +```csharp |
| 145 | +instance.DestroyWith(token); |
| 146 | +lifecycle.RegisterUpdate(() => instance.NoErrorUntilDisposed(), token); // <-- same token |
| 147 | +
|
| 148 | +// if don't, action will raise error after depending instance is destroyed |
| 149 | +lifecycle.RegisterUpdate(() => instance.NoErrorUntilDisposed()); // <-- error!! |
| 150 | +``` |
| 151 | + |
| 152 | + |
| 153 | +Controlling Order of Multiple Update Managers |
| 154 | +--------------------------------------------- |
| 155 | +To meet your app requirement, it allows to change lifecycle execution order while keeping |
| 156 | +registered actions order. |
| 157 | + |
| 158 | +```csharp |
| 159 | +// simple but effective! don't try to dive into UnityEngine.LowLevel.PlayerLoop system!! |
| 160 | +class UpdateManagerOrganizer : MonoBehaviour |
| 161 | +{ |
| 162 | + public LifecycleBehaviour lifecycle1; |
| 163 | + public LifecycleBehaviour lifecycle2; |
| 164 | + public LifecycleBehaviour lifecycle3; |
| 165 | + |
| 166 | + void OnEnable() |
| 167 | + { |
| 168 | + lifecycle1.enabled = false; |
| 169 | + lifecycle2.enabled = false; |
| 170 | + lifecycle3.enabled = false; |
| 171 | + } |
| 172 | + void Update() |
| 173 | + { |
| 174 | + lifecycle3.Update(); |
| 175 | + lifecycle1.Update(); |
| 176 | + lifecycle2.Update(); |
| 177 | + } |
| 178 | + void LateUpdate() |
| 179 | + { |
| 180 | + lifecycle1.LateUpdate(); |
| 181 | + lifecycle3.LateUpdate(); |
| 182 | + lifecycle2.LateUpdate(); |
| 183 | + } |
| 184 | + void FixedUpdate() |
| 185 | + { |
| 186 | + lifecycle2.FixedUpdate(); |
| 187 | + lifecycle3.FixedUpdate(); |
| 188 | + lifecycle1.FixedUpdate(); |
| 189 | + } |
| 190 | +} |
| 191 | +``` |
0 commit comments