-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathReactiveRecord.cs
314 lines (255 loc) · 9.96 KB
/
ReactiveRecord.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
#if !(ENABLE_IL2CPP || REACT_DISABLE_CLEARSCRIPT || (UNITY_ANDROID && !UNITY_EDITOR)) && REACT_CLEARSCRIPT_AVAILABLE
#define REACT_CLEARSCRIPT
#endif
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ReactUnity.Helpers;
#if REACT_CLEARSCRIPT
using Microsoft.ClearScript;
using EnginePrototypeTable = System.Runtime.CompilerServices.ConditionalWeakTable<Microsoft.ClearScript.ScriptEngine, ReactUnity.Reactive.PrototypeEntry>;
#endif
namespace ReactUnity.Reactive
{
[UnityEngine.Scripting.Preserve]
public class ReactiveDictionary<TKey, T> : IDictionary<TKey, T>, IDisposable, IReactive<Dictionary<TKey, T>>
{
protected Dictionary<TKey, T> collection;
internal event Action<TKey, T, ReactiveDictionary<TKey, T>> changed;
public T this[TKey key]
{
get => RetrieveValue(key);
set => SaveValue(key, value, true);
}
public ReactiveDictionary()
{
collection = new Dictionary<TKey, T>();
}
public ReactiveDictionary(IDictionary<TKey, T> dict)
{
collection = new Dictionary<TKey, T>(dict);
}
public void Set(TKey key, T value) => SaveValue(key, value, true);
public void SetWithoutNotify(TKey key, T value) => SaveValue(key, value, false);
public ICollection<TKey> Keys => collection.Keys;
public ICollection<T> Values => collection.Values;
public int Count => collection.Count;
public bool IsReadOnly => false;
Dictionary<TKey, T> IReactive<Dictionary<TKey, T>>.Value => collection;
public void Add(TKey key, T value)
{
collection.Add(key, value);
Change(key, value);
}
public void Add(KeyValuePair<TKey, T> item)
{
collection.Add(item.Key, item.Value);
Change(item.Key, item.Value);
}
public void Clear()
{
collection.Clear();
Change(default, default);
}
public void ClearWithoutNotify()
{
collection.Clear();
}
public bool ContainsKey(TKey key)
{
return key != null && collection.ContainsKey(key);
}
public IEnumerator<KeyValuePair<TKey, T>> GetEnumerator()
{
return collection.GetEnumerator();
}
public bool Remove(TKey key)
{
var res = collection.Remove(key);
if (res) Change(key, default);
return res;
}
public bool RemoveWithoutNotify(TKey key)
{
return collection.Remove(key);
}
public bool TryGetValue(TKey key, out T value)
{
return collection.TryGetValue(key, out value);
}
void ICollection<KeyValuePair<TKey, T>>.CopyTo(KeyValuePair<TKey, T>[] array, int arrayIndex)
{
(collection as ICollection<KeyValuePair<TKey, T>>).CopyTo(array, arrayIndex);
}
IEnumerator IEnumerable.GetEnumerator()
{
return collection.GetEnumerator();
}
bool ICollection<KeyValuePair<TKey, T>>.Remove(KeyValuePair<TKey, T> item)
{
return (collection as ICollection<KeyValuePair<TKey, T>>).Remove(item);
}
bool ICollection<KeyValuePair<TKey, T>>.Contains(KeyValuePair<TKey, T> item)
{
return (collection as ICollection<KeyValuePair<TKey, T>>).Contains(item);
}
public Action AddListener(object cb)
{
var callback = Callback.From(cb);
var listener = new Action<Dictionary<TKey, T>>((dc) => callback.Call(dc));
return AddListener(listener);
}
public Action AddListener(Action<Dictionary<TKey, T>> listener)
{
var cb = new Action<TKey, T, ReactiveDictionary<TKey, T>>((key, value, dc) => listener.Invoke(dc.collection));
changed += cb;
return () => changed -= cb;
}
public Action AddListener(Action<TKey, T, ReactiveDictionary<TKey, T>> listener)
{
changed += listener;
return () => changed -= listener;
}
public T GetValueOrDefault(TKey key)
{
if (!TryGetValue(key, out var value)) value = default;
return value;
}
protected void Change(TKey key, T value)
{
changed?.Invoke(key, value, this);
}
protected virtual T RetrieveValue(TKey key)
{
if (collection.TryGetValue(key, out var val)) return val;
return default;
}
protected virtual void SaveValue(TKey key, T value, bool notify)
{
collection[key] = value;
if (notify) Change(key, value);
}
public void Dispose()
{
changed = null;
}
public void Change()
{
changed?.Invoke(default(TKey), default(T), this);
}
}
public abstract class ReactiveAdaptibleRecord<TKey, T> : ReactiveDictionary<TKey, T>, IDictionary<string, object>
{
public object this[string key]
{
get => RetrieveValueAdaptible(key);
set => SaveValueAdaptible(key, value, true);
}
ICollection<string> IDictionary<string, object>.Keys => base.Keys.Select(KeyToString).ToArray();
ICollection<object> IDictionary<string, object>.Values => base.Values.OfType<object>().ToArray();
public void Add(string key, object value)
{
if (ContainsKey(key)) throw new ArgumentException("A key with this name already exists", nameof(key));
this[key] = value;
}
public void Add(KeyValuePair<string, object> item)
{
if (ContainsKey(item.Key)) throw new ArgumentException("A key with this name already exists", nameof(item));
this[item.Key] = item.Value;
}
public bool Contains(KeyValuePair<string, object> item) => base.ContainsKey(StringToKey(item.Key));
public bool ContainsKey(string key) => base.ContainsKey(StringToKey(key));
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
var ind = arrayIndex;
foreach (var item in this)
{
array[ind] = new KeyValuePair<string, object>(KeyToString(item.Key), item.Value);
ind++;
}
}
public bool Remove(string key) => base.Remove(StringToKey(key));
public bool Remove(KeyValuePair<string, object> item) => Remove(item.Key);
public bool TryGetValue(string key, out object value)
{
var val = StringToKey(key);
if (val == null)
{
value = default;
return false;
}
if (base.TryGetValue(val, out var outVal))
{
value = outVal;
return true;
}
value = default;
return false;
}
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
foreach (var item in collection)
{
yield return new KeyValuePair<string, object>(KeyToString(item.Key), item.Value);
}
}
protected virtual string KeyToString(TKey key) => key?.ToString();
protected virtual TKey StringToKey(string val) => default;
protected virtual object RetrieveValueAdaptible(string key) => RetrieveValue(StringToKey(key));
protected virtual void SaveValueAdaptible(string key, object value, bool notify) => SaveValue(StringToKey(key), (T) value, notify);
public void Set(string key, object value) => SaveValueAdaptible(key, value, true);
public void SetWithoutNotify(string key, object value) => SaveValueAdaptible(key, value, false);
public object GetValueOrDefault(string key) => GetValueOrDefault(StringToKey(key));
}
public abstract class ReactiveAdaptibleRecordBag<TKey, T> : ReactiveAdaptibleRecord<TKey, T>
#if REACT_CLEARSCRIPT
, IPropertyBag
, IScriptableObject
#endif
{
#if REACT_CLEARSCRIPT
private readonly EnginePrototypeTable map = new EnginePrototypeTable();
public void OnExposedToScriptCode(ScriptEngine engine)
{
var entry = map.GetOrCreateValue(engine);
entry.ExposeObject<ReactiveAdaptibleRecord<TKey, T>>(this, engine);
}
#endif
}
public class ReactiveRecord<T> : ReactiveDictionary<string, T> { }
public class ReactiveObjectRecord : ReactiveRecord<object>
#if REACT_CLEARSCRIPT
, IPropertyBag
, IScriptableObject
#endif
{
#if REACT_CLEARSCRIPT
private readonly EnginePrototypeTable map = new EnginePrototypeTable();
public void OnExposedToScriptCode(ScriptEngine engine)
{
var entry = map.GetOrCreateValue(engine);
entry.ExposeObject<ReactiveRecord<object>>(this, engine);
}
#endif
}
#if REACT_CLEARSCRIPT
/// <summary>
/// Class required to hold the prototyped object in .NET side to prevent it from GCed in script side
/// </summary>
internal class PrototypeEntry
{
public object Prototype;
public object ProxyHolder;
public void ExposeObject<T>(T obj, ScriptEngine engine)
{
if (Prototype == null)
{
Prototype = obj.ToRestrictedHostObject(engine);
Callback.From(engine.Evaluate("Object.setPrototypeOf")).Call(obj, Prototype);
ProxyHolder = Callback.From(engine.Evaluate("Object.create")).Call(obj);
}
}
}
#endif
}