NativeCore.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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. nativeEventData.sourceEventData = eventData;
  161. }
  162. receiver.HandleEvent(eventType, scriptMap, nativeEventData);
  163. }
  164. if (scriptMap != null)
  165. {
  166. svmDepth--;
  167. if (nativeEventData != null)
  168. {
  169. NativeEvents.ReleaseNativeEventData(nativeEventData);
  170. }
  171. }
  172. }
  173. static void ExpireNatives()
  174. {
  175. var watch = new Stopwatch();
  176. watch.Start();
  177. // expire event listeners
  178. //int eventListenersRemoved = 0;
  179. //int nativesRemoved = 0;
  180. AObject obj;
  181. foreach (List<EventSubscription> receiverList in eventReceiverLookup.Values)
  182. {
  183. foreach (EventSubscription er in receiverList.ToList())
  184. {
  185. if (!er.Receiver.TryGetTarget(out obj))
  186. {
  187. receiverList.Remove(er);
  188. //eventListenersRemoved++;
  189. }
  190. if (watch.ElapsedMilliseconds > 16)
  191. break;
  192. }
  193. if (watch.ElapsedMilliseconds > 16)
  194. break;
  195. }
  196. RefCounted r;
  197. foreach (var native in nativeLookup.Keys.ToList())
  198. {
  199. var w = nativeLookup[native];
  200. if (!w.TryGetTarget(out r))
  201. {
  202. // expired
  203. csi_AtomicEngine_ReleaseRef(native);
  204. nativeLookup.Remove(native);
  205. //nativesRemoved++;
  206. }
  207. if (watch.ElapsedMilliseconds > 16)
  208. break;
  209. }
  210. /*
  211. if (nativesRemoved != 0 || eventListenersRemoved != 0)
  212. {
  213. Console.WriteLine("Released {0} natives and {1} event receivers", nativesRemoved, eventListenersRemoved);
  214. }
  215. */
  216. }
  217. // Called from RefCounted native destructor, refCounted is not valid for any operations here
  218. #if ATOMIC_IOS
  219. [MonoPInvokeCallback(typeof(RefCountedDeletedDelegate))]
  220. #endif
  221. public static void RefCountedDeleted(IntPtr refCounted)
  222. {
  223. nativeLookup.Remove(refCounted);
  224. RemoveEventSender(refCounted);
  225. }
  226. // register a newly created native
  227. public static IntPtr RegisterNative(IntPtr native, RefCounted r)
  228. {
  229. if (nativeLookup.ContainsKey(native))
  230. {
  231. throw new InvalidOperationException("NativeCore.RegisterNative - Duplicate IntPtr key");
  232. }
  233. r.nativeInstance = native;
  234. // keep native side alive
  235. r.AddRef();
  236. nativeLookup[native] = new WeakReference<RefCounted>(r);
  237. r.InstantiationType = InstantiationType.INSTANTIATION_NET;
  238. r.PostNativeUpdate();
  239. return native;
  240. }
  241. // wraps an existing native instance, with downcast support
  242. public static T WrapNative<T>(IntPtr native) where T : RefCounted
  243. {
  244. if (native == IntPtr.Zero)
  245. return null;
  246. RefCounted r;
  247. WeakReference<RefCounted> w;
  248. // first see if we're already available
  249. if (nativeLookup.TryGetValue(native, out w))
  250. {
  251. if (w.TryGetTarget(out r))
  252. {
  253. // we're alive!
  254. return (T)r;
  255. }
  256. else
  257. {
  258. // we were seen before, but have since been GC'd, remove!
  259. nativeLookup.Remove(native);
  260. if (csi_Atomic_RefCounted_Refs(native) == 1)
  261. {
  262. // only managed ref remains, so release and return null
  263. csi_AtomicEngine_ReleaseRef(native);
  264. return null;
  265. }
  266. csi_AtomicEngine_ReleaseRef(native);
  267. }
  268. }
  269. IntPtr classID = RefCounted.csi_Atomic_RefCounted_GetClassID(native);
  270. // and store, with downcast support for instance Component -> StaticModel
  271. // we never want to hit this path for script inherited natives
  272. NativeType nativeType;
  273. if (!nativeClassIDToNativeType.TryGetValue(classID, out nativeType))
  274. {
  275. throw new InvalidOperationException("NativeCore.WrapNative - Attempting to wrap unknown native class id");
  276. }
  277. // TODO: make CSComponent abstract and have general abstract logic here?
  278. if (nativeType.Type == typeof(CSComponent))
  279. return null;
  280. r = nativeType.managedConstructor(native);
  281. w = new WeakReference<RefCounted>(r);
  282. NativeCore.nativeLookup[native] = w;
  283. // store a ref, so native side will not be released while we still have a reference in managed code
  284. r.AddRef();
  285. // Note: r.InstantiationType may be INSTANTIATION_NET here is we were GC'd, native still had a reference, and we came back
  286. if (r.InstantiationType == InstantiationType.INSTANTIATION_NET)
  287. {
  288. //Log.Warn($"Wrapped {r.GetType().Name} was originally instantiated in NET, changing to native, this is likely an error");
  289. //r.InstantiationType = InstantiationType.INSTANTIATION_NATIVE;
  290. }
  291. r.PostNativeUpdate();
  292. return (T)r;
  293. }
  294. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  295. private static extern IntPtr csi_Atomic_AObject_GetTypeName(IntPtr self);
  296. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  297. private static extern int csi_Atomic_RefCounted_Refs(IntPtr self);
  298. public static void RegisterNativeType(NativeType nativeType)
  299. {
  300. if (nativeClassIDToNativeType.ContainsKey(nativeType.nativeClassID))
  301. {
  302. throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType class id registered");
  303. }
  304. if (typeToNativeType.ContainsKey(nativeType.type))
  305. {
  306. throw new InvalidOperationException("NativeCore.RegisterNativeType - Duplicate NativeType type registered");
  307. }
  308. nativeClassIDToNativeType[nativeType.nativeClassID] = nativeType;
  309. typeToNativeType[nativeType.type] = nativeType;
  310. }
  311. public static bool IsNativeType(Type type)
  312. {
  313. if (typeToNativeType.ContainsKey(type))
  314. return true;
  315. return false;
  316. }
  317. public static Type GetNativeAncestorType(Type type)
  318. {
  319. Type ancestorType = type;
  320. do
  321. {
  322. ancestorType = ancestorType.GetTypeInfo().BaseType;
  323. } while (ancestorType != null && !IsNativeType(ancestorType));
  324. return ancestorType;
  325. }
  326. static public IntPtr NativeContructorOverride
  327. {
  328. get
  329. {
  330. IntPtr value = nativeContructorOverride;
  331. nativeContructorOverride = IntPtr.Zero;
  332. return value;
  333. }
  334. set
  335. {
  336. if (nativeContructorOverride != IntPtr.Zero)
  337. {
  338. throw new InvalidOperationException("NativeCore.NativeContructorOverride - Previous nativeContructorOverride not consumed");
  339. }
  340. nativeContructorOverride = value;
  341. }
  342. }
  343. static public void VerifyNativeContructorOverrideConsumed()
  344. {
  345. if (nativeContructorOverride != IntPtr.Zero)
  346. {
  347. throw new InvalidOperationException("NativeCore.VerifyNativeContructorOverrideConsumed - NativeContructorOverride not consumed");
  348. }
  349. }
  350. private static IntPtr nativeContructorOverride = IntPtr.Zero;
  351. // weak references here, hold a ref native side
  352. internal static Dictionary<IntPtr, WeakReference<RefCounted>> nativeLookup = new Dictionary<IntPtr, WeakReference<RefCounted>>();
  353. // weak references here, hold a ref native side
  354. internal static Dictionary<uint, List<EventSubscription>> eventReceiverLookup = new Dictionary<uint, List<EventSubscription>>();
  355. // Native ClassID to NativeType lookup
  356. internal static Dictionary<IntPtr, NativeType> nativeClassIDToNativeType = new Dictionary<IntPtr, NativeType>();
  357. // Managed Type to NativeType lookup
  358. internal static Dictionary<Type, NativeType> typeToNativeType = new Dictionary<Type, NativeType>();
  359. [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  360. private static extern void csi_AtomicEngine_ReleaseRef(IntPtr refCounted);
  361. internal struct EventSubscription
  362. {
  363. public WeakReference<AObject> Receiver;
  364. public IntPtr Sender;
  365. public EventSubscription(AObject receiver)
  366. {
  367. Receiver = new WeakReference<AObject>(receiver);
  368. Sender = IntPtr.Zero;
  369. }
  370. public EventSubscription(AObject receiver, IntPtr sender)
  371. {
  372. Receiver = new WeakReference<AObject>(receiver);
  373. Sender = sender;
  374. }
  375. }
  376. }
  377. }