-
Notifications
You must be signed in to change notification settings - Fork 34
/
Signals.cs
347 lines (311 loc) · 11.4 KB
/
Signals.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// ========================================================================================
// Signals - A typesafe, lightweight messaging lib for Unity.
// ========================================================================================
// 2017-2019, Yanko Oliveira / http://yankooliveira.com / http://twitter.com/yankooliveira
// Special thanks to Max Knoblich for code review and Aswhin Sudhir for the anonymous
// function asserts suggestion.
// ========================================================================================
// Inspired by StrangeIOC, minus the clutter.
// Based on http://wiki.unity3d.com/index.php/CSharpMessenger_Extended
// Converted to use strongly typed parameters and prevent use of strings as ids.
//
// Supports up to 3 parameters. More than that, and you should probably use a VO.
//
// Usage:
// 1) Define your class, eg:
// ScoreSignal : ASignal<int> {}
// 2) Add listeners on portions that should react, eg on Awake():
// Signals.Get<ScoreSignal>().AddListener(OnScore);
// 3) Dispatch, eg:
// Signals.Get<ScoreSignal>().Dispatch(userScore);
// 4) Don't forget to remove the listeners upon destruction! Eg on OnDestroy():
// Signals.Get<ScoreSignal>().RemoveListener(OnScore);
// 5) If you don't want to use global Signals, you can have your very own SignalHub
// instance in your class
//
// ========================================================================================
using System;
using System.Collections.Generic;
namespace deVoid.Utils
{
/// <summary>
/// Base interface for Signals
/// </summary>
public interface ISignal
{
string Hash { get; }
}
/// <summary>
/// Signals main facade class for global, game-wide signals
/// </summary>
public static class Signals
{
private static readonly SignalHub hub = new SignalHub();
public static SType Get<SType>() where SType : ISignal, new()
{
return hub.Get<SType>();
}
public static void AddListenerToHash(string signalHash, Action handler)
{
hub.AddListenerToHash(signalHash, handler);
}
public static void RemoveListenerFromHash(string signalHash, Action handler)
{
hub.RemoveListenerFromHash(signalHash, handler);
}
}
/// <summary>
/// A hub for Signals you can implement in your classes
/// </summary>
public class SignalHub
{
private Dictionary<Type, ISignal> signals = new Dictionary<Type, ISignal>();
/// <summary>
/// Getter for a signal of a given type
/// </summary>
/// <typeparam name="SType">Type of signal</typeparam>
/// <returns>The proper signal binding</returns>
public SType Get<SType>() where SType : ISignal, new()
{
Type signalType = typeof(SType);
ISignal signal;
if (signals.TryGetValue (signalType, out signal))
{
return (SType)signal;
}
return (SType)Bind(signalType);
}
/// <summary>
/// Manually provide a SignalHash and bind it to a given listener
/// (you most likely want to use an AddListener, unless you know exactly
/// what you are doing)
/// </summary>
/// <param name="signalHash">Unique hash for signal</param>
/// <param name="handler">Callback for signal listener</param>
public void AddListenerToHash(string signalHash, Action handler)
{
ISignal signal = GetSignalByHash(signalHash);
if(signal != null && signal is ASignal)
{
(signal as ASignal).AddListener(handler);
}
}
/// <summary>
/// Manually provide a SignalHash and unbind it from a given listener
/// (you most likely want to use a RemoveListener, unless you know exactly
/// what you are doing)
/// </summary>
/// <param name="signalHash">Unique hash for signal</param>
/// <param name="handler">Callback for signal listener</param>
public void RemoveListenerFromHash(string signalHash, Action handler)
{
ISignal signal = GetSignalByHash(signalHash);
if (signal != null && signal is ASignal)
{
(signal as ASignal).RemoveListener(handler);
}
}
private ISignal Bind(Type signalType)
{
ISignal signal;
if(signals.TryGetValue(signalType, out signal))
{
UnityEngine.Debug.LogError(string.Format("Signal already registered for type {0}", signalType.ToString()));
return signal;
}
signal = (ISignal)Activator.CreateInstance(signalType);
signals.Add(signalType, signal);
return signal;
}
private ISignal Bind<T>() where T : ISignal, new()
{
return Bind(typeof(T));
}
private ISignal GetSignalByHash(string signalHash)
{
foreach (ISignal signal in signals.Values)
{
if (signal.Hash == signalHash)
{
return signal;
}
}
return null;
}
}
/// <summary>
/// Abstract class for Signals, provides hash by type functionality
/// </summary>
public abstract class ABaseSignal : ISignal
{
protected string _hash;
/// <summary>
/// Unique id for this signal
/// </summary>
public string Hash
{
get
{
if (string.IsNullOrEmpty(_hash))
{
_hash = this.GetType().ToString();
}
return _hash;
}
}
}
/// <summary>
/// Strongly typed messages with no parameters
/// </summary>
public abstract class ASignal : ABaseSignal
{
private Action callback;
/// <summary>
/// Adds a listener to this Signal
/// </summary>
/// <param name="handler">Method to be called when signal is fired</param>
public void AddListener(Action handler)
{
#if UNITY_EDITOR
UnityEngine.Debug.Assert(handler.Method.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), inherit: false).Length == 0,
"Adding anonymous delegates as Signal callbacks is not supported (you wouldn't be able to unregister them later).");
#endif
callback += handler;
}
/// <summary>
/// Removes a listener from this Signal
/// </summary>
/// <param name="handler">Method to be unregistered from signal</param>
public void RemoveListener(Action handler)
{
callback -= handler;
}
/// <summary>
/// Dispatch this signal
/// </summary>
public void Dispatch()
{
if(callback != null)
{
callback();
}
}
}
/// <summary>
/// Strongly typed messages with 1 parameter
/// </summary>
/// <typeparam name="T">Parameter type</typeparam>
public abstract class ASignal<T>: ABaseSignal
{
private Action<T> callback;
/// <summary>
/// Adds a listener to this Signal
/// </summary>
/// <param name="handler">Method to be called when signal is fired</param>
public void AddListener(Action<T> handler)
{
#if UNITY_EDITOR
UnityEngine.Debug.Assert(handler.Method.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), inherit: false).Length == 0,
"Adding anonymous delegates as Signal callbacks is not supported (you wouldn't be able to unregister them later).");
#endif
callback += handler;
}
/// <summary>
/// Removes a listener from this Signal
/// </summary>
/// <param name="handler">Method to be unregistered from signal</param>
public void RemoveListener(Action<T> handler)
{
callback -= handler;
}
/// <summary>
/// Dispatch this signal with 1 parameter
/// </summary>
public void Dispatch(T arg1)
{
if (callback != null)
{
callback(arg1);
}
}
}
/// <summary>
/// Strongly typed messages with 2 parameters
/// </summary>
/// <typeparam name="T">First parameter type</typeparam>
/// <typeparam name="U">Second parameter type</typeparam>
public abstract class ASignal<T, U>: ABaseSignal
{
private Action<T, U> callback;
/// <summary>
/// Adds a listener to this Signal
/// </summary>
/// <param name="handler">Method to be called when signal is fired</param>
public void AddListener(Action<T, U> handler)
{
#if UNITY_EDITOR
UnityEngine.Debug.Assert(handler.Method.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), inherit: false).Length == 0,
"Adding anonymous delegates as Signal callbacks is not supported (you wouldn't be able to unregister them later).");
#endif
callback += handler;
}
/// <summary>
/// Removes a listener from this Signal
/// </summary>
/// <param name="handler">Method to be unregistered from signal</param>
public void RemoveListener(Action<T, U> handler)
{
callback -= handler;
}
/// <summary>
/// Dispatch this signal
/// </summary>
public void Dispatch(T arg1, U arg2)
{
if (callback != null)
{
callback(arg1, arg2);
}
}
}
/// <summary>
/// Strongly typed messages with 3 parameter
/// </summary>
/// <typeparam name="T">First parameter type</typeparam>
/// <typeparam name="U">Second parameter type</typeparam>
/// <typeparam name="V">Third parameter type</typeparam>
public abstract class ASignal<T, U, V>: ABaseSignal
{
private Action<T, U, V> callback;
/// <summary>
/// Adds a listener to this Signal
/// </summary>
/// <param name="handler">Method to be called when signal is fired</param>
public void AddListener(Action<T, U, V> handler)
{
#if UNITY_EDITOR
UnityEngine.Debug.Assert(handler.Method.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), inherit: false).Length == 0,
"Adding anonymous delegates as Signal callbacks is not supported (you wouldn't be able to unregister them later).");
#endif
callback += handler;
}
/// <summary>
/// Removes a listener from this Signal
/// </summary>
/// <param name="handler">Method to be unregistered from signal</param>
public void RemoveListener(Action<T, U, V> handler)
{
callback -= handler;
}
/// <summary>
/// Dispatch this signal
/// </summary>
public void Dispatch(T arg1, U arg2, V arg3)
{
if (callback != null)
{
callback(arg1, arg2, arg3);
}
}
}
}