Runtime.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. //
  2. // Runtime C# support
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. // Copyrigh 2015 Xamarin INc
  8. //
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Reflection;
  12. using System.Runtime.CompilerServices;
  13. using System.Runtime.InteropServices;
  14. using Urho.IO;
  15. using Urho.Resources;
  16. namespace Urho
  17. {
  18. internal class Runtime
  19. {
  20. static Dictionary<Type, int> hashDict;
  21. [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  22. delegate void NativeCallback(CallbackType type, IntPtr target, IntPtr param1, int param2, string param3);
  23. [DllImport(Consts.NativeImport, CallingConvention = CallingConvention.Cdecl)]
  24. static extern void RegisterMonoNativeCallbacks(NativeCallback callback);
  25. // ReSharper disable once NotAccessedField.Local
  26. static NativeCallback nativeCallback; //keep references to native callbacks (protect from GC)
  27. static bool isStarted;
  28. internal static RefCountedCache RefCountedCache { get; private set; } = new RefCountedCache();
  29. internal static bool IsClosing { get; private set; }
  30. internal static Action<RefCounted, IntPtr> KnownObjectDeleted;
  31. internal static void Start()
  32. {
  33. IsClosing = false;
  34. isStarted = true;
  35. }
  36. internal static void Setup()
  37. {
  38. isStarted = false;
  39. RegisterMonoNativeCallbacks(nativeCallback = OnNativeCallback);
  40. }
  41. /// <summary>
  42. /// This method is called by RefCounted::~RefCounted or RefCounted::AddRef
  43. /// </summary>
  44. [MonoPInvokeCallback(typeof(NativeCallback))]
  45. static void OnNativeCallback(CallbackType type, IntPtr target, IntPtr param1, int param2, string param3)
  46. {
  47. const string typeNameKey = "SharpTypeName";
  48. // while app is not started - accept only Log callbacks
  49. if (!isStarted && type != CallbackType.Log_Write)
  50. return;
  51. switch (type)
  52. {
  53. //Component:
  54. case CallbackType.Component_OnSceneSet:
  55. {
  56. var component = LookupObject<Component>(target, false);
  57. component?.OnSceneSet(LookupObject<Scene>(param1, false));
  58. }
  59. break;
  60. case CallbackType.Component_SaveXml:
  61. {
  62. var component = LookupObject<Component>(target, false);
  63. if (component != null && component.TypeName != component.GetType().Name)
  64. {
  65. var xmlElement = new XmlElement(param1);
  66. xmlElement.SetString(typeNameKey, component.GetType().AssemblyQualifiedName);
  67. component.OnSerialize(new XmlComponentSerializer(xmlElement));
  68. }
  69. }
  70. break;
  71. case CallbackType.Component_LoadXml:
  72. {
  73. var xmlElement = new XmlElement(param1);
  74. var name = xmlElement.GetAttribute(typeNameKey);
  75. if (!string.IsNullOrEmpty(name))
  76. {
  77. Component component;
  78. try
  79. {
  80. var typeObj = Type.GetType(name);
  81. if (typeObj == null)
  82. {
  83. Log.Write(LogLevel.Warning, $"{name} doesn't exist. Probably was removed by Linker. Add it to a some LinkerPleaseInclude.cs in case if you need it.");
  84. return;
  85. }
  86. component = (Component)Activator.CreateInstance(typeObj, target);
  87. }
  88. catch (Exception exc)
  89. {
  90. throw new InvalidOperationException($"{name} doesn't override constructor Component(IntPtr handle).", exc);
  91. }
  92. component.OnDeserialize(new XmlComponentSerializer(xmlElement));
  93. if (component.Node != null)
  94. {
  95. component.AttachedToNode(component.Node);
  96. }
  97. }
  98. }
  99. break;
  100. case CallbackType.Component_AttachedToNode:
  101. {
  102. var component = LookupObject<Component>(target, false);
  103. component?.AttachedToNode(component.Node);
  104. }
  105. break;
  106. case CallbackType.Component_OnNodeSetEnabled:
  107. {
  108. var component = LookupObject<Component>(target, false);
  109. component?.OnNodeSetEnabled();
  110. }
  111. break;
  112. //RefCounted:
  113. case CallbackType.RefCounted_AddRef:
  114. {
  115. //if we have an object with this handle and it's reference is weak - then change it to strong.
  116. var referenceHolder = RefCountedCache.Get(target);
  117. referenceHolder?.MakeStrong();
  118. }
  119. break;
  120. case CallbackType.RefCounted_Delete:
  121. {
  122. var referenceHolder = RefCountedCache.Get(target);
  123. if (referenceHolder == null)
  124. return; //we don't have this object in the cache so let's just skip it
  125. var reference = referenceHolder.Reference;
  126. if (reference == null)
  127. // seems like the reference was Weak and GC has removed it - remove item from the dictionary
  128. RefCountedCache.Remove(target);
  129. else
  130. {
  131. reference.HandleNativeDelete();
  132. KnownObjectDeleted(reference, target);
  133. }
  134. }
  135. break;
  136. case CallbackType.Log_Write:
  137. Urho.Application.ThrowUnhandledException(
  138. new Exception(param3 + ". You can omit this exception by subscribing to Urho.Application.UnhandledException event and set Handled property to True.\nApplicationOptions: " + Application.CurrentOptions));
  139. break;
  140. }
  141. }
  142. public static T LookupRefCounted<T> (IntPtr ptr, bool createIfNotFound = true) where T:RefCounted
  143. {
  144. if (ptr == IntPtr.Zero)
  145. return null;
  146. var reference = RefCountedCache.Get(ptr)?.Reference;
  147. if (reference is T)
  148. return (T) reference;
  149. if (!createIfNotFound)
  150. return null;
  151. var refCounted = (T)Activator.CreateInstance(typeof(T), ptr);
  152. return refCounted;
  153. }
  154. public static T LookupObject<T>(IntPtr ptr, bool createIfNotFound = true) where T : UrhoObject
  155. {
  156. if (ptr == IntPtr.Zero)
  157. return null;
  158. var referenceHolder = RefCountedCache.Get(ptr);
  159. var reference = referenceHolder?.Reference;
  160. if (reference is T) //possible collisions
  161. return (T)reference;
  162. if (!createIfNotFound)
  163. return null;
  164. var name = Marshal.PtrToStringAnsi(UrhoObject.UrhoObject_GetTypeName(ptr));
  165. var type = FindTypeByName(name);
  166. var typeInfo = type.GetTypeInfo();
  167. if (typeInfo.IsSubclassOf(typeof(Component)) || type == typeof(Component))
  168. {
  169. //TODO: special case, handle managed subclasses
  170. }
  171. var urhoObject = (T)Activator.CreateInstance(type, ptr);
  172. return urhoObject;
  173. }
  174. public static void UnregisterObject (IntPtr handle)
  175. {
  176. RefCountedCache.Remove(handle);
  177. }
  178. public static void RegisterObject (RefCounted refCounted)
  179. {
  180. RefCountedCache.Add(refCounted);
  181. }
  182. public static StringHash LookupStringHash (Type t)
  183. {
  184. if (hashDict == null)
  185. hashDict = new Dictionary<Type, int> ();
  186. int c;
  187. if (hashDict.TryGetValue (t, out c))
  188. return new StringHash (c);
  189. var hash = GetTypeStatic(t);
  190. hashDict [t] = hash.Code;
  191. return hash;
  192. }
  193. static StringHash GetTypeStatic(Type type)
  194. {
  195. var typeStatic = type.GetRuntimeProperty("TypeStatic");
  196. while (typeStatic == null)
  197. {
  198. type = type.GetTypeInfo().BaseType;
  199. if (type == typeof(object))
  200. throw new InvalidOperationException("The type doesn't have static TypeStatic property");
  201. typeStatic = type.GetRuntimeProperty("TypeStatic");
  202. }
  203. return (StringHash)typeStatic.GetValue(null);
  204. }
  205. // for RefCounted, UrhoObjects
  206. internal static void ValidateRefCounted<T>(T obj, [CallerMemberName] string name = "") where T : RefCounted
  207. {
  208. //TODO: remove ValidateRefCounted from IsExiting in the Binder
  209. if (name == "IsExisting")
  210. return;
  211. if (IsClosing)
  212. {
  213. var errorText = $"{typeof(T).Name}.{name} (Handle={obj.Handle}) was invoked after Application.Stop";
  214. LogSharp.Error(errorText);
  215. throw new InvalidOperationException(errorText);
  216. }
  217. if (obj.IsDeleted) //IsDeleted is set to True when we receive a native callback from RefCounted::~RefCounted
  218. {
  219. var errorText = $"Underlying native object was deleted for Handle={obj.Handle}. {typeof(T).Name}.{name}";
  220. LogSharp.Error(errorText);
  221. throw new InvalidOperationException(errorText);
  222. }
  223. //if (obj.Handle == IntPtr.Zero)
  224. //{
  225. //}
  226. //TODO: check current thread?
  227. }
  228. // non-RefCounted classes
  229. internal static void ValidateObject<T>(T obj, [CallerMemberName] string name = "") where T : class
  230. {
  231. if (IsClosing)
  232. {
  233. var errorText = $"{typeof(T).Name}.{name} was invoked after Application.Stop";
  234. LogSharp.Error(errorText);
  235. throw new InvalidOperationException(errorText);
  236. }
  237. }
  238. // constructors, static methods, value types
  239. internal static void Validate(Type type, [CallerMemberName] string name = "")
  240. {
  241. if (IsClosing)
  242. {
  243. var errorText = $"{type.Name}.{name} was invoked after Application.Stop";
  244. LogSharp.Error(errorText);
  245. throw new InvalidOperationException(errorText);
  246. }
  247. }
  248. internal static IReadOnlyList<T> CreateVectorSharedPtrProxy<T> (IntPtr handle) where T : UrhoObject
  249. {
  250. return new Vectors.ProxyUrhoObject<T> (handle);
  251. }
  252. internal static IReadOnlyList<T> CreateVectorSharedPtrRefcountedProxy<T>(IntPtr handle) where T : RefCounted
  253. {
  254. return new Vectors.ProxyRefCounted<T>(handle);
  255. }
  256. internal static void Cleanup(bool disposeContext = true)
  257. {
  258. IsClosing = true;
  259. RefCountedCache.Clean(disposeContext);
  260. GC.Collect();
  261. GC.WaitForPendingFinalizers();
  262. GC.Collect();
  263. }
  264. static Dictionary<string, Type> typesByNativeNames;
  265. // special cases: (TODO: share this code with SharpieBinder somehow)
  266. static Dictionary<string, string> typeNamesMap = new Dictionary<string, string>
  267. {
  268. {nameof(UrhoObject), "Object"},
  269. {nameof(UrhoConsole), "Console"},
  270. {nameof(XmlFile), "XMLFile"},
  271. {nameof(JsonFile), "JSONFile"},
  272. };
  273. static Type FindTypeByName(string name)
  274. {
  275. if (typesByNativeNames == null)
  276. {
  277. typesByNativeNames = new Dictionary<string, Type>(200);
  278. foreach (var type in typeof(Runtime).GetTypeInfo().Assembly.ExportedTypes)
  279. {
  280. if (!type.GetTypeInfo().IsSubclassOf(typeof(RefCounted)))
  281. continue;
  282. string remappedName;
  283. if (!typeNamesMap.TryGetValue(type.Name, out remappedName))
  284. remappedName = type.Name;
  285. typesByNativeNames[remappedName] = type;
  286. }
  287. }
  288. Type result;
  289. if (!typesByNativeNames.TryGetValue(name, out result))
  290. throw new Exception($"Type {name} not found.");
  291. return result;
  292. }
  293. }
  294. }