using System; using System.IO; using System.Threading.Tasks; using UnityEngine; using Object = UnityEngine.Object; #if UNITY_EDITOR using System.Reflection; using Needle.Engine.Utils; using UnityEditor; #endif namespace Needle.Engine { [Flags] internal enum TracingScenario { Any = 0, Types = 1 << 0, NetworkRequests = 1 << 1, ComponentGeneration = 1 << 2, ColorSpaces = 1 << 3, FileExport = 1 << 4, EditorSync = 1 << 5, Samples = 1 << 6, /// /// E.g. Tools package or BuildPipeline package logs /// Tools = 1 << 7, AuthenticationState = 1 << 8, IPC = 1 << 9, WebProject = 1 << 10, } internal static class OptionOverrides { public static bool OverrideNodeJSVersion { #if UNITY_EDITOR get => SessionState.GetBool("NEEDLE_ENGINE_OVERRIDE_NODEJS", false); set => SessionState.SetBool("NEEDLE_ENGINE_OVERRIDE_NODEJS", value); #else get => false; #endif } public static string NodeJSVersion { #if UNITY_EDITOR get => SessionState.GetString("NEEDLE_ENGINE_OVERRIDE_VALUE_NODEJS", "v14").Trim(); set => SessionState.SetString("NEEDLE_ENGINE_OVERRIDE_VALUE_NODEJS", value); #else get => ""; #endif } public static bool OverrideLicenseType { #if UNITY_EDITOR get => SessionState.GetBool("NEEDLE_ENGINE_OVERRIDE_LICENSE_TYPE", false); set => SessionState.SetBool("NEEDLE_ENGINE_OVERRIDE_LICENSE_TYPE", value); #else get => false; #endif } public static string LicenseType { #if UNITY_EDITOR get => SessionState.GetString("NEEDLE_ENGINE_OVERRIDE_VALUE_LICENSE_TYPE", "free").Trim(); set { SessionState.SetString("NEEDLE_ENGINE_OVERRIDE_VALUE_LICENSE_TYPE", value); #pragma warning disable CS4014 LicenseCheck.QueryLicense(false); #pragma warning restore CS4014 } #else get => ""; #endif } } internal static class NeedleDebug { internal static bool DeveloperMode { #if UNITY_EDITOR get { if (!UnityThreads.IsMainThread()) return false; return SessionState.GetBool("NEEDLE_ENGINE_DEVELOPER_MODE", false); } set => SessionState.SetBool("NEEDLE_ENGINE_DEVELOPER_MODE", value); #else get => false; set { } #endif } private static string LogFilePath => Application.dataPath + "/../Logs/Needle.log"; private static string LogFilePathPrev => Application.dataPath + "/../Logs/Needle-prev.log"; private static StreamWriter LogFileStream { get; set; } #if UNITY_EDITOR [InitializeOnLoadMethod] private static void Init() { if (!SessionState.GetBool("NEEDLE_ENGINE_LOG_TO_FILE", false)) { SessionState.SetBool("NEEDLE_ENGINE_LOG_TO_FILE", true); if (File.Exists(LogFilePath)) { File.Copy(LogFilePath, LogFilePathPrev, true); File.Delete(LogFilePath); } } } #endif private static void WriteLogToFile(TracingScenario scenario, object obj, Object context = null) { #if UNITY_EDITOR try { switch (scenario) { case TracingScenario.Any: case TracingScenario.Samples: case TracingScenario.Tools: case TracingScenario.AuthenticationState: case TracingScenario.IPC: case TracingScenario.WebProject: LogFileStream ??= new StreamWriter(LogFilePath, true); var dt = DateTime.Now; var str = dt.ToString("yyyy-MM-dd HH:mm:ss") + " - " + scenario.ToString(); if(context) str += " (" + context.name + ")"; str += ": " + obj; LogFileStream.WriteLine(str); LogFileStream.Flush(); break; } } catch(Exception e) { if(DeveloperMode) Debug.LogError("Failed to write log to file: " + e); } #endif } #if HAS_HIDE_IN_CALLSTACKS [HideInCallstack] #endif public static void Log(TracingScenario scenario, object obj, Object context = null) { WriteLogToFile(scenario, obj, context); if (IsEnabled(scenario)) Debug.Log(obj, context); } #if HAS_HIDE_IN_CALLSTACKS [HideInCallstack] #endif public static void Log(TracingScenario scenario, Func obj, Object context = null) { WriteLogToFile(scenario, obj, context); if (IsEnabled(scenario)) Debug.Log(obj.Invoke(), context); } #if HAS_HIDE_IN_CALLSTACKS [HideInCallstack] #endif public static async void LogAsync(TracingScenario scenario, Func> obj, Object context = null) { WriteLogToFile(scenario, obj, context); if (IsEnabled(scenario)) Debug.Log(await obj.Invoke(), context); } #if HAS_HIDE_IN_CALLSTACKS [HideInCallstack] #endif public static void LogWarning(TracingScenario scenario, object obj, Object context = null) { WriteLogToFile(scenario, obj, context); if (IsEnabled(scenario)) Debug.LogWarning(obj, context); } #if HAS_HIDE_IN_CALLSTACKS [HideInCallstack] #endif public static void LogError(TracingScenario scenario, object obj, Object context = null) { WriteLogToFile(scenario, obj, context); if (IsEnabled(scenario)) Debug.LogError(obj, context); } #if HAS_HIDE_IN_CALLSTACKS [HideInCallstack] #endif public static void LogException(TracingScenario scenario, Exception e, Object context = null) { WriteLogToFile(scenario, e, context); if (IsEnabled(scenario)) Debug.LogException(e, context); } public static bool IsEnabled(TracingScenario scenario) { if (scenario == TracingScenario.Any) return true; return currentTracingScenarios.HasFlag(scenario); } // We cache it to not access EditorPrefs every time we need it private static int cachedTracingScenario = -1; private static TracingScenario currentTracingScenarios { get { if (cachedTracingScenario != -1) return (TracingScenario)cachedTracingScenario; #if UNITY_EDITOR if(UnityThreads.IsMainThread()) cachedTracingScenario = EditorPrefs.GetInt("NeedleTracingScenario", 0); else cachedTracingScenario = 0; #else cachedTracingScenario = 0; #endif return (TracingScenario)cachedTracingScenario; } set { if (cachedTracingScenario == (int)value) return; cachedTracingScenario = (int)value; #if UNITY_EDITOR EditorPrefs.SetInt("NeedleTracingScenario", (int)value); #endif } } #if UNITY_EDITOR private class TracingScenarioEditor : EditorWindow { [MenuItem("Needle Engine/Internal/Tracing Scenarios", false, 50)] private static void Open() { var window = GetWindow(); if (window == null) window = CreateInstance(); window.Show(); } private void OnEnable() { titleContent = new GUIContent("Tracing Scenarios"); } private readonly string[] tracingScenarioTypes = Enum.GetNames(typeof(TracingScenario)); private readonly Array tracingScenarioValues = Enum.GetValues(typeof(TracingScenario)); private PropertyInfo[] optionOverrideFields; private void OnGUI() { var newValue = 0; GUILayout.Label("Logging", EditorStyles.boldLabel); for (var i = 0; i < tracingScenarioTypes.Length; i++) { var val = (TracingScenario)tracingScenarioValues.GetValue(i); if ((int)val == 0) continue; var isFlagEnabled = currentTracingScenarios.HasFlag((TracingScenario)tracingScenarioValues.GetValue(i)); var enabled = EditorGUILayout.ToggleLeft(ObjectNames.NicifyVariableName(tracingScenarioValues.GetValue(i).ToString()), isFlagEnabled); newValue |= enabled ? (int)tracingScenarioValues.GetValue(i) : 0; } currentTracingScenarios = (TracingScenario)newValue; EditorGUILayout.Space(); if (DeveloperMode) { GUILayout.Label("Overrides", EditorStyles.boldLabel); optionOverrideFields ??= typeof(OptionOverrides).GetProperties(BindingFlags.Static | BindingFlags.Public); foreach (var field in optionOverrideFields) { if (field.PropertyType == typeof(bool)) { var val = (bool)field.GetValue(null); var enabled = EditorGUILayout.ToggleLeft(ObjectNames.NicifyVariableName(field.Name), val); field.SetValue(null, enabled); } else if (field.PropertyType == typeof(string)) { var val = (string)field.GetValue(null); var enabled = EditorGUILayout.DelayedTextField(ObjectNames.NicifyVariableName(field.Name), val); field.SetValue(null, enabled); } } } } } #endif } }