// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using FlaxEngine.GUI; namespace FlaxEngine { /// /// The async tasks scheduler that runs the tasks on a main thread during game update. /// /// /// internal class MainThreadTaskScheduler : TaskScheduler, IDisposable { private readonly BlockingCollection _tasks = new BlockingCollection(); internal void Execute() { while (_tasks.TryTake(out var task)) { TryExecuteTask(task); } } private void Dispose(bool disposing) { if (!disposing) return; _tasks.CompleteAdding(); _tasks.Dispose(); } /// protected override void QueueTask(Task task) { _tasks.Add(task); } /// protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; } /// protected override IEnumerable GetScheduledTasks() { return _tasks.ToArray(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } /// /// C# scripting service. /// public static class Scripting { private static readonly List UpdateActions = new List(); private static readonly MainThreadTaskScheduler MainThreadTaskScheduler = new MainThreadTaskScheduler(); /// /// Occurs on scripting update. /// public static event Action Update; /// /// Occurs on scripting 'late' update. /// public static event Action LateUpdate; /// /// Occurs on scripting `fixed` update. /// public static event Action FixedUpdate; /// /// Occurs on scripting `draw` update. Called during frame rendering and can be used to invoke custom rendering with GPUDevice. /// public static event Action Draw; /// /// Occurs when scripting engine is disposing. Engine is during closing and some services may be unavailable (eg. loading scenes). This may be called after the engine fatal error event. /// public static event Action Exit; /// /// Gets the async tasks scheduler that runs the tasks on a main thread during game update. /// public static TaskScheduler MainThreadScheduler => MainThreadTaskScheduler; /// /// Calls the given action on the next scripting update. /// /// The action to invoke. public static void InvokeOnUpdate(Action action) { lock (UpdateActions) { UpdateActions.Add(action); } } /// /// Queues the specified work to run on the main thread and returns a object that represents that work. /// /// The work to execute asynchronously /// A task that represents the work queued to execute in the pool. public static Task RunOnUpdate(Action action) { return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, MainThreadTaskScheduler); } private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject is Exception exception) { Debug.LogError("Unhandled Exception: " + exception.Message); Debug.LogException(exception); } } private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { if (e.Exception != null) { foreach (var ex in e.Exception.InnerExceptions) { Debug.LogError("Unhandled Task Exception: " + ex.Message); Debug.LogException(ex); } } } /// /// Initializes Flax API. Called before everything else from native code. /// internal static void Init() { #if BUILD_DEBUG Debug.Logger.LogHandler.LogWrite(LogType.Info, "Using FlaxAPI in Debug"); #elif BUILD_DEVELOPMENT Debug.Logger.LogHandler.LogWrite(LogType.Info, "Using FlaxAPI in Development"); #elif BUILD_RELEASE Debug.Logger.LogHandler.LogWrite(LogType.Info, "Using FlaxAPI in Release"); #endif AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; Localization.LocalizationChanged += OnLocalizationChanged; OnLocalizationChanged(); if (!Engine.IsEditor) { CreateGuiStyle(); } } private static void OnLocalizationChanged() { var currentThread = Thread.CurrentThread; currentThread.CurrentUICulture = Localization.CurrentLanguage; currentThread.CurrentCulture = Localization.CurrentCulture; } /// /// Sets the managed window as a main game window. Called after creating game window by the native code. /// /// The window. internal static void SetWindow(Window window) { // Link it as a GUI root control window.GUI.BackgroundColor = Color.Transparent; RootControl.GameRoot = window.GUI; } internal static Type MakeGenericType(Type genericType, Type[] genericArgs) { return genericType.MakeGenericType(genericArgs); } internal static void AddDictionaryItem(IDictionary dictionary, object key, object value) { dictionary.Add(key, value); } internal static object[] GetDictionaryKeys(IDictionary dictionary) { var keys = dictionary.Keys; var result = new object[keys.Count]; keys.CopyTo(result, 0); return result; } private static void CreateGuiStyle() { var style = new Style { Background = Color.FromBgra(0xFF1C1C1C), LightBackground = Color.FromBgra(0xFF2D2D30), Foreground = Color.FromBgra(0xFFFFFFFF), ForegroundGrey = Color.FromBgra(0xFFA9A9B3), ForegroundDisabled = Color.FromBgra(0xFF787883), BackgroundHighlighted = Color.FromBgra(0xFF54545C), BorderHighlighted = Color.FromBgra(0xFF6A6A75), BackgroundSelected = Color.FromBgra(0xFF007ACC), BorderSelected = Color.FromBgra(0xFF1C97EA), BackgroundNormal = Color.FromBgra(0xFF3F3F46), BorderNormal = Color.FromBgra(0xFF54545C), TextBoxBackground = Color.FromBgra(0xFF333337), ProgressNormal = Color.FromBgra(0xFF0ad328), TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), SharedTooltip = new Tooltip(), }; style.DragWindow = style.BackgroundSelected * 0.7f; Style.Current = style; } internal static void Internal_Update() { Update?.Invoke(); lock (UpdateActions) { for (int i = 0; i < UpdateActions.Count; i++) { try { UpdateActions[i](); } catch (Exception ex) { Debug.LogException(ex); } } UpdateActions.Clear(); } MainThreadTaskScheduler.Execute(); } internal static void Internal_LateUpdate() { LateUpdate?.Invoke(); } internal static void Internal_FixedUpdate() { FixedUpdate?.Invoke(); } internal static void Internal_Draw() { Draw?.Invoke(); } internal static void Internal_Exit() { Exit?.Invoke(); MainThreadTaskScheduler.Dispose(); Json.JsonSerializer.Dispose(); } /// /// Returns true if game scripts assembly has been loaded. /// /// True if game scripts assembly is loaded, otherwise false. [MethodImpl(MethodImplOptions.InternalCall)] public static extern bool HasGameModulesLoaded(); /// /// Returns true if given type is from one of the game scripts assemblies. /// /// True if the type is from game assembly, otherwise false. [MethodImpl(MethodImplOptions.InternalCall)] public static extern bool IsTypeFromGameScripts(Type type); /// /// Flushes the removed objects (disposed objects using Object.Destroy). /// [MethodImpl(MethodImplOptions.InternalCall)] public static extern void FlushRemovedObjects(); } /// /// Provides C# scripts profiling methods. /// /// /// Profiler is available in the editor and Debug/Development builds. Release builds don't have profiling tools. /// public static class Profiler { /// /// Begins profiling a piece of code with a custom label. /// /// The name of the event. [MethodImpl(MethodImplOptions.InternalCall)] public static extern void BeginEvent(string name); /// /// Ends profiling an event. /// [MethodImpl(MethodImplOptions.InternalCall)] public static extern void EndEvent(); /// /// Begins GPU profiling a piece of code with a custom label. /// /// The name of the event. [MethodImpl(MethodImplOptions.InternalCall)] public static extern void BeginEventGPU(string name); /// /// Ends GPU profiling an event. /// [MethodImpl(MethodImplOptions.InternalCall)] public static extern void EndEventGPU(); } }