forked from AtomicGameEngine/AtomicGameEngine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNativeCore.cs
More file actions
472 lines (356 loc) · 15.9 KB
/
NativeCore.cs
File metadata and controls
472 lines (356 loc) · 15.9 KB
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using static System.Reflection.IntrospectionExtensions;
#if ATOMIC_IOS
using ObjCRuntime;
#endif
namespace AtomicEngine
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void EventDispatchDelegate(IntPtr sender, uint eventType, IntPtr eventData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void UpdateDispatchDelegate(float timeStep);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void RefCountedDeletedDelegate(IntPtr refCounted);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public delegate void ThrowManagedExceptionDelegate(string errorMsg);
public class NativeType
{
public Type Type => type;
public NativeType(IntPtr nativeClassID, Type type, Func<IntPtr, RefCounted> managedConstructor)
{
this.nativeClassID = nativeClassID;
this.type = type;
this.managedConstructor = managedConstructor;
NativeCore.RegisterNativeType(this);
}
internal Type type;
internal IntPtr nativeClassID;
internal Func<IntPtr, RefCounted> managedConstructor;
}
/// <summary>
/// Internal class for native interop
/// </summary>
internal static class NativeCore
{
public static string GetCacheStatus() => refCountedCache.GetCacheStatus();
static internal void SubscribeToEvent(AObject receiver, uint eventType)
{
SubscribeToEvent(receiver, null, eventType);
}
static internal void SubscribeToEvent(AObject receiver, AObject sender, uint eventType)
{
List<EventSubscription> eventReceivers;
if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
{
eventReceivers = eventReceiverLookup[eventType] = new List<EventSubscription>();
}
AObject obj;
foreach (EventSubscription er in eventReceivers)
{
if (!er.Receiver.TryGetTarget(out obj))
continue; // GC'd
// already on list?
if (obj == receiver)
return;
}
eventReceivers.Add(new EventSubscription(receiver, sender));
}
static internal void UnsubscribeFromEvent(AObject receiver, uint eventType)
{
List<EventSubscription> eventReceivers;
if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
return;
AObject obj;
foreach (EventSubscription er in eventReceivers)
{
if (!er.Receiver.TryGetTarget(out obj))
continue; // GC'd
if (obj == receiver)
{
eventReceivers.Remove(er);
return;
}
}
}
static internal void UnsubscribeFromAllEvents(AObject receiver)
{
// TODO: Optimize
AObject obj;
foreach (var subList in eventReceiverLookup.Values)
{
subList.RemoveAll(item => item.Receiver.TryGetTarget(out obj) && obj == receiver);
}
}
static internal void RemoveEventSender(IntPtr sender)
{
if (sender == IntPtr.Zero)
return;
var refCounted = refCountedCache.Get(sender)?.Reference;
// If we're no longer registered or not an Object, early out
if (refCounted == null || !refCounted.IsObject())
return;
foreach (var subList in eventReceiverLookup.Values)
{
var len = subList.Count;
subList.RemoveAll(item => item.Sender == sender);
//TODO: The events are still in the receiver lookup tables!
}
}
static ScriptVariantMap[] svm;
static int svmDepth = 0;
const int svmMax = 256;
internal static void Initialize()
{
// preallocate script variant maps
svm = new ScriptVariantMap[svmMax];
for (int i = 0; i < svmMax; i++)
svm[i] = new ScriptVariantMap();
}
// called ahead of E_UPDATE event
#if ATOMIC_IOS
[MonoPInvokeCallback(typeof(UpdateDispatchDelegate))]
#endif
public static void UpdateDispatch(float timeStep)
{
RefCounted.ReleaseFinalized();
}
#if ATOMIC_IOS
[MonoPInvokeCallback(typeof(EventDispatchDelegate))]
#endif
public static void EventDispatch(IntPtr sender, uint eventType, IntPtr eventData)
{
List<EventSubscription> eventReceivers;
if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
{
// This should not happen, as event NET objects are subscribed to are filtered
throw new InvalidOperationException("NativeCore.EventDispatch - received unregistered event type");
}
// iterate over copy of list so we can modify it while running
ScriptVariantMap scriptMap = null;
NativeEventData nativeEventData = null;
AObject receiver;
foreach (EventSubscription er in eventReceivers.ToList())
{
// GC'd?
if (!er.Receiver.TryGetTarget(out receiver))
continue;
if (er.Sender != IntPtr.Zero && er.Sender != sender)
continue;
if (scriptMap == null)
{
if (svmDepth == svmMax)
{
throw new InvalidOperationException("NativeCore.EventDispatch - exceeded max svm");
}
scriptMap = svm[svmDepth++];
scriptMap.CopyVariantMap(eventData);
nativeEventData = NativeEvents.GetNativeEventData(eventType, scriptMap);
// This check can be removed once ATOMIC-1381 is resolved
// https://github.com/AtomicGameEngine/AtomicGameEngine/issues/1381
if (nativeEventData != null)
nativeEventData.sourceEventData = eventData;
}
receiver.HandleEvent(eventType, scriptMap, nativeEventData);
}
if (scriptMap != null)
{
svmDepth--;
if (nativeEventData != null)
{
NativeEvents.ReleaseNativeEventData(nativeEventData);
}
}
}
/// <summary>
/// Runs a GC collection, waits for any finalizers, and then expires any natives collected
/// </summary>
public static void RunGC()
{
// run a GC collection
GC.Collect();
// finalizers can run on any thread, we're explicitly running a GC here
// so wait for all the finalizers to finish
GC.WaitForPendingFinalizers();
// Anything finalized on another thread will now be available to release
// in main thread
RefCounted.ReleaseFinalized();
ExpireNatives();
}
private static void ExpireNatives()
{
var watch = new Stopwatch();
watch.Start();
// expire event listeners
//int eventListenersRemoved = 0;
//int nativesRemoved = 0;
AObject obj;
foreach (List<EventSubscription> receiverList in eventReceiverLookup.Values)
{
foreach (EventSubscription er in receiverList.ToList())
{
if (!er.Receiver.TryGetTarget(out obj))
{
receiverList.Remove(er);
//eventListenersRemoved++;
}
if (watch.ElapsedMilliseconds > 16)
break;
}
if (watch.ElapsedMilliseconds > 16)
break;
}
}
// Called from RefCounted native destructor, refCounted is not valid for any operations here
#if ATOMIC_IOS
[MonoPInvokeCallback(typeof(RefCountedDeletedDelegate))]
#endif
public static void RefCountedDeleted(IntPtr refCounted)
{
}
// Called to throw a managed exception from native code
#if ATOMIC_IOS
[MonoPInvokeCallback(typeof(RefCountedDeletedDelegate))]
#endif
public static void ThrowManagedException(string errorMsg)
{
throw new InvalidOperationException("Native Exception: " + errorMsg);
}
internal static void RemoveNative(IntPtr refCounted)
{
if (refCounted == IntPtr.Zero)
return;
RemoveEventSender(refCounted);
refCountedCache.Remove(refCounted);
}
// register a newly created native
public static IntPtr RegisterNative(IntPtr native, RefCounted r, InstantiationType instantiationType = InstantiationType.INSTANTIATION_NET)
{
if (native == IntPtr.Zero || r == null)
{
throw new InvalidOperationException("NativeCore.RegisterNative - native == IntPtr.Zero || RefCounted instance == null");
}
if (instantiationType == InstantiationType.INSTANTIATION_NET)
{
if (r.nativeInstance != IntPtr.Zero)
{
throw new InvalidOperationException("NativeCore.RegisterNative - NET Instantiated RefCounted with initialized nativeInstance");
}
r.nativeInstance = native;
}
r.InstantiationType = instantiationType;
r.InternalInit();
refCountedCache.Add(r);
r.PostNativeUpdate();
return native;
}
// wraps an existing native instance, with downcast support
public static T WrapNative<T>(IntPtr native) where T : RefCounted
{
if (native == IntPtr.Zero)
{
throw new InvalidOperationException("NativeCore.WrapNative - Attempting to wrap native instance IntPtr.Zero");
}
var reference = refCountedCache.Get(native)?.Reference;
// This isn't really a good test to verify right object, better to test if not a T and error?
if (reference is T)
return (T)reference;
IntPtr classID = RefCounted.csi_Atomic_RefCounted_GetClassID(native);
// Check whether this is a valid native class to wrap
NativeType nativeType;
if (!nativeClassIDToNativeType.TryGetValue(classID, out nativeType))
{
if (logWrapUnknownNative)
{
Log.Info("WrapNative returning null for unknown class: " + GetNativeTypeName(native));
}
return null;
}
// TODO: make CSComponent abstract and have general abstract logic here?
if (nativeType.Type == typeof(CSComponent))
return null;
// Construct managed instance wrapper for native instance
// this has downcast support for instance Component -> StaticModel
// note, we never want to hit this path for script inherited natives
var r = nativeType.managedConstructor(native);
// IMPORTANT: if a RefCounted instance is created in managed code, has reference count increased in native code
// and managed side is GC'd, the original NET created instance will be gone and we can get it back here reported
// as instantiated in native code. May want a transative boolean to be able to tell when an object has passed this "barrier"
// which is somewhat common
RegisterNative(native, r, InstantiationType.INSTANTIATION_NATIVE);
return (T)r;
}
internal static string GetNativeTypeName(IntPtr native)
{
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(csi_Atomic_RefCounted_GetTypeName(native));
}
[DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern IntPtr csi_Atomic_RefCounted_GetTypeName(IntPtr self);
[DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern int csi_Atomic_RefCounted_Refs(IntPtr self);
public static void RegisterNativeType(NativeType nativeType)
{
if (nativeClassIDToNativeType.ContainsKey(nativeType.nativeClassID))
{
throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType class id registered");
}
if (typeToNativeType.ContainsKey(nativeType.type))
{
throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType type registered");
}
nativeClassIDToNativeType[nativeType.nativeClassID] = nativeType;
typeToNativeType[nativeType.type] = nativeType;
}
public static bool IsNativeType(Type type)
{
if (typeToNativeType.ContainsKey(type))
return true;
return false;
}
public static Type GetNativeAncestorType(Type type)
{
Type ancestorType = type;
do
{
ancestorType = ancestorType.GetTypeInfo().BaseType;
} while (ancestorType != null && !IsNativeType(ancestorType));
return ancestorType;
}
private static RefCountedCache refCountedCache = new RefCountedCache();
// weak references here, hold a ref native side
internal static Dictionary<uint, List<EventSubscription>> eventReceiverLookup = new Dictionary<uint, List<EventSubscription>>();
// Native ClassID to NativeType lookup
internal static Dictionary<IntPtr, NativeType> nativeClassIDToNativeType = new Dictionary<IntPtr, NativeType>();
// Managed Type to NativeType lookup
internal static Dictionary<Type, NativeType> typeToNativeType = new Dictionary<Type, NativeType>();
// Access to native reference counting not needing a managed RefCounted instance
[DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern void csi_AtomicEngine_AddRef(IntPtr refCounted);
[DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern void csi_AtomicEngine_AddRefSilent(IntPtr refCounted);
[DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern void csi_AtomicEngine_ReleaseRef(IntPtr refCounted);
[DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern void csi_AtomicEngine_ReleaseSilent(IntPtr refCounted);
internal struct EventSubscription
{
public WeakReference<AObject> Receiver;
public IntPtr Sender;
public EventSubscription(AObject receiver)
{
Receiver = new WeakReference<AObject>(receiver);
Sender = IntPtr.Zero;
}
public EventSubscription(AObject receiver, IntPtr sender)
{
Receiver = new WeakReference<AObject>(receiver);
Sender = sender;
}
}
// If true, we will log any attempted access to instances of unknown native classes
static bool logWrapUnknownNative = false;
}
}