NativeCore.cs 15 KB

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