NativeCore.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. using System;
  2. using System.Diagnostics;
  3. using System.Collections.Generic;
  4. using System.Runtime.InteropServices;
  5. using System.Linq;
  6. using static System.Reflection.IntrospectionExtensions;
  7. #if ATOMIC_IOS
  8. using ObjCRuntime;
  9. #endif
  10. namespace AtomicEngine
  11. {
  12. [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  13. public delegate void EventDispatchDelegate(IntPtr sender, uint eventType, IntPtr eventData);
  14. [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  15. public delegate void UpdateDispatchDelegate(float timeStep);
  16. [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  17. public delegate void RefCountedDeletedDelegate(IntPtr refCounted);
  18. [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  19. public delegate void ThrowManagedExceptionDelegate(string errorMsg);
  20. public class NativeType
  21. {
  22. public Type Type => type;
  23. public NativeType(IntPtr nativeClassID, Type type, Func<IntPtr, RefCounted> managedConstructor)
  24. {
  25. this.nativeClassID = nativeClassID;
  26. this.type = type;
  27. this.managedConstructor = managedConstructor;
  28. NativeCore.RegisterNativeType(this);
  29. }
  30. internal Type type;
  31. internal IntPtr nativeClassID;
  32. internal Func<IntPtr, RefCounted> managedConstructor;
  33. }
  34. /// <summary>
  35. /// Internal class for native interop
  36. /// </summary>
  37. internal static class NativeCore
  38. {
  39. public static string GetCacheStatus() => refCountedCache.GetCacheStatus();
  40. static internal void SubscribeToEvent(AObject receiver, uint eventType)
  41. {
  42. SubscribeToEvent(receiver, null, eventType);
  43. }
  44. static internal void SubscribeToEvent(AObject receiver, AObject sender, uint eventType)
  45. {
  46. List<EventSubscription> eventReceivers;
  47. if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
  48. {
  49. eventReceivers = eventReceiverLookup[eventType] = new List<EventSubscription>();
  50. }
  51. AObject obj;
  52. foreach (EventSubscription er in eventReceivers)
  53. {
  54. if (!er.Receiver.TryGetTarget(out obj))
  55. continue; // GC'd
  56. // already on list?
  57. if (obj == receiver)
  58. return;
  59. }
  60. eventReceivers.Add(new EventSubscription(receiver, sender));
  61. }
  62. static internal void UnsubscribeFromEvent(AObject receiver, uint eventType)
  63. {
  64. List<EventSubscription> eventReceivers;
  65. if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
  66. return;
  67. AObject obj;
  68. foreach (EventSubscription er in eventReceivers)
  69. {
  70. if (!er.Receiver.TryGetTarget(out obj))
  71. continue; // GC'd
  72. if (obj == receiver)
  73. {
  74. eventReceivers.Remove(er);
  75. return;
  76. }
  77. }
  78. }
  79. static internal void UnsubscribeFromAllEvents(AObject receiver)
  80. {
  81. // TODO: Optimize
  82. AObject obj;
  83. foreach (var subList in eventReceiverLookup.Values)
  84. {
  85. subList.RemoveAll(item => item.Receiver.TryGetTarget(out obj) && obj == receiver);
  86. }
  87. }
  88. static internal void RemoveEventSender(IntPtr sender)
  89. {
  90. if (sender == IntPtr.Zero)
  91. return;
  92. var refCounted = refCountedCache.Get(sender)?.Reference;
  93. // If we're no longer registered or not an Object, early out
  94. if (refCounted == null || !refCounted.IsObject())
  95. return;
  96. foreach (var subList in eventReceiverLookup.Values)
  97. {
  98. var len = subList.Count;
  99. subList.RemoveAll(item => item.Sender == sender);
  100. //TODO: The events are still in the receiver lookup tables!
  101. }
  102. }
  103. static ScriptVariantMap[] svm;
  104. static int svmDepth = 0;
  105. const int svmMax = 256;
  106. internal static void Initialize()
  107. {
  108. // preallocate script variant maps
  109. svm = new ScriptVariantMap[svmMax];
  110. for (int i = 0; i < svmMax; i++)
  111. svm[i] = new ScriptVariantMap();
  112. }
  113. // called ahead of E_UPDATE event
  114. #if ATOMIC_IOS
  115. [MonoPInvokeCallback(typeof(UpdateDispatchDelegate))]
  116. #endif
  117. public static void UpdateDispatch(float timeStep)
  118. {
  119. RefCounted.ReleaseFinalized();
  120. }
  121. #if ATOMIC_IOS
  122. [MonoPInvokeCallback(typeof(EventDispatchDelegate))]
  123. #endif
  124. public static void EventDispatch(IntPtr sender, uint eventType, IntPtr eventData)
  125. {
  126. List<EventSubscription> eventReceivers;
  127. if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
  128. {
  129. // This should not happen, as event NET objects are subscribed to are filtered
  130. throw new InvalidOperationException("NativeCore.EventDispatch - received unregistered event type");
  131. }
  132. // iterate over copy of list so we can modify it while running
  133. ScriptVariantMap scriptMap = null;
  134. NativeEventData nativeEventData = null;
  135. AObject receiver;
  136. foreach (EventSubscription er in eventReceivers.ToList())
  137. {
  138. // GC'd?
  139. if (!er.Receiver.TryGetTarget(out receiver))
  140. continue;
  141. if (er.Sender != IntPtr.Zero && er.Sender != sender)
  142. continue;
  143. if (scriptMap == null)
  144. {
  145. if (svmDepth == svmMax)
  146. {
  147. throw new InvalidOperationException("NativeCore.EventDispatch - exceeded max svm");
  148. }
  149. scriptMap = svm[svmDepth++];
  150. scriptMap.CopyVariantMap(eventData);
  151. nativeEventData = NativeEvents.GetNativeEventData(eventType, scriptMap);
  152. // This check can be removed once ATOMIC-1381 is resolved
  153. // https://github.com/AtomicGameEngine/AtomicGameEngine/issues/1381
  154. if (nativeEventData != null)
  155. nativeEventData.sourceEventData = eventData;
  156. }
  157. receiver.HandleEvent(eventType, scriptMap, nativeEventData);
  158. }
  159. if (scriptMap != null)
  160. {
  161. svmDepth--;
  162. if (nativeEventData != null)
  163. {
  164. NativeEvents.ReleaseNativeEventData(nativeEventData);
  165. }
  166. }
  167. }
  168. /// <summary>
  169. /// Runs a GC collection, waits for any finalizers, and then expires any natives collected
  170. /// </summary>
  171. public static void RunGC()
  172. {
  173. // run a GC collection
  174. GC.Collect();
  175. // finalizers can run on any thread, we're explicitly running a GC here
  176. // so wait for all the finalizers to finish
  177. GC.WaitForPendingFinalizers();
  178. // Anything finalized on another thread will now be available to release
  179. // in main thread
  180. RefCounted.ReleaseFinalized();
  181. ExpireNatives();
  182. }
  183. private static void ExpireNatives()
  184. {
  185. var watch = new Stopwatch();
  186. watch.Start();
  187. // expire event listeners
  188. //int eventListenersRemoved = 0;
  189. //int nativesRemoved = 0;
  190. AObject obj;
  191. foreach (List<EventSubscription> receiverList in eventReceiverLookup.Values)
  192. {
  193. foreach (EventSubscription er in receiverList.ToList())
  194. {
  195. if (!er.Receiver.TryGetTarget(out obj))
  196. {
  197. receiverList.Remove(er);
  198. //eventListenersRemoved++;
  199. }
  200. if (watch.ElapsedMilliseconds > 16)
  201. break;
  202. }
  203. if (watch.ElapsedMilliseconds > 16)
  204. break;
  205. }
  206. }
  207. // Called from RefCounted native destructor, refCounted is not valid for any operations here
  208. #if ATOMIC_IOS
  209. [MonoPInvokeCallback(typeof(RefCountedDeletedDelegate))]
  210. #endif
  211. public static void RefCountedDeleted(IntPtr refCounted)
  212. {
  213. }
  214. // Called to throw a managed exception from native code
  215. #if ATOMIC_IOS
  216. [MonoPInvokeCallback(typeof(RefCountedDeletedDelegate))]
  217. #endif
  218. public static void ThrowManagedException(string errorMsg)
  219. {
  220. throw new InvalidOperationException("Native Exception: " + errorMsg);
  221. }
  222. internal static void RemoveNative(IntPtr refCounted)
  223. {
  224. if (refCounted == IntPtr.Zero)
  225. return;
  226. RemoveEventSender(refCounted);
  227. refCountedCache.Remove(refCounted);
  228. }
  229. // register a newly created native
  230. public static IntPtr RegisterNative(IntPtr native, RefCounted r, InstantiationType instantiationType = InstantiationType.INSTANTIATION_NET)
  231. {
  232. if (native == IntPtr.Zero || r == null)
  233. {
  234. throw new InvalidOperationException("NativeCore.RegisterNative - native == IntPtr.Zero || RefCounted instance == null");
  235. }
  236. if (instantiationType == InstantiationType.INSTANTIATION_NET)
  237. {
  238. if (r.nativeInstance != IntPtr.Zero)
  239. {
  240. throw new InvalidOperationException("NativeCore.RegisterNative - NET Instantiated RefCounted with initialized nativeInstance");
  241. }
  242. r.nativeInstance = native;
  243. }
  244. r.InstantiationType = instantiationType;
  245. r.InternalInit();
  246. refCountedCache.Add(r);
  247. r.PostNativeUpdate();
  248. return native;
  249. }
  250. // wraps an existing native instance, with downcast support
  251. public static T WrapNative<T>(IntPtr native) where T : RefCounted
  252. {
  253. if (native == IntPtr.Zero)
  254. {
  255. throw new InvalidOperationException("NativeCore.WrapNative - Attempting to wrap native instance IntPtr.Zero");
  256. }
  257. var reference = refCountedCache.Get(native)?.Reference;
  258. // This isn't really a good test to verify right object, better to test if not a T and error?
  259. if (reference is T)
  260. return (T)reference;
  261. IntPtr classID = RefCounted.csi_Atomic_RefCounted_GetClassID(native);
  262. // Check whether this is a valid native class to wrap
  263. NativeType nativeType;
  264. if (!nativeClassIDToNativeType.TryGetValue(classID, out nativeType))
  265. {
  266. if (logWrapUnknownNative)
  267. {
  268. Log.Info("WrapNative returning null for unknown class: " + GetNativeTypeName(native));
  269. }
  270. return null;
  271. }
  272. // TODO: make CSComponent abstract and have general abstract logic here?
  273. if (nativeType.Type == typeof(CSComponent))
  274. return null;
  275. // Construct managed instance wrapper for native instance
  276. // this has downcast support for instance Component -> StaticModel
  277. // note, we never want to hit this path for script inherited natives
  278. var r = nativeType.managedConstructor(native);
  279. // IMPORTANT: if a RefCounted instance is created in managed code, has reference count increased in native code
  280. // and managed side is GC'd, the original NET created instance will be gone and we can get it back here reported
  281. // as instantiated in native code. May want a transative boolean to be able to tell when an object has passed this "barrier"
  282. // which is somewhat common
  283. RegisterNative(native, r, InstantiationType.INSTANTIATION_NATIVE);
  284. return (T)r;
  285. }
  286. internal static string GetNativeTypeName(IntPtr native)
  287. {
  288. return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(csi_Atomic_RefCounted_GetTypeName(native));
  289. }
  290. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  291. private static extern IntPtr csi_Atomic_RefCounted_GetTypeName(IntPtr self);
  292. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  293. internal static extern int csi_Atomic_RefCounted_Refs(IntPtr self);
  294. public static void RegisterNativeType(NativeType nativeType)
  295. {
  296. if (nativeClassIDToNativeType.ContainsKey(nativeType.nativeClassID))
  297. {
  298. throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType class id registered");
  299. }
  300. if (typeToNativeType.ContainsKey(nativeType.type))
  301. {
  302. throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType type registered");
  303. }
  304. nativeClassIDToNativeType[nativeType.nativeClassID] = nativeType;
  305. typeToNativeType[nativeType.type] = nativeType;
  306. }
  307. public static bool IsNativeType(Type type)
  308. {
  309. if (typeToNativeType.ContainsKey(type))
  310. return true;
  311. return false;
  312. }
  313. public static Type GetNativeAncestorType(Type type)
  314. {
  315. Type ancestorType = type;
  316. do
  317. {
  318. ancestorType = ancestorType.GetTypeInfo().BaseType;
  319. } while (ancestorType != null && !IsNativeType(ancestorType));
  320. return ancestorType;
  321. }
  322. private static RefCountedCache refCountedCache = new RefCountedCache();
  323. // weak references here, hold a ref native side
  324. internal static Dictionary<uint, List<EventSubscription>> eventReceiverLookup = new Dictionary<uint, List<EventSubscription>>();
  325. // Native ClassID to NativeType lookup
  326. internal static Dictionary<IntPtr, NativeType> nativeClassIDToNativeType = new Dictionary<IntPtr, NativeType>();
  327. // Managed Type to NativeType lookup
  328. internal static Dictionary<Type, NativeType> typeToNativeType = new Dictionary<Type, NativeType>();
  329. // Access to native reference counting not needing a managed RefCounted instance
  330. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  331. internal static extern void csi_AtomicEngine_AddRef(IntPtr refCounted);
  332. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  333. internal static extern void csi_AtomicEngine_AddRefSilent(IntPtr refCounted);
  334. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  335. internal static extern void csi_AtomicEngine_ReleaseRef(IntPtr refCounted);
  336. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  337. internal static extern void csi_AtomicEngine_ReleaseSilent(IntPtr refCounted);
  338. internal struct EventSubscription
  339. {
  340. public WeakReference<AObject> Receiver;
  341. public IntPtr Sender;
  342. public EventSubscription(AObject receiver)
  343. {
  344. Receiver = new WeakReference<AObject>(receiver);
  345. Sender = IntPtr.Zero;
  346. }
  347. public EventSubscription(AObject receiver, IntPtr sender)
  348. {
  349. Receiver = new WeakReference<AObject>(receiver);
  350. Sender = sender;
  351. }
  352. }
  353. // If true, we will log any attempted access to instances of unknown native classes
  354. static bool logWrapUnknownNative = false;
  355. }
  356. }