Documentation for the Unity C# Library
Loading...
Searching...
No Matches
EventBetter.cs
Go to the documentation of this file.
1// EventBetter
2// Copyright (c) 2018, Piotr Gwiazdowski <gwiazdorrr+github at gmail.com>
3
4using System;
5using System.Collections.Generic;
6using System.Reflection;
7using UnityEngine;
8
9#if NET_4_6
10using System.Threading.Tasks;
11#endif
12
13namespace PixoVR.Core
14{
15
19 public static partial class EventBetter
20 {
35 public static void Listen<ListenerType, MessageType>(ListenerType listener, System.Action<MessageType> handler,
36 bool once = false,
37 bool exculdeInactive = false)
38 where ListenerType : UnityEngine.Object
39 {
40 HandlerFlags flags = HandlerFlags.IsUnityObject;
41
42 if (once)
43 flags |= HandlerFlags.Once;
45 flags |= HandlerFlags.OnlyIfActiveAndEnabled;
46
47 RegisterInternal(listener, handler, flags);
48 }
49
57 public static IDisposable ListenManual<MessageType>(System.Action<MessageType> handler)
58 {
59 // use the dict as a listener here, it will ensure the handler is going to live forever
61 return new ManualHandlerDisposable()
62 {
63 Handler = actualHandler,
64 MessageType = typeof(MessageType)
65 };
66 }
67
74 public static bool Raise<MessageType>(MessageType message)
75 {
76 return RaiseInternal(message);
77 }
78
85 public static bool Unlisten<MessageType>(UnityEngine.Object listener)
86 {
87 if (listener == null)
88 throw new ArgumentNullException("listener");
89
90 return UnregisterInternal(typeof(MessageType), listener, (eventEntry, index, referenceListener) => object.ReferenceEquals(eventEntry.listeners[index], referenceListener));
91 }
92
98 public static bool UnlistenAll(UnityEngine.Object listener)
99 {
100 if (listener == null)
101 throw new ArgumentNullException("listener");
102
103 bool anyListeners = false;
104 foreach (var entry in s_entriesList)
105 {
107 }
108
109 return anyListeners;
110 }
111
115 public static void Clear()
116 {
117 s_entries.Clear();
118 s_entriesList.Clear();
119 if (s_worker)
120 {
121 UnityEngine.Object.Destroy(s_worker.gameObject);
122 s_worker = null;
123 }
124 }
125
130 public static void RemoveUnusedHandlers()
131 {
132 foreach (var entry in s_entriesList)
133 {
135 }
136 }
137
138
139 #region Coroutine Support
140
149 where MessageType : class
150 {
151 return new YieldListener<MessageType>();
152 }
153
154 public class YieldListener<MessageType> : System.Collections.IEnumerator, IDisposable
155 where MessageType : class
156 {
157 private Delegate handler;
158 public List<MessageType> Messages { get; private set; }
159
160 public MessageType First
161 {
162 get
163 {
164 if (Messages == null || Messages.Count == 0)
165 return null;
166 return Messages[0];
167 }
168 }
169
170 internal YieldListener()
171 {
172 handler = EventBetter.RegisterInternal<YieldListener<MessageType>, MessageType>(
173 this, (msg) => OnMessage(msg), HandlerFlags.DontInvokeIfAddedInAHandler);
174 }
175
176 public void Dispose()
177 {
178 if (handler != null)
179 {
181 handler = null;
182 }
183 }
184
185 private void OnMessage(MessageType msg)
186 {
187 if (Messages == null)
188 {
190 }
191 Messages.Add(msg);
192 }
193
194 bool System.Collections.IEnumerator.MoveNext()
195 {
196 if (Messages != null)
197 {
198 Dispose();
199 return false;
200 }
201 return true;
202 }
203
205 {
206 get { return null; }
207 }
208
209 void System.Collections.IEnumerator.Reset()
210 {
211 }
212 }
213
214 #endregion
215
216 #region Async Support
217
218#if NET_4_6
219
220 public static async Task<MessageType> ListenAsync<MessageType>()
221 {
222 var tcs = new TaskCompletionSource<MessageType>();
223
224 var handler = RegisterInternal<object, MessageType>(s_entries,
225 (msg) => tcs.SetResult(msg), HandlerFlags.DontInvokeIfAddedInAHandler);
226
227 try
228 {
229 return await tcs.Task;
230 }
231 finally
232 {
233 EventBetter.UnlistenHandler(typeof(MessageType), handler);
234 }
235 }
236
237#endif
238
239 #endregion
240
241 #region Private
242
244 {
245 public Type MessageType { get; set; }
246 public Delegate Handler { get; set; }
247
248 public void Dispose()
249 {
250 if (Handler == null)
251 return;
252
253 try
254 {
256 }
257 finally
258 {
259 MessageType = null;
260 Handler = null;
261 }
262 }
263 }
264
265 [Flags]
266 private enum HandlerFlags
267 {
268 None = 0,
269 OnlyIfActiveAndEnabled = 1 << 0,
270 Once = 1 << 1,
272 IsUnityObject = 1 << 3,
273 }
274
275 private sealed class EventBetterWorker : MonoBehaviour
276 {
277 private int instanceId;
278
279 void Awake()
280 {
281 instanceId = this.GetInstanceID();
282 }
283
284 private void LateUpdate()
285 {
286 Debug.Assert(instanceId == s_worker.instanceId);
288 }
289 }
290
291 private class EventEntry
292 {
293 public uint invocationCount = 0;
294 public bool needsCleanup = false;
298
299 public int Count
300 {
301 get { return listeners.Count; }
302 }
303
304 public bool HasFlag(int i, HandlerFlags flag)
305 {
306 return (flags[i] & flag) == flag;
307 }
308
309 public void SetFlag(int i, HandlerFlags flag, bool value)
310 {
311 if (value)
312 {
313 flags[i] |= flag;
314 }
315 else
316 {
317 flags[i] &= ~flag;
318 }
319 }
320
321 public void Add(object listener, Delegate handler, HandlerFlags flag)
322 {
323 UnityEngine.Debug.Assert(listeners.Count == handlers.Count);
324
325 // if not in a handler, don't set this flag as it would ignore first
326 // nested handler
327 if (invocationCount == 0)
328 flag &= ~HandlerFlags.DontInvokeIfAddedInAHandler;
329
330 listeners.Add(listener);
331 handlers.Add(handler);
332 flags.Add(flag);
333 }
334
335 public void NullifyAt(int i)
336 {
337 UnityEngine.Debug.Assert(listeners.Count == handlers.Count);
338 listeners[i] = null;
339 handlers[i] = null;
340 flags[i] = HandlerFlags.None;
341 }
342
343 public void RemoveAt(int i)
344 {
345 UnityEngine.Debug.Assert(listeners.Count == handlers.Count);
346 listeners.RemoveAt(i);
347 handlers.RemoveAt(i);
348 flags.RemoveAt(i);
349 }
350 }
351
364
365
366 private static bool RaiseInternal<T>(T message)
367 {
369
370 if (!s_entries.TryGetValue(typeof(T), out entry))
371 return false;
372
373 bool hadActiveHandlers = false;
374
375 var invocationCount = ++entry.invocationCount;
376
377 try
378 {
379 int initialCount = entry.Count;
380
381 for (int i = 0; i < entry.Count; ++i)
382 {
383 var listener = GetAliveTarget(entry.listeners[i]);
384
385 bool removeHandler = true;
386
387 if (listener != null)
388 {
389 if (entry.HasFlag(i, HandlerFlags.OnlyIfActiveAndEnabled))
390 {
391 var behaviour = listener as UnityEngine.Behaviour;
392 if (!ReferenceEquals(behaviour, null))
393 {
394 if (!behaviour.isActiveAndEnabled)
395 continue;
396 }
397
398 var go = listener as GameObject;
399 if (!ReferenceEquals(go, null))
400 {
401 if (!go.activeInHierarchy)
402 continue;
403 }
404 }
405
406 if (i >= initialCount)
407 {
408 // this is a new handler; if it has a protection flag, don't call it
409 if (entry.HasFlag(i, HandlerFlags.DontInvokeIfAddedInAHandler))
410 {
411 entry.SetFlag(i, HandlerFlags.DontInvokeIfAddedInAHandler, false);
412 continue;
413 }
414 }
415
416 if (!entry.HasFlag(i, HandlerFlags.Once))
417 {
418 removeHandler = false;
419 }
420
421 ((Action<T>)entry.handlers[i])(message);
422
423 hadActiveHandlers = true;
424 }
425
426 if (removeHandler)
427 {
428 if (invocationCount == 1)
429 {
430 // it's OK to compact now
431 entry.RemoveAt(i);
432 --i;
433 --initialCount;
434 }
435 else
436 {
437 // need to wait
438 entry.needsCleanup = true;
439 entry.NullifyAt(i);
440 }
441 }
442 }
443 }
444 finally
445 {
446 UnityEngine.Debug.Assert(invocationCount == entry.invocationCount);
447 --entry.invocationCount;
448
449 if (invocationCount == 1 && entry.needsCleanup)
450 {
451 entry.needsCleanup = false;
453 }
454 }
455
456 return hadActiveHandlers;
457 }
458
459 private static Delegate RegisterInternal<ListenerType, MessageType>(ListenerType listener, System.Action<MessageType> handler, HandlerFlags flags)
460 {
461 return RegisterInternal<MessageType>(listener, handler, flags);
462 }
463
464 private static Delegate RegisterInternal<T>(object listener, Action<T> handler, HandlerFlags flags)
465 {
466 if (listener == null)
467 throw new ArgumentNullException("listener");
468 if (handler == null)
469 throw new ArgumentNullException("handler");
470
471 if ((flags & HandlerFlags.IsUnityObject) == HandlerFlags.IsUnityObject)
472 {
473 Debug.Assert(listener is UnityEngine.Object);
475 }
476
478 if (!s_entries.TryGetValue(typeof(T), out entry))
479 {
480 entry = new EventEntry();
481 s_entries.Add(typeof(T), entry);
482 s_entriesList.Add(entry);
483 }
484
485 entry.Add(listener, handler, flags);
486
487 return handler;
488 }
489
490 private static bool UnlistenHandler(Type messageType, Delegate handler)
491 {
492 return EventBetter.UnregisterInternal(messageType, handler, (eventEntry, index, _handler) => eventEntry.handlers[index] == _handler);
493 }
494
496 {
498 if (!s_entries.TryGetValue(messageType, out entry))
499 {
500 return false;
501 }
502
504 }
505
507 {
508 bool found = false;
509
510 for (int i = 0; i < entry.Count; ++i)
511 {
512 if (entry.listeners[i] == null)
513 continue;
514
515 if (predicate != null && !predicate(entry, i, param))
516 continue;
517
518 found = true;
519 if (entry.invocationCount == 0)
520 {
521 // it's ok to compact now
522 entry.RemoveAt(i);
523 --i;
524 }
525 else
526 {
527 // need to wait
528 entry.needsCleanup = true;
529 entry.NullifyAt(i);
530 }
531 }
532
533 return found;
534 }
535
536 private static object GetAliveTarget(object target)
537 {
538 if (target == null)
539 return null;
540
541 var targetAsUnityObject = target as UnityEngine.Object;
542 if (object.ReferenceEquals(targetAsUnityObject, null))
543 return target;
544
546 return target;
547
548 return null;
549 }
550
552 {
553 for (int i = 0; i < entry.Count; ++i)
554 {
555 var listener = entry.listeners[i];
556 if (entry.HasFlag(i, HandlerFlags.IsUnityObject))
557 {
558 if ((UnityEngine.Object)listener != null)
559 continue;
560 }
561 else
562 {
563 if (listener != null)
564 continue;
565 }
566
567 if (entry.invocationCount == 0)
568 entry.RemoveAt(i--);
569 else
570 entry.NullifyAt(i);
571 }
572 }
573
574 private static void EnsureWorkerExistsAndIsActive()
575 {
576 if (s_worker)
577 {
578 if (!s_worker.isActiveAndEnabled)
579 throw new InvalidOperationException("EventBetterWorker is disabled");
580
581 return;
582 }
583
584 var go = new GameObject("EventBetterWorker", typeof(EventBetterWorker));
585 go.hideFlags = HideFlags.HideAndDontSave;
586 GameObject.DontDestroyOnLoad(go);
587
588 s_worker = go.GetComponent<EventBetterWorker>();
589 if (!s_worker)
590 throw new InvalidOperationException("Unable to create EventBetterWorker");
591 }
592
593 #endregion
594 }
595}
readonly List< Delegate > handlers
void Add(object listener, Delegate handler, HandlerFlags flag)
uint invocationCount
int Count
bool HasFlag(int i, HandlerFlags flag)
readonly List< object > listeners
void SetFlag(int i, HandlerFlags flag, bool value)
readonly List< HandlerFlags > flags
bool needsCleanup
void RemoveAt(int i)
void NullifyAt(int i)
bool System.Collections.IEnumerator. MoveNext()
Intentionally made partial, in case you want to extend it easily.
static Delegate RegisterInternal< ListenerType, MessageType >(ListenerType listener, System.Action< MessageType > handler, HandlerFlags flags)
static bool Unlisten< MessageType >(UnityEngine.Object listener)
Unregisters all MessageType handlers for a given listener.
static object GetAliveTarget(object target)
static void Listen< ListenerType, MessageType >(ListenerType listener, System.Action< MessageType > handler, bool once=false, bool exculdeInactive=false)
Register a message handler.
static IDisposable ListenManual< MessageType >(System.Action< MessageType > handler)
Register a message handler. No listener, you unregister by calling Dispose on returned object....
static bool RaiseInternal< T >(T message)
static void EnsureWorkerExistsAndIsActive()
static List< EventEntry > s_entriesList
For faster iteration.
static YieldListener< MessageType > ListenWait< MessageType >()
Use this in coroutines. Yield will return when at least one event of type MessageType has been raise...
static bool UnlistenAll(UnityEngine.Object listener)
Unregisters all message types for a given listener.
static bool Raise< MessageType >(MessageType message)
Invoke all registered handlers for this message type immediately.
static Dictionary< Type, EventEntry > s_entries
For lookups.
static bool UnlistenHandler(Type messageType, Delegate handler)
static void RemoveUnusedHandlers(EventEntry entry)
static void Clear()
Unregisters everything.
static void RemoveUnusedHandlers()
Removes handlers that will now longer be called because their listeners have been destroyed....
static Delegate RegisterInternal< T >(object listener, Action< T > handler, HandlerFlags flags)
static bool UnregisterInternal< ParamType >(Type messageType, ParamType param, Func< EventEntry, int, ParamType, bool > predicate)
static EventBetterWorker s_worker
For removing dead handlers.