Application.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. //
  2. // Support for bubbling up to C# the virtual methods calls for Setup, Start and Stop in Application
  3. //
  4. // This is done by using an ApplicationProxy in C++ that bubbles up
  5. //
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. using System.Linq;
  10. using System.Reflection;
  11. using System.Runtime.CompilerServices;
  12. using System.Runtime.InteropServices;
  13. using System.Threading.Tasks;
  14. using Urho.IO;
  15. using Urho.Audio;
  16. using Urho.Resources;
  17. using Urho.Actions;
  18. using Urho.Gui;
  19. using System.Threading;
  20. namespace Urho
  21. {
  22. [PreserveAttribute(AllMembers = true)]
  23. public partial class Application
  24. {
  25. // references needed to prevent GC from collecting callbacks passed to native code
  26. static ActionIntPtr setupCallback;
  27. static ActionIntPtr startCallback;
  28. static ActionIntPtr stopCallback;
  29. static TaskCompletionSource<bool> exitTask;
  30. static TaskCompletionSource<bool> waitFrameEndTaskSource;
  31. AutoResetEvent frameEndResetEvent;
  32. static bool isExiting = false;
  33. [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  34. public delegate void ActionIntPtr(IntPtr value);
  35. [DllImport(Consts.NativeImport, CallingConvention = CallingConvention.Cdecl)]
  36. static extern IntPtr ApplicationProxy_ApplicationProxy(IntPtr contextHandle, ActionIntPtr setup, ActionIntPtr start, ActionIntPtr stop, string args, IntPtr externalWindow);
  37. static Application current;
  38. public static Application Current
  39. {
  40. get
  41. {
  42. if (current == null)
  43. throw new InvalidOperationException("The application is not configured yet");
  44. return current;
  45. }
  46. private set { current = value; }
  47. }
  48. public static bool HasCurrent => current != null;
  49. static Context currentContext;
  50. public static Context CurrentContext
  51. {
  52. get
  53. {
  54. if (currentContext == null)
  55. throw new InvalidOperationException("Urho.Application is not started yet. All urho objects should be initialized after app.Run() since they need an active Context.\n");
  56. return currentContext;
  57. }
  58. private set { currentContext = value; }
  59. }
  60. public static WeakReference CurrentSurface { get; internal set; }
  61. public static WeakReference CurrentWindow { get; internal set; }
  62. // see Drawable2D.h:66
  63. public const float PixelSize = 0.01f;
  64. [Preserve]
  65. public Application(ApplicationOptions options) : this(new Context(), options) { }
  66. Application(Context context, ApplicationOptions options = null) : base(UrhoObjectFlag.Empty)
  67. {
  68. //Workbooks specific:
  69. CancelActiveActionsOnStop = this is SimpleApplication;
  70. if (context == null)
  71. throw new ArgumentNullException(nameof(context));
  72. //keep references to callbacks (supposed to be passed to native code) as long as the App is alive
  73. setupCallback = ProxySetup;
  74. startCallback = ProxyStart;
  75. stopCallback = ProxyStop;
  76. #if !__ANDROID__
  77. if (context.Refs() < 1)
  78. context.AddRef();
  79. #endif
  80. Options = options ?? new ApplicationOptions(assetsFolder: null);
  81. handle = ApplicationProxy_ApplicationProxy(context.Handle, setupCallback, startCallback, stopCallback, Options.ToString(), Options.ExternalWindow);
  82. Runtime.RegisterObject(this);
  83. }
  84. public bool IsClosed { get; private set; }
  85. internal static ApplicationOptions CurrentOptions => current?.Options;
  86. internal object UrhoSurface { get; set; }
  87. /// <summary>
  88. /// Application options
  89. /// </summary>
  90. public ApplicationOptions Options { get; private set; }
  91. /// <summary>
  92. /// True means Urho3D is somewhere between E_BEGINFRAME and E_ENDFRAME in Engine::RunFrame()
  93. /// </summary>
  94. public bool IsFrameRendering { get; private set; }
  95. /// <summary>
  96. /// Frame update event
  97. /// </summary>
  98. public event Action<UpdateEventArgs> Update;
  99. /// <summary>
  100. /// Invoke actions in the Main Thread (the next Update call)
  101. /// </summary>
  102. public static void InvokeOnMain(Action action) => MainLoopDispatcher.InvokeOnMain(action);
  103. /// <summary>
  104. /// Dispatch to OnUpdate
  105. /// </summary>
  106. public static ConfiguredTaskAwaitable<bool> ToMainThreadAsync() => MainLoopDispatcher.ToMainThreadAsync();
  107. /// <summary>
  108. /// Invoke actions in the Main Thread (the next Update call)
  109. /// </summary>
  110. public static Task<bool> InvokeOnMainAsync(Action action) => MainLoopDispatcher.InvokeOnMainAsync(action);
  111. public ConfiguredTaskAwaitable<bool> Delay(float seconds) => MainLoopDispatcher.Delay(seconds);
  112. public ConfiguredTaskAwaitable<bool> Delay(TimeSpan timeSpan) => MainLoopDispatcher.Delay((float)timeSpan.TotalSeconds);
  113. static Application GetApp(IntPtr h) => Runtime.LookupObject<Application>(h);
  114. void Time_FrameStarted(FrameStartedEventArgs args)
  115. {
  116. IsFrameRendering = true;
  117. }
  118. void Time_FrameEnded(FrameEndedEventArgs args)
  119. {
  120. IsFrameRendering = false;
  121. waitFrameEndTaskSource?.TrySetResult(true);
  122. if (frameEndResetEvent != null)
  123. {
  124. frameEndResetEvent.Set();
  125. frameEndResetEvent = null;
  126. }
  127. }
  128. public void WaitFrameEnd()
  129. {
  130. frameEndResetEvent = new AutoResetEvent(false);
  131. frameEndResetEvent.WaitOne();
  132. }
  133. void HandleUpdate(UpdateEventArgs args)
  134. {
  135. var timeStep = args.TimeStep;
  136. Update?.Invoke(args);
  137. ActionManager.Update(timeStep);
  138. OnUpdate(timeStep);
  139. MainLoopDispatcher.HandleUpdate(timeStep);
  140. }
  141. [MonoPInvokeCallback(typeof(ActionIntPtr))]
  142. static void ProxySetup(IntPtr h)
  143. {
  144. isExiting = false;
  145. Runtime.Setup();
  146. Current = GetApp(h);
  147. CurrentContext = Current.Context;
  148. Current.Setup();
  149. }
  150. [MonoPInvokeCallback(typeof(ActionIntPtr))]
  151. static void ProxyStart(IntPtr h)
  152. {
  153. Runtime.Start();
  154. Current = GetApp(h);
  155. Current.SubscribeToAppEvents();
  156. #if WINDOWS_UWP
  157. // UWP temp workaround:
  158. var text = new Text();
  159. text.SetFont(CoreAssets.Fonts.AnonymousPro, 1);
  160. text.Value = " ";
  161. Current.UI.Root.AddChild(text);
  162. #endif
  163. Current.Start();
  164. Started?.Invoke();
  165. }
  166. public static bool CancelActiveActionsOnStop { get; set; }
  167. [MonoPInvokeCallback(typeof(ActionIntPtr))]
  168. static void ProxyStop(IntPtr h)
  169. {
  170. isExiting = true;
  171. if (CancelActiveActionsOnStop)
  172. Current.ActionManager.CancelActiveActions();
  173. LogSharp.Debug("ProxyStop");
  174. UrhoPlatformInitializer.Initialized = false;
  175. var context = Current.Context;
  176. var app = GetApp(h);
  177. app.IsClosed = true;
  178. app.Stop();
  179. LogSharp.Debug("ProxyStop: Runtime.Cleanup");
  180. Runtime.Cleanup(Platform != Platforms.Android);
  181. LogSharp.Debug("ProxyStop: Disposing context");
  182. Current = null;
  183. Stopped?.Invoke();
  184. LogSharp.Debug("ProxyStop: end");
  185. exitTask?.TrySetResult(true);
  186. }
  187. void SubscribeToAppEvents()
  188. {
  189. Engine.SubscribeToUpdate(HandleUpdate);
  190. Time.FrameStarted += Time_FrameStarted;
  191. Time.FrameEnded += Time_FrameEnded;
  192. }
  193. SemaphoreSlim stopSemaphore = new SemaphoreSlim(1);
  194. internal static void WaitStart()
  195. {
  196. waitFrameEndTaskSource = new TaskCompletionSource<bool>();
  197. waitFrameEndTaskSource.Task.Wait(3000);
  198. }
  199. internal static async Task StopCurrent()
  200. {
  201. if (current == null || !current.IsActive)
  202. return;
  203. #if __ANDROID__
  204. current.WaitFrameEnd();
  205. Org.Libsdl.App.SDLActivity.OnDestroy();
  206. return;
  207. #endif
  208. Current.Input.Enabled = false;
  209. isExiting = true;
  210. #if __IOS__
  211. iOS.UrhoSurface.StopRendering(current);
  212. #endif
  213. #if WINDOWS_UWP && !UWP_HOLO
  214. UWP.UrhoSurface.StopRendering().Wait();
  215. #endif
  216. LogSharp.Debug($"StopCurrent: Current.IsFrameRendering={Current.IsFrameRendering}");
  217. if (Current.IsFrameRendering)// && !Current.Engine.PauseMinimized)
  218. {
  219. waitFrameEndTaskSource = new TaskCompletionSource<bool>();
  220. await waitFrameEndTaskSource.Task;
  221. LogSharp.Debug($"StopCurrent: waitFrameEndTaskSource awaited");
  222. waitFrameEndTaskSource = null;
  223. }
  224. LogSharp.Debug($"StopCurrent: Engine.Exit");
  225. Current.Engine.Exit();
  226. #if NET46
  227. if (Current.Options.DelayedStart)
  228. #endif
  229. ProxyStop(Current.Handle);
  230. GC.Collect();
  231. GC.WaitForPendingFinalizers();
  232. GC.Collect();
  233. }
  234. public bool IsExiting => isExiting || Runtime.IsClosing || Engine.Exiting;
  235. public bool IsActive => !IsClosed && !IsDeleted && Engine != null && !Engine.IsDeleted && !IsExiting;
  236. public async Task Exit()
  237. {
  238. try
  239. {
  240. await stopSemaphore.WaitAsync();
  241. if (!IsActive)
  242. return;
  243. await StopCurrent();
  244. }
  245. finally
  246. {
  247. stopSemaphore.Release();
  248. }
  249. }
  250. protected override bool AllowNativeDelete => false;
  251. protected virtual void Setup() { }
  252. public static event Action Started;
  253. protected virtual void Start() { }
  254. public static event Action Stopped;
  255. protected virtual void Stop() { }
  256. protected virtual void OnUpdate(float timeStep) { }
  257. public event Action Paused;
  258. internal static void HandlePause()
  259. {
  260. if (HasCurrent)
  261. Current.Paused?.Invoke();
  262. }
  263. public event Action Resumed;
  264. internal static void HandleResume()
  265. {
  266. if (HasCurrent)
  267. Current.Resumed?.Invoke();
  268. }
  269. internal ActionManager ActionManager { get; } = new ActionManager();
  270. [DllImport(Consts.NativeImport, EntryPoint = "Urho_GetPlatform", CallingConvention = CallingConvention.Cdecl)]
  271. static extern IntPtr GetPlatform();
  272. static Platforms platform;
  273. public static Platforms Platform
  274. {
  275. get
  276. {
  277. #if __ANDROID__ // avoid redundant pinvoke for iOS and Android
  278. return Platforms.Android;
  279. #elif __IOS__
  280. return Platforms.iOS;
  281. #elif UWP_HOLO
  282. return Platforms.SharpReality;
  283. #elif WINDOWS_UWP
  284. return Platforms.UWP;
  285. #endif
  286. Runtime.Validate(typeof(Application));
  287. if (platform == Platforms.Unknown)
  288. platform = PlatformsMap.FromString(Marshal.PtrToStringAnsi(GetPlatform()));
  289. return platform;
  290. }
  291. }
  292. //
  293. // GetSubsystem helpers
  294. //
  295. ResourceCache resourceCache;
  296. public ResourceCache ResourceCache
  297. {
  298. get
  299. {
  300. Runtime.Validate(typeof(Application));
  301. if (resourceCache == null)
  302. resourceCache = new ResourceCache(UrhoObject_GetSubsystem(handle, ResourceCache.TypeStatic.Code));
  303. return resourceCache;
  304. }
  305. }
  306. UrhoConsole console;
  307. public UrhoConsole Console
  308. {
  309. get
  310. {
  311. Runtime.Validate(typeof(Application));
  312. if (console == null)
  313. console = new UrhoConsole(UrhoObject_GetSubsystem(handle, UrhoConsole.TypeStatic.Code));
  314. return console;
  315. }
  316. }
  317. Urho.Network.Network network;
  318. public Urho.Network.Network Network
  319. {
  320. get
  321. {
  322. Runtime.Validate(typeof(Application));
  323. if (network == null)
  324. network = new Urho.Network.Network(UrhoObject_GetSubsystem(handle, Urho.Network.Network.TypeStatic.Code));
  325. return network;
  326. }
  327. }
  328. Time time;
  329. public Time Time
  330. {
  331. get
  332. {
  333. Runtime.Validate(typeof(Application));
  334. if (time == null)
  335. time = new Time(UrhoObject_GetSubsystem(handle, Time.TypeStatic.Code));
  336. return time;
  337. }
  338. }
  339. WorkQueue workQueue;
  340. public WorkQueue WorkQueue
  341. {
  342. get
  343. {
  344. Runtime.Validate(typeof(Application));
  345. if (workQueue == null)
  346. workQueue = new WorkQueue(UrhoObject_GetSubsystem(handle, WorkQueue.TypeStatic.Code));
  347. return workQueue;
  348. }
  349. }
  350. Profiler profiler;
  351. public Profiler Profiler
  352. {
  353. get
  354. {
  355. Runtime.Validate(typeof(Application));
  356. if (profiler == null)
  357. profiler = new Profiler(UrhoObject_GetSubsystem(handle, Profiler.TypeStatic.Code));
  358. return profiler;
  359. }
  360. }
  361. FileSystem fileSystem;
  362. public FileSystem FileSystem
  363. {
  364. get
  365. {
  366. Runtime.Validate(typeof(Application));
  367. if (fileSystem == null)
  368. fileSystem = new FileSystem(UrhoObject_GetSubsystem(handle, FileSystem.TypeStatic.Code));
  369. return fileSystem;
  370. }
  371. }
  372. Log log;
  373. public Log Log
  374. {
  375. get
  376. {
  377. Runtime.Validate(typeof(Application));
  378. if (log == null)
  379. log = new Log(UrhoObject_GetSubsystem(handle, Log.TypeStatic.Code));
  380. return log;
  381. }
  382. }
  383. Input input;
  384. public Input Input
  385. {
  386. get
  387. {
  388. Runtime.Validate(typeof(Application));
  389. if (input == null)
  390. input = new Input(UrhoObject_GetSubsystem(handle, Input.TypeStatic.Code));
  391. return input;
  392. }
  393. }
  394. Urho.Audio.Audio audio;
  395. public Urho.Audio.Audio Audio
  396. {
  397. get
  398. {
  399. Runtime.Validate(typeof(Application));
  400. if (audio == null)
  401. audio = new Audio.Audio(UrhoObject_GetSubsystem(handle, Urho.Audio.Audio.TypeStatic.Code));
  402. return audio;
  403. }
  404. }
  405. UI uI;
  406. public UI UI
  407. {
  408. get
  409. {
  410. Runtime.Validate(typeof(Application));
  411. if (uI == null)
  412. uI = new UI(UrhoObject_GetSubsystem(handle, UI.TypeStatic.Code));
  413. return uI;
  414. }
  415. }
  416. Graphics graphics;
  417. public Graphics Graphics
  418. {
  419. get
  420. {
  421. Runtime.Validate(typeof(Application));
  422. if (graphics == null)
  423. graphics = new Graphics(UrhoObject_GetSubsystem(handle, Graphics.TypeStatic.Code));
  424. return graphics;
  425. }
  426. }
  427. Renderer renderer;
  428. public Renderer Renderer
  429. {
  430. get
  431. {
  432. Runtime.Validate(typeof(Application));
  433. if (renderer == null)
  434. renderer = new Renderer(UrhoObject_GetSubsystem(handle, Renderer.TypeStatic.Code));
  435. return renderer;
  436. }
  437. }
  438. [DllImport(Consts.NativeImport, CallingConvention = CallingConvention.Cdecl)]
  439. extern static IntPtr Application_GetEngine(IntPtr handle);
  440. Engine engine;
  441. public Engine Engine
  442. {
  443. get
  444. {
  445. if (engine == null)
  446. engine = new Engine(Application_GetEngine(handle));
  447. return engine;
  448. }
  449. }
  450. public static T CreateInstance<T>(ApplicationOptions options = null) where T : Application
  451. {
  452. return (T)CreateInstance(typeof(T), options);
  453. }
  454. public static Application CreateInstance(Type applicationType, ApplicationOptions options = null)
  455. {
  456. try
  457. {
  458. return (Application)Activator.CreateInstance(applicationType, options);
  459. }
  460. catch (Exception exc)
  461. {
  462. throw new InvalidOperationException($"Constructor {applicationType}(ApplicationOptions) was not found.", exc);
  463. }
  464. }
  465. internal static void ThrowUnhandledException(Exception exc)
  466. {
  467. string[] errorsToSkip =
  468. {
  469. "Could not initialize audio output.",
  470. "Failed to create input layout for shader",
  471. "Failed to create texture"
  472. };
  473. foreach (var item in errorsToSkip)
  474. if (exc.Message.StartsWith(item))
  475. return;
  476. if (exc.Message.StartsWith("Failed to add resource path") ||
  477. exc.Message.StartsWith("Failed to add resource package"))
  478. {
  479. string msg = exc.Message.Remove(exc.Message.IndexOf("',") + 1);
  480. string assetDir = msg.Remove(0, msg.IndexOf('\'') + 1);
  481. msg +=
  482. #if __ANDROID__
  483. $"\n Assets must be located in '/Assets/{assetDir}' with 'AndroidAsset' Build Action.";
  484. #elif __iOS__
  485. $"\n Assets must be located in '/Resources/{assetDir}' with 'BundleResource' Build Action.";
  486. #elif WINDOWS_UWP || UWP_HOLO
  487. $"\n Assets must be located in '/{assetDir}' with 'Content' Build Action";
  488. #else
  489. $"\n Assets must be located in '/{assetDir}'";
  490. #endif
  491. exc = new Exception(msg);
  492. }
  493. var args = new UnhandledExceptionEventArgs(exc);
  494. UnhandledException?.Invoke(null, args);
  495. if (!args.Handled && !isExiting)
  496. throw exc;
  497. }
  498. public static event EventHandler<UnhandledExceptionEventArgs> UnhandledException;
  499. class DelayState
  500. {
  501. public float Duration { get; set; }
  502. public TaskCompletionSource<bool> Task { get; set; }
  503. }
  504. }
  505. public class UnhandledExceptionEventArgs : EventArgs
  506. {
  507. public Exception Exception { get; private set; }
  508. public bool Handled { get; set; }
  509. public UnhandledExceptionEventArgs(Exception exception)
  510. {
  511. Exception = exception;
  512. }
  513. }
  514. }