NativeCore.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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. public class NativeType
  19. {
  20. public Type Type => type;
  21. public NativeType(IntPtr nativeClassID, Type type, Func<IntPtr, RefCounted> managedConstructor)
  22. {
  23. this.nativeClassID = nativeClassID;
  24. this.type = type;
  25. this.managedConstructor = managedConstructor;
  26. NativeCore.RegisterNativeType(this);
  27. }
  28. internal Type type;
  29. internal IntPtr nativeClassID;
  30. internal Func<IntPtr, RefCounted> managedConstructor;
  31. }
  32. public static class NativeCore
  33. {
  34. static internal void SubscribeToEvent(AObject receiver, uint eventType)
  35. {
  36. SubscribeToEvent(receiver, null, eventType);
  37. }
  38. static internal void SubscribeToEvent(AObject receiver, AObject sender, uint eventType)
  39. {
  40. List<EventSubscription> eventReceivers;
  41. if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
  42. {
  43. eventReceivers = eventReceiverLookup[eventType] = new List<EventSubscription>();
  44. }
  45. AObject obj;
  46. foreach (EventSubscription er in eventReceivers)
  47. {
  48. if (!er.Receiver.TryGetTarget(out obj))
  49. continue; // GC'd
  50. // already on list?
  51. if (obj == receiver)
  52. return;
  53. }
  54. WeakReference<RefCounted> w = null;
  55. if (!nativeLookup.TryGetValue(receiver.nativeInstance, out w))
  56. {
  57. throw new InvalidOperationException("NativeCore.SubscribeToEvent - unable to find native receiver instance");
  58. }
  59. eventReceivers.Add(new EventSubscription(receiver, sender));
  60. }
  61. static internal void UnsubscribeFromEvent(AObject receiver, uint eventType)
  62. {
  63. List<EventSubscription> eventReceivers;
  64. if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
  65. return;
  66. AObject obj;
  67. foreach (EventSubscription er in eventReceivers)
  68. {
  69. if (!er.Receiver.TryGetTarget(out obj))
  70. continue; // GC'd
  71. if (obj == receiver)
  72. {
  73. eventReceivers.Remove(er);
  74. return;
  75. }
  76. }
  77. }
  78. static internal void UnsubscribeFromAllEvents(AObject receiver)
  79. {
  80. // TODO: Optimize
  81. AObject obj;
  82. foreach (var subList in eventReceiverLookup.Values)
  83. {
  84. subList.RemoveAll(item => item.Receiver.TryGetTarget(out obj) && obj == receiver);
  85. }
  86. }
  87. static internal void RemoveEventSender(IntPtr sender)
  88. {
  89. //TODO: OPTIMIZE
  90. if (sender == IntPtr.Zero)
  91. return;
  92. foreach (var subList in eventReceiverLookup.Values)
  93. {
  94. var len = subList.Count;
  95. subList.RemoveAll(item => item.Sender == sender);
  96. //TODO: The events are still in the receiver lookup tables!
  97. }
  98. }
  99. static ScriptVariantMap[] svm;
  100. static int svmDepth = 0;
  101. const int svmMax = 256;
  102. internal static void Initialize()
  103. {
  104. // preallocate script variant maps
  105. svm = new ScriptVariantMap[svmMax];
  106. for (int i = 0; i < svmMax; i++)
  107. svm[i] = new ScriptVariantMap();
  108. }
  109. static float expireDelta = 0.0f;
  110. // called ahead of E_UPDATE event
  111. #if ATOMIC_IOS
  112. [MonoPInvokeCallback(typeof(UpdateDispatchDelegate))]
  113. #endif
  114. public static void UpdateDispatch(float timeStep)
  115. {
  116. expireDelta += timeStep;
  117. if (expireDelta > 2.0f)
  118. {
  119. expireDelta = 0.0f;
  120. // TODO: tune GC
  121. /*
  122. GC.Collect();
  123. GC.WaitForPendingFinalizers();
  124. GC.Collect();
  125. */
  126. ExpireNatives();
  127. }
  128. }
  129. #if ATOMIC_IOS
  130. [MonoPInvokeCallback(typeof(EventDispatchDelegate))]
  131. #endif
  132. public static void EventDispatch(IntPtr sender, uint eventType, IntPtr eventData)
  133. {
  134. List<EventSubscription> eventReceivers;
  135. if (!eventReceiverLookup.TryGetValue(eventType, out eventReceivers))
  136. {
  137. // This should not happen, as event NET objects are subscribed to are filtered
  138. throw new InvalidOperationException("NativeCore.EventDispatch - received unregistered event type");
  139. }
  140. // iterate over copy of list so we can modify it while running
  141. ScriptVariantMap scriptMap = null;
  142. NativeEventData nativeEventData = null;
  143. AObject receiver;
  144. foreach (EventSubscription er in eventReceivers.ToList())
  145. {
  146. // GC'd?
  147. if (!er.Receiver.TryGetTarget(out receiver))
  148. continue;
  149. if (er.Sender != IntPtr.Zero && er.Sender != sender)
  150. continue;
  151. if (scriptMap == null)
  152. {
  153. if (svmDepth == svmMax)
  154. {
  155. throw new InvalidOperationException("NativeCore.EventDispatch - exceeded max svm");
  156. }
  157. scriptMap = svm[svmDepth++];
  158. scriptMap.CopyVariantMap(eventData);
  159. nativeEventData = NativeEvents.GetNativeEventData(eventType, scriptMap);
  160. // This check can be removed once ATOMIC-1381 is resolved
  161. // https://github.com/AtomicGameEngine/AtomicGameEngine/issues/1381
  162. if (nativeEventData != null)
  163. nativeEventData.sourceEventData = eventData;
  164. }
  165. receiver.HandleEvent(eventType, scriptMap, nativeEventData);
  166. }
  167. if (scriptMap != null)
  168. {
  169. svmDepth--;
  170. if (nativeEventData != null)
  171. {
  172. NativeEvents.ReleaseNativeEventData(nativeEventData);
  173. }
  174. }
  175. }
  176. static void ExpireNatives()
  177. {
  178. var watch = new Stopwatch();
  179. watch.Start();
  180. // expire event listeners
  181. //int eventListenersRemoved = 0;
  182. //int nativesRemoved = 0;
  183. AObject obj;
  184. foreach (List<EventSubscription> receiverList in eventReceiverLookup.Values)
  185. {
  186. foreach (EventSubscription er in receiverList.ToList())
  187. {
  188. if (!er.Receiver.TryGetTarget(out obj))
  189. {
  190. receiverList.Remove(er);
  191. //eventListenersRemoved++;
  192. }
  193. if (watch.ElapsedMilliseconds > 16)
  194. break;
  195. }
  196. if (watch.ElapsedMilliseconds > 16)
  197. break;
  198. }
  199. RefCounted r;
  200. foreach (var native in nativeLookup.Keys.ToList())
  201. {
  202. var w = nativeLookup[native];
  203. if (!w.TryGetTarget(out r))
  204. {
  205. // expired
  206. csi_AtomicEngine_ReleaseRef(native);
  207. nativeLookup.Remove(native);
  208. //nativesRemoved++;
  209. }
  210. if (watch.ElapsedMilliseconds > 16)
  211. break;
  212. }
  213. /*
  214. if (nativesRemoved != 0 || eventListenersRemoved != 0)
  215. {
  216. Console.WriteLine("Released {0} natives and {1} event receivers", nativesRemoved, eventListenersRemoved);
  217. }
  218. */
  219. }
  220. // Called from RefCounted native destructor, refCounted is not valid for any operations here
  221. #if ATOMIC_IOS
  222. [MonoPInvokeCallback(typeof(RefCountedDeletedDelegate))]
  223. #endif
  224. public static void RefCountedDeleted(IntPtr refCounted)
  225. {
  226. nativeLookup.Remove(refCounted);
  227. RemoveEventSender(refCounted);
  228. }
  229. // register a newly created native
  230. public static IntPtr RegisterNative(IntPtr native, RefCounted r)
  231. {
  232. if (nativeLookup.ContainsKey(native))
  233. {
  234. throw new InvalidOperationException("NativeCore.RegisterNative - Duplicate IntPtr key");
  235. }
  236. r.nativeInstance = native;
  237. // keep native side alive
  238. r.AddRef();
  239. nativeLookup[native] = new WeakReference<RefCounted>(r);
  240. r.InstantiationType = InstantiationType.INSTANTIATION_NET;
  241. r.PostNativeUpdate();
  242. return native;
  243. }
  244. // wraps an existing native instance, with downcast support
  245. public static T WrapNative<T>(IntPtr native) where T : RefCounted
  246. {
  247. if (native == IntPtr.Zero)
  248. return null;
  249. RefCounted r;
  250. WeakReference<RefCounted> w;
  251. // first see if we're already available
  252. if (nativeLookup.TryGetValue(native, out w))
  253. {
  254. if (w.TryGetTarget(out r))
  255. {
  256. // we're alive!
  257. return (T)r;
  258. }
  259. else
  260. {
  261. // we were seen before, but have since been GC'd, remove!
  262. nativeLookup.Remove(native);
  263. if (csi_Atomic_RefCounted_Refs(native) == 1)
  264. {
  265. // only managed ref remains, so release and return null
  266. csi_AtomicEngine_ReleaseRef(native);
  267. return null;
  268. }
  269. csi_AtomicEngine_ReleaseRef(native);
  270. }
  271. }
  272. IntPtr classID = RefCounted.csi_Atomic_RefCounted_GetClassID(native);
  273. // Check whether this is a valid native class to wrap
  274. NativeType nativeType;
  275. if (!nativeClassIDToNativeType.TryGetValue(classID, out nativeType))
  276. {
  277. if (logWrapUnknownNative)
  278. {
  279. Log.Info("WrapNative returning null for unknown class: " + GetNativeTypeName(native));
  280. }
  281. return null;
  282. }
  283. // TODO: make CSComponent abstract and have general abstract logic here?
  284. if (nativeType.Type == typeof(CSComponent))
  285. return null;
  286. // and store, with downcast support for instance Component -> StaticModel
  287. // we never want to hit this path for script inherited natives
  288. r = nativeType.managedConstructor(native);
  289. w = new WeakReference<RefCounted>(r);
  290. NativeCore.nativeLookup[native] = w;
  291. // store a ref, so native side will not be released while we still have a reference in managed code
  292. r.AddRef();
  293. // Note: r.InstantiationType may be INSTANTIATION_NET here is we were GC'd, native still had a reference, and we came back
  294. if (r.InstantiationType == InstantiationType.INSTANTIATION_NET)
  295. {
  296. //Log.Warn($"Wrapped {r.GetType().Name} was originally instantiated in NET, changing to native, this is likely an error");
  297. //r.InstantiationType = InstantiationType.INSTANTIATION_NATIVE;
  298. }
  299. r.PostNativeUpdate();
  300. return (T)r;
  301. }
  302. internal static string GetNativeTypeName(IntPtr native)
  303. {
  304. return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(csi_Atomic_RefCounted_GetTypeName(native));
  305. }
  306. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  307. private static extern IntPtr csi_Atomic_RefCounted_GetTypeName(IntPtr self);
  308. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  309. private static extern int csi_Atomic_RefCounted_Refs(IntPtr self);
  310. public static void RegisterNativeType(NativeType nativeType)
  311. {
  312. if (nativeClassIDToNativeType.ContainsKey(nativeType.nativeClassID))
  313. {
  314. throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType class id registered");
  315. }
  316. if (typeToNativeType.ContainsKey(nativeType.type))
  317. {
  318. throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType type registered");
  319. }
  320. nativeClassIDToNativeType[nativeType.nativeClassID] = nativeType;
  321. typeToNativeType[nativeType.type] = nativeType;
  322. }
  323. public static bool IsNativeType(Type type)
  324. {
  325. if (typeToNativeType.ContainsKey(type))
  326. return true;
  327. return false;
  328. }
  329. public static Type GetNativeAncestorType(Type type)
  330. {
  331. Type ancestorType = type;
  332. do
  333. {
  334. ancestorType = ancestorType.GetTypeInfo().BaseType;
  335. } while (ancestorType != null && !IsNativeType(ancestorType));
  336. return ancestorType;
  337. }
  338. // weak references here, hold a ref native side
  339. internal static Dictionary<IntPtr, WeakReference<RefCounted>> nativeLookup = new Dictionary<IntPtr, WeakReference<RefCounted>>();
  340. // weak references here, hold a ref native side
  341. internal static Dictionary<uint, List<EventSubscription>> eventReceiverLookup = new Dictionary<uint, List<EventSubscription>>();
  342. // Native ClassID to NativeType lookup
  343. internal static Dictionary<IntPtr, NativeType> nativeClassIDToNativeType = new Dictionary<IntPtr, NativeType>();
  344. // Managed Type to NativeType lookup
  345. internal static Dictionary<Type, NativeType> typeToNativeType = new Dictionary<Type, NativeType>();
  346. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  347. private static extern void csi_AtomicEngine_ReleaseRef(IntPtr refCounted);
  348. internal struct EventSubscription
  349. {
  350. public WeakReference<AObject> Receiver;
  351. public IntPtr Sender;
  352. public EventSubscription(AObject receiver)
  353. {
  354. Receiver = new WeakReference<AObject>(receiver);
  355. Sender = IntPtr.Zero;
  356. }
  357. public EventSubscription(AObject receiver, IntPtr sender)
  358. {
  359. Receiver = new WeakReference<AObject>(receiver);
  360. Sender = sender;
  361. }
  362. }
  363. // If true, we will log any attempted access to instances of unknown native classes
  364. static bool logWrapUnknownNative = false;
  365. }
  366. }