├── .gitattributes ├── .gitignore ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── Scripts.meta ├── Scripts ├── AssemblyInfo.cs ├── AssemblyInfo.cs.meta ├── Core.meta ├── Core │ ├── DefaultSubscriberOptions.cs │ ├── DefaultSubscriberOptions.cs.meta │ ├── EventBusImpl.cs │ ├── EventBusImpl.cs.meta │ ├── Extensions.cs │ ├── Extensions.cs.meta │ ├── GlobalBus.cs │ ├── GlobalBus.cs.meta │ ├── Interfaces.cs │ ├── Interfaces.cs.meta │ ├── ListenerAttribute.cs │ ├── ListenerAttribute.cs.meta │ ├── SubscriberBusWrapper.cs │ ├── SubscriberBusWrapper.cs.meta │ ├── SubscriberWrapper.cs │ ├── SubscriberWrapper.cs.meta │ ├── SubscriberWrapperComparer.cs │ └── SubscriberWrapperComparer.cs.meta ├── EventBus.cs ├── EventBus.cs.meta ├── EventBusBase.cs ├── EventBusBase.cs.meta ├── Extensions.meta ├── Extensions │ ├── Action.meta │ ├── Action │ │ ├── Action.cs │ │ ├── Action.cs.meta │ │ ├── ActionExtensions.cs │ │ └── ActionExtensions.cs.meta │ ├── Event.meta │ ├── Event │ │ ├── Event.cs │ │ ├── Event.cs.meta │ │ ├── EventExtensions.cs │ │ └── EventExtensions.cs.meta │ ├── EventListener.cs │ ├── EventListener.cs.meta │ ├── Request.meta │ ├── Request │ │ ├── Request.cs │ │ ├── Request.cs.meta │ │ ├── RequestExtensions.cs │ │ └── RequestExtensions.cs.meta │ ├── Signal.meta │ └── Signal │ │ ├── DirectorSignalEmitter.cs │ │ ├── DirectorSignalEmitter.cs.meta │ │ ├── SignalEmitter.cs │ │ ├── SignalEmitter.cs.meta │ │ ├── SignalExtensions.cs │ │ ├── SignalExtensions.cs.meta │ │ ├── SignalListener.cs │ │ ├── SignalListener.cs.meta │ │ ├── SignalReceiverListener.cs │ │ └── SignalReceiverListener.cs.meta ├── GenericListener.cs ├── GenericListener.cs.meta ├── Listener.cs ├── Listener.cs.meta ├── Subscriber.cs ├── Subscriber.cs.meta ├── Utils.meta └── Utils │ ├── Logger.cs │ ├── Logger.cs.meta │ ├── SortedCollection.cs │ ├── SortedCollection.cs.meta │ ├── Utils.cs │ └── Utils.cs.meta ├── UnityEventBus.asmdef ├── UnityEventBus.asmdef.meta ├── package.json └── package.json.meta /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NullTale/UnityEventBus/548f8b22a1b4b45c2876c340b314177b549b98a9/.gitignore -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Null Tale 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: fbaa2cd6b0013044e8b8f7d8884f843e 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Description 3 | Designed to be easy to use, extendable and optimized where it's possible. 4 | 5 | An EventBus is a mechanism that allows different components to communicate with each other without knowing about each other. A component can send an Event to the EventBus without knowing who will pick it up or how many others will pick it up. 6 | #### Features 7 | - interface based 8 | - listeners priority sorting 9 | - ability to send functions and messages 10 | - local buses creation with the ability to subscribe as a listener 11 | - timeline compatibility 12 | - filtered messaging 13 | - expandability 14 | 15 | ### Minimal usage example 16 | 17 | ```c# 18 | // subscriber class definition 19 | public class SampleListener : Subscriber, 20 | IListener, // react on GlobalEvent(Enum) messages 21 | IListener>, // react on IEvent messages 22 | IDamageTaker, 23 | IHandle // provide IDamageTaker interface for invokation 24 | ``` 25 | ```c# 26 | // send enum event to the GlobalBus 27 | GlobalBus.Send(GlobalEvent.Activate); 28 | 29 | // send IEvent with custom data 30 | GlobalBus.SendEvent(GlobalEvent.Activate, 1f); 31 | 32 | // send action to the GlobalBus 33 | GlobalBus.SendAction(damageTaker => damageTaker.TakeDamage(1)); 34 | 35 | // send with filtration 36 | GlobalBus.Send(GlobalEvent.Activate, sub => sub is Unit); 37 | ``` 38 | ```c# 39 | // none MonoBehaviour subscriber 40 | public class SampleListenerClass : ISubscriberOptions, // optional priority and name options 41 | IListener, 42 | IDamageTaker, 43 | IHandle 44 | { 45 | // react on sent data 46 | public void React(in GlobalEvent e) 47 | { 48 | if (e == GlobalEvent.GameStart) 49 | { 50 | // do something 51 | } 52 | } 53 | 54 | // provide interface for method invocation 55 | public void IDamageTaker.Damage(int dmg) 56 | { 57 | // do something 58 | } 59 | 60 | // subscriber extra options 61 | public string Name => name; // name for debugging purposes 62 | public int Priority => 1; // execution order related to other subscribed listeners 63 | 64 | // subscription methods (bus can be any, in example used global bus singleton) 65 | public void Subscribe() => GlobalBus.Subscribe(this); 66 | public void Unsubscribe() => GlobalBus.Unsubscribe(this); 67 | } 68 | 69 | ``` 70 | ### Installation 71 | Through Unity Package Manager git URL: 72 | ``` 73 | https://github.com/NullTale/UnityEventBus.git 74 | ``` 75 | 76 | ![PackageManager](https://user-images.githubusercontent.com/1497430/123476308-32c6c500-d605-11eb-8eca-9266624ad58b.gif) 77 | 78 | Or copy-paste somewhere inside your project Assets folder. 79 | 80 | 81 | ## Table of Content 82 | 83 | * [Event Listener](#event-listener) 84 | * [Event Bus](#event-bus) 85 | * [Extentions](#extentions) 86 | + [Action](#action) 87 | + [Event](#event) 88 | + [Signals](#signals) 89 | + [Request](#request) 90 | 91 | ## Event Listener 92 | 93 | On OnEnable event Listener will connect to the desired bus and will start receiving messages from it. 94 | 95 | ```c# 96 | using UnityEngine; 97 | using UnityEventBus; 98 | 99 | // same as SimpleListener : Subscriber, IListener 100 | public class SimpleListener : Listener 101 | { 102 | public override void React(in string e) 103 | { 104 | Debug.Log(e); 105 | } 106 | } 107 | ``` 108 | 109 | In the unity editor, you can set up the behavior in detail. Such as subscription targets and priority. 110 | 111 | >Note: Lower priority triggers first, highest last, equal invokes in order of subscription 112 | 113 | ![Listener](https://user-images.githubusercontent.com/1497430/123495864-1c812f00-d62e-11eb-81a9-0144b56529dd.png) 114 | 115 | To create your custom listener you need to implement at least one `IListener<>` interface and subscribe to the desired bus. 116 | 117 | >Note: Listener can have any number of IListener<> interfaces. 118 | 119 | ```c# 120 | using UnityEngine; 121 | using UnityEventBus; 122 | 123 | public class SimpleListener : MonoBehaviour, IListener 124 | { 125 | private void Start() 126 | { 127 | // somewhere in code... 128 | // send string event to the global bus 129 | GlobalBus.Send("String event"); 130 | } 131 | 132 | // subscribe to the global bus 133 | private void OnEnable() 134 | { 135 | GlobalBus.Subscribe(this); 136 | } 137 | 138 | // unsubscribe from the global bus 139 | private void OnDisable() 140 | { 141 | GlobalBus.UnSubscribe(this); 142 | } 143 | 144 | // react to an event 145 | public void React(in string e) 146 | { 147 | Debug.Log(e); 148 | } 149 | } 150 | ``` 151 | 152 | You can also implement `ISubscriberOptions` interface to setup debug name and listener priority. 153 | 154 | ```c# 155 | public class SimpleListener : MonoBehaviour, IListener, ISubscriberOptions 156 | { 157 | public string Name => name; 158 | public int Priority => 1; 159 | ``` 160 | 161 | ## Event Bus 162 | To create a local bus, you need to derive it from the `EventBusBase` class 163 | 164 | > Note: Event bus can be subscribed to the other buses like a listener, using Subscribe and UnSubscribe methods. 165 | 166 | ```c# 167 | using UnityEngine; 168 | using UnityEventBus; 169 | 170 | public class Unit : EventBusBase 171 | { 172 | private void Start() 173 | { 174 | // send event to this 175 | this.Send("String event"); 176 | } 177 | } 178 | ``` 179 | 180 | 181 | You can also derive it from the `EventBus` class to configure auto-subscription and priority. 182 | > Note: If EventBus implements any Subscribers interfaces, they will be automatically subscribed to it. 183 | 184 | ```c# 185 | public class Unit : EventBus 186 | ``` 187 | 188 | ![Bus](https://user-images.githubusercontent.com/1497430/123495869-2145e300-d62e-11eb-8594-094b221f2bb8.png) 189 | 190 | ## Extentions 191 | 192 | ### Action 193 | Sometimes it can be more convenient to look at listeners as a set of interfaces, the `SendAction` method extension is used for this. For an object to be an action target it must execute an interface` IHandle<>` with the interface type it provides. 194 | ```c# 195 | public class Unit : EventBus 196 | { 197 | [ContextMenu("Heal Action")] 198 | public void Heal() 199 | { 200 | // send invoke heal action on the IUnitHP interface 201 | this.SendAction(hp => hp.Heal(1)); 202 | } 203 | } 204 | ``` 205 | ```c# 206 | public interface IUnitHP 207 | { 208 | void Heal(int val); 209 | } 210 | 211 | // implement IHandle interface to be an action target 212 | public class UnitHP : Subscriber, 213 | IUnitHP, 214 | IHandle 215 | { 216 | public int HP = 2; 217 | 218 | public void Heal(int val) 219 | { 220 | HP += val; 221 | } 222 | } 223 | ``` 224 | 225 | 226 | ### Event 227 | Event is a message that contains keys and optional data. To send an Event, the `SendEvent` extension method is used. To receive events must be implemented `IListener>` interface, where TEventKey is a key of events, which listener wants to react. 228 | 229 | ```c# 230 | using UnityEngine; 231 | using UnityEventBus; 232 | 233 | // unit derived from the EventBus class, he is receives events and propagate them to subscribers 234 | public class Unit : EventBusBase 235 | { 236 | private void Start() 237 | { 238 | // send creation event without data 239 | this.SendEvent(UnitEvent.Created); 240 | } 241 | 242 | [ContextMenu("Damage")] 243 | public void DamageSelf() 244 | { 245 | // send event with int data 246 | this.SendEvent(UnitEvent.Damage, 1); 247 | } 248 | 249 | [ContextMenu("Heal")] 250 | public void HealSelf() 251 | { 252 | this.SendEvent(UnitEvent.Heal, 1); 253 | } 254 | } 255 | 256 | // unit event keys 257 | public enum UnitEvent 258 | { 259 | Created, 260 | Damage, 261 | Heal 262 | } 263 | ``` 264 | 265 | ```c# 266 | using UnityEngine; 267 | using UnityEventBus; 268 | 269 | // OnEnable will subscribe to the unit and start to listen messages 270 | // same as UnitHP : EventListener 271 | public class UnitHP : Subscriber, 272 | IListener> 273 | { 274 | public int HP = 2; 275 | 276 | // reacts to UnitEvents 277 | public override void React(in IEvent e) 278 | { 279 | switch (e.Key) 280 | { 281 | // event with damage or heal key always containts int data 282 | case UnitEvent.Damage: 283 | HP -= e.GetData(); 284 | break; 285 | case UnitEvent.Heal: 286 | HP += e.GetData(); 287 | break; 288 | 289 | case UnitEvent.Created: 290 | break; 291 | } 292 | } 293 | } 294 | ``` 295 | 296 | ![UnitUsage](https://user-images.githubusercontent.com/1497430/123495873-260a9700-d62e-11eb-9e80-f729b71c480b.gif) 297 | 298 | Also multiple data can be sent through an event. 299 | 300 | ```c# 301 | // send multiple data 302 | SendEvent(UnitEvent.Created, 1, 0.2f, this); 303 | ``` 304 | 305 | ```c# 306 | // get multiple data 307 | var (n, f, unit) = e.GetData(); 308 | 309 | // or 310 | if (e.TryGetData(out int n, out float f, out Unit unit)) 311 | { 312 | // do something with data 313 | } 314 | ``` 315 | 316 | ### Signals 317 | The small extension that allow you to use `Timeline.SignalAsset` as messages in order to. 318 | 319 | React to signals. 320 | 321 | ![Menu](https://user-images.githubusercontent.com/1497430/165080026-0a674094-2ea1-4a3d-8c1f-e0c69fba03ef.png) 322 | 323 | Send signals from the director, 324 | 325 | ![Director](https://user-images.githubusercontent.com/1497430/165080029-279f5b63-d134-43c3-9385-e5e2f3d3433a.png) 326 | 327 | or through script or MonoBehaviour. 328 | 329 | ![SignalEmitter](https://user-images.githubusercontent.com/1497430/165080020-b40a7d6c-342f-42ea-a9bc-af1439776764.png) 330 | 331 | 332 | ### Request 333 | Request is needed to get permission for something from another subscriber. Request works just like Event, contains key and optional data, but it can be either approved or ignored and he will propagate until first approval. It is in fact a usable event with the only difference that you can get the result of execution. The `SendRequest` method extension is used to send a Request. 334 | ```c# 335 | public class Unit : EventBus 336 | { 337 | [ContextMenu("HealRequest ")] 338 | public void HealRequest() 339 | { 340 | if (this.SendRequest(UnitEvent.Heal, 1)) 341 | { 342 | // request was approved 343 | // do something, play animation or implement logic 344 | } 345 | } 346 | } 347 | ``` 348 | ```c# 349 | public class UnitHP : Subscriber, 350 | IListener> 351 | { 352 | public int HPMax = 2; 353 | public int HP = 2; 354 | 355 | public void React(in IRequest e) 356 | { 357 | switch (e.Key) 358 | { 359 | case UnitEvent.Heal: 360 | { 361 | // if can heal, approve heal request 362 | var heal = e.GetData(); 363 | if (heal > 0 && HP < HPMax) 364 | { 365 | HP += heal; 366 | e.Approve(); 367 | } 368 | } break; 369 | } 370 | } 371 | } 372 | ``` 373 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b41d8ba9c4595da45aac813ec36ab82b 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f24d0a41d5acd494bb2143544ec84d48 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EventBus")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("NullTale")] 12 | [assembly: AssemblyProduct("Core")] 13 | [assembly: AssemblyCopyright("Copyright © Null Tale, 2022")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | [assembly: InternalsVisibleTo("Core")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | //[assembly: Guid("807e068c-2a0e-4c81-a303-4b4fd3924511")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | [assembly: AssemblyVersion("1.0.0.0")] 38 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Scripts/AssemblyInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 316c52cc735d4d0ba755fe17c863e658 3 | timeCreated: 1657625432 -------------------------------------------------------------------------------- /Scripts/Core.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3057ffd9b313dc3459425b495edea519 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Core/DefaultSubscriberOptions.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEventBus 2 | { 3 | internal class DefaultSubscriberName : ISubscriberName 4 | { 5 | public string Name => string.Empty; 6 | } 7 | 8 | internal class DefaultSubscriberPriority : ISubscriberPriority 9 | { 10 | public int Priority => 0; 11 | } 12 | } -------------------------------------------------------------------------------- /Scripts/Core/DefaultSubscriberOptions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 68625a60ba9b48e8885e24d09a5b6860 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/EventBusImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using UnityEventBus.Utils; 6 | 7 | 8 | namespace UnityEventBus 9 | { 10 | /// 11 | /// EventBus functionality 12 | /// 13 | public class EventBusImpl : IEventBusImpl 14 | { 15 | private const int k_DefaultSetSize = 4; 16 | 17 | private static readonly IComparer k_OrderComparer = new SubscriberWrapperComparer(); 18 | 19 | private Dictionary> m_Subscribers = new Dictionary>(); 20 | private SortedCollection m_Buses = new SortedCollection(k_OrderComparer); 21 | private int m_AddIndex; 22 | 23 | // ======================================================================= 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public void Send(in TEvent e, in TInvoker invoker) 26 | where TInvoker : IEventInvoker 27 | { 28 | var hasListeners = m_Subscribers.TryGetValue(typeof(TEvent), out var listeners) && listeners.Count > 0; 29 | var hasBusses = m_Buses.Count > 0; 30 | 31 | // optimization mess 32 | if (hasListeners && hasBusses) 33 | { 34 | var buses = m_Buses.m_Collection.ToArray(); 35 | var subs = listeners.m_Collection.ToArray(); 36 | 37 | var busIndex = 0; 38 | var subIndex = 0; 39 | 40 | var bus = buses[0]; 41 | var sub = subs[0]; 42 | 43 | while (true) 44 | { 45 | // skip inactive subs, because the order check might refer to the destroyed object 46 | if (sub.IsActive == false) 47 | { 48 | if (++ subIndex >= subs.Length) 49 | { 50 | if (bus.IsActive) 51 | bus.Invoke(in e, in invoker); 52 | while (++ busIndex < buses.Length) 53 | { 54 | bus = buses[busIndex]; 55 | if (bus.IsActive) 56 | bus.Invoke(in e, in invoker); 57 | } 58 | break; 59 | } 60 | 61 | sub = subs[subIndex]; 62 | continue; 63 | } 64 | 65 | if (bus.IsActive == false) 66 | { 67 | if (++ busIndex >= buses.Length) 68 | { 69 | if (sub.IsActive) 70 | sub.Invoke(in e, in invoker); 71 | while (++ subIndex < subs.Length) 72 | { 73 | sub = subs[subIndex]; 74 | if (sub.IsActive) 75 | sub.Invoke(in e, in invoker); 76 | } 77 | break; 78 | } 79 | 80 | bus = buses[busIndex]; 81 | continue; 82 | } 83 | 84 | if (sub.Order == bus.Order ? sub.Index < bus.Index : sub.Order < bus.Order) 85 | { 86 | // invoke listener, move next, if no more listeners invoke remaining buses 87 | sub.Invoke(in e, in invoker); 88 | if (++ subIndex >= subs.Length) 89 | { 90 | if (bus.IsActive) 91 | bus.Invoke(in e, in invoker); 92 | while (++ busIndex < buses.Length) 93 | { 94 | bus = buses[busIndex]; 95 | if (bus.IsActive) 96 | bus.Invoke(in e, in invoker); 97 | } 98 | break; 99 | } 100 | 101 | sub = subs[subIndex]; 102 | } 103 | else 104 | { 105 | // invoke bus, move next, if no more buses invoke remaining listeners 106 | bus.Invoke(in e, in invoker); 107 | if (++ busIndex >= buses.Length) 108 | { 109 | if (sub.IsActive) 110 | sub.Invoke(in e, in invoker); 111 | while (++ subIndex < subs.Length) 112 | { 113 | sub = subs[subIndex]; 114 | if (sub.IsActive) 115 | sub.Invoke(in e, in invoker); 116 | } 117 | break; 118 | } 119 | 120 | bus = buses[busIndex]; 121 | } 122 | } 123 | } 124 | else if (hasBusses) 125 | foreach (var bus in m_Buses.m_Collection.ToArray()) 126 | { 127 | if (bus.IsActive) 128 | bus.Invoke(in e, in invoker); 129 | } 130 | else if (hasListeners) 131 | foreach (var listener in listeners.m_Collection.ToArray()) 132 | { 133 | if (listener.IsActive) 134 | listener.Invoke(in e, in invoker); 135 | } 136 | } 137 | 138 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 139 | public void Subscribe(ISubscriber sub) 140 | { 141 | if (sub is IEventBus bus) 142 | Subscribe(bus); 143 | else 144 | foreach (var wrapper in sub.ExtractWrappers()) 145 | Subscribe(wrapper); 146 | } 147 | 148 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 149 | public void UnSubscribe(ISubscriber sub) 150 | { 151 | if (sub is IEventBus bus) 152 | UnSubscribe(bus); 153 | else 154 | foreach (var wrapper in sub.ExtractWrappers()) 155 | UnSubscribe(wrapper); 156 | } 157 | 158 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 159 | public void Subscribe(SubscriberWrapper sub) 160 | { 161 | if (sub == null) 162 | throw new ArgumentNullException(nameof(sub)); 163 | 164 | sub.Index = m_AddIndex ++; 165 | 166 | // get or create group 167 | if (m_Subscribers.TryGetValue(sub.Key, out var set) == false) 168 | { 169 | set = new SortedCollection(k_OrderComparer, k_DefaultSetSize); 170 | m_Subscribers.Add(sub.Key, set); 171 | } 172 | 173 | set.Add(sub); 174 | } 175 | 176 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 177 | public void UnSubscribe(SubscriberWrapper subscriber) 178 | { 179 | if (subscriber == null) 180 | throw new ArgumentNullException(nameof(subscriber)); 181 | 182 | // remove first match 183 | if (m_Subscribers.TryGetValue(subscriber.Key, out var set)) 184 | { 185 | // remove & dispose 186 | if (set.Extract(in subscriber, out var extracted)) 187 | extracted.Dispose(); 188 | 189 | if (set.Count == 0) 190 | m_Subscribers.Remove(subscriber.Key); 191 | } 192 | 193 | subscriber.Dispose(); 194 | } 195 | 196 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 197 | public void Subscribe(IEventBus bus) 198 | { 199 | if (bus == null) 200 | throw new ArgumentNullException(nameof(bus)); 201 | 202 | m_Buses.Add(SubscriberBusWrapper.Create(in bus, m_AddIndex ++)); 203 | } 204 | 205 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 206 | public void UnSubscribe(IEventBus bus) 207 | { 208 | if (bus == null) 209 | throw new ArgumentNullException(nameof(bus)); 210 | 211 | var busWrapper = SubscriberBusWrapper.Create(in bus, m_AddIndex ++); 212 | if (m_Buses.Extract(busWrapper, out var extracted)) 213 | extracted.Dispose(); 214 | 215 | busWrapper.Dispose(); 216 | } 217 | 218 | public IEnumerable GetSubscribers() 219 | { 220 | return m_Subscribers.SelectMany>, object>(group => group.Value).Concat(m_Buses).OfType(); 221 | } 222 | 223 | public void Dispose() 224 | { 225 | foreach (var wrappers in m_Subscribers.Values) 226 | foreach (var wrapper in wrappers) 227 | wrapper.Dispose(); 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /Scripts/Core/EventBusImpl.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4441fa7a9e9858e44ab7fff64caff489 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace UnityEventBus 7 | { 8 | public static class Extensions 9 | { 10 | internal static Dictionary s_SubscribersTypeCache = new Dictionary(); 11 | internal static readonly DefaultSubscriberName s_DefaultSubscriberName = new DefaultSubscriberName(); 12 | internal static readonly DefaultSubscriberPriority s_DefaultSubscriberPriority = new DefaultSubscriberPriority(); 13 | public static readonly DefaultInvoker s_DefaultInvoker = new DefaultInvoker(); 14 | 15 | // ======================================================================= 16 | public class DefaultInvoker : IEventInvoker 17 | { 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public void Invoke(in TEvent e, in ISubscriber listener) 20 | { 21 | ((IListener)listener).React(in e); 22 | } 23 | } 24 | 25 | public class DefaultInvokerConditional : IEventInvoker 26 | { 27 | public Func m_Filter; 28 | 29 | // ======================================================================= 30 | public void Invoke(in TEvent e, in ISubscriber listener) 31 | { 32 | if (m_Filter(listener)) 33 | ((IListener)listener).React(in e); 34 | } 35 | } 36 | 37 | // ======================================================================= 38 | public static void Send(this IEventBus bus, in TEvent e) 39 | { 40 | bus.Send(in e, in s_DefaultInvoker); 41 | } 42 | 43 | public static void Send(this IEventBus bus, in TEvent e, in Func check) 44 | { 45 | bus.Send(in e, new DefaultInvokerConditional() { m_Filter = check }); 46 | } 47 | 48 | public static void Send(this IListener listener, in TEvent e) 49 | { 50 | s_DefaultInvoker.Invoke(in e, listener); 51 | } 52 | 53 | public static IEnumerable ExtractWrappers(this ISubscriber listener) 54 | { 55 | var listenerType = listener.GetType(); 56 | 57 | // try get cache 58 | if (s_SubscribersTypeCache.TryGetValue(listenerType, out var types)) 59 | return types.Select(type => SubscriberWrapper.Create(listener, type)); 60 | 61 | // extract, get type arguments from implemented ISubscriber<> interfaces 62 | types = listenerType.GetInterfaces() 63 | .Where(it => it.IsGenericType && it.GetGenericTypeDefinition() == typeof(ISubscriber<>)) 64 | .Select(n => n.GenericTypeArguments[0]) 65 | .ToArray(); 66 | 67 | // add to cache 68 | s_SubscribersTypeCache.Add(listenerType, types); 69 | return types.Select(type => SubscriberWrapper.Create(listener, type)); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Scripts/Core/Extensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c8f26974e0196414da3c0601d974fbcd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/GlobalBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using UnityEventBus.Utils; 5 | using UnityEngine; 6 | 7 | namespace UnityEventBus 8 | { 9 | /// 10 | /// EventBus singleton 11 | /// 12 | [DefaultExecutionOrder(-100)] 13 | public sealed partial class GlobalBus : MonoBehaviour, IEventBus 14 | { 15 | private static GlobalBus s_Instance; 16 | public static GlobalBus Instance 17 | { 18 | get 19 | { 20 | #if !UNITY_EVENT_BUS_DISABLE_AUTO_INITIALIZATION 21 | if (s_Instance.IsNull()) 22 | Create(false, false); 23 | #endif 24 | 25 | return s_Instance; 26 | } 27 | 28 | private set 29 | { 30 | if (s_Instance == value) 31 | return; 32 | 33 | s_Instance = value; 34 | 35 | // instance discarded 36 | if (s_Instance.IsNull()) 37 | return; 38 | } 39 | } 40 | 41 | public const object k_DefaultEventData = null; 42 | public const int k_DefaultPriority = 0; 43 | public const string k_DefaultName = ""; 44 | 45 | private IEventBusImpl m_Impl; 46 | internal IEventBusImpl Impl => m_Impl; 47 | 48 | public bool InitOnAwake; 49 | public bool CollectClasses; 50 | public bool CollectFunctions; 51 | 52 | // ======================================================================= 53 | private abstract class ListenerActionBase : IListener, ISubscriberOptions 54 | { 55 | protected string m_Name; 56 | protected int m_Proprity; 57 | 58 | public string Name => m_Name; 59 | public int Priority => m_Proprity; 60 | 61 | // ======================================================================= 62 | public abstract void React(in T e); 63 | 64 | protected ListenerActionBase(string name, int proprity) 65 | { 66 | m_Name = string.IsNullOrEmpty(name) ? Guid.NewGuid().ToString() : name; 67 | m_Proprity = proprity; 68 | } 69 | } 70 | 71 | private class ListenerStaticFunction : ListenerActionBase 72 | { 73 | private ProcessDelagate m_Action; 74 | 75 | // ======================================================================= 76 | private delegate void ProcessDelagate(T e); 77 | 78 | // ======================================================================= 79 | public override void React(in T e) 80 | { 81 | // if key matches invoke action 82 | m_Action(e); 83 | } 84 | 85 | public ListenerStaticFunction(string name, MethodInfo method, int proprity) 86 | : base(name, proprity) 87 | { 88 | // proceed call 89 | m_Action = (ProcessDelagate)Delegate.CreateDelegate(typeof(ProcessDelagate), method); 90 | 91 | // set defaults from method info 92 | if (string.IsNullOrEmpty(name)) 93 | m_Name = method.Name; 94 | } 95 | } 96 | 97 | // ======================================================================= 98 | private void Awake() 99 | { 100 | if (InitOnAwake) 101 | { 102 | Init(new EventBusImpl()); 103 | DontDestroyOnLoad(gameObject); 104 | } 105 | } 106 | 107 | private void Init(IEventBusImpl impl) 108 | { 109 | if (s_Instance != null && s_Instance != this) 110 | throw new NotSupportedException("Can't initialize EventSystem singleton twice."); 111 | 112 | if (impl == null) 113 | throw new ArgumentNullException(nameof(impl)); 114 | 115 | // set implementation 116 | m_Impl = impl; 117 | 118 | // set instance 119 | Instance = this; 120 | 121 | // parse assembly for listeners 122 | _collectAssemblyListeners(); 123 | } 124 | 125 | private void OnDestroy() 126 | { 127 | if (s_Instance == this) 128 | Instance = null; 129 | 130 | if (m_Impl != null) 131 | { 132 | m_Impl.Dispose(); 133 | m_Impl = null; 134 | } 135 | } 136 | 137 | // ======================================================================= 138 | void IEventBus.Send(in TEvent e, in TInvoker invoker) 139 | { 140 | Send(in e, in invoker); 141 | } 142 | 143 | void IEventBus.Subscribe(ISubscriber sub) 144 | { 145 | Subscribe(sub); 146 | } 147 | 148 | void IEventBus.UnSubscribe(ISubscriber sub) 149 | { 150 | UnSubscribe(sub); 151 | } 152 | 153 | // ======================================================================= 154 | /// Create and initialize EventSystem singleton game object, if singleton already created nothing will happen 155 | public static void Create(bool collectClasses, bool collectFunctions) 156 | { 157 | if (s_Instance != null) 158 | return; 159 | 160 | var go = new GameObject(nameof(GlobalBus)); 161 | DontDestroyOnLoad(go); 162 | 163 | var es = go.AddComponent(); 164 | es.CollectClasses = collectClasses; 165 | es.CollectFunctions = collectFunctions; 166 | 167 | es.Init(new EventBusImpl()); 168 | } 169 | 170 | public static void Send(in TEvent e, in TInvoker invoker) where TInvoker : IEventInvoker 171 | { 172 | Instance.m_Impl.Send(in e, in invoker); 173 | } 174 | 175 | public static void Send(in TEvent e, in Func check) 176 | { 177 | Instance.m_Impl.Send(in e, new Extensions.DefaultInvokerConditional() { m_Filter = check }); 178 | } 179 | 180 | public static void Send(in TEvent e) 181 | { 182 | Instance.Send(in e); 183 | } 184 | 185 | public static void Subscribe(ISubscriber sub) 186 | { 187 | Instance.m_Impl.Subscribe(sub); 188 | } 189 | 190 | public static void UnSubscribe(ISubscriber sub) 191 | { 192 | #if UNITY_EDITOR 193 | if (s_Instance == null) 194 | return; 195 | #endif 196 | Instance.m_Impl.UnSubscribe(sub); 197 | } 198 | 199 | // ======================================================================= 200 | [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 201 | private static void _domainReloadCapability() 202 | { 203 | s_Instance = null; 204 | } 205 | 206 | private void _collectAssemblyListeners() 207 | { 208 | // not tested with AOT, questionable usefulness, confusional behavior 209 | if (CollectClasses || CollectFunctions) 210 | { 211 | var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(n => n.GetTypes()).ToArray(); 212 | 213 | // create listener instances 214 | if (CollectClasses) 215 | { 216 | foreach (var type in types) 217 | { 218 | var attribure = type.GetCustomAttribute(); 219 | // not null & active 220 | if (attribure != null && attribure.Active) 221 | { 222 | // must be creatable class 223 | if (type.IsAbstract || type.IsClass == false || type.IsGenericType) 224 | continue; 225 | 226 | // must implement event listener interface 227 | if (typeof(ISubscriber).IsAssignableFrom(type) == false) 228 | continue; 229 | 230 | // create & register listener 231 | try 232 | { 233 | if (typeof(MonoBehaviour).IsAssignableFrom(type)) 234 | { 235 | // listener is monobehaviour type 236 | var el = new GameObject(attribure.Name, type).GetComponent(type) as MonoBehaviour; 237 | el.transform.SetParent(transform); 238 | 239 | Subscribe(el as ISubscriber); 240 | } 241 | else 242 | { 243 | // listener is class 244 | var el = (ISubscriber)Activator.CreateInstance(type); 245 | Subscribe(el); 246 | } 247 | } 248 | catch (Exception e) 249 | { 250 | Debug.LogWarning(e); 251 | } 252 | } 253 | } 254 | } 255 | 256 | // create static function listeners 257 | if (CollectFunctions) 258 | { 259 | foreach (var type in types) 260 | { 261 | // check all static methods 262 | foreach (var methodInfo in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)) 263 | { 264 | try 265 | { 266 | // must be static 267 | if (methodInfo.IsStatic == false) 268 | continue; 269 | 270 | // not generic 271 | if (methodInfo.IsGenericMethod) 272 | continue; 273 | 274 | var attribure = methodInfo.GetCustomAttribute(); 275 | // not null & active attribute 276 | if (attribure == null || attribure.Active == false) 277 | continue; 278 | 279 | var args = methodInfo.GetParameters(); 280 | 281 | // must have input parameter 282 | if (args.Length != 1) 283 | continue; 284 | 285 | // create & register listener 286 | var keyType = args[0].ParameterType; 287 | var el = Activator.CreateInstance( 288 | typeof(ListenerStaticFunction<>).MakeGenericType(keyType), 289 | attribure.Name, methodInfo, attribure.Order) as ISubscriber; 290 | Subscribe(el); 291 | } 292 | catch (Exception e) 293 | { 294 | Debug.LogWarning(e); 295 | } 296 | } 297 | } 298 | } 299 | } 300 | } 301 | 302 | // ======================================================================= 303 | [ContextMenu("Log subscribers")] 304 | public void LogSubscribers() 305 | { 306 | foreach (var subscriber in m_Impl.GetSubscribers()) 307 | Debug.Log(subscriber, subscriber.Target as MonoBehaviour); 308 | } 309 | } 310 | } -------------------------------------------------------------------------------- /Scripts/Core/GlobalBus.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3242f4ad6a2e5a94989f5acfb5fe9ba1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/Interfaces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace UnityEventBus 5 | { 6 | /// Event messages receiver interface 7 | public interface IEventBus : ISubscriber 8 | { 9 | void Send(in TEvent e, in TInvoker invoker) 10 | where TInvoker : IEventInvoker; 11 | 12 | void Subscribe(ISubscriber sub); 13 | void UnSubscribe(ISubscriber sub); 14 | } 15 | 16 | /// Implementation 17 | public interface IEventBusImpl : IDisposable 18 | { 19 | void Send(in TEvent e, in TInvoker invoker) 20 | where TInvoker : IEventInvoker; 21 | 22 | void Subscribe(ISubscriber sub); 23 | void UnSubscribe(ISubscriber sub); 24 | 25 | void Subscribe(SubscriberWrapper sub); 26 | 27 | IEnumerable GetSubscribers(); 28 | } 29 | 30 | /// Invokes events on the listener 31 | public interface IEventInvoker 32 | { 33 | void Invoke(in TEvent e, in ISubscriber listener); 34 | } 35 | 36 | /// Container interface without generic constraints 37 | public interface ISubscriber 38 | { 39 | } 40 | 41 | /// Marker interface, basically event key container in form of generic argument 42 | public interface ISubscriber 43 | { 44 | } 45 | 46 | /// Reaction interface 47 | public interface IListener : ISubscriber, ISubscriber 48 | { 49 | void React(in TEvent e); 50 | } 51 | 52 | /// Provides additional options for event listener 53 | public interface ISubscriberOptions : ISubscriberPriority, ISubscriberName 54 | { 55 | } 56 | 57 | public interface ISubscriberPriority 58 | { 59 | /// Order in listeners queue, lower first, same order listeners will be added at the back of the ordered stack 60 | int Priority { get; } 61 | } 62 | 63 | public interface ISubscriberName 64 | { 65 | /// Listener id, used in logs 66 | string Name { get; } 67 | } 68 | 69 | public interface ISubscriberWrapper 70 | { 71 | int Order { get; } 72 | int Index { get; } 73 | ISubscriber Target { get; } 74 | 75 | void Invoke(in TEvent e, in TInvoker invoker) where TInvoker : IEventInvoker; 76 | } 77 | } -------------------------------------------------------------------------------- /Scripts/Core/Interfaces.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d5ac337232e045c29e1328e8f1bfed16 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/ListenerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityEventBus 4 | { 5 | /// 6 | /// Marker attribute for EventSystem singleton collection function 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 9 | public class ListenerAttribute : Attribute 10 | { 11 | /// Should this method/class be collected 12 | public bool Active { get; set; } = true; 13 | /// Listener order 14 | public int Order { get; set; } = GlobalBus.k_DefaultPriority; 15 | /// Listener name/id, if not set it will be set to the MethodInfo name 16 | public string Name { get; set; } = GlobalBus.k_DefaultName; 17 | } 18 | } -------------------------------------------------------------------------------- /Scripts/Core/ListenerAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 319d8889e58d263468c0c8304b362475 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/SubscriberBusWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace UnityEventBus 6 | { 7 | internal sealed class SubscriberBusWrapper : IDisposable, ISubscriberWrapper 8 | { 9 | internal static Stack s_WrappersPool = new Stack(512); 10 | private ISubscriberName m_Name; 11 | private ISubscriberPriority m_Priority; 12 | 13 | internal bool IsActive; 14 | public IEventBus Bus; 15 | public string Name => m_Name.Name; 16 | public ISubscriber Target => Bus; 17 | public int Order => m_Priority.Priority; 18 | public int Index { get; private set; } 19 | 20 | 21 | // ======================================================================= 22 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 23 | public void Invoke(in TEvent e, in TInvoker invoker) where TInvoker : IEventInvoker 24 | { 25 | #if DEBUG 26 | Bus.Send(in e, in invoker); 27 | #else 28 | try 29 | { 30 | Bus.Send(in e, in invoker); 31 | } 32 | catch (Exception exception) 33 | { 34 | UnityEngine.Debug.LogError($"{this}; Exception: {exception}"); 35 | } 36 | #endif 37 | } 38 | 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public SubscriberBusWrapper(in IEventBus bus, int index) 41 | { 42 | Setup(bus, index); 43 | } 44 | 45 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 | public void Setup(in IEventBus bus, int index) 47 | { 48 | Index = index; 49 | IsActive = true; 50 | Bus = bus; 51 | m_Name = bus as ISubscriberName ?? Extensions.s_DefaultSubscriberName; 52 | m_Priority = bus as ISubscriberPriority ?? Extensions.s_DefaultSubscriberPriority; 53 | } 54 | 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public override bool Equals(object obj) 57 | { 58 | return Bus == ((SubscriberBusWrapper)obj).Bus; 59 | } 60 | 61 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 62 | public override int GetHashCode() 63 | { 64 | return Bus.GetHashCode(); 65 | } 66 | 67 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 68 | public void Dispose() 69 | { 70 | IsActive = false; 71 | Bus = null; 72 | m_Name = null; 73 | m_Priority = null; 74 | s_WrappersPool.Push(this); 75 | } 76 | 77 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 78 | public static SubscriberBusWrapper Create(in IEventBus bus, int index) 79 | { 80 | if (s_WrappersPool.Count > 0) 81 | { 82 | var wrapper = s_WrappersPool.Pop(); 83 | wrapper.Setup(in bus, index); 84 | return wrapper; 85 | } 86 | else 87 | return new SubscriberBusWrapper(in bus, index); 88 | } 89 | 90 | public override string ToString() 91 | { 92 | return $"Bus: {Name}, {Bus.GetType()}"; 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /Scripts/Core/SubscriberBusWrapper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ccfcea427edc4900bc9fcd64ce7d5533 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/SubscriberWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace UnityEventBus 6 | { 7 | /// 8 | /// Subscriber container, helper class 9 | /// 10 | public sealed class SubscriberWrapper : IDisposable, ISubscriberWrapper 11 | { 12 | internal static Stack s_WrappersPool = new Stack(512); 13 | 14 | private ISubscriberName m_Name; 15 | private ISubscriberPriority m_Priority; 16 | 17 | internal bool IsActive; 18 | public Type Key; 19 | public ISubscriber Subscriber; 20 | public string Name => m_Name.Name; 21 | public ISubscriber Target => Subscriber; 22 | public int Order => m_Priority.Priority; 23 | public int Index { get; set; } 24 | 25 | // ======================================================================= 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public void Invoke(in TEvent e, in TInvoker invoker) where TInvoker : IEventInvoker 28 | { 29 | #if DEBUG 30 | invoker.Invoke(in e, in Subscriber); 31 | #else 32 | try 33 | { 34 | invoker.Invoke(in e, in Subscriber); 35 | } 36 | catch (Exception exception) 37 | { 38 | UnityEngine.Debug.LogError($"{this}; Exception: {exception}"); 39 | } 40 | #endif 41 | } 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public SubscriberWrapper(ISubscriber listener, Type type) 45 | { 46 | Setup(listener, type); 47 | } 48 | 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | public void Setup(ISubscriber listener, Type type) 51 | { 52 | IsActive = true; 53 | Subscriber = listener; 54 | m_Name = listener as ISubscriberName ?? Extensions.s_DefaultSubscriberName; 55 | m_Priority = listener as ISubscriberPriority ?? Extensions.s_DefaultSubscriberPriority; 56 | Key = type; 57 | } 58 | 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | public override bool Equals(object obj) 61 | { 62 | return Subscriber == ((SubscriberWrapper)obj).Subscriber; 63 | } 64 | 65 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 | public override int GetHashCode() 67 | { 68 | return Subscriber.GetHashCode(); 69 | } 70 | 71 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 72 | public void Dispose() 73 | { 74 | IsActive = false; 75 | Subscriber = null; 76 | m_Name = null; 77 | m_Priority = null; 78 | s_WrappersPool.Push(this); 79 | } 80 | 81 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 82 | public static SubscriberWrapper Create(ISubscriber listener, Type key) 83 | { 84 | if (s_WrappersPool.Count > 0) 85 | { 86 | var wrapper = s_WrappersPool.Pop(); 87 | wrapper.Setup(listener, key); 88 | return wrapper; 89 | } 90 | else 91 | return new SubscriberWrapper(listener, key); 92 | } 93 | 94 | public override string ToString() 95 | { 96 | return $"Sub: {Name}, {Subscriber.GetType()}, {Key}"; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Scripts/Core/SubscriberWrapper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5b7463bac57e4a0f8df608be39d93a78 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Core/SubscriberWrapperComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace UnityEventBus 5 | { 6 | internal sealed class SubscriberWrapperComparer : IComparer 7 | { 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | public int Compare(ISubscriberWrapper x, ISubscriberWrapper y) 10 | { 11 | return x.Order - y.Order; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Scripts/Core/SubscriberWrapperComparer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a0106eeb9094410c84aa2e236bf2753e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/EventBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace UnityEventBus 6 | { 7 | /// 8 | /// Event bus, with auto subscription functionality 9 | /// 10 | public class EventBus : EventBusBase, ISubscriberOptions 11 | { 12 | public string Name => name; 13 | public int Priority 14 | { 15 | get => m_Priority; 16 | set 17 | { 18 | if (m_Priority == value) 19 | return; 20 | 21 | m_Priority = value; 22 | 23 | // reconnect if order was changed 24 | if (m_Connected) 25 | { 26 | _disconnectBus(); 27 | _connectBus(); 28 | } 29 | } 30 | } 31 | 32 | [SerializeField] 33 | private SubscriptionTarget m_SubscribeTo = SubscriptionTarget.Global; 34 | [SerializeField] 35 | private int m_Priority; 36 | private bool m_Connected; 37 | private List m_Subscriptions = new List(); 38 | 39 | // ======================================================================= 40 | [Serializable] [Flags] 41 | public enum SubscriptionTarget 42 | { 43 | None = 0, 44 | /// EventBus singleton 45 | Global = 1, 46 | /// First parent EventBus 47 | FirstParent = 1 << 1, 48 | } 49 | 50 | public SubscriptionTarget SubscribeTo 51 | { 52 | get => m_SubscribeTo; 53 | set 54 | { 55 | if (m_SubscribeTo == value) 56 | return; 57 | 58 | m_SubscribeTo = value; 59 | 60 | if (m_Connected) 61 | { 62 | _disconnectBus(); 63 | _buildSubscriptionList(); 64 | _connectBus(); 65 | } 66 | else 67 | _buildSubscriptionList(); 68 | } 69 | } 70 | 71 | // ======================================================================= 72 | protected override void Awake() 73 | { 74 | base.Awake(); 75 | _buildSubscriptionList(); 76 | } 77 | 78 | protected virtual void OnEnable() 79 | { 80 | _connectBus(); 81 | } 82 | 83 | protected virtual void OnDisable() 84 | { 85 | _disconnectBus(); 86 | } 87 | 88 | // ======================================================================= 89 | private void _disconnectBus() 90 | { 91 | if (m_Connected == false) 92 | return; 93 | 94 | m_Connected = false; 95 | 96 | foreach (var bus in m_Subscriptions) 97 | bus.UnSubscribe(this); 98 | } 99 | 100 | private void _connectBus() 101 | { 102 | if (m_Connected) 103 | return; 104 | 105 | m_Connected = true; 106 | 107 | foreach (var bus in m_Subscriptions) 108 | bus.Subscribe(this); 109 | } 110 | 111 | private void _buildSubscriptionList() 112 | { 113 | m_Subscriptions.Clear(); 114 | 115 | if (m_SubscribeTo == SubscriptionTarget.None) 116 | return; 117 | 118 | if (m_SubscribeTo.HasFlag(SubscriptionTarget.Global) && GlobalBus.Instance != null) 119 | m_Subscriptions.Add(GlobalBus.Instance); 120 | 121 | if (m_SubscribeTo.HasFlag(SubscriptionTarget.FirstParent) && transform.parent != null) 122 | { 123 | var firstParent = transform.parent.GetComponentInParent(); 124 | if (firstParent != null) 125 | m_Subscriptions.Add(firstParent); 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /Scripts/EventBus.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c6aaef6701d8c54e98e54ea1e3f9a9a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/EventBusBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace UnityEventBus 4 | { 5 | /// 6 | /// EventBusImpl MonoBehaviour wrapper 7 | /// 8 | public class EventBusBase : MonoBehaviour, IEventBus 9 | { 10 | private IEventBusImpl m_Impl = new EventBusImpl(); 11 | 12 | // ======================================================================= 13 | protected virtual void Awake() 14 | { 15 | foreach (var sub in this.ExtractWrappers()) 16 | m_Impl.Subscribe(sub); 17 | } 18 | 19 | public void Send(in TEvent e, in TInvoker invoker) where TInvoker : IEventInvoker 20 | { 21 | m_Impl.Send(in e, in invoker); 22 | } 23 | 24 | public void Subscribe(ISubscriber sub) 25 | { 26 | // add listeners to the bus 27 | m_Impl.Subscribe(sub); 28 | } 29 | 30 | public void UnSubscribe(ISubscriber sub) 31 | { 32 | m_Impl.UnSubscribe(sub); 33 | } 34 | 35 | public void Subscribe(IEventBus bus) 36 | { 37 | m_Impl.Subscribe(bus); 38 | } 39 | 40 | public void UnSubscribe(IEventBus bus) 41 | { 42 | m_Impl.UnSubscribe(bus); 43 | } 44 | 45 | protected virtual void OnDestroy() 46 | { 47 | m_Impl.Dispose(); 48 | m_Impl = null; 49 | } 50 | 51 | // ======================================================================= 52 | [ContextMenu("Log subscribers", false, 0)] 53 | public void LogSubscribers() 54 | { 55 | foreach (var subscriber in m_Impl.GetSubscribers()) 56 | Debug.Log(subscriber.ToString(), subscriber.Target as MonoBehaviour); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Scripts/EventBusBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27d10006829cb4c4caea87068d9a82d6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8481334bae9664c4a911e2e8362137f3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Extensions/Action.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b5e06a5306a503540be146a93b783e93 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Extensions/Action/Action.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace UnityEventBus 5 | { 6 | public interface IHandle 7 | { 8 | } 9 | 10 | /// SendAction target interface 11 | public interface IHandle : ISubscriber>, ISubscriber, IHandle 12 | { 13 | } 14 | 15 | /// Invocation interface without generic constraints 16 | public interface IHandleInvoker 17 | { 18 | Type Type { get; } 19 | void Invoke(ISubscriber listener); 20 | } 21 | 22 | /// Event key interface 23 | public interface IHandleInvoker : IHandleInvoker 24 | { 25 | } 26 | 27 | internal class HandleInvoker : IHandleInvoker 28 | { 29 | private Action m_Action; 30 | public Type Type => typeof(THandle); 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | public void Invoke(ISubscriber listener) 34 | { 35 | m_Action.Invoke((THandle)listener); 36 | } 37 | 38 | public HandleInvoker(Action action) 39 | { 40 | m_Action = action; 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return $"Action({typeof(THandle)})"; 46 | } 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Action/Action.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 118b2358412d469eba7e033d8c38a478 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Action/ActionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace UnityEventBus 5 | { 6 | public static class ActionExtensions 7 | { 8 | public static readonly ActionInvoker s_ActionInvoker = new ActionInvoker(); 9 | 10 | // ======================================================================= 11 | public class ActionInvoker: IEventInvoker 12 | { 13 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 14 | public void Invoke(in TEvent e, in ISubscriber listener) 15 | { 16 | ((IHandleInvoker)e).Invoke(listener); 17 | } 18 | } 19 | 20 | public class ActionInvokerConditional: IEventInvoker 21 | { 22 | public Func m_Filter; 23 | 24 | // ======================================================================= 25 | public void Invoke(in TEvent e, in ISubscriber listener) 26 | { 27 | if (m_Filter(listener)) 28 | ((IHandleInvoker)e).Invoke(listener); 29 | } 30 | } 31 | 32 | // ======================================================================= 33 | public static void SendAction(this IEventBus bus, in Action action) 34 | { 35 | bus.Send, ActionInvoker>(new HandleInvoker(action), s_ActionInvoker); 36 | } 37 | 38 | public static void SendAction(this IEventBus bus, in Action action, in Func check) 39 | { 40 | bus.Send, ActionInvokerConditional>(new HandleInvoker(action), new ActionInvokerConditional(){ m_Filter = check }); 41 | } 42 | 43 | /*public static void SendAction(this IHandle handle, in Action action) 44 | { 45 | s_ActionInvoker.Invoke(new HandleInvoker(action), handle); 46 | }*/ 47 | } 48 | 49 | public sealed partial class GlobalBus 50 | { 51 | public static void SendAction(in Action action) 52 | { 53 | Instance.SendAction(in action); 54 | } 55 | 56 | public static void SendAction(in Action action, in Func check) 57 | { 58 | Instance.SendAction(in action, in check); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Action/ActionExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b77e1c395809468387b3524293701179 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Event.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b15e212fd7b9fa0459aa5fdb1ef04d3f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Extensions/Event/Event.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace UnityEventBus 4 | { 5 | /// Base event helper class 6 | public interface IEventBase { } 7 | 8 | /// Event with key only 9 | /// Type of event key 10 | public interface IEvent : IEventBase 11 | { 12 | TKey Key { get; } 13 | } 14 | 15 | /// Event with key and custom data 16 | /// Type of event data 17 | public interface IEventData : IEventBase 18 | { 19 | TData Data { get; } 20 | } 21 | 22 | /// Base event class 23 | internal class Event : IEvent 24 | { 25 | public TKey Key { get; } 26 | 27 | // ======================================================================= 28 | public Event(in TKey key) 29 | { 30 | Key = key; 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return Key.ToString(); 36 | } 37 | } 38 | 39 | /// Event with data 40 | internal class EventData : Event, IEventData 41 | { 42 | public TData Data { get; } 43 | 44 | // ======================================================================= 45 | public EventData(in TKey key, in TData data) 46 | : base(in key) 47 | { 48 | Data = data; 49 | } 50 | 51 | public override string ToString() 52 | { 53 | return $"{Key} {(typeof(TData) == typeof(object[]) ? (Data as object[])?.Aggregate("", (s, o) => s + " " + o) : " " + Data)}"; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Event/Event.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d76cbdb047da878499a741e842ebe853 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Event/EventExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityEventBus 4 | { 5 | public static class EventExtensions 6 | { 7 | // to the bus 8 | public static void SendEvent(this IEventBus bus, in TKey key) 9 | { 10 | bus.Send>(new Event(in key)); 11 | } 12 | 13 | public static void SendEvent(this IEventBus bus, in TKey key, in TData data) 14 | { 15 | bus.Send>(new EventData(in key, in data)); 16 | } 17 | 18 | public static void SendEvent(this IEventBus bus, in TKey key, params object[] data) 19 | { 20 | bus.Send>(new EventData(in key, in data)); 21 | } 22 | 23 | public static void SendEvent(this IEventBus bus, in TKey key, Func check, params object[] data) 24 | { 25 | bus.Send>(new EventData(in key, in data), check); 26 | } 27 | 28 | public static void SendEvent(this IEventBus bus, in Func check, in TKey key) 29 | { 30 | bus.Send>(new Event(in key), in check); 31 | } 32 | 33 | public static void SendEvent(this IEventBus bus, in TKey key, in Func check, in TData data) 34 | { 35 | bus.Send>(new EventData(in key, in data), in check); 36 | } 37 | 38 | public static void SendEvent(this IEventBus bus, in TKey key, in Func check, params object[] data) 39 | { 40 | bus.Send>(new EventData(in key, in data), in check); 41 | } 42 | 43 | // to the listener 44 | public static void SendEvent(this IListener> receiver, in TKey key) 45 | { 46 | receiver.React(new Event(in key)); 47 | } 48 | 49 | public static void SendEvent(this IListener> receiver, in TKey key, in TData data) 50 | { 51 | receiver.React(new EventData(in key, in data)); 52 | } 53 | 54 | public static void SendEvent(this IListener> receiver, in TKey key, params object[] data) 55 | { 56 | receiver.React(new EventData(in key, in data)); 57 | } 58 | 59 | 60 | public static TData GetData(this IEventBase e) 61 | { 62 | // try get data 63 | return ((IEventData)e).Data; 64 | } 65 | 66 | public static bool TryGetData(this IEventBase e, out TData data) 67 | { 68 | // try get data 69 | if (e is IEventData eventData) 70 | { 71 | data = eventData.Data; 72 | return true; 73 | } 74 | 75 | data = default; 76 | return false; 77 | } 78 | 79 | // deconstructors 80 | public static (T1, T2) GetData(this IEventBase e) 81 | { 82 | // try get data 83 | var dataArray = e.GetData(); 84 | 85 | return ((T1)dataArray[0], (T2)dataArray[1]); 86 | } 87 | 88 | public static (T1, T2, T3) GetData(this IEventBase e) 89 | { 90 | // try get data 91 | var dataArray = e.GetData(); 92 | 93 | return ((T1)dataArray[0], (T2)dataArray[1], (T3)dataArray[2]); 94 | } 95 | 96 | public static (T1, T2, T3, T4) GetData(this IEventBase e) 97 | { 98 | // try get data 99 | var dataArray = e.GetData(); 100 | 101 | return ((T1)dataArray[0], (T2)dataArray[1], (T3)dataArray[2], (T4)dataArray[3]); 102 | } 103 | 104 | public static bool TryGetData(this IEventBase e, out T1 dataA, out T2 dataB) 105 | { 106 | // safe deconstruction version 107 | if (e.TryGetData(out object[] dataArray) == false || dataArray.Length < 4) 108 | { 109 | dataA = default; 110 | dataB = default; 111 | return false; 112 | } 113 | 114 | try 115 | { 116 | dataA = (T1)dataArray[0]; 117 | dataB = (T2)dataArray[1]; 118 | return true; 119 | } 120 | catch 121 | { 122 | dataA = default; 123 | dataB = default; 124 | return false; 125 | } 126 | } 127 | 128 | public static bool TryGetData(this IEventBase e, out T1 dataA, out T2 dataB, out T3 dataC) 129 | { 130 | // safe deconstruction version 131 | if (e.TryGetData(out object[] dataArray) == false || dataArray.Length < 4) 132 | { 133 | dataA = default; 134 | dataB = default; 135 | dataC = default; 136 | return false; 137 | } 138 | 139 | try 140 | { 141 | dataA = (T1)dataArray[0]; 142 | dataB = (T2)dataArray[1]; 143 | dataC = (T3)dataArray[2]; 144 | return true; 145 | } 146 | catch 147 | { 148 | dataA = default; 149 | dataB = default; 150 | dataC = default; 151 | return false; 152 | } 153 | } 154 | 155 | public static bool TryGetData(this IEventBase e, out T1 dataA, out T2 dataB, out T3 dataC, out T4 dataD) 156 | { 157 | // safe deconstruction version 158 | if (e.TryGetData(out object[] dataArray) == false || dataArray.Length < 4) 159 | { 160 | dataA = default; 161 | dataB = default; 162 | dataC = default; 163 | dataD = default; 164 | return false; 165 | } 166 | 167 | try 168 | { 169 | dataA = (T1)dataArray[0]; 170 | dataB = (T2)dataArray[1]; 171 | dataC = (T3)dataArray[2]; 172 | dataD = (T4)dataArray[3]; 173 | return true; 174 | } 175 | catch 176 | { 177 | dataA = default; 178 | dataB = default; 179 | dataC = default; 180 | dataD = default; 181 | return false; 182 | } 183 | } 184 | } 185 | 186 | public sealed partial class GlobalBus 187 | { 188 | /// Send IEvent message 189 | public static void SendEvent(in TKey key) 190 | { 191 | Instance.SendEvent(in key); 192 | } 193 | 194 | /// Send IEventData message with filtration 195 | public static void SendEvent(in TKey key, in Func check, in TData data) 196 | { 197 | Instance.SendEvent(in key, in check, in data); 198 | } 199 | 200 | /// Send IEventData message with filtration 201 | public static void SendEvent(in TKey key, in Func check, params object[] data) 202 | { 203 | Instance.SendEvent(in key, in check, data); 204 | } 205 | 206 | /// Send IEventData message 207 | public static void SendEvent(in TKey key, in TData data) 208 | { 209 | Instance.SendEvent(in key, in data); 210 | } 211 | 212 | /// Send IEventData message 213 | public static void SendEvent(in TKey key, params object[] data) 214 | { 215 | Instance.SendEvent(in key, in data); 216 | } 217 | } 218 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Event/EventExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6fae1366ea8a4532950d02f809dddc21 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/EventListener.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEventBus 2 | { 3 | // helper generic 4 | public abstract class EventListener : Subscriber, IListener> 5 | { 6 | public abstract void React(in IEvent e); 7 | } 8 | 9 | public abstract class EventListener : EventListener, IListener> 10 | { 11 | public abstract void React(in IEvent e); 12 | } 13 | 14 | public abstract class EventListener : EventListener, IListener> 15 | { 16 | public abstract void React(in IEvent e); 17 | } 18 | 19 | public abstract class EventListener : EventListener, IListener> 20 | { 21 | public abstract void React(in IEvent e); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /Scripts/Extensions/EventListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9498df6fd61692742b588ff9682eb73f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Request.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 05aefdec83e1b8f47825a2234ef9ede8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Extensions/Request/Request.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEventBus 2 | { 3 | /// Base request class, can be only approved or ignored 4 | public interface IRequestBase 5 | { 6 | bool IsApproved { get; } 7 | 8 | void Approve(); 9 | } 10 | 11 | /// Worker interface of request 12 | public interface IRequest: IRequestBase, IEvent 13 | { 14 | } 15 | 16 | /// Request extends IEvent 17 | internal class EventRequest : Event, IRequest 18 | { 19 | public bool IsApproved { get; private set; } 20 | 21 | // ======================================================================= 22 | public EventRequest(in TKey key) : base(in key) { } 23 | 24 | public void Approve() 25 | { 26 | IsApproved = true; 27 | } 28 | } 29 | 30 | /// Request extends IEventData 31 | internal class EventDataRequest : EventData, IRequest 32 | { 33 | public bool IsApproved { get; private set; } 34 | 35 | // ======================================================================= 36 | public EventDataRequest(in TKey key, in TData data) : base(in key, in data) { } 37 | 38 | public void Approve() 39 | { 40 | IsApproved = true; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Request/Request.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0dc7657528ad436b9e316a904154cb85 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Request/RequestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace UnityEventBus 5 | { 6 | public static class RequestExtensions 7 | { 8 | public static readonly RequestInvoker s_RequestInvoker = new RequestInvoker(); 9 | 10 | // ======================================================================= 11 | public class RequestInvoker : IEventInvoker 12 | { 13 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 14 | public void Invoke(in TEvent e, in ISubscriber listener) 15 | { 16 | if (((IRequestBase)e).IsApproved) 17 | return; 18 | 19 | ((IListener)listener).React(in e); 20 | } 21 | } 22 | 23 | public class RequestInvokerConditional : IEventInvoker 24 | { 25 | public Func m_Filter; 26 | 27 | // ======================================================================= 28 | public void Invoke(in TEvent e, in ISubscriber listener) 29 | { 30 | if (((IRequestBase)e).IsApproved) 31 | return; 32 | 33 | if (m_Filter(listener)) 34 | ((IListener)listener).React(in e); 35 | } 36 | } 37 | 38 | // ======================================================================= 39 | // to bus 40 | public static bool SendRequest(this IEventBus bus, in TKey key) 41 | { 42 | IRequest request = new EventRequest(in key); 43 | bus.Send(in request, in s_RequestInvoker); 44 | return request.IsApproved; 45 | } 46 | 47 | public static bool SendRequest(this IEventBus bus, in TKey key, in TData data) 48 | { 49 | IRequest request = new EventDataRequest(in key, in data); 50 | bus.Send(in request, in s_RequestInvoker); 51 | return request.IsApproved; 52 | } 53 | 54 | public static bool SendRequest(this IEventBus bus, in TKey key, params object[] data) 55 | { 56 | IRequest request = new EventDataRequest(in key, in data); 57 | bus.Send(in request, in s_RequestInvoker); 58 | return request.IsApproved; 59 | } 60 | 61 | public static bool SendRequest(this IEventBus bus, in TKey key, in Func check) 62 | { 63 | IRequest request = new EventRequest(in key); 64 | bus.Send(in request, new RequestInvokerConditional() { m_Filter = check }); 65 | return request.IsApproved; 66 | } 67 | 68 | public static bool SendRequest(this IEventBus bus, in TKey key, in Func check, in TData data) 69 | { 70 | IRequest request = new EventDataRequest(in key, in data); 71 | bus.Send(in request, new RequestInvokerConditional() { m_Filter = check }); 72 | return request.IsApproved; 73 | } 74 | 75 | public static bool SendRequest(this IEventBus bus, in TKey key, in Func check, params object[] data) 76 | { 77 | IRequest request = new EventDataRequest(in key, in data); 78 | bus.Send(in request, new RequestInvokerConditional() { m_Filter = check }); 79 | return request.IsApproved; 80 | } 81 | 82 | // to listener 83 | public static bool SendRequest(this IListener listener, in TKey key) 84 | { 85 | IRequest request = new EventRequest(in key); 86 | s_RequestInvoker.Invoke(in request, listener); 87 | return request.IsApproved; 88 | } 89 | 90 | public static bool SendRequest(this IListener listener, in TKey key, in TData data) 91 | { 92 | IRequest request = new EventDataRequest(in key, in data); 93 | s_RequestInvoker.Invoke(in request, listener); 94 | return request.IsApproved; 95 | } 96 | 97 | public static bool SendRequest(this IListener listener, in TKey key, params object[] data) 98 | { 99 | IRequest request = new EventDataRequest(in key, in data); 100 | s_RequestInvoker.Invoke(in request, listener); 101 | return request.IsApproved; 102 | } 103 | } 104 | 105 | public sealed partial class GlobalBus 106 | { 107 | public static bool SendRequest(in TKey key) 108 | { 109 | return Instance.SendRequest(in key); 110 | } 111 | 112 | public static bool SendRequest(in TKey key, in Data data) 113 | { 114 | return Instance.SendRequest(in key, data); 115 | } 116 | 117 | public static bool SendRequest(in TKey key, params object[] data) 118 | { 119 | return Instance.SendRequest(key, data); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Request/RequestExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 839e6439b31a4c15af7d6ab047cf33b8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Signal.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b90048b16aee0ca4b82354eaa8be07b4 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/DirectorSignalEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Playables; 4 | using UnityEngine.Timeline; 5 | using UnityEventBus.Utils; 6 | 7 | namespace UnityEventBus 8 | { 9 | public class DirectorSignalEmitter : MonoBehaviour, INotificationReceiver 10 | { 11 | [SerializeField] 12 | private EmitTarget m_EmitTo = EmitTarget.Global; 13 | 14 | // ======================================================================= 15 | [Serializable] [Flags] 16 | public enum EmitTarget 17 | { 18 | None = 0, 19 | Global = 1, 20 | This = 1 << 1, 21 | Parent = 1 << 2, 22 | Childs = 1 << 3, 23 | } 24 | 25 | // ======================================================================= 26 | public void Invoke(SignalAsset signal) 27 | { 28 | // emit the default signal if the argument is null, do not emit null signals 29 | if (signal.IsNull()) 30 | return; 31 | 32 | if (m_EmitTo.HasFlag(EmitTarget.Global)) 33 | signal.Invoke(); 34 | 35 | if (m_EmitTo.HasFlag(EmitTarget.This)) 36 | GetComponent()?.Send(in signal); 37 | 38 | if (m_EmitTo.HasFlag(EmitTarget.Parent)) 39 | transform.parent?.GetComponentInParent()?.Send(in signal); 40 | 41 | // childs can be destroyed throw execution 42 | if (m_EmitTo.HasFlag(EmitTarget.Childs)) 43 | foreach (var child in GetComponentsInChildren()) 44 | child?.Send(in signal); 45 | } 46 | 47 | public void OnNotify(Playable origin, INotification notification, object context) 48 | { 49 | #if UNITY_EDITOR 50 | if (Application.isPlaying == false) 51 | return; 52 | #endif 53 | if (notification is UnityEngine.Timeline.SignalEmitter signal) 54 | Invoke(signal.asset); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/DirectorSignalEmitter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6f1d38b071de3e4993eb0ff4921069f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalEmitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Timeline; 4 | using UnityEventBus.Utils; 5 | 6 | namespace UnityEventBus 7 | { 8 | public class SignalEmitter : MonoBehaviour 9 | { 10 | [SerializeField] 11 | private EmitTarget m_EmitTo = EmitTarget.Global; 12 | [SerializeField] 13 | private SignalAsset m_Signal; 14 | 15 | // ======================================================================= 16 | [Serializable] [Flags] 17 | public enum EmitTarget 18 | { 19 | None = 0, 20 | Global = 1, 21 | This = 1 << 1, 22 | Parent = 1 << 2, 23 | Childs = 1 << 3, 24 | } 25 | 26 | // ======================================================================= 27 | public void Invoke() => Invoke(m_Signal); 28 | public void Invoke(SignalAsset signal) 29 | { 30 | // emit the default signal if the argument is null, do not emit null signals 31 | signal ??= m_Signal; 32 | if (signal.IsNull()) 33 | return; 34 | 35 | if (m_EmitTo.HasFlag(EmitTarget.Global)) 36 | signal.Invoke(); 37 | 38 | if (m_EmitTo.HasFlag(EmitTarget.This)) 39 | GetComponent()?.Send(in signal); 40 | 41 | if (m_EmitTo.HasFlag(EmitTarget.Parent)) 42 | transform.parent?.GetComponentInParent()?.Send(in signal); 43 | 44 | // childs can be destroyed throw execution 45 | if (m_EmitTo.HasFlag(EmitTarget.Childs)) 46 | foreach (var child in GetComponentsInChildren()) 47 | child?.Send(in signal); 48 | } 49 | 50 | [ContextMenu("Invoke", false, 0)] 51 | private void _invokeContextMenu() => Invoke(); 52 | } 53 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalEmitter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0eccb01343fa4e6780133402a51c4ed1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Timeline; 5 | 6 | namespace UnityEventBus 7 | { 8 | public static class SignalExtensions 9 | { 10 | private static Dictionary<(SignalAsset, Action), GenericListener> s_SignalListeners = new Dictionary<(SignalAsset, Action), GenericListener>(32); 11 | private const string k_SignalListenerName = "GSL"; 12 | 13 | // ======================================================================= 14 | public static void Invoke(this SignalAsset signal) 15 | { 16 | GlobalBus.Send(in signal); 17 | } 18 | 19 | #if UNITY_EDITOR 20 | [UnityEditor.MenuItem("CONTEXT/SignalAsset/Invoke")] 21 | private static void InvokeSignalMenu(UnityEditor.MenuCommand menuCommand) 22 | { 23 | if (Application.isPlaying == false) 24 | return; 25 | 26 | if (menuCommand.context is SignalAsset sa) 27 | sa.Invoke(); 28 | } 29 | #endif 30 | 31 | // questionable 32 | private static void Subscribe(this SignalAsset signal, Action action, int order) 33 | { 34 | var signalListener = new GenericListener(s => 35 | { 36 | if (s == signal) 37 | action.Invoke(); 38 | }, order, k_SignalListenerName); 39 | 40 | GlobalBus.Subscribe(signalListener); 41 | s_SignalListeners.Add((signal, action), signalListener); 42 | } 43 | 44 | private static void UnSubscribe(this SignalAsset signal, Action action) 45 | { 46 | if (s_SignalListeners.Remove((signal, action), out var signalListener)) 47 | GlobalBus.UnSubscribe(signalListener); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92de39f4646840beb374167f68085884 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalListener.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | using UnityEngine.Timeline; 4 | 5 | namespace UnityEventBus 6 | { 7 | public class SignalListener : Subscriber, IListener 8 | { 9 | [SerializeField] 10 | private SignalAsset m_Signal; 11 | [SerializeField] 12 | private UnityEvent m_React; 13 | 14 | // ======================================================================= 15 | public void React(in SignalAsset e) 16 | { 17 | if (m_Signal != e) 18 | return; 19 | 20 | m_React.Invoke(e); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 903468561ead4bc5ab78fe211c61b8f2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalReceiverListener.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Timeline; 3 | 4 | namespace UnityEventBus 5 | { 6 | [RequireComponent(typeof(SignalReceiver))] 7 | public class SignalReceiverListener : Subscriber, IListener 8 | { 9 | private SignalReceiver m_SignalReceiver; 10 | 11 | // ======================================================================= 12 | protected override void Awake() 13 | { 14 | base.Awake(); 15 | m_SignalReceiver = GetComponent(); 16 | } 17 | 18 | public void React(in SignalAsset e) 19 | { 20 | m_SignalReceiver.GetReaction(e)?.Invoke(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Scripts/Extensions/Signal/SignalReceiverListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0834afc14ec9471298000f05328e07b9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/GenericListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace UnityEventBus 5 | { 6 | public sealed class GenericListener : IListener, ISubscriberOptions, IDisposable 7 | { 8 | private string m_Name; 9 | private int m_Order; 10 | 11 | public string Name => m_Name; 12 | public int Priority => m_Order; 13 | 14 | private List m_Subscriptions = new List(1); 15 | private Action m_Reaction; 16 | 17 | // ======================================================================= 18 | public GenericListener(Action reaction, int order, string name) 19 | { 20 | m_Reaction = reaction; 21 | m_Order = order; 22 | m_Name = name; 23 | } 24 | 25 | public void Subscribe(IEventBus bus) 26 | { 27 | if (m_Subscriptions.Contains(bus)) 28 | return; 29 | m_Subscriptions.Add(bus); 30 | bus.Subscribe(this); 31 | } 32 | 33 | public void UnSubscribe(IEventBus bus) 34 | { 35 | if (m_Subscriptions.Remove(bus)) 36 | bus.UnSubscribe(this); 37 | } 38 | 39 | public void Dispose() 40 | { 41 | foreach (var bus in m_Subscriptions) 42 | { 43 | bus.UnSubscribe(this); 44 | } 45 | } 46 | 47 | public void React(in T e) 48 | { 49 | m_Reaction(e); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Scripts/GenericListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6f6e5746ffcc4f68805fcfe4486243b1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Listener.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEventBus 2 | { 3 | // helper generic 4 | public abstract class Listener : Subscriber, IListener 5 | { 6 | public abstract void React(in A e); 7 | } 8 | 9 | public abstract class Listener : Listener, IListener 10 | { 11 | public abstract void React(in B e); 12 | } 13 | 14 | public abstract class Listener : Listener, IListener 15 | { 16 | public abstract void React(in C e); 17 | } 18 | 19 | public abstract class Listener : Listener, IListener 20 | { 21 | public abstract void React(in D e); 22 | } 23 | } -------------------------------------------------------------------------------- /Scripts/Listener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f6a278e636849ab98c8691b9732d5da 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Subscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace UnityEventBus 6 | { 7 | /// Base class for EventListener & Listener MonoBehavior 8 | public abstract class Subscriber : MonoBehaviour, ISubscriber, ISubscriberOptions 9 | { 10 | [SerializeField] [Tooltip("Subscription targets")] 11 | private SubscriptionTarget m_SubscribeTo = SubscriptionTarget.FirstParent; 12 | [SerializeField] [Tooltip("Listener priority, lowest first, same last")] 13 | private int m_Priority; 14 | private bool m_Connected; 15 | private List m_Buses = new List(); 16 | 17 | public List Subscriptions => m_Buses; 18 | public string Name => gameObject.name; 19 | 20 | public int Priority 21 | { 22 | get => m_Priority; 23 | set 24 | { 25 | if (m_Priority == value) 26 | return; 27 | 28 | m_Priority = value; 29 | 30 | // reconnect if order was changed 31 | if (m_Connected) 32 | { 33 | _disconnectListener(); 34 | _connectListener(); 35 | } 36 | } 37 | } 38 | 39 | public SubscriptionTarget SubscribeTo 40 | { 41 | get => m_SubscribeTo; 42 | set 43 | { 44 | if (m_SubscribeTo == value) 45 | return; 46 | 47 | m_SubscribeTo = value; 48 | 49 | if (m_Connected) 50 | { 51 | _disconnectListener(); 52 | _buildSubscriptionList(); 53 | _connectListener(); 54 | } 55 | else 56 | _buildSubscriptionList(); 57 | } 58 | } 59 | 60 | // ======================================================================= 61 | [Serializable] [Flags] 62 | public enum SubscriptionTarget 63 | { 64 | None = 0, 65 | /// EventBus singleton 66 | Global = 1, 67 | /// First parent EventBus 68 | FirstParent = 1 << 1, 69 | /// This gameObject EventBus 70 | This = 1 << 2, 71 | } 72 | 73 | // ======================================================================= 74 | protected virtual void Awake() 75 | { 76 | _buildSubscriptionList(); 77 | m_Connected = false; 78 | } 79 | 80 | protected virtual void OnEnable() 81 | { 82 | _connectListener(); 83 | } 84 | 85 | protected virtual void OnDisable() 86 | { 87 | _disconnectListener(); 88 | } 89 | 90 | // ======================================================================= 91 | private void _connectListener() 92 | { 93 | // connect if disconnected 94 | if (m_Connected) 95 | return; 96 | 97 | foreach (var bus in m_Buses) 98 | bus.Subscribe(this); 99 | 100 | m_Connected = true; 101 | } 102 | 103 | private void _disconnectListener() 104 | { 105 | // disconnect if connected 106 | if (m_Connected == false) 107 | return; 108 | 109 | foreach (var bus in m_Buses) 110 | bus.UnSubscribe(this); 111 | 112 | m_Connected = false; 113 | } 114 | 115 | private void _buildSubscriptionList() 116 | { 117 | m_Buses.Clear(); 118 | if (m_SubscribeTo == SubscriptionTarget.None) 119 | return; 120 | 121 | // EventSystem singleton 122 | if (m_SubscribeTo.HasFlag(SubscriptionTarget.Global) && ReferenceEquals(GlobalBus.Instance, null) == false) 123 | m_Buses.Add(GlobalBus.Instance); 124 | 125 | // first parent EventBus 126 | if (m_SubscribeTo.HasFlag(SubscriptionTarget.FirstParent) && ReferenceEquals(transform.parent, null) == false) 127 | { 128 | var firstParent = transform.parent.GetComponentInParent(); 129 | if (firstParent != null) 130 | m_Buses.Add(firstParent); 131 | } 132 | 133 | // self if has IEventBus component 134 | if (m_SubscribeTo.HasFlag(SubscriptionTarget.This)) 135 | { 136 | if (transform.TryGetComponent(out var thisBus)) 137 | m_Buses.Add(thisBus); 138 | } 139 | } 140 | 141 | private void _resubscribe(SubscriptionTarget subscribeTo) 142 | { 143 | var unsibscribe = m_SubscribeTo & ~subscribeTo; 144 | var subscribe = m_SubscribeTo ^ subscribeTo; 145 | 146 | // unsubscribe from 147 | if (unsibscribe.HasFlag(SubscriptionTarget.Global)) 148 | m_Buses.Remove(GlobalBus.Instance); 149 | if (unsibscribe.HasFlag(SubscriptionTarget.FirstParent)) 150 | m_Buses.Remove(transform.parent.GetComponentInParent()); 151 | if (unsibscribe.HasFlag(SubscriptionTarget.This)) 152 | m_Buses.Remove(GetComponent()); 153 | 154 | // subscribe to 155 | if (subscribe.HasFlag(SubscriptionTarget.Global) && ReferenceEquals(GlobalBus.Instance, null) == false) 156 | m_Buses.Remove(GlobalBus.Instance); 157 | if (subscribe.HasFlag(SubscriptionTarget.FirstParent) && ReferenceEquals(transform.parent, null) == false) 158 | { 159 | var firstParent = transform.parent.GetComponentInParent(); 160 | if (firstParent != null) 161 | m_Buses.Add(firstParent); 162 | } 163 | if (subscribe.HasFlag(SubscriptionTarget.This)) 164 | { 165 | if (transform.TryGetComponent(out var thisBus)) 166 | m_Buses.Add(thisBus); 167 | } 168 | 169 | m_SubscribeTo = subscribeTo; 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /Scripts/Subscriber.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df9e375205d145318d45ee8fa1df22b4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Utils.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba1262dd4f252b44baf8228925b7d615 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Utils/Logger.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace UnityEventBus.Utils 5 | { 6 | public sealed class Logger : MonoBehaviour, IEventBus, ISubscriberOptions 7 | { 8 | public string Name => nameof(Logger); 9 | public int Priority => int.MinValue; 10 | 11 | [SerializeField] 12 | private GameObject m_ListenTo; 13 | 14 | [SerializeField] 15 | private int m_LogLenght = 8; 16 | [SerializeField] 17 | private List m_Log; 18 | 19 | private IEventBus m_ConnectedTo; 20 | 21 | // ======================================================================= 22 | private void OnEnable() 23 | { 24 | if (m_ListenTo != null && m_ListenTo.TryGetComponent(out IEventBus bus)) 25 | { 26 | m_ConnectedTo = bus; 27 | m_ConnectedTo.Subscribe(this); 28 | } 29 | } 30 | 31 | private void OnDisable() 32 | { 33 | if (m_ListenTo != null && m_ConnectedTo != null) 34 | { 35 | m_ConnectedTo.UnSubscribe(this); 36 | m_ConnectedTo = null; 37 | } 38 | } 39 | 40 | public void Send(in TEvent e, in TInvoker invoker) where TInvoker : IEventInvoker 41 | { 42 | m_Log.Add(e.ToString()); 43 | 44 | if (m_Log.Count > m_LogLenght) 45 | m_Log.RemoveAt(0); 46 | } 47 | 48 | public void Subscribe(ISubscriber sub) 49 | { 50 | throw new System.NotImplementedException(); 51 | } 52 | 53 | public void UnSubscribe(ISubscriber sub) 54 | { 55 | throw new System.NotImplementedException(); 56 | } 57 | 58 | public void Subscribe(IEventBus bus) 59 | { 60 | throw new System.NotImplementedException(); 61 | } 62 | 63 | public void UnSubscribe(IEventBus bus) 64 | { 65 | throw new System.NotImplementedException(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Scripts/Utils/Logger.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7f23dbb3bdb40daa2e19c1c8411c4a3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Utils/SortedCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace UnityEventBus.Utils 7 | { 8 | internal class SortedCollection : ICollection 9 | { 10 | public int Count => m_Collection.Count; 11 | public bool IsReadOnly => false; 12 | public List m_Collection; 13 | private IComparer m_Comparer; 14 | 15 | // ======================================================================= 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 17 | public SortedCollection(Comparison compare) 18 | { 19 | m_Collection = new List(); 20 | m_Comparer = Comparer.Create(compare); 21 | } 22 | 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public SortedCollection(IComparer comparer) 25 | { 26 | m_Collection = new List(); 27 | m_Comparer = comparer; 28 | } 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public SortedCollection(IComparer comparer, int size) 31 | { 32 | m_Collection = new List(size); 33 | m_Comparer = comparer; 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public IEnumerator GetEnumerator() => m_Collection.GetEnumerator(); 38 | 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | IEnumerator IEnumerable.GetEnumerator() => m_Collection.GetEnumerator(); 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public void Add(T item) 44 | { 45 | var index = m_Collection.FindIndex(n => m_Comparer.Compare(n, item) > 0); 46 | 47 | if (index != -1) 48 | m_Collection.Insert(index, item); 49 | else 50 | m_Collection.Add(item); 51 | } 52 | 53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 | public void Clear() => m_Collection.Clear(); 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public bool Contains(T item) => m_Collection.Contains(item); 58 | 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | public void CopyTo(T[] array, int arrayIndex) => m_Collection.CopyTo(array, arrayIndex); 61 | 62 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 63 | public bool Remove(T item) => m_Collection.Remove(item); 64 | 65 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 | public bool Extract(in T item, out T extracted) 67 | { 68 | var index = m_Collection.IndexOf(item); 69 | 70 | if (index == -1) 71 | { 72 | extracted = default; 73 | return false; 74 | } 75 | 76 | extracted = m_Collection[index]; 77 | m_Collection.RemoveAt(index); 78 | return true; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Scripts/Utils/SortedCollection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7467d0e2e04bc8641a96f30f3fb61487 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Utils/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace UnityEventBus.Utils 5 | { 6 | internal static class Utils 7 | { 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | internal static bool Implements(this Type source) where T : class 10 | { 11 | return typeof(T).IsAssignableFrom(source); 12 | } 13 | 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | internal static bool IsNull(this T o) where T : class 16 | { 17 | return ReferenceEquals(o, null); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Scripts/Utils/Utils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5dbc5e9bced248c7bcfb7e37ec88913c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityEventBus.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UnityEventBus", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:f06555f75b070af458a003d92f9efb00" 6 | ], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /UnityEventBus.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 087426a28fd9b174e9d8a004f9a52b11 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "www.nulltale.eventsystem", 3 | "displayName": "Unity Event Bus", 4 | "version": "1.0.0", 5 | "unity": "2020.1", 6 | "description": "Unity Event System", 7 | "author": { 8 | "name": "NullTale", 9 | "email": "nulltale@gmail.com", 10 | "url": "https://twitter.com/nulltale" 11 | }, 12 | "documentationUrl": "https://github.com/NullTale/UnityEventBus", 13 | "type": "library", 14 | "license" : "MIT", 15 | "dependencies": {}, 16 | "hideInEditor": false 17 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75125ccaac96ea14496f0a938558d23e 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------