Game UI ready
This commit is contained in:
304
Packages/com.merry-yellow.code-assist/CHANGELOG.md
Normal file
304
Packages/com.merry-yellow.code-assist/CHANGELOG.md
Normal file
@@ -0,0 +1,304 @@
|
||||
List of new features, bug fixes and improvements
|
||||
|
||||
# Version 1.4.19
|
||||
* AGENTS.md support for agentic AI, copilot-instructions.md support is deprecated
|
||||
* Visual Studio 2026 compatibility
|
||||
* Bugfix for completions and inline visuals for sorting layer related members
|
||||
* Can unspecify max_tokens,temperature and top_p in the gpt request options
|
||||
* Gpt libraries and models are updated
|
||||
|
||||
# Version 1.4.18
|
||||
* New code completions: Material/Shader
|
||||
* New inline visuals: Material/Shader
|
||||
* Bugfix for inline text visuals when document is edited
|
||||
* Bugfix for completions for constructor method arguments
|
||||
* Hotfix for InputManager data fetching with custom tags
|
||||
* Gpt models are updated
|
||||
|
||||
# Version 1.4.17
|
||||
* Hotfix for Unity compile error
|
||||
|
||||
# Version 1.4.16
|
||||
* New code completions: RenderingLayerMask (Unity 6 and newer only)
|
||||
* New inline visuals: RenderingLayerMask (Unity 6 and newer only), and LayerMask
|
||||
* Hotfix for Unity editor freezes
|
||||
* Hotfix for inline visuals not showing properly when changing document in Visual Studio
|
||||
* Category SortingLayer removed, its items are now under category Layer
|
||||
* Minor fix for output window in Visual Studio
|
||||
* Gpt libraries and models are updated
|
||||
|
||||
# Version 1.4.15
|
||||
* Hotfix for code completion for attributes
|
||||
|
||||
# Version 1.4.14
|
||||
* New code completions: Attributes and preprocessor directives (#if, #elif)
|
||||
* New gpt feature: Add mode, gpt can add new files to the project
|
||||
* New gpt feature: For CodeLens, follow-up question and custom prompt
|
||||
* Gpt responses are logged to Visual Studio output window
|
||||
|
||||
# Version 1.4.13
|
||||
* Hotfix for gpt requests of OpenAI-API-Compatible platforms
|
||||
|
||||
# Version 1.4.12
|
||||
* Additional properties options added for gpt requests
|
||||
* Version number mistype fix for Unity asset package
|
||||
|
||||
# Version 1.4.11
|
||||
* Hotfix for gpt options page
|
||||
* Fix for applying transformers for newer versions of Visual Studio
|
||||
* Arm64 architecture support
|
||||
|
||||
# Version 1.4.10
|
||||
* Animations added to context aware instructions
|
||||
* Hotfix for context aware integrations (for GitHub Copilot)
|
||||
|
||||
# Version 1.4.9
|
||||
* Hotfix for gpt options page
|
||||
|
||||
# Version 1.4.8
|
||||
* Version bump for sister Visual Studio extensions
|
||||
|
||||
# Version 1.4.7
|
||||
* New gpt feature: Integration into GitHub Copilot. Can now feed GitHub Copilot with project/scene/game-object details.
|
||||
* Auto updater for itch.io builds
|
||||
|
||||
# Version 1.4.6
|
||||
* Updated binaries with new build parameters
|
||||
|
||||
# Version 1.4.5
|
||||
* Version skipped by mistake ^-^
|
||||
|
||||
# Version 1.4.4
|
||||
* Hotfix for Unity package compile error
|
||||
|
||||
# Version 1.4.3
|
||||
* New gpt provider: OpenAI API Compatible, for web/cloud services that supports OpenAI SDK standards
|
||||
|
||||
# Version 1.4.2
|
||||
* Bugfix for formatting and title of logging
|
||||
* Bugfix for CodeLens2Gpt when closing document
|
||||
|
||||
# Version 1.4.1
|
||||
* New gpt provider: Ollama, can use LLMs from local machine or private network
|
||||
|
||||
# Version 1.4.0
|
||||
* Gpt backend has been reimplemented for latest models and LLM trends
|
||||
* New gpt provider: Anthropic (Claude), can be used as an alternative to OpenAI ChatGPT and Google Gemini
|
||||
* New gpt provider: DeepSeek, can be used as an alternative to OpenAI ChatGPT and Google Gemini
|
||||
* New code completions: Scene management classes can be auto completed
|
||||
* New inline visuals: Scene management classes can display inline information
|
||||
|
||||
# Version 1.3.12
|
||||
* Hotfix for Microsoft Marketplace manifest file
|
||||
|
||||
# Version 1.3.11
|
||||
* Hotfix for Microsoft Marketplace manifest file
|
||||
|
||||
# Version 1.3.10
|
||||
* Hotfix for type resolving
|
||||
* Usability improvements for VSCode exporter
|
||||
* More filters for expected error logs
|
||||
|
||||
# Version 1.3.9
|
||||
* Hotfix for Unity sink
|
||||
|
||||
# Version 1.3.8
|
||||
* Hotfix for Unity sink
|
||||
|
||||
# Version 1.3.7
|
||||
* Bugfix for exporter/updater
|
||||
* Bugfix for logging options
|
||||
|
||||
# Version 1.3.6
|
||||
* VSCode readme updated
|
||||
|
||||
# Version 1.3.5
|
||||
* JSON library switched back to Newtonsoft.JSON
|
||||
* Rule xml files removed from Unity asset
|
||||
* Unnessary analyzer binaries are removed from Roslyn Analyzer
|
||||
|
||||
# Version 1.3.4
|
||||
* Stability improvements for network (mqttnet)
|
||||
* Hotfix for JSON mapping
|
||||
|
||||
# Version 1.3.3
|
||||
* Full version for VSCode is released!
|
||||
* Fix for VSCode adorments when document is modified
|
||||
* Binary files removed from Unity asset
|
||||
* Bugfix for network (mqttnet server)
|
||||
* VSCode extension stability improvements
|
||||
* VSCode roslyn analyzer stability improvements
|
||||
|
||||
# Version 1.3.2
|
||||
* Fixed cross-platform issues for Linux and macOS
|
||||
|
||||
# Version 1.3.1
|
||||
* Updated VSCode manifest and readme
|
||||
|
||||
# Version 1.3.0
|
||||
* Initial release for VSCode
|
||||
|
||||
# Version 1.2.6
|
||||
* Exporting when Unity is in play mode
|
||||
* Some error messages have been made more user friendly
|
||||
|
||||
# Version 1.2.5
|
||||
* Released on itch.io https://meryel.itch.io/unity-code-assist
|
||||
* Url changes
|
||||
|
||||
# Version 1.2.4
|
||||
* Bugfix for exporter with long file paths
|
||||
* Bugfix for retrieving animation and animator data
|
||||
|
||||
# Version 1.2.3
|
||||
* Bugfix for exporter when overwriting files
|
||||
* Bugfix for inline visuals when active game object changes
|
||||
|
||||
# Version 1.2.2
|
||||
* Bugfix for exporter when facing race condition
|
||||
|
||||
# Version 1.2.1
|
||||
* Typo fix for Options page
|
||||
* Bugfix for retrieving animation and animator data
|
||||
|
||||
# Version 1.2.0
|
||||
* New gpt provider: Google Gemini, can be used as an alternative to OpenAI ChatGPT
|
||||
* New feature: CodeLens2Gpt. Can request gpt queries from the CodeLens of methods and classes
|
||||
* New feature: Context aware gpt, gpt prompts are embedded with Unity, scene and object information
|
||||
* New feature: Package, asset has relocated under Packages directory (from Assets direct<63>ry), along with Unity setup menu items
|
||||
* New code completions: Animation and Animator classes and Invoke/Coroutine/Broadcast methods can be auto completed
|
||||
* New inline visuals: Animation and Animator classes can display inline information
|
||||
|
||||
# Version 1.1.12
|
||||
* External binary files have been customized and minimized
|
||||
* Domain reloading time have been reduced
|
||||
* Stability and usability improvements for exporter/updater
|
||||
* Bugfix for Feedback window
|
||||
|
||||
# Version 1.1.11
|
||||
* Gpt completion endpoint fixed for OpenAI API changes
|
||||
* Bugfix for Input Manager monitor
|
||||
* Bugfix for logging to Visual Studio output window
|
||||
* Bugfix for About window
|
||||
|
||||
# Version 1.1.10
|
||||
* Version skipped for compatibility with other assets
|
||||
|
||||
# Version 1.1.9
|
||||
* Gpt support for chat and edit
|
||||
* More options added for Gpt
|
||||
* Overall stability improvements
|
||||
* Exporter shows file locks if update/export is unsuccessful
|
||||
|
||||
# Version 1.1.8
|
||||
* Bugfix for non-Unity solutions
|
||||
|
||||
# Version 1.1.7
|
||||
* Bugfix for Visual Studio freeze
|
||||
|
||||
# Version 1.1.6
|
||||
* Gpt support added for shader files
|
||||
* Stability improvements for Unity.ScriptFinder
|
||||
|
||||
# Version 1.1.5
|
||||
* Stability and usability improvements for exporter/updater
|
||||
* Bugfix for Transformer Linq and Auto Input Manager
|
||||
* Usability for Transformer window, disabling it if not connected to Unity
|
||||
* Enhancement for completions, sorting numerical values correctly https://github.com/merryyellow/Unity-Code-Assist/issues/6
|
||||
|
||||
# Version 1.1.4
|
||||
* Auto Input Manager is now compatible with binary asset files
|
||||
* Stability and usability improvements for Transformer windows
|
||||
|
||||
# Version 1.1.3
|
||||
* Analyzers are working at a separate process https://github.com/merryyellow/Unity-Code-Assist/issues/20
|
||||
* Inline visuals stability and performance improvements https://github.com/merryyellow/Unity-Code-Assist/issues/22 https://github.com/merryyellow/Unity-Code-Assist/issues/24
|
||||
* Exporter/updater stability improvements https://github.com/merryyellow/Unity-Code-Assist/issues/19 https://github.com/merryyellow/Unity-Code-Assist/issues/23
|
||||
* Transformer window stability improvements https://github.com/merryyellow/Unity-Code-Assist/issues/21
|
||||
* Bugfix for Gpt busy icon positioning https://github.com/merryyellow/Unity-Code-Assist/issues/24
|
||||
|
||||
# Version 1.1.2
|
||||
* Bugfix for Yaml file parsing of InputManager
|
||||
|
||||
# Version 1.1.1
|
||||
* Bugfix for error handling of binary file parsing
|
||||
|
||||
# Version 1.1.0
|
||||
* New feature: Generative AI. Use OpenAI ChatGPT within comments to complete your code
|
||||
* New feature: Visual Studio menus. Access Unity Code Assist from "Extensions"->"Unity Code Assist"
|
||||
* New code completions: PlayerPrefs, EditorPrefs and Input classes' methods can be auto completed
|
||||
* New inline visuals: PlayerPrefs, EditorPrefs and Input classes' methods can display inline information
|
||||
* New code transformer: Auto Input Manager. Converts legacy input code into the new Input Manager
|
||||
|
||||
# Version 1.0.0.21
|
||||
* Stability improvements for Unity ScriptFinder class
|
||||
|
||||
# Version 1.0.0.20
|
||||
* Bugfix for crash at startup https://github.com/merryyellow/Unity-Code-Assist/issues/18
|
||||
|
||||
# Version 1.0.0.19
|
||||
* More logging for error tracking
|
||||
|
||||
# Version 1.0.0.18
|
||||
* Overall stability improvements, nullable references enabled for codebase
|
||||
* Stability improvements for communications, when reconnection occurs
|
||||
|
||||
# Version 1.0.0.17
|
||||
* Stability improvements for Unity ScriptFinder class
|
||||
|
||||
# Version 1.0.0.16
|
||||
* Bugfix for Visual Studio events concurrency
|
||||
|
||||
# Version 1.0.0.15
|
||||
* Visual Studio events are reimplemented for both stability and performance
|
||||
* Removal of possible Task deadlocks
|
||||
* Usability improvements for exporter/updater
|
||||
* Bugfix for exporter/updater, where prompts can appear twice
|
||||
* Bugfix for Inline Texts, where const null identifier may raise exceptions https://github.com/merryyellow/Unity-Code-Assist/issues/16
|
||||
* Bugfix for communications, where tags&layers are sent for the first time
|
||||
|
||||
# Version 1.0.0.14
|
||||
* Usability improvements for Visual Studio Status window where project is not a Unity project
|
||||
* Bugfix for communication reinitialization, where projects are closed and opened from Visual Studio https://github.com/merryyellow/Unity-Code-Assist/issues/15
|
||||
* Bugfix for Visual Studio events' initialization https://github.com/merryyellow/Unity-Code-Assist/issues/14
|
||||
|
||||
# Version 1.0.0.13
|
||||
* Bugfix for Unity where target object is neither Component nor MonoBehaviour https://github.com/merryyellow/Unity-Code-Assist/issues/13
|
||||
|
||||
# Version 1.0.0.12
|
||||
* New Feature: Updating Unity asset from Visual Studio
|
||||
* New Feature: Online error reporting of Unity errors
|
||||
* Usability improvement for Visual Studio Feedback window
|
||||
* Bugfix for Inline Texts where there is no class declaration https://github.com/merryyellow/Unity-Code-Assist/issues/5
|
||||
* Bugfix for Code Completion where there is no class declaration https://github.com/merryyellow/Unity-Code-Assist/issues/10
|
||||
* Bugfix for Visual Studio output window, where it may be unavailable
|
||||
|
||||
# Version 1.0.0.11
|
||||
* Bugfix for Visual Studio events' initialization https://github.com/merryyellow/Unity-Code-Assist/issues/3
|
||||
* Exporter now has more logs
|
||||
|
||||
# Version 1.0.0.10
|
||||
* Bugfix for Inline Texts where leading trivia is absent https://github.com/merryyellow/Unity-Code-Assist/issues/1
|
||||
* Bugfix for Visual Studio events' initialization https://github.com/merryyellow/Unity-Code-Assist/issues/2
|
||||
|
||||
# Version 1.0.0.9
|
||||
* Minor adjustments for initialization logging
|
||||
|
||||
# Version 1.0.0.8
|
||||
* Online analytics added
|
||||
* Bugfix for Inline Texts, Visual Studio code preview screen does not raise any exception anymore
|
||||
|
||||
# Version 1.0.0.7
|
||||
* Online error tracker added
|
||||
|
||||
# Version 1.0.0.6
|
||||
* Exporter is always disabled for non Unity projects
|
||||
|
||||
# Version 1.0.0.5
|
||||
* Lite version released at Visual Studio Marketplace
|
||||
* Exporting Unity asset from Visual Studio
|
||||
* Unity package become package independent (Removed Newtonsoft.Json dependency)
|
||||
|
||||
# Version 1.0.0
|
||||
* First release!
|
||||
* Released on Unity Asset Store
|
||||
7
Packages/com.merry-yellow.code-assist/CHANGELOG.md.meta
Normal file
7
Packages/com.merry-yellow.code-assist/CHANGELOG.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c541f26362738d45ad369a0458b7559
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.merry-yellow.code-assist/Editor.meta
Normal file
8
Packages/com.merry-yellow.code-assist/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e33cd3b7ea2de540b2cf6dce580c330
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
75
Packages/com.merry-yellow.code-assist/Editor/AboutWindow.cs
Normal file
75
Packages/com.merry-yellow.code-assist/Editor/AboutWindow.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
public class AboutWindow : EditorWindow
|
||||
{
|
||||
GUIStyle? styleLabel;
|
||||
|
||||
public static void Display()
|
||||
{
|
||||
// Get existing open window or if none, make a new one:
|
||||
var window = GetWindow<AboutWindow>();
|
||||
window.Show();
|
||||
|
||||
Serilog.Log.Debug("Displaying about window");
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "AboutWindow_Display");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
//**--icon
|
||||
//var icon = AssetDatabase.LoadAssetAtPath<Texture>("Assets/Sprites/Gear.png");
|
||||
//titleContent = new GUIContent("Code Assist", icon);
|
||||
titleContent = new GUIContent("Code Assist About");
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
styleLabel ??= new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
wordWrap = true,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
};
|
||||
|
||||
EditorGUILayout.LabelField($"Version number: {Assister.Version}", styleLabel, GUILayout.ExpandWidth(true));
|
||||
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
EditorGUILayout.LabelField($"License type: Lite", styleLabel, GUILayout.ExpandWidth(true));
|
||||
#else // MERYEL_UCA_LITE_VERSION
|
||||
EditorGUILayout.LabelField($"License type: Full", styleLabel, GUILayout.ExpandWidth(true));
|
||||
#endif // MERYEL_UCA_LITE_VERSION
|
||||
|
||||
if (GUILayout.Button("Update"))
|
||||
{
|
||||
Updater.CheckUpdateForced();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("View changelog"))
|
||||
{
|
||||
Application.OpenURL("https://unitycodeassist.netlify.app/changelog");
|
||||
}
|
||||
|
||||
if (GUILayout.Button("View third party notices"))
|
||||
{
|
||||
Application.OpenURL("https://unitycodeassist.netlify.app/thirdpartynotices");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b773a1d4b9561324fa0bc568c62c3770
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
// according to documentation, https://docs.unity3d.com/2023.2/Documentation/Manual/roslyn-analyzers.html
|
||||
// if analyzers are under any asmdef file, they are bound to the asmdef's scope
|
||||
// to declare out analyzers globally, had to write custom AssetPostprocessor and don't use "RoslynAnalyzer" asset label
|
||||
|
||||
public class AnalyzerPostProcessor : AssetPostprocessor
|
||||
{
|
||||
public static string OnGeneratedCSProject(string path, string content)
|
||||
{
|
||||
// do not add roslyn analyzers to Visual Studio projects for performance
|
||||
if (Assister.GetCodeEditor(false, out var isVisualStudio, out _, out _) && isVisualStudio)
|
||||
return content;
|
||||
|
||||
var analyzerGroup = new StringBuilder();
|
||||
analyzerGroup.Append(NewLine);
|
||||
analyzerGroup.Append(" <!--This section is added by Unity Code Assist-->");
|
||||
analyzerGroup.Append(NewLine);
|
||||
analyzerGroup.Append(" <ItemGroup>");
|
||||
|
||||
var prefix = $"{NewLine} <Analyzer Include=\"{CommonTools.GetExternalReferencesPath().Replace('/', '\\')}\\";
|
||||
var suffix = $"\" />";
|
||||
|
||||
foreach (var analyzer in Analyzers)
|
||||
{
|
||||
analyzerGroup.Append(prefix);
|
||||
analyzerGroup.Append(analyzer);
|
||||
analyzerGroup.Append(suffix);
|
||||
}
|
||||
|
||||
analyzerGroup.Append(NewLine);
|
||||
analyzerGroup.Append(" </ItemGroup>");
|
||||
//analyzerGroup.Append(NewLine);
|
||||
|
||||
|
||||
//content = Regex.Replace(content, $"[{NewLine}]*</Project>[{NewLine}]*", $"{analyzerGroup.ToString()}$&");
|
||||
var matches = Regex.Matches(content, $"[{NewLine}]*</Project>");
|
||||
var index = matches.LastOrDefault(m => m.Success)?.Index ?? -1;
|
||||
//var index = content.LastIndexOf("</Project>");
|
||||
|
||||
if (index >= 0)
|
||||
content = content.Insert(index, analyzerGroup.ToString());
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private const string NewLine = "\r\n";
|
||||
|
||||
private readonly static string[] Analyzers = new string[]
|
||||
{
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
"Meryel.UnityCodeAssist.AnalyzersLite.dll",
|
||||
#else
|
||||
"Meryel.UnityCodeAssist.Analyzers.dll",
|
||||
#endif
|
||||
"Meryel.UnityCodeAssist.Common.dll",
|
||||
"Meryel.UnityCodeAssist.Completion.dll",
|
||||
"Meryel.UnityCodeAssist.CompletionInternals.dll",
|
||||
"Meryel.UnityCodeAssist.Logger.dll",
|
||||
"Meryel.UnityCodeAssist.MQTTnet.dll",
|
||||
"Meryel.UnityCodeAssist.MQTTnet.Extensions.ManagedClient.dll",
|
||||
"Meryel.UnityCodeAssist.Newtonsoft.Json.dll",
|
||||
"Meryel.UnityCodeAssist.ProjectData.dll",
|
||||
"Meryel.UnityCodeAssist.RoslynAnalyzerManager.dll",
|
||||
"Meryel.UnityCodeAssist.Synchronizer.dll",
|
||||
"Meryel.UnityCodeAssist.SynchronizerModel.dll",
|
||||
"Meryel.UnityCodeAssist.VSIXLibrary.dll",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17f8bf228c22bbb438ef36b16a18652c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
609
Packages/com.merry-yellow.code-assist/Editor/Assister.cs
Normal file
609
Packages/com.merry-yellow.code-assist/Editor/Assister.cs
Normal file
@@ -0,0 +1,609 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
using CodeEditor = Unity.CodeEditor.CodeEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
public class Assister
|
||||
{
|
||||
public const string Version = "1.4.19"; //do NOT modify this line, except the number value, its being used by VSCode/Typescript for version detection (in exporter.ts.getVersionOfUnitySide())
|
||||
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
public const string Title = "Code Assist Lite";
|
||||
#else
|
||||
public const string Title = "Code Assist";
|
||||
#endif
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Status", false, 1)]
|
||||
static void DisplayStatusWindow()
|
||||
{
|
||||
StatusWindow.Display();
|
||||
}
|
||||
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Synchronize", false, 2)]
|
||||
static void Sync()
|
||||
{
|
||||
EditorCoroutines.EditorCoroutineUtility.StartCoroutine(SyncAux(), MQTTnetInitializer.Publisher);
|
||||
|
||||
//MQTTnetInitializer.Publisher.SendConnect();
|
||||
//Serilog.Log.Information("Code Assist is looking for more IDEs to connect to...");
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "Synchronize_MenuItem");
|
||||
}
|
||||
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Report error", false, 91)]
|
||||
static void DisplayFeedbackWindow()
|
||||
{
|
||||
FeedbackWindow.Display();
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + Title + "/About", false, 92)]
|
||||
static void DisplayAboutWindow()
|
||||
{
|
||||
AboutWindow.Display();
|
||||
}
|
||||
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
[MenuItem("Tools/" + Title + "/Compare versions", false, 31)]
|
||||
static void CompareVersions()
|
||||
{
|
||||
Application.OpenURL("http://unitycodeassist.netlify.app/compare");
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "CompareVersions_MenuItem");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Get full version", false, 32)]
|
||||
static void GetFullVersion()
|
||||
{
|
||||
Application.OpenURL("https://unitycodeassist.netlify.app/purchase?utm_source=unity_getfull");
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "FullVersion_MenuItem");
|
||||
}
|
||||
#endif // MERYEL_UCA_LITE_VERSION
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Setup/Upgrade to full version", false, 65)]
|
||||
public static void Upgrade()
|
||||
{
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "Upgrade_MenuItem");
|
||||
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
Serilog.Log.Information("Purchase <a href=\"https://unitycodeassist.netlify.app/purchase?utm_source=unity_upgrade\">Unity Code Assist</a> from the <a href=\"http://u3d.as/2N2H\">Asset Store</a> or <a href=\"https://meryel.itch.io/unity-code-assist\">itch.io</a> first. Then download it from the package manager or itch.io");
|
||||
return;
|
||||
#else
|
||||
if (GetCodeEditor(true, out var isVisualStudio, out var isVisualStudioCode, out var error))
|
||||
{
|
||||
if (isVisualStudio)
|
||||
{
|
||||
var vsixPath = CommonTools.GetInstallerPath("CodeAssist.Full.VisualStudio.Installer.vsix");
|
||||
if (System.IO.File.Exists(vsixPath))
|
||||
{
|
||||
CallVisualStudioInstaller(vsixPath);
|
||||
return;
|
||||
}
|
||||
|
||||
var zipPath = CommonTools.GetInstallerPath("CodeAssist.Full.VisualStudio.Installer.zip");
|
||||
if (System.IO.File.Exists(zipPath))
|
||||
{
|
||||
var tempVsixPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "CodeAssist.Full.VisualStudio.Installer.vsix");
|
||||
System.IO.File.Copy(zipPath, tempVsixPath, true);
|
||||
|
||||
CallVisualStudioInstaller(tempVsixPath);
|
||||
return;
|
||||
}
|
||||
|
||||
Serilog.Log.Information("Installer for Visual Studio couldn't be found at {ZipPath}. Please try re-importing the asset from the package manager", zipPath);
|
||||
return;
|
||||
}
|
||||
else if (isVisualStudioCode)
|
||||
{
|
||||
var vsixPath = CommonTools.GetInstallerPath("CodeAssist.Full.VSCode.Installer.vsix");
|
||||
if (System.IO.File.Exists(vsixPath))
|
||||
{
|
||||
CallVSCodeInstaller(vsixPath);
|
||||
return;
|
||||
}
|
||||
|
||||
var zipPath = CommonTools.GetInstallerPath("CodeAssist.Full.VSCode.Installer.zip");
|
||||
if (System.IO.File.Exists(zipPath))
|
||||
{
|
||||
var tempVsixPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "CodeAssist.Full.VSCode.Installer.vsix");
|
||||
System.IO.File.Copy(zipPath, tempVsixPath, true);
|
||||
|
||||
CallVSCodeInstaller(tempVsixPath);
|
||||
return;
|
||||
}
|
||||
|
||||
Serilog.Log.Information("Installer for VS Code couldn't be found at {ZipPath}. Please try re-importing the asset from the package manager", zipPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Information(error!);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
[MenuItem("Tools/" + Title + "/Setup/Update", false, 61)]
|
||||
static void Update()
|
||||
{
|
||||
//UnityEditor.PackageManager.Client.
|
||||
}
|
||||
*/
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Setup/Re-import package", false, 62)]
|
||||
static void RepairFiles()
|
||||
{
|
||||
if (MQTTnetInitializer.Publisher?.Clients.Any() != true)
|
||||
Serilog.Log.Information("No connected IDE found. Please start up Visual Studio or VS Code first");
|
||||
|
||||
//var cleanupPath = CommonTools.GetToolPath("CleanupObsoleteFiles.bat");
|
||||
//Execute(cleanupPath);
|
||||
Cleanup.DoCleanup();
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendRequestUpdate("Unity", string.Empty, true);
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "Reimport_MenuItem");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Setup/Import files for .NET Standard 2.0", false, 63)]
|
||||
static void ImportSystemBinariesForDotNetStandard20()
|
||||
{
|
||||
var solutionDirectory = CommonTools.GetProjectPath();
|
||||
var cSharpVersion = Cleanup.GetCSharpVersionFromUnityProjectVersionFile(solutionDirectory);
|
||||
|
||||
if (cSharpVersion >= 9)
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("Import files for .NET Standard 2.0",
|
||||
"This is not required for versions of Unity 2021.2 and newer. Do you still want to continue?",
|
||||
"Okay", "Cancel"))
|
||||
{
|
||||
Serilog.Log.Debug("ImportNetStandard20_MenuItem cancelled via confirm dialog");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (MQTTnetInitializer.Publisher?.Clients.Any() != true)
|
||||
Serilog.Log.Information("No connected IDE found. Please start up Visual Studio or VS Code first");
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendRequestUpdate("SystemBinariesForDotNetStandard20", string.Empty, true);
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "ImportNetStandard20_MenuItem");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + Title + "/Setup/Regenerate project files", false, 64)]
|
||||
public static void RegenerateProjectFiles() => RegenerateProjectFilesAux(true);
|
||||
|
||||
public static void RegenerateProjectFilesAux(bool showError)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (GetCodeEditor(true, out _, out _, out var error))
|
||||
{
|
||||
CodeEditor.Editor.CurrentCodeEditor.SyncAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showError && error != null)
|
||||
Serilog.Log.Information(error);
|
||||
|
||||
// other similar approaches
|
||||
// https://www.reddit.com/r/Unity3D/comments/s1joc6/help_with_generating_csproj_and_sln_for_github/
|
||||
// https://discussions.unity.com/t/manually-generate-sln-and-csproj-files/648686/6
|
||||
// https://discussions.unity.com/t/how-can-i-generate-csproj-files-during-continuous-integration-builds/842493/3
|
||||
// https://github.com/Unity-Technologies/UnityCsReference/blob/f45f297f342239326ea865a57a1bb8ddf93e38c6/Editor/Mono/CodeEditor/SyncVS.cs#L22
|
||||
var t = ScriptFinder.GetType123("Microsoft.Unity.VisualStudio.Editor.Cli");
|
||||
var m = t!.GetMethod("GenerateSolution", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
|
||||
m.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Couldn't invoke GenerateSolution");
|
||||
Serilog.Log.Information("Please 'Regenerate project files' manually. 'Edit'->'Preferences'->'External Tools'->'Regenerate project files'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static IEnumerator CallShell(string command, string ide)
|
||||
{
|
||||
Serilog.Log.Debug("calling shell with command: {Command}", command);
|
||||
var task = Shell.UnityEditorShell.Execute(command);
|
||||
task.OnLog += (logType, log) =>
|
||||
{
|
||||
Serilog.Log.Debug("shell log: {Log}", log);
|
||||
};
|
||||
task.OnExit += (code) =>
|
||||
{
|
||||
Serilog.Log.Debug("shell exit: {Code}", code);
|
||||
if (code == 0)
|
||||
Serilog.Log.Information($"{ide} extension installed successfully. Please restart {ide}");
|
||||
else
|
||||
Serilog.Log.Information($"{ide} extension installation failed. Please try manual installition at {CommonTools.GetInstallerPath(string.Empty)}");
|
||||
};
|
||||
yield return new Shell.ShellCommandYieldable(task);
|
||||
}
|
||||
|
||||
static void CallVisualStudioInstaller(string vsixPath)
|
||||
{
|
||||
EditorCoroutines.EditorCoroutineUtility.StartCoroutine(CallShell(
|
||||
$"@for /f \"usebackq delims=\" %i in (`\"%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe\" -latest -prerelease -products * -property enginePath`) do @set enginePath=%i & if exist \"%i\\VSIXInstaller.exe\" call \"%i\\VSIXInstaller.exe\" /u:VSIXLite2.6815b720-6186-48a1-a405-1387e54b41c6 & call \"%i\\VSIXInstaller.exe\" \"{vsixPath}\"", "Visual Studio"), MQTTnetInitializer.Publisher);
|
||||
}
|
||||
|
||||
static void CallVSCodeInstaller(string vsixPath)
|
||||
{
|
||||
string command;
|
||||
#if UNITY_EDITOR_WIN
|
||||
command = $"code --uninstall-extension MerryYellow.uca-lite-vscode & code --install-extension \"{vsixPath}\"";
|
||||
#elif UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
command = $"code --uninstall-extension MerryYellow.uca-lite-vscode ; code --install-extension \"{vsixPath}\"";
|
||||
#else
|
||||
Serilog.Log.Error("invalid platform at {Location}", nameof(CallVSCodeInstaller));
|
||||
command = string.Empty;
|
||||
#endif
|
||||
|
||||
EditorCoroutines.EditorCoroutineUtility.StartCoroutine(CallShell(command, "VS Code"), MQTTnetInitializer.Publisher);
|
||||
}
|
||||
|
||||
internal static string Execute(string vsixPath, bool isVisualStudio = false, bool isVSCode = false)
|
||||
{
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
|
||||
//startInfo.FileName = GetExePath();
|
||||
FileName = vsixPath,
|
||||
//startInfo.Arguments = args;
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
//startInfo.WorkingDirectory = workingDirectoryPath;
|
||||
};
|
||||
var process = new System.Diagnostics.Process
|
||||
{
|
||||
StartInfo = startInfo
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Error at running bat file {File}", vsixPath);
|
||||
}
|
||||
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
static IEnumerator SyncAux()
|
||||
{
|
||||
var clientCount = MQTTnetInitializer.Publisher?.Clients.Count() ?? 0;
|
||||
MQTTnetInitializer.Publisher?.SendConnect();
|
||||
Serilog.Log.Information("Code Assist is looking for more IDEs to connect to...");
|
||||
|
||||
//yield return new WaitForSeconds(3);
|
||||
yield return new EditorCoroutines.EditorWaitForSeconds(3);
|
||||
|
||||
var newClientCount = MQTTnetInitializer.Publisher?.Clients.Count() ?? 0;
|
||||
|
||||
var dif = newClientCount - clientCount;
|
||||
|
||||
if (dif <= 0)
|
||||
Serilog.Log.Information("Code Assist couldn't find any new IDE to connect to.");
|
||||
else
|
||||
Serilog.Log.Information("Code Assist is connected to {Dif} new IDE(s).", dif);
|
||||
}
|
||||
|
||||
#if MERYEL_DEBUG
|
||||
|
||||
[MenuItem("Code Assist/Binary2Text")]
|
||||
static void Binary2Text()
|
||||
{
|
||||
var filePath = CommonTools.GetInputManagerFilePath();
|
||||
var hash = Input.UnityInputManager.GetMD5Hash(filePath);
|
||||
var convertedPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"UCA_IM_{hash}.txt");
|
||||
|
||||
var b = new Input.Binary2TextExec();
|
||||
b.Exec(filePath, convertedPath, detailed: false, largeBinaryHashOnly: false, hexFloat: false);
|
||||
}
|
||||
|
||||
[MenuItem("Code Assist/Bump InputManager")]
|
||||
static void BumpInputManager()
|
||||
{
|
||||
Input.InputManagerMonitor.Instance.Bump();
|
||||
}
|
||||
|
||||
|
||||
[MenuItem("Code Assist/Layer Check")]
|
||||
static void UpdateLayers()
|
||||
{
|
||||
var names = UnityEditorInternal.InternalEditorUtility.layers;
|
||||
var indices = names.Select(l => LayerMask.NameToLayer(l).ToString()).ToArray();
|
||||
MQTTnetInitializer.Publisher?.SendLayers(indices, names);
|
||||
|
||||
var sls = SortingLayer.layers;
|
||||
var sortingNames = sls.Select(sl => sl.name).ToArray();
|
||||
var sortingIds = sls.Select(sl => sl.id.ToString()).ToArray();
|
||||
var sortingValues = sls.Select(sl => sl.value.ToString()).ToArray();
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendSortingLayers(sortingNames, sortingIds, sortingValues);
|
||||
|
||||
/*
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
var name = LayerMask.LayerToName(i);
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
Debug.Log(i + ":" + name);
|
||||
}
|
||||
}
|
||||
|
||||
if (ScriptFinder.FindGameObjectOfType("Deneme", out var go))
|
||||
MQTTnetInitializer.Publisher.SendGameObject(go);
|
||||
*/
|
||||
}
|
||||
|
||||
[MenuItem("Code Assist/Tag Check")]
|
||||
static void UpdateTags()
|
||||
{
|
||||
Serilog.Log.Debug("Listing tags {Count}", UnityEditorInternal.InternalEditorUtility.tags.Length);
|
||||
|
||||
foreach (var tag in UnityEditorInternal.InternalEditorUtility.tags)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(tag))
|
||||
{
|
||||
Serilog.Log.Debug("{Tag}", tag);
|
||||
}
|
||||
}
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendTags(UnityEditorInternal.InternalEditorUtility.tags);
|
||||
|
||||
}
|
||||
|
||||
[MenuItem("Code Assist/GO Check")]
|
||||
|
||||
static void TestGO()
|
||||
{
|
||||
|
||||
var go = GameObject.Find("Deneme");
|
||||
//var go = MonoBehaviour.FindObjectOfType<Deneme>().gameObject;
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendGameObject(go);
|
||||
}
|
||||
|
||||
[MenuItem("Code Assist/Undo Records Test")]
|
||||
static void UndoTest()
|
||||
{
|
||||
var undos = new List<string>();
|
||||
var redos = new List<string>();
|
||||
|
||||
var type = typeof(Undo);
|
||||
System.Reflection.MethodInfo dynMethod = type.GetMethod("GetRecords",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||
dynMethod.Invoke(null, new object[] { undos, redos });
|
||||
|
||||
Serilog.Log.Debug("undos:{UndoCount},redos:{RedoCount}", undos.Count, redos.Count);
|
||||
|
||||
var last = undos.LastOrDefault();
|
||||
if (last != null)
|
||||
{
|
||||
Serilog.Log.Debug("last:{Last}", last);
|
||||
Serilog.Log.Debug("group:{UndoCurrentGroup},{UndoCurrentGroupName}",
|
||||
Undo.GetCurrentGroup(), Undo.GetCurrentGroupName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MenuItem("Code Assist/Undo List Test")]
|
||||
static void Undo2Test()
|
||||
{
|
||||
|
||||
//List<string> undoList, out int undoCursor
|
||||
var undoList = new List<string>();
|
||||
int undoCursor = int.MaxValue;
|
||||
var type = typeof(Undo);
|
||||
System.Reflection.MethodInfo dynMethod = type.GetMethod("GetUndoList",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||
|
||||
dynMethod = type.GetMethod("GetUndoList",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,
|
||||
null,
|
||||
new System.Type[] { typeof(List<string>), typeof(int).MakeByRefType() },
|
||||
null);
|
||||
|
||||
|
||||
dynMethod.Invoke(null, new object[] { undoList, undoCursor });
|
||||
|
||||
Serilog.Log.Debug("undo count: {Count}", undoList.Count);
|
||||
|
||||
}
|
||||
|
||||
[MenuItem("Code Assist/Reload Domain")]
|
||||
static void ReloadDomain()
|
||||
{
|
||||
EditorUtility.RequestScriptReload();
|
||||
}
|
||||
|
||||
/*
|
||||
[MenuItem("Code Assist/TEST")]
|
||||
static void TEST()
|
||||
{
|
||||
//if (ScriptFinder.FindGameObjectOfType("Deneme_OtherScene", out var go))
|
||||
if (ScriptFinder.FindInstanceOfType("Deneme_SO", out var go, out var so))
|
||||
{
|
||||
MQTTnetInitializer.Publisher.SendScriptableObject(so);
|
||||
}
|
||||
|
||||
ScriptFinder.DENEMEEEE();
|
||||
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
#endif // MERYEL_DEBUG
|
||||
|
||||
|
||||
public static void SendTagsAndLayers()
|
||||
{
|
||||
Serilog.Log.Debug(nameof(SendTagsAndLayers));
|
||||
|
||||
var tags = UnityEditorInternal.InternalEditorUtility.tags;
|
||||
MQTTnetInitializer.Publisher?.SendTags(tags);
|
||||
|
||||
var layerNames = UnityEditorInternal.InternalEditorUtility.layers;
|
||||
var layerIndices = layerNames.Select(l => LayerMask.NameToLayer(l).ToString()).ToArray();
|
||||
MQTTnetInitializer.Publisher?.SendLayers(layerNames, layerIndices);
|
||||
|
||||
var sls = SortingLayer.layers;
|
||||
var sortingNames = sls.Select(sl => sl.name).ToArray();
|
||||
var sortingIds = sls.Select(sl => sl.id.ToString()).ToArray();
|
||||
var sortingValues = sls.Select(sl => sl.value.ToString()).ToArray();
|
||||
MQTTnetInitializer.Publisher?.SendSortingLayers(sortingNames, sortingIds, sortingValues);
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
// Version 6+ only, 2022.3 doesn't have class RenderingLayerMask, even though some renderingLayerMask fields/properties are declared
|
||||
|
||||
var renderingLayerCount = RenderingLayerMask.GetRenderingLayerCount();
|
||||
var renderingLayerIndices = new string[renderingLayerCount];
|
||||
var renderingLayerNames = new string[renderingLayerCount];
|
||||
for (var i = 0; i < renderingLayerCount; i++)
|
||||
{
|
||||
renderingLayerIndices[i] = i.ToString();
|
||||
renderingLayerNames[i] = RenderingLayerMask.RenderingLayerToName(i);
|
||||
}
|
||||
MQTTnetInitializer.Publisher?.SendRenderingLayers(renderingLayerNames, renderingLayerIndices);
|
||||
|
||||
#endif // UNITY_6000_0_OR_NEWER
|
||||
}
|
||||
|
||||
public static bool GetCodeEditor(bool checkVersion, out bool isVisualStudio, out bool isVisualStudioCode, out string? error)
|
||||
{
|
||||
isVisualStudio = false;
|
||||
isVisualStudioCode = false;
|
||||
|
||||
if (CodeEditor.Editor.CurrentCodeEditor.TryGetInstallationForPath(CodeEditor.CurrentEditorInstallation, out var installation))
|
||||
{
|
||||
if (installation.Name.StartsWith("Visual Studio Code"))
|
||||
isVisualStudioCode = true;
|
||||
else if (installation.Name.StartsWith("Visual Studio"))
|
||||
isVisualStudio = true;
|
||||
|
||||
if (!isVisualStudioCode && !isVisualStudio)
|
||||
{
|
||||
error = $"Unsupported code editor: {installation.Name}. Unity Code Assist only supports Visual Studio and Visual Studio Code";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (installation.Name.Contains("(internal)"))
|
||||
{
|
||||
error = "Code editor set but not working properly. Please try updating 'Visual Studio Editor' package";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkVersion)
|
||||
{
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
var versionRegex = new System.Text.RegularExpressions.Regex(".*\\[([\\d\\.]+)\\]");
|
||||
var versionStr = versionRegex.Match(installation.Name).Groups.ElementAtOrDefault(1)?.Value;
|
||||
|
||||
if (isVisualStudioCode && !string.IsNullOrEmpty(versionStr) && (VersionCompare(versionStr!, "1.76") < 0))
|
||||
{
|
||||
error = $"Version {versionStr} of Visual Studio Code is not supported by Unity Code Assist. Please update Visual Studio Code";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isVisualStudio && !string.IsNullOrEmpty(versionStr) && (VersionCompare(versionStr!, "17") < 0))
|
||||
{
|
||||
error = $"Version {versionStr} of Visual Studio is not supported by Unity Code Assist. Please update Visual Studio";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
error = "No code editor found. Please set it through 'Edit'->'Preferences'->'External Tools'->'External Script Editor'";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//https://www.geeksforgeeks.org/compare-two-version-numbers/amp/
|
||||
public static int VersionCompare(string v1, string v2)
|
||||
{
|
||||
// vnum stores each numeric
|
||||
|
||||
// part of version
|
||||
|
||||
int vnum1 = 0, vnum2 = 0;
|
||||
|
||||
// loop until both string are
|
||||
// processed
|
||||
|
||||
for (int i = 0, j = 0; (i < v1.Length || j < v2.Length);)
|
||||
|
||||
{
|
||||
// storing numeric part of
|
||||
// version 1 in vnum1
|
||||
while (i < v1.Length && v1[i] != '.')
|
||||
{
|
||||
|
||||
vnum1 = vnum1 * 10 + (v1[i] - '0');
|
||||
|
||||
i++;
|
||||
}
|
||||
// storing numeric part of
|
||||
|
||||
// version 2 in vnum2
|
||||
|
||||
while (j < v2.Length && v2[j] != '.')
|
||||
{
|
||||
vnum2 = vnum2 * 10 + (v2[j] - '0');
|
||||
j++;
|
||||
}
|
||||
if (vnum1 > vnum2)
|
||||
return 1;
|
||||
|
||||
if (vnum2 > vnum1)
|
||||
return -1;
|
||||
|
||||
// if equal, reset variables and
|
||||
|
||||
// go for next numeric part
|
||||
vnum1 = vnum2 = 0;
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 394bf783968f6dd4ab2ca0e1e7258147
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
390
Packages/com.merry-yellow.code-assist/Editor/Cleanup.cs
Normal file
390
Packages/com.merry-yellow.code-assist/Editor/Cleanup.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
|
||||
// copied from Exporter.cs in VSIX
|
||||
public static class Cleanup
|
||||
{
|
||||
public static bool DoCleanup()
|
||||
{
|
||||
var assetsPath = UnityEngine.Application.dataPath;
|
||||
|
||||
var _solutionDirectory = CommonTools.GetProjectPath();
|
||||
var destination = _solutionDirectory;
|
||||
var oldDestination = assetsPath;
|
||||
|
||||
|
||||
var succeed = true;
|
||||
try
|
||||
{
|
||||
Cleanup1(oldDestination);
|
||||
Cleanup2(oldDestination);
|
||||
Cleanup3(oldDestination, _solutionDirectory);
|
||||
Cleanup4(oldDestination);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
succeed = false;
|
||||
Serilog.Log.Error(ex, "DoCleanup failed at {Destination}", destination);
|
||||
}
|
||||
|
||||
return succeed;
|
||||
}
|
||||
|
||||
|
||||
private static void DeleteFileAndItsMeta(string filePath)
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
Serilog.Log.Debug("Deleting file {File}", filePath);
|
||||
//File.Delete(filePath);
|
||||
DeleteFileAux(filePath);
|
||||
Serilog.Log.Debug("Deleted file {File} {Exists}", filePath, File.Exists(filePath));
|
||||
}
|
||||
var metaFilePath = filePath + ".meta";
|
||||
if (File.Exists(metaFilePath))
|
||||
{
|
||||
Serilog.Log.Debug("Deleting meta file {File}", metaFilePath);
|
||||
//File.Delete(metaFilePath);
|
||||
DeleteFileAux(metaFilePath);
|
||||
Serilog.Log.Debug("Deleted file {File} {Exists}", metaFilePath, File.Exists(metaFilePath));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDirectoryExistsAndEmpty(string path)
|
||||
{
|
||||
return Directory.Exists(path) && !Directory.EnumerateFileSystemEntries(path).Any();
|
||||
}
|
||||
|
||||
private static void DeleteDirectoryAndItsMeta(string directoryPath)
|
||||
{
|
||||
if (IsDirectoryExistsAndEmpty(directoryPath))
|
||||
{
|
||||
Serilog.Log.Debug("Deleting directory {Dir}", directoryPath);
|
||||
Directory.Delete(directoryPath);
|
||||
Serilog.Log.Debug("Deleted directory {Dir} {Exists}", directoryPath, IsDirectoryExistsAndEmpty(directoryPath));
|
||||
|
||||
var metaFilePath = directoryPath + ".meta";
|
||||
if (File.Exists(metaFilePath))
|
||||
{
|
||||
Serilog.Log.Debug("Deleting directory meta file {File}", metaFilePath);
|
||||
//File.Delete(metaFilePath);
|
||||
DeleteFileAux(metaFilePath);
|
||||
Serilog.Log.Debug("Deleted directory meta file {File} {Exists}", metaFilePath, File.Exists(metaFilePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteFileAux(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
var fileDirectoryPath = Path.GetDirectoryName(filePath);
|
||||
SetEveryoneAccessToDirectory(fileDirectoryPath, out _);
|
||||
TakeOwnership(filePath);
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set Everyone Full Control permissions for selected directory
|
||||
/// </summary>
|
||||
/// <param name="dirName"></param>
|
||||
/// <returns></returns>
|
||||
static bool SetEveryoneAccessToDirectory(string dirName, out string _lastError)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure directory exists
|
||||
if (Directory.Exists(dirName) == false)
|
||||
throw new Exception(string.Format("Directory {0} does not exist, so permissions cannot be set.", dirName));
|
||||
|
||||
// Get directory access info
|
||||
DirectoryInfo dinfo = new DirectoryInfo(dirName);
|
||||
DirectorySecurity dSecurity = dinfo.GetAccessControl();
|
||||
|
||||
// Add the FileSystemAccessRule to the security settings.
|
||||
dSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.NoPropagateInherit, AccessControlType.Allow));
|
||||
|
||||
// Set the access control
|
||||
dinfo.SetAccessControl(dSecurity);
|
||||
|
||||
_lastError = String.Format("Everyone FullControl Permissions were set for directory {0}", dirName);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_lastError = ex.Message;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="solutionDirectory"></param>
|
||||
/// <param name="cs_7_3_orLower"></param>
|
||||
/// <param name="cs_8_0"></param>
|
||||
/// <param name="cs_9_0_orHigher"></param>
|
||||
/// <returns>
|
||||
/// 7 if C# 7.3 or lower,
|
||||
/// 8 if C# 8.0 (.netstandard2.0),
|
||||
/// 9 if C# 9.0 (.netstandard2.1),
|
||||
/// -1 if error
|
||||
/// </returns>
|
||||
public static int GetCSharpVersionFromUnityProjectVersionFile(string? solutionDirectory)
|
||||
{
|
||||
if (string.IsNullOrEmpty(solutionDirectory))
|
||||
return -1;
|
||||
|
||||
var projectVersionFilePath = System.IO.Path.Combine(solutionDirectory, "ProjectSettings/ProjectVersion.txt");
|
||||
if (!System.IO.File.Exists(projectVersionFilePath))
|
||||
return -1;
|
||||
|
||||
|
||||
string? version = null;
|
||||
string[]? readText = null;
|
||||
try
|
||||
{
|
||||
readText = System.IO.File.ReadAllLines(projectVersionFilePath);
|
||||
// format is m_EditorVersion: 2018.2.0b7
|
||||
string[] versionText = readText[0].Split(' ');
|
||||
version = versionText[1];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Project version file parsing error {FirstLine}", readText?.FirstOrDefault());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (version == null)
|
||||
{
|
||||
Serilog.Log.Error("Parsed project version is null");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// see my personal notes for Unity version X C# version table
|
||||
// which is at OneNote->ShinSekai->CEPostRelease->UnityCompilerC#VersionTable
|
||||
|
||||
if (version.StartsWith("5.") || version.StartsWith("2017.") ||
|
||||
version.StartsWith("2018.") || version.StartsWith("2019.") || version.StartsWith("2020.1."))
|
||||
return 7;
|
||||
|
||||
if (version.StartsWith("2020.") || version.StartsWith("2021.1."))
|
||||
return 8;
|
||||
|
||||
if (version.StartsWith("2021.") || version.StartsWith("2022.") ||
|
||||
version.StartsWith("2023.") || version.StartsWith("6000."))
|
||||
return 9;
|
||||
|
||||
Serilog.Log.Error("Parsed project version is unknown {Version}", version);
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void TakeOwnership(string filename)
|
||||
{
|
||||
// Remove read-only attribute
|
||||
File.SetAttributes(filename, File.GetAttributes(filename) & ~FileAttributes.ReadOnly);
|
||||
|
||||
FileSecurity security = new FileSecurity();
|
||||
|
||||
SecurityIdentifier sid = WindowsIdentity.GetCurrent().User;
|
||||
security.SetOwner(sid);
|
||||
security.SetAccessRule(new FileSystemAccessRule(sid, FileSystemRights.FullControl, AccessControlType.Allow));
|
||||
|
||||
File.SetAccessControl(filename, security);
|
||||
}
|
||||
|
||||
private static void Cleanup1(string destination)
|
||||
{
|
||||
// prior to version UCA.v.1.1.9, syncronizerModel and yamlDotNet dll files were located at ProjectPath/Assets/Plugins/CodeAssist/Editor/ExternalReferences/Release/netstandard2.0
|
||||
// with version UCA.v.1.1.9 and newer versions, they are located at ProjectPath/Assets/Plugins/CodeAssist/Editor/ExternalReferences
|
||||
// delete ProjectPath/Assets/Plugins/CodeAssist/Editor/ExternalReferences/Release
|
||||
var oldBinariesDirectory = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences/Release/netstandard2.0");
|
||||
if (Directory.Exists(oldBinariesDirectory))
|
||||
{
|
||||
// dont just delete the directory for security reasons, instead delete binary files one by one
|
||||
|
||||
Serilog.Log.Debug("Old binaries directory exists at {Location}", oldBinariesDirectory);
|
||||
|
||||
var files = new string[]
|
||||
{
|
||||
"UnityCodeAssistSynchronizerModel.deps.json",
|
||||
"UnityCodeAssistSynchronizerModel.dll",
|
||||
"UnityCodeAssistSynchronizerModel.pdb",
|
||||
"UnityCodeAssistYamlDotNet.deps.json",
|
||||
"UnityCodeAssistYamlDotNet.dll",
|
||||
"UnityCodeAssistYamlDotNet.pdb",
|
||||
"UnityCodeAssistYamlDotNet.xml",
|
||||
};
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var filePath = Path.Combine(oldBinariesDirectory, file);
|
||||
DeleteFileAndItsMeta(filePath);
|
||||
}
|
||||
|
||||
DeleteDirectoryAndItsMeta(oldBinariesDirectory);
|
||||
}
|
||||
|
||||
var oldBinariesDirectory2 = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences/Release");
|
||||
DeleteDirectoryAndItsMeta(oldBinariesDirectory2);
|
||||
|
||||
|
||||
// also delete old vsix, it's now renamed as Meryel.UnityCodeAssist.VSIX.vsix
|
||||
var oldVsixFilePath = Path.Combine(destination, "Plugins/CodeAssist/UnityCodeAssistVSIX.vsix");
|
||||
DeleteFileAndItsMeta(oldVsixFilePath);
|
||||
}
|
||||
|
||||
private static void Cleanup2(string destination)
|
||||
{
|
||||
// with version 1.1.12, dll files has been customized (renamed and changed their namespace) (so that they dont conflict with user's other dll files, if he tries to use them)
|
||||
// delete AsyncIO.dll, and use Meryel.UnityCodeAssist.AsyncIO.dll instead
|
||||
var files = new string[]
|
||||
{
|
||||
"AsyncIO.dll",
|
||||
"NaCl.dll",
|
||||
"NetMQ.dll",
|
||||
"Serilog.dll",
|
||||
"Serilog.Sinks.PersistentFile.dll",
|
||||
};
|
||||
|
||||
var binariesDirectory = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences");
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var filePath = Path.Combine(binariesDirectory, file);
|
||||
DeleteFileAndItsMeta(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Cleanup3(string destination, string solutionDirectory)
|
||||
{
|
||||
// as it turns out, .netstandard2.1 does not need system binaries (for C#9.0, unity versions 2021.2 and newer)
|
||||
|
||||
//var cSharpVersion = CommonVS.VSCommonTools.GetCSharpVersionFromUnityProjectVersionFile(solutionDirectory);
|
||||
var cSharpVersion = GetCSharpVersionFromUnityProjectVersionFile(solutionDirectory);
|
||||
if (cSharpVersion < 9)
|
||||
return;
|
||||
|
||||
var systemBinaryFiles = new string[]
|
||||
{
|
||||
"System.Buffers.dll",
|
||||
"System.Memory.dll",
|
||||
"System.Runtime.CompilerServices.Unsafe.dll",
|
||||
"System.Threading.Tasks.Extensions.dll",
|
||||
};
|
||||
|
||||
var binariesDirectory = Path.Combine(destination, "Plugins/CodeAssist/Editor/ExternalReferences");
|
||||
|
||||
foreach (var file in systemBinaryFiles)
|
||||
{
|
||||
var filePath = Path.Combine(binariesDirectory, file);
|
||||
DeleteFileAndItsMeta(filePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void Cleanup4(string destination)
|
||||
{
|
||||
// with version 1.2, asset directory moved from Assets/Plugins/CodeAssist to Packages/com.merry-yellow.code-assist
|
||||
// so remove all files from the old directory
|
||||
|
||||
var directory = Path.Combine(destination, "Plugins/CodeAssist/Editor");
|
||||
|
||||
var content = new string[]
|
||||
{
|
||||
@"TinyJson/JsonWriter.cs",
|
||||
@"TinyJson/JsonParser.cs",
|
||||
@"Preferences/RegistryMonitor.cs",
|
||||
@"Preferences/PreferenceStorageAccessor.cs",
|
||||
@"Preferences/PreferenceMonitor.cs",
|
||||
@"Preferences/PreferenceEntryHolder.cs",
|
||||
@"Logger/UnitySink.cs",
|
||||
@"Logger/MemorySink.cs",
|
||||
@"Logger/ELogger.cs",
|
||||
@"Logger/DomainHashEnricher.cs",
|
||||
@"Logger/CommonTools.cs",
|
||||
@"Logger/Attributes.cs",
|
||||
@"Input/UnityInputManager.cs",
|
||||
@"Input/Text2Yaml.cs",
|
||||
@"Input/InputManagerMonitor.cs",
|
||||
@"Input/Binary2TextExec.cs",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.YamlDotNet.xml",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.YamlDotNet.pdb",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.YamlDotNet.dll",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.YamlDotNet.deps.json",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.SynchronizerModel.pdb",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.SynchronizerModel.dll",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.SynchronizerModel.deps.json",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.Serilog.xml",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.Serilog.Sinks.PersistentFile.pdb",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.Serilog.Sinks.PersistentFile.dll",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.Serilog.Sinks.PersistentFile.deps.json",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.Serilog.pdb",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.Serilog.dll",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.NetMQ.xml",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.NetMQ.pdb",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.NetMQ.dll",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.NetMQ.deps.json",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.NaCl.xml",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.NaCl.pdb",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.NaCl.dll",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.AsyncIO.pdb",
|
||||
@"ExternalReferences/Meryel.UnityCodeAssist.AsyncIO.dll",
|
||||
@"EditorCoroutines/EditorWindowCoroutineExtension.cs",
|
||||
@"EditorCoroutines/EditorWaitForSeconds.cs",
|
||||
@"EditorCoroutines/EditorCoroutineUtility.cs",
|
||||
@"EditorCoroutines/EditorCoroutine.cs",
|
||||
@"UnityClassExtensions.cs",
|
||||
@"StatusWindow.cs",
|
||||
@"ScriptFinder.cs",
|
||||
@"NetMQPublisher.cs",
|
||||
@"NetMQInitializer.cs",
|
||||
@"Monitor.cs",
|
||||
@"MerryYellow.CodeAssist.Editor.asmdef",
|
||||
@"MainThreadDispatcher.cs",
|
||||
@"LazyInitializer.cs",
|
||||
@"FeedbackWindow.cs",
|
||||
@"Assister.cs",
|
||||
@"AboutWindow.cs",
|
||||
//@"TinyJson",
|
||||
//@"Preferences",
|
||||
//@"Logger",
|
||||
//@"Input",
|
||||
//@"ExternalReferences",
|
||||
//@"EditorCoroutines",
|
||||
};
|
||||
|
||||
foreach (var c in content)
|
||||
{
|
||||
var path = Path.Combine(directory, c);
|
||||
DeleteFileAndItsMeta(path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Packages/com.merry-yellow.code-assist/Editor/Cleanup.cs.meta
Normal file
11
Packages/com.merry-yellow.code-assist/Editor/Cleanup.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f03bf638663c69e41a0cc01055bc4ad4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90e331117e4d0f4428e4a702405e3a8b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Derived from Unity package
|
||||
* https://docs.unity3d.com/Packages/com.unity.editorcoroutines@0.0/api/Unity.EditorCoroutines.Editor.html
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
//namespace Unity.EditorCoroutines.Editor
|
||||
namespace Meryel.UnityCodeAssist.Editor.EditorCoroutines
|
||||
{
|
||||
/// <summary>
|
||||
/// A handle to an EditorCoroutine, can be passed to <see cref="EditorCoroutineUtility">EditorCoroutineUtility</see> methods to control lifetime.
|
||||
/// </summary>
|
||||
public class EditorCoroutine
|
||||
{
|
||||
private struct YieldProcessor
|
||||
{
|
||||
enum DataType : byte
|
||||
{
|
||||
None = 0,
|
||||
WaitForSeconds = 1,
|
||||
EditorCoroutine = 2,
|
||||
AsyncOP = 3,
|
||||
}
|
||||
struct ProcessorData
|
||||
{
|
||||
public DataType type;
|
||||
public double targetTime;
|
||||
public object current;
|
||||
}
|
||||
|
||||
ProcessorData data;
|
||||
|
||||
public void Set(object yield)
|
||||
{
|
||||
if (yield == data.current)
|
||||
return;
|
||||
|
||||
var type = yield.GetType();
|
||||
var dataType = DataType.None;
|
||||
double targetTime = -1;
|
||||
|
||||
if(type == typeof(EditorWaitForSeconds))
|
||||
{
|
||||
targetTime = EditorApplication.timeSinceStartup + (yield as EditorWaitForSeconds).WaitTime;
|
||||
dataType = DataType.WaitForSeconds;
|
||||
}
|
||||
else if(type == typeof(EditorCoroutine))
|
||||
{
|
||||
dataType = DataType.EditorCoroutine;
|
||||
}
|
||||
else if(type == typeof(AsyncOperation) || type.IsSubclassOf(typeof(AsyncOperation)))
|
||||
{
|
||||
dataType = DataType.AsyncOP;
|
||||
}
|
||||
|
||||
data = new ProcessorData { current = yield, targetTime = targetTime, type = dataType };
|
||||
}
|
||||
|
||||
public bool MoveNext(IEnumerator enumerator)
|
||||
{
|
||||
var advance = data.type switch
|
||||
{
|
||||
DataType.WaitForSeconds => data.targetTime <= EditorApplication.timeSinceStartup,
|
||||
DataType.EditorCoroutine => (data.current as EditorCoroutine).m_IsDone,
|
||||
DataType.AsyncOP => (data.current as AsyncOperation).isDone,
|
||||
_ => data.current == enumerator.Current,//a IEnumerator or a plain object was passed to the implementation
|
||||
};
|
||||
if (advance)
|
||||
{
|
||||
data = default;// (ProcessorData);
|
||||
return enumerator.MoveNext();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal WeakReference m_Owner;
|
||||
IEnumerator m_Routine;
|
||||
YieldProcessor m_Processor;
|
||||
|
||||
bool m_IsDone;
|
||||
|
||||
internal EditorCoroutine(IEnumerator routine)
|
||||
{
|
||||
m_Owner = null;
|
||||
m_Routine = routine;
|
||||
EditorApplication.update += MoveNext;
|
||||
}
|
||||
|
||||
internal EditorCoroutine(IEnumerator routine, object owner)
|
||||
{
|
||||
m_Processor = new YieldProcessor();
|
||||
m_Owner = new WeakReference(owner);
|
||||
m_Routine = routine;
|
||||
EditorApplication.update += MoveNext;
|
||||
}
|
||||
|
||||
internal void MoveNext()
|
||||
{
|
||||
if (m_Owner != null && !m_Owner.IsAlive)
|
||||
{
|
||||
EditorApplication.update -= MoveNext;
|
||||
return;
|
||||
}
|
||||
|
||||
bool done = ProcessIEnumeratorRecursive(m_Routine);
|
||||
m_IsDone = !done;
|
||||
|
||||
if (m_IsDone)
|
||||
EditorApplication.update -= MoveNext;
|
||||
}
|
||||
|
||||
static readonly Stack<IEnumerator> kIEnumeratorProcessingStack = new Stack<IEnumerator>(32);
|
||||
private bool ProcessIEnumeratorRecursive(IEnumerator enumerator)
|
||||
{
|
||||
var root = enumerator;
|
||||
while(enumerator.Current as IEnumerator != null)
|
||||
{
|
||||
kIEnumeratorProcessingStack.Push(enumerator);
|
||||
enumerator = enumerator.Current as IEnumerator;
|
||||
}
|
||||
|
||||
//process leaf
|
||||
m_Processor.Set(enumerator.Current);
|
||||
var result = m_Processor.MoveNext(enumerator);
|
||||
|
||||
while (kIEnumeratorProcessingStack.Count > 1)
|
||||
{
|
||||
if (!result)
|
||||
{
|
||||
result = kIEnumeratorProcessingStack.Pop().MoveNext();
|
||||
}
|
||||
else
|
||||
kIEnumeratorProcessingStack.Clear();
|
||||
}
|
||||
|
||||
if (kIEnumeratorProcessingStack.Count > 0 && !result && root == kIEnumeratorProcessingStack.Pop())
|
||||
{
|
||||
result = root.MoveNext();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void Stop()
|
||||
{
|
||||
m_Owner = null;
|
||||
m_Routine = null;
|
||||
EditorApplication.update -= MoveNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c1e3c1846518ae4da27dcaf08ef85f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Derived from Unity package
|
||||
* https://docs.unity3d.com/Packages/com.unity.editorcoroutines@0.0/api/Unity.EditorCoroutines.Editor.html
|
||||
*/
|
||||
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
//namespace Unity.EditorCoroutines.Editor
|
||||
namespace Meryel.UnityCodeAssist.Editor.EditorCoroutines
|
||||
{
|
||||
public static class EditorCoroutineUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts an <see cref ="EditorCoroutine">EditorCoroutine</see> with the specified owner object.
|
||||
/// If the garbage collector collects the owner object, while the resulting coroutine is still executing, the coroutine will stop running.
|
||||
/// <code>
|
||||
/// using System.Collections;
|
||||
/// using Unity.EditorCoroutines.Editor;
|
||||
/// using UnityEditor;
|
||||
///
|
||||
/// public class ExampleWindow : EditorWindow
|
||||
/// {
|
||||
/// int m_Updates = 0;
|
||||
/// void OnEnable()
|
||||
/// {
|
||||
/// EditorCoroutineUtility.StartCoroutine(CountEditorUpdates(), this);
|
||||
/// }
|
||||
///
|
||||
/// IEnumerator CountEditorUpdates()
|
||||
/// {
|
||||
/// while (true)
|
||||
/// {
|
||||
/// ++m_Updates;
|
||||
/// yield return null;
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="routine"> IEnumerator to iterate over. </param>
|
||||
/// <param name="owner">Object owning the coroutine. </param>
|
||||
/// <remarks>
|
||||
/// Only types that don't inherit from <see cref="UnityEngine.Object">UnityEngine.Object</see> will get collected the next time the GC runs instead of getting null-ed immediately.
|
||||
/// </remarks>
|
||||
/// <returns>A handle to an <see cref="EditorCoroutine">EditorCoroutine</see>.</returns>
|
||||
public static EditorCoroutine StartCoroutine(IEnumerator routine, object owner)
|
||||
{
|
||||
return new EditorCoroutine(routine, owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method starts an <see cref="EditorCoroutine">EditorCoroutine</see> without an owning object. The <see cref="EditorCoroutine">EditorCoroutine</see> runs until it completes or is canceled using <see cref="StopCoroutine(EditorCoroutine)">StopCoroutine</see>.
|
||||
/// <code>
|
||||
/// using System.Collections;
|
||||
/// using Unity.EditorCoroutines.Editor;
|
||||
/// using UnityEditor;
|
||||
/// using UnityEngine;
|
||||
///
|
||||
/// public class ExampleWindow : EditorWindow
|
||||
/// {
|
||||
/// void OnEnable()
|
||||
/// {
|
||||
/// EditorCoroutineUtility.StartCoroutineOwnerless(LogTimeSinceStartup());
|
||||
/// }
|
||||
///
|
||||
/// IEnumerator LogTimeSinceStartup()
|
||||
/// {
|
||||
/// while (true)
|
||||
/// {
|
||||
/// Debug.LogFormat("Time since startup: {0} s", Time.realtimeSinceStartup);
|
||||
/// yield return null;
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="routine"> Generator function to execute. </param>
|
||||
/// <returns>A handle to an <see cref="EditorCoroutine">EditorCoroutine.</see></returns>
|
||||
public static EditorCoroutine StartCoroutineOwnerless(IEnumerator routine)
|
||||
{
|
||||
return new EditorCoroutine(routine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately stop an <see cref="EditorCoroutine">EditorCoroutine</see>. This method is safe to call on an already completed <see cref="EditorCoroutine">EditorCoroutine</see>.
|
||||
/// <code>
|
||||
/// using System.Collections;
|
||||
/// using Unity.EditorCoroutines.Editor;
|
||||
/// using UnityEditor;
|
||||
/// using UnityEngine;
|
||||
///
|
||||
/// public class ExampleWindow : EditorWindow
|
||||
/// {
|
||||
/// EditorCoroutine m_LoggerCoroutine;
|
||||
/// void OnEnable()
|
||||
/// {
|
||||
/// m_LoggerCoroutine = EditorCoroutineUtility.StartCoroutineOwnerless(LogRunning());
|
||||
/// }
|
||||
///
|
||||
/// void OnDisable()
|
||||
/// {
|
||||
/// EditorCoroutineUtility.StopCoroutine(m_LoggerCoroutine);
|
||||
/// }
|
||||
///
|
||||
/// IEnumerator LogRunning()
|
||||
/// {
|
||||
/// while (true)
|
||||
/// {
|
||||
/// Debug.Log("Running");
|
||||
/// yield return null;
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="coroutine">A handle to an <see cref="EditorCoroutine">EditorCoroutine.</see></param>
|
||||
public static void StopCoroutine(EditorCoroutine coroutine)
|
||||
{
|
||||
if (coroutine == null)
|
||||
{
|
||||
Serilog.Log.Warning("EditorCoroutine handle is null.");
|
||||
return;
|
||||
}
|
||||
coroutine.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3d162669c5693a47bf42827686f73d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Derived from Unity package
|
||||
* https://docs.unity3d.com/Packages/com.unity.editorcoroutines@0.0/api/Unity.EditorCoroutines.Editor.html
|
||||
*/
|
||||
|
||||
//namespace Unity.EditorCoroutines.Editor
|
||||
namespace Meryel.UnityCodeAssist.Editor.EditorCoroutines
|
||||
{
|
||||
/// <summary>
|
||||
/// Suspends the <see cref="EditorCoroutine">EditorCoroutine</see> execution for the given amount of seconds, using unscaled time.
|
||||
/// The coroutine execution continues after the specified time has elapsed.
|
||||
/// <code>
|
||||
/// using System.Collections;
|
||||
/// using UnityEngine;
|
||||
/// using Unity.EditorCoroutines.Editor;
|
||||
/// using UnityEditor;
|
||||
///
|
||||
/// public class MyEditorWindow : EditorWindow
|
||||
/// {
|
||||
/// IEnumerator PrintEachSecond()
|
||||
/// {
|
||||
/// var waitForOneSecond = new EditorWaitForSeconds(1.0f);
|
||||
///
|
||||
/// while (true)
|
||||
/// {
|
||||
/// yield return waitForOneSecond;
|
||||
/// Debug.Log("Printing each second");
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class EditorWaitForSeconds
|
||||
{
|
||||
/// <summary>
|
||||
/// The time to wait in seconds.
|
||||
/// </summary>
|
||||
public float WaitTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a instruction object for yielding inside a generator function.
|
||||
/// </summary>
|
||||
/// <param name="time">The amount of time to wait in seconds.</param>
|
||||
public EditorWaitForSeconds(float time)
|
||||
{
|
||||
WaitTime = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d8612ff14468214aad7600138a50b79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Derived from Unity package
|
||||
* https://docs.unity3d.com/Packages/com.unity.editorcoroutines@0.0/api/Unity.EditorCoroutines.Editor.html
|
||||
*/
|
||||
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
//namespace Unity.EditorCoroutines.Editor
|
||||
namespace Meryel.UnityCodeAssist.Editor.EditorCoroutines
|
||||
{
|
||||
public static class EditorWindowCoroutineExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Start an <see cref="EditorCoroutine">EditorCoroutine</see>, owned by the calling <see cref="EditorWindow">EditorWindow</see> instance.
|
||||
/// <code>
|
||||
/// using System.Collections;
|
||||
/// using Unity.EditorCoroutines.Editor;
|
||||
/// using UnityEditor;
|
||||
///
|
||||
/// public class ExampleWindow : EditorWindow
|
||||
/// {
|
||||
/// void OnEnable()
|
||||
/// {
|
||||
/// this.StartCoroutine(CloseWindowDelayed());
|
||||
/// }
|
||||
///
|
||||
/// IEnumerator CloseWindowDelayed() //close the window after 1000 frames have elapsed
|
||||
/// {
|
||||
/// int count = 1000;
|
||||
/// while (count > 0)
|
||||
/// {
|
||||
/// yield return null;
|
||||
/// }
|
||||
/// Close();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="routine"></param>
|
||||
/// <returns></returns>
|
||||
public static EditorCoroutine StartCoroutine(this EditorWindow window, IEnumerator routine)
|
||||
{
|
||||
return new EditorCoroutine(routine, window);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately stop an <see cref="EditorCoroutine">EditorCoroutine</see> that was started by the calling <see cref="EditorWindow"/> instance. This method is safe to call on an already completed <see cref="EditorCoroutine">EditorCoroutine</see>.
|
||||
/// <code>
|
||||
/// using System.Collections;
|
||||
/// using Unity.EditorCoroutines.Editor;
|
||||
/// using UnityEditor;
|
||||
/// using UnityEngine;
|
||||
///
|
||||
/// public class ExampleWindow : EditorWindow
|
||||
/// {
|
||||
/// EditorCoroutine coroutine;
|
||||
/// void OnEnable()
|
||||
/// {
|
||||
/// coroutine = this.StartCoroutine(CloseWindowDelayed());
|
||||
/// }
|
||||
///
|
||||
/// private void OnDisable()
|
||||
/// {
|
||||
/// this.StopCoroutine(coroutine);
|
||||
/// }
|
||||
///
|
||||
/// IEnumerator CloseWindowDelayed()
|
||||
/// {
|
||||
/// while (true)
|
||||
/// {
|
||||
/// Debug.Log("Running");
|
||||
/// yield return null;
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="coroutine"></param>
|
||||
public static void StopCoroutine(this EditorWindow window, EditorCoroutine coroutine)
|
||||
{
|
||||
if(coroutine == null)
|
||||
{
|
||||
Serilog.Log.Warning("Provided EditorCoroutine handle is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(coroutine.m_Owner == null)
|
||||
{
|
||||
Serilog.Log.Error("The EditorCoroutine is ownerless. Please use EditorCoroutineEditor.StopCoroutine to terminate such coroutines.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!coroutine.m_Owner.IsAlive)
|
||||
return; //The EditorCoroutine's owner was already terminated execution will cease next time it is processed
|
||||
|
||||
var owner = coroutine.m_Owner.Target as EditorWindow;
|
||||
|
||||
if (owner == null || owner != null && owner != window)
|
||||
{
|
||||
Serilog.Log.Error("The EditorCoroutine is owned by another object: {0}.", coroutine.m_Owner.Target);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorCoroutineUtility.StopCoroutine(coroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64c15e0c4e36aa84193d4acb3c63afc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c442647f0e4e1ed40b80269bf6a4d02e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,69 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37e44dd86f563cf40a81ef1fdf1f1b54
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,69 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d7e4808a93371242b38476718f4d4cc
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed466bfa2ad216b41acde183e7706352
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e70477d38c7ca2841971b35549d1477f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aaca88d50145a3041a4ccab964c5793d
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d55f65d499dd354a9c086ab6420c8f1
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfd80cce2d0a99b4ba669e989eceafe5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4adc17866ec41ac4e9ce6c47070aeef5
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca15dd8e239d5d6468333cbdacbb55d2
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ff40589a33e8a2458503b2391a96a83
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94a257e1b1239d94589fe96db62b03a7
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 849ce1048cc324a46b0fe21149770a3f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd39e32872afc47418c75a05b559debf
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 582b34ab8eec62a4c87693272b9bc6ab
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 1
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
181
Packages/com.merry-yellow.code-assist/Editor/FeedbackWindow.cs
Normal file
181
Packages/com.merry-yellow.code-assist/Editor/FeedbackWindow.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
public class FeedbackWindow : EditorWindow
|
||||
{
|
||||
|
||||
GUIStyle? styleLabel;
|
||||
|
||||
public static void Display()
|
||||
{
|
||||
MQTTnetInitializer.Publisher?.SendRequestInternalLog();
|
||||
|
||||
// Get existing open window or if none, make a new one:
|
||||
var window = GetWindow<FeedbackWindow>();
|
||||
window.Show();
|
||||
|
||||
Serilog.Log.Debug("Displaying feedback window");
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "FeedbackWindow_Display");
|
||||
}
|
||||
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
//**--icon
|
||||
//var icon = AssetDatabase.LoadAssetAtPath<Texture>("Assets/Sprites/Gear.png");
|
||||
//titleContent = new GUIContent("Code Assist", icon);
|
||||
titleContent = new GUIContent("Code Assist Feedback");
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
var errorCount = Logger.ELogger.GetErrorCountInInternalLog();
|
||||
var warningCount = Logger.ELogger.GetWarningCountInInternalLog();
|
||||
var logContent = Logger.ELogger.GetInternalLogContent();
|
||||
if (!string.IsNullOrEmpty(Logger.ELogger.VsInternalLog))
|
||||
logContent += Logger.ELogger.VsInternalLog;
|
||||
|
||||
styleLabel ??= new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
wordWrap = true,
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
};
|
||||
|
||||
if (errorCount > 0)
|
||||
EditorGUILayout.LabelField($"{errorCount} error(s) found in logs. Please submit a feedback (via e-mail, Discord or GitHub) with the logs if possible.", styleLabel, GUILayout.ExpandWidth(true));
|
||||
else if (warningCount > 0)
|
||||
EditorGUILayout.LabelField($"{warningCount} warnings(s) found in logs. Please submit a feedback (via e-mail, Discord or GitHub) with the logs if possible.", styleLabel, GUILayout.ExpandWidth(true));
|
||||
else
|
||||
EditorGUILayout.LabelField("No errors found in logs. Please submit a feedback (via e-mail, Discord or GitHub) describing what went wrong or unexpected.", styleLabel, GUILayout.ExpandWidth(true));
|
||||
|
||||
if (GUILayout.Button("Send e-mail"))
|
||||
{
|
||||
var uri = "mailto:merryyellow@outlook.com";
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(uri));
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Send Discord message"))
|
||||
{
|
||||
//var uri = "discord://invites/2CgKHDq";
|
||||
var uri = "https://discord.gg/2CgKHDq";
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(uri));
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Submit GitHub issue"))
|
||||
{
|
||||
var uri = "https://github.com/merryyellow/Unity-Code-Assist/issues/new/choose";
|
||||
Application.OpenURL(uri);
|
||||
}
|
||||
|
||||
EditorGUILayout.Separator();
|
||||
|
||||
if (GUILayout.Button("Open Unity full log"))
|
||||
{
|
||||
var filePath = Logger.ELogger.FilePath;
|
||||
System.Diagnostics.Process.Start(filePath);
|
||||
}
|
||||
if (GUILayout.Button("Reveal Unity full log"))
|
||||
{
|
||||
var filePath = Logger.ELogger.FilePath;
|
||||
ShowInFileExplorer(filePath);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Open Visual Studio full log"))
|
||||
{
|
||||
var filePath = Logger.ELogger.VSFilePath;
|
||||
System.Diagnostics.Process.Start(filePath);
|
||||
}
|
||||
if (GUILayout.Button("Reveal Visual Studio full log"))
|
||||
{
|
||||
var filePath = Logger.ELogger.VSFilePath;
|
||||
ShowInFileExplorer(filePath);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Copy recent logs to clipboard"))
|
||||
{
|
||||
GUIUtility.systemCopyBuffer = logContent;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Recent logs:", styleLabel, GUILayout.ExpandWidth(true));
|
||||
EditorGUILayout.SelectableLabel(logContent, EditorStyles.textArea, GUILayout.ExpandHeight(true));
|
||||
}
|
||||
|
||||
public static void ShowInFileExplorer(string? filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
Serilog.Log.Error("Argument {Arg} is null or empty at {Location}", nameof(filePath), nameof(ShowInFileExplorer));
|
||||
return;
|
||||
}
|
||||
|
||||
filePath = System.IO.Path.GetFullPath(filePath);
|
||||
|
||||
if (!System.IO.File.Exists(filePath) && !System.IO.Directory.Exists(filePath))
|
||||
{
|
||||
Serilog.Log.Error("Argument {Arg} is not found at {Location}, value: {Value}", nameof(filePath), nameof(ShowInFileExplorer), filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||
{
|
||||
// Windows: highlight the file in Explorer
|
||||
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\"");
|
||||
}
|
||||
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX))
|
||||
{
|
||||
// macOS: reveal in Finder
|
||||
System.Diagnostics.Process.Start("open", $"-R \"{filePath}\"");
|
||||
}
|
||||
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
|
||||
{
|
||||
// Linux: open containing folder in a common file manager
|
||||
string? directory = System.IO.Path.GetDirectoryName(filePath);
|
||||
if (directory == null)
|
||||
return;
|
||||
|
||||
string[] managers = { "xdg-open", "nautilus", "dolphin", "nemo", "thunar" };
|
||||
|
||||
foreach (string manager in managers)
|
||||
{
|
||||
if (TryStart(manager, directory))
|
||||
return; // success
|
||||
}
|
||||
|
||||
Serilog.Log.Error("No supported file manager found to open the directory at {Location}", nameof(ShowInFileExplorer));
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Error("Unsupported OS platform at {Location}", nameof(ShowInFileExplorer));
|
||||
}
|
||||
|
||||
// static local function for cleaner structure
|
||||
static bool TryStart(string command, string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(command, $"\"{args}\"");
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f84a1c080c184e48920a0124c3e9257
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.merry-yellow.code-assist/Editor/Input.meta
Normal file
8
Packages/com.merry-yellow.code-assist/Editor/Input.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2480df7d4d1ec4448a90b6a15026eeb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
//namespace UTJ.UnityCommandLineTools
|
||||
namespace Meryel.UnityCodeAssist.Editor.Input
|
||||
{
|
||||
// <summary>
|
||||
// bin2textをUnityEditorから実行する為のClass
|
||||
// programed by Katsumasa.Kimura
|
||||
// </summary>
|
||||
public class Binary2TextExec : EditorToolExec
|
||||
{
|
||||
public Binary2TextExec() : base("binary2text") { }
|
||||
|
||||
// <summary>
|
||||
// bin2text filePath outPath options
|
||||
// /summary>
|
||||
public int Exec(string filePath, string outPath, string options)
|
||||
{
|
||||
var args = string.Format(@"""{0}"" ""{1}"" {2}", filePath, outPath, options);
|
||||
return Exec(args);
|
||||
}
|
||||
|
||||
public int Exec(string filePath, string outPath, bool detailed = false, bool largeBinaryHashOnly = false, bool hexFloat = false)
|
||||
{
|
||||
//var args = string.Format(@"""{0}"" ""{1}"" {2}", filePath, outPath, options);
|
||||
var args = string.Format(@"""{0}"" ""{1}""", filePath, outPath);
|
||||
|
||||
if (detailed)
|
||||
args += " -detailed";
|
||||
if (largeBinaryHashOnly)
|
||||
args += " -largebinaryhashonly";
|
||||
if (hexFloat)
|
||||
args += " -hexfloat";
|
||||
|
||||
return Exec(args);
|
||||
}
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// UnityEditorに含まれるコマンドラインツールを実行する為の基底Class
|
||||
// programed by Katsumasa.Kimura
|
||||
//</summary>
|
||||
public class EditorToolExec
|
||||
{
|
||||
// <value>
|
||||
// UnityEditorがインストールされているディレクトリへのパス
|
||||
// </value>
|
||||
protected string mEditorPath;
|
||||
|
||||
// <value>
|
||||
// Toolsディレクトリへのパス
|
||||
// </value>
|
||||
protected string mToolsPath;
|
||||
|
||||
// <value>
|
||||
// 実行ファイル名
|
||||
// </value>
|
||||
protected string mExecFname;
|
||||
|
||||
// <value>
|
||||
// 実行ファイルへのフルパス
|
||||
// </value>
|
||||
protected string mExecFullPath;
|
||||
|
||||
// <value>
|
||||
// 実行結果のOUTPUT
|
||||
// </value>
|
||||
private string? mOutput;
|
||||
|
||||
// <value>
|
||||
// 実行結果のOUTPUT
|
||||
// </value>
|
||||
public string? Output
|
||||
{
|
||||
get { return mOutput; }
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// コンストラクタ
|
||||
// <param>
|
||||
// mExecFname : 実行ファイル名
|
||||
// </param>
|
||||
// /summary>
|
||||
public EditorToolExec(string mExecFname)
|
||||
{
|
||||
mEditorPath = Path.GetDirectoryName(EditorApplication.applicationPath);
|
||||
mToolsPath = Path.Combine(mEditorPath, @"Data/Tools");
|
||||
this.mExecFname = mExecFname;
|
||||
//var files = Directory.GetFiles(mToolsPath, mExecFname, SearchOption.AllDirectories);
|
||||
var files = Directory.GetFiles(mEditorPath, mExecFname + "*", SearchOption.AllDirectories);
|
||||
|
||||
if (files.Length == 0)
|
||||
Serilog.Log.Error("{App} app couldn't be found at {Path}", mExecFname, mEditorPath);
|
||||
|
||||
mExecFullPath = files[0];
|
||||
}
|
||||
|
||||
// <summary>
|
||||
// コマンドラインツールを実行する
|
||||
// <param>
|
||||
// arg : コマンドラインツールに渡す引数
|
||||
// </param>
|
||||
// </summary>
|
||||
public int Exec(string arg)
|
||||
{
|
||||
int exitCode = -1;
|
||||
|
||||
try
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo.FileName = mExecFullPath;
|
||||
process.StartInfo.Arguments = arg;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.Start();
|
||||
mOutput = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
exitCode = process.ExitCode;
|
||||
process.Close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//UnityEngine.Debug.Log(e);
|
||||
Serilog.Log.Error(e, "Exception while running process at {Scope}.{Location}", nameof(EditorToolExec), nameof(Exec));
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13d464c749e33e043b94d94c82365823
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Input
|
||||
{
|
||||
|
||||
public class InputManagerMonitor
|
||||
{
|
||||
private static readonly Lazy<InputManagerMonitor> _instance = new Lazy<InputManagerMonitor>(() => new InputManagerMonitor());
|
||||
public static InputManagerMonitor Instance => _instance.Value;
|
||||
|
||||
//UnityInputManager inputManager;
|
||||
readonly string inputManagerFilePath;
|
||||
DateTime previousTagManagerLastWrite;
|
||||
|
||||
public InputManagerMonitor()
|
||||
{
|
||||
EditorApplication.update += Update;
|
||||
inputManagerFilePath = CommonTools.GetInputManagerFilePath();
|
||||
|
||||
if (!System.IO.File.Exists(inputManagerFilePath))
|
||||
{
|
||||
Serilog.Log.Error("InputManager file not found at {location}", inputManagerFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
previousTagManagerLastWrite = System.IO.File.GetLastWriteTime(inputManagerFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Debug(ex, "Exception at {Location}", nameof(System.IO.File.GetLastWriteTime));
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
#if !ENABLE_LEGACY_INPUT_MANAGER
|
||||
return;
|
||||
#endif
|
||||
|
||||
#pragma warning disable CS0162
|
||||
#pragma warning disable IDE0035
|
||||
|
||||
var currentInputManagerLastWrite = previousTagManagerLastWrite;
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(inputManagerFilePath))
|
||||
currentInputManagerLastWrite = System.IO.File.GetLastWriteTime(inputManagerFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Debug(ex, "Exception at {Location}", nameof(System.IO.File.GetLastWriteTime));
|
||||
}
|
||||
if (currentInputManagerLastWrite != previousTagManagerLastWrite)
|
||||
{
|
||||
previousTagManagerLastWrite = currentInputManagerLastWrite;
|
||||
Bump();
|
||||
}
|
||||
|
||||
#pragma warning restore CS0162
|
||||
#pragma warning restore IDE0035
|
||||
}
|
||||
|
||||
public void Bump()
|
||||
{
|
||||
#if !ENABLE_LEGACY_INPUT_MANAGER
|
||||
return;
|
||||
#endif
|
||||
#pragma warning disable CS0162
|
||||
#pragma warning disable IDE0035
|
||||
|
||||
Serilog.Log.Debug("InputMonitor {Event}", nameof(Bump));
|
||||
|
||||
if (!System.IO.File.Exists(inputManagerFilePath))
|
||||
{
|
||||
Serilog.Log.Error("InputManager file not found at {location}", inputManagerFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
var inputManager = new UnityInputManager();
|
||||
inputManager.ReadFromPath(inputManagerFilePath);
|
||||
inputManager.SendData();
|
||||
|
||||
|
||||
#pragma warning restore CS0162
|
||||
#pragma warning restore IDE0035
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static string GetInfo(this List<InputAxis> axes, string? name)
|
||||
{
|
||||
if (name == null || string.IsNullOrEmpty(name))
|
||||
return string.Empty;
|
||||
|
||||
//axis.descriptiveName
|
||||
var axesWithName = axes.Where(a => a.Name == name);
|
||||
|
||||
int threshold = 80;
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
foreach (var axis in axesWithName)
|
||||
if (!string.IsNullOrEmpty(axis.descriptiveName))
|
||||
sb.Append($"{axis.descriptiveName} ");
|
||||
|
||||
if (sb.Length > threshold)
|
||||
return sb.ToString();
|
||||
|
||||
foreach (var axis in axesWithName)
|
||||
if (!string.IsNullOrEmpty(axis.descriptiveNegativeName))
|
||||
sb.Append($"{axis.descriptiveNegativeName} ");
|
||||
|
||||
if (sb.Length > threshold)
|
||||
return sb.ToString();
|
||||
|
||||
foreach (var axis in axesWithName)
|
||||
if (!string.IsNullOrEmpty(axis.positiveButton))
|
||||
sb.Append($"[{axis.positiveButton}] ");
|
||||
|
||||
if (sb.Length > threshold)
|
||||
return sb.ToString();
|
||||
|
||||
foreach (var axis in axesWithName)
|
||||
if (!string.IsNullOrEmpty(axis.altPositiveButton))
|
||||
sb.Append($"{{{axis.altPositiveButton}}} ");
|
||||
|
||||
if (sb.Length > threshold)
|
||||
return sb.ToString();
|
||||
|
||||
foreach (var axis in axesWithName)
|
||||
if (!string.IsNullOrEmpty(axis.negativeButton))
|
||||
sb.Append($"-[{axis.negativeButton}] ");
|
||||
|
||||
if (sb.Length > threshold)
|
||||
return sb.ToString();
|
||||
|
||||
foreach (var axis in axesWithName)
|
||||
if (!string.IsNullOrEmpty(axis.altNegativeButton))
|
||||
sb.Append($"-{{{axis.altNegativeButton}}} ");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9c9772cbc184d74dbdb770fec5ff76c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
215
Packages/com.merry-yellow.code-assist/Editor/Input/Text2Yaml.cs
Normal file
215
Packages/com.merry-yellow.code-assist/Editor/Input/Text2Yaml.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Input
|
||||
{
|
||||
|
||||
public class Text2Yaml
|
||||
{
|
||||
public static string Convert(IEnumerable<string> textLines)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var stack = new Stack<(string typeName, string identifier, int indentation)>();
|
||||
|
||||
sb.AppendLine(@"%YAML 1.1");
|
||||
sb.AppendLine(@"%TAG !u! tag:unity3d.com,2011:");
|
||||
sb.AppendLine(@"--- !u!13 &1");
|
||||
sb.AppendLine(@"InputManager:");
|
||||
|
||||
var regexIndentation = new Regex("^\\s*");
|
||||
|
||||
var regexString = new Regex("^(\\s+)(\\w+)\\s+\"([a-zA-Z0-9_ \\/\\.\\-]*)\"\\s+\\(string\\)$");
|
||||
var regexValue = new Regex("^(\\s+)(\\w+)\\s+(-?[0-9.]*)\\s+\\(((bool)|(int)|(float)|(unsigned int))\\)$");
|
||||
var regexType = new Regex("^(\\s+)(\\w+)\\s+\\((\\w+)\\)$");
|
||||
|
||||
var regexVectorSize = new Regex("(\\s+)size\\s+(\\d)+\\s+\\(int\\)");
|
||||
//var regexVectorData = new Regex("(\\s+)data \\(InputAxis\\)"); // remove InputAxis to make it more generic
|
||||
|
||||
string curTextLine;
|
||||
var curTextLineNo = 3;
|
||||
var textIndentation = 1;
|
||||
var indentationPrefix = new string(' ', textIndentation * 2);
|
||||
stack.Push(("InputManager", "InputManager", textIndentation));
|
||||
|
||||
|
||||
foreach (var line in textLines.Skip(4))
|
||||
{
|
||||
curTextLine = line;
|
||||
curTextLineNo++;
|
||||
|
||||
|
||||
// Skip empty lines
|
||||
if (line.Length == 0)
|
||||
continue;
|
||||
|
||||
// Check if type undeclared, scope goes down, indentation decrements
|
||||
{
|
||||
var indentationMatch = regexIndentation.Match(line);
|
||||
if (indentationMatch.Success)
|
||||
{
|
||||
var indentation = indentationMatch.Groups[0].Value.Length;
|
||||
|
||||
if (indentation > textIndentation)
|
||||
Error($"indentation({indentation}) > textIndentation({textIndentation})");
|
||||
|
||||
while (indentation < textIndentation)
|
||||
{
|
||||
stack.Pop();
|
||||
textIndentation--;
|
||||
var typeIndentation = textIndentation;
|
||||
if (stack.TryPeek(out var curType2))
|
||||
typeIndentation = curType2.indentation;
|
||||
else if (line.Length > 0)
|
||||
Error("stack empty at type undeclaration");
|
||||
indentationPrefix = new string(' ', typeIndentation * 2);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Error($"{nameof(regexIndentation)} failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Skip size field of vectors
|
||||
if (stack.TryPeek(out var curType1) && curType1.typeName == "vector")
|
||||
{
|
||||
var vectorSizeMatch = regexVectorSize.Match(line);
|
||||
if (vectorSizeMatch.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Read string fields
|
||||
{
|
||||
var stringMatch = regexString.Match(line);
|
||||
if (stringMatch.Success)
|
||||
{
|
||||
AddLine(stringMatch.Groups[2] + ": " + stringMatch.Groups[3]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Read bool/int/float/unsignedInt fields
|
||||
{
|
||||
var valueMatch = regexValue.Match(line);
|
||||
if (valueMatch.Success)
|
||||
{
|
||||
AddLine(valueMatch.Groups[2] + ": " + valueMatch.Groups[3]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if new type declared, scope goes up, indentation increases
|
||||
{
|
||||
var typeMatch = regexType.Match(line);
|
||||
if (typeMatch.Success)
|
||||
{
|
||||
var identifier = typeMatch.Groups[2].Value;
|
||||
var typeName = typeMatch.Groups[3].Value;
|
||||
|
||||
var isVectorData = false;
|
||||
if (stack.TryPeek(out var curType2) && curType2.typeName == "vector" && identifier == "data")
|
||||
isVectorData = true;
|
||||
|
||||
var typeIndentation = textIndentation;
|
||||
if (stack.TryPeek(out var curType3))
|
||||
typeIndentation = curType3.indentation;
|
||||
else if (line.Length > 0)
|
||||
Error("stack empty at type declaration");
|
||||
|
||||
if (!isVectorData)
|
||||
{
|
||||
AddLine(typeMatch.Groups[2] + ":");
|
||||
}
|
||||
else
|
||||
{
|
||||
var customIndentation = typeIndentation - 1;
|
||||
if (customIndentation < 0)
|
||||
Error($"customIndentation({customIndentation}) < 0");
|
||||
var customIndentationPrefix = new string(' ', customIndentation * 2);
|
||||
AddLine("- serializedVersion: 3", customIndentationPrefix);
|
||||
}
|
||||
|
||||
|
||||
textIndentation++;
|
||||
typeIndentation++;
|
||||
|
||||
if (isVectorData)
|
||||
typeIndentation--;
|
||||
|
||||
stack.Push((typeName, identifier, typeIndentation));
|
||||
indentationPrefix = new string(' ', typeIndentation * 2);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Error("line failed to match all cases");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
|
||||
void AddLine(string line, string? customIndentationPrefix = null)
|
||||
{
|
||||
string suffix;
|
||||
if (stack.TryPeek(out var top))
|
||||
suffix = $" # {textIndentation}, {top.indentation}, {top.typeName} {top.identifier}";
|
||||
else
|
||||
suffix = $" # {textIndentation}, nil";
|
||||
|
||||
if (customIndentationPrefix != null)
|
||||
sb.AppendLine(customIndentationPrefix + line + suffix);
|
||||
else
|
||||
sb.AppendLine(indentationPrefix + line + suffix);
|
||||
}
|
||||
|
||||
void Error(string message)
|
||||
{
|
||||
var errorMessage = $"Text2Yaml error '{message}' at lineNo: {curTextLineNo}, line: '{curTextLine}' at {Environment.StackTrace}";
|
||||
//throw new Exception(errorMessage);
|
||||
Serilog.Log.Warning(errorMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static bool TryPeek<T>(this Stack<T> stack, /*[MaybeNullWhen(false)]*/ out T result)
|
||||
{
|
||||
if (stack.Count > 0)
|
||||
{
|
||||
result = stack.Peek();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = default!;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e191fef871d30041a55c3c8af5aab43
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,429 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
using YamlDotNet = Meryel.UnityCodeAssist.YamlDotNet;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Input
|
||||
{
|
||||
|
||||
|
||||
internal class UnityInputManager
|
||||
{
|
||||
//string yamlPath;
|
||||
TextReader? reader;
|
||||
InputManager? inputManager;
|
||||
|
||||
public void ReadFromText(string text)
|
||||
{
|
||||
reader = new StringReader(text);
|
||||
ReadAux(false, out _);
|
||||
}
|
||||
|
||||
public void ReadFromPath(string yamlPath)
|
||||
{
|
||||
|
||||
switch (UnityEditor.EditorSettings.serializationMode)
|
||||
{
|
||||
case UnityEditor.SerializationMode.ForceText:
|
||||
{
|
||||
reader = new StreamReader(yamlPath);
|
||||
ReadAux(false, out _);
|
||||
}
|
||||
break;
|
||||
|
||||
case UnityEditor.SerializationMode.ForceBinary:
|
||||
{
|
||||
// this approach will work for InputManager since its file size is small and limited
|
||||
// but in the future, we may need to switch to reading binary files for big files
|
||||
// like this https://github.com/Unity-Technologies/UnityDataTools
|
||||
// or this https://github.com/SeriousCache/UABE
|
||||
var converted = GetOrCreateConvertedFile(yamlPath);
|
||||
if (!File.Exists(converted))
|
||||
{
|
||||
Serilog.Log.Warning("Temp file {TempFile} couldn't found for converted yaml input file. Auto Input Manager will not work!", converted);
|
||||
return;
|
||||
}
|
||||
var rawLines = File.ReadLines(converted);
|
||||
var yamlText = Text2Yaml.Convert(rawLines);
|
||||
reader = new StringReader(yamlText);
|
||||
ReadAux(false, out _);
|
||||
}
|
||||
break;
|
||||
|
||||
case UnityEditor.SerializationMode.Mixed:
|
||||
{
|
||||
reader = new StreamReader(yamlPath);
|
||||
ReadAux(true, out var hasSemanticError);
|
||||
if (hasSemanticError)
|
||||
{
|
||||
var converted = GetOrCreateConvertedFile(yamlPath);
|
||||
if (!File.Exists(converted))
|
||||
{
|
||||
Serilog.Log.Warning("Temp file {TempFile} couldn't found for converted yaml input file. Auto Input Manager will not work!", converted);
|
||||
return;
|
||||
}
|
||||
var rawLines = File.ReadLines(converted);
|
||||
var yamlText = Text2Yaml.Convert(rawLines);
|
||||
reader = new StringReader(yamlText);
|
||||
ReadAux(false, out _);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ReadAux(bool canHaveSemanticError, out bool hasSemanticError)
|
||||
{
|
||||
hasSemanticError = false;
|
||||
|
||||
if (reader == null)
|
||||
{
|
||||
Serilog.Log.Warning($"{nameof(UnityInputManager)}.{nameof(reader)} is null");
|
||||
return;
|
||||
}
|
||||
|
||||
//var reader = new StreamReader(yamlPath);
|
||||
var deserializer = new YamlDotNet.Serialization.DeserializerBuilder()
|
||||
//.WithTagMapping("tag:unity3d.com,2011:13", typeof(Class13Mapper))
|
||||
.WithNodeTypeResolver(new Tag13Resolver()) // some users have "tag:yousandi.cn,2023:13" instead, so use a more generic approach
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
//serializer.Settings.RegisterTagMapping("tag:unity3d.com,2011:13", typeof(Class13));
|
||||
//serializer.Settings.ComparerForKeySorting = null;
|
||||
Class13Mapper? result;
|
||||
try
|
||||
{
|
||||
result = deserializer.Deserialize<Class13Mapper>(reader);
|
||||
}
|
||||
catch (YamlDotNet.Core.SemanticErrorException semanticErrorException)
|
||||
{
|
||||
Serilog.Log.Debug(semanticErrorException, "Couldn't parse InputManager.asset yaml file");
|
||||
if (!canHaveSemanticError)
|
||||
Serilog.Log.Error(semanticErrorException, "Couldn't parse InputManager.asset yaml file unexpectedly");
|
||||
|
||||
hasSemanticError = true;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.Close();
|
||||
}
|
||||
|
||||
var inputManagerMapper = result?.InputManager;
|
||||
if (inputManagerMapper == null)
|
||||
{
|
||||
Serilog.Log.Warning($"{nameof(inputManagerMapper)} is null");
|
||||
return;
|
||||
}
|
||||
|
||||
inputManager = new InputManager(inputManagerMapper);
|
||||
}
|
||||
|
||||
|
||||
public void SendData()
|
||||
{
|
||||
if (inputManager == null)
|
||||
return;
|
||||
|
||||
var axisNames = inputManager.Axes.Select(a => a.Name!).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToArray();
|
||||
var axisInfos = axisNames.Select(a => inputManager.Axes.GetInfo(a)).ToArray();
|
||||
if (!CreateBindingsMap(out var buttonKeys, out var buttonAxis))
|
||||
return;
|
||||
|
||||
string[] joystickNames;
|
||||
try
|
||||
{
|
||||
joystickNames = UnityEngine.Input.GetJoystickNames();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Occurs if user have switched active Input handling to Input System package in Player Settings.
|
||||
joystickNames = new string[0];
|
||||
}
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendInputManager(axisNames, axisInfos, buttonKeys, buttonAxis, joystickNames);
|
||||
|
||||
/*
|
||||
MQTTnetInitializer.Publisher?.SendInputManager(
|
||||
inputManager.Axes.Select(a => a.Name).Distinct().ToArray(),
|
||||
inputManager.Axes.Select(a => a.positiveButton).ToArray(),
|
||||
inputManager.Axes.Select(a => a.negativeButton).ToArray(),
|
||||
inputManager.Axes.Select(a => a.altPositiveButton).ToArray(),
|
||||
inputManager.Axes.Select(a => a.altNegativeButton).ToArray(),
|
||||
UnityEngine.Input.GetJoystickNames()
|
||||
);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool CreateBindingsMap([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string[]? inputKeys, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string[]? inputAxis)
|
||||
{
|
||||
if (inputManager == null)
|
||||
{
|
||||
inputKeys = null;
|
||||
inputAxis = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var dict = new Dictionary<string, string?>();
|
||||
|
||||
foreach (var axis in inputManager.Axes)
|
||||
{
|
||||
if (axis.altNegativeButton != null && !string.IsNullOrEmpty(axis.altNegativeButton))
|
||||
dict[axis.altNegativeButton] = axis.Name;
|
||||
}
|
||||
foreach (var axis in inputManager.Axes)
|
||||
{
|
||||
if (axis.negativeButton != null && !string.IsNullOrEmpty(axis.negativeButton))
|
||||
dict[axis.negativeButton] = axis.Name;
|
||||
}
|
||||
foreach (var axis in inputManager.Axes)
|
||||
{
|
||||
if (axis.altPositiveButton != null && !string.IsNullOrEmpty(axis.altPositiveButton))
|
||||
dict[axis.altPositiveButton] = axis.Name;
|
||||
}
|
||||
foreach (var axis in inputManager.Axes)
|
||||
{
|
||||
if (axis.positiveButton != null && !string.IsNullOrEmpty(axis.positiveButton))
|
||||
dict[axis.positiveButton] = axis.Name;
|
||||
}
|
||||
|
||||
var keys = new string[dict.Count];
|
||||
var values = new string[dict.Count];
|
||||
dict.Keys.CopyTo(keys, 0);
|
||||
dict.Values.CopyTo(values, 0);
|
||||
|
||||
inputKeys = keys;
|
||||
inputAxis = values;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static string GetOrCreateConvertedFile(string filePath)
|
||||
{
|
||||
var hash = GetMD5Hash(filePath);
|
||||
var convertedPath = Path.Combine(Path.GetTempPath(), $"UCA_IM_{hash}.txt");
|
||||
|
||||
if (!File.Exists(convertedPath))
|
||||
{
|
||||
Serilog.Log.Debug("Converting binary to text format of {File} to {Target}", filePath, convertedPath);
|
||||
var converter = new Binary2TextExec();
|
||||
converter.Exec(filePath, convertedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Debug("Converted file already exists at {Target}", convertedPath);
|
||||
}
|
||||
|
||||
return convertedPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hash of the file using MD5.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetMD5Hash(string filePath)
|
||||
{
|
||||
using var md5 = new MD5CryptoServiceProvider();
|
||||
return GetHash(filePath, md5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hash of the file using MD5.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetMD5Hash(Stream s)
|
||||
{
|
||||
using var md5 = new MD5CryptoServiceProvider();
|
||||
return GetHash(s, md5);
|
||||
}
|
||||
|
||||
private static string GetHash(string filePath, HashAlgorithm hasher)
|
||||
{
|
||||
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
return GetHash(fs, hasher);
|
||||
}
|
||||
|
||||
private static string GetHash(Stream s, HashAlgorithm hasher)
|
||||
{
|
||||
var hash = hasher.ComputeHash(s);
|
||||
var hashStr = Convert.ToBase64String(hash);
|
||||
//return hashStr.TrimEnd('=');
|
||||
var hashStrAlphaNumeric = System.Text.RegularExpressions.Regex.Replace(hashStr, "[^A-Za-z0-9]", "");
|
||||
return hashStrAlphaNumeric;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum AxisType
|
||||
{
|
||||
KeyOrMouseButton = 0,
|
||||
MouseMovement = 1,
|
||||
JoystickAxis = 2
|
||||
};
|
||||
|
||||
#pragma warning disable IDE1006
|
||||
|
||||
public class InputAxisMapper
|
||||
{
|
||||
public int serializedVersion { get; set; }
|
||||
|
||||
public string? m_Name { get; set; }
|
||||
public string? descriptiveName { get; set; }
|
||||
public string? descriptiveNegativeName { get; set; }
|
||||
public string? negativeButton { get; set; }
|
||||
public string? positiveButton { get; set; }
|
||||
public string? altNegativeButton { get; set; }
|
||||
public string? altPositiveButton { get; set; }
|
||||
|
||||
//public float gravity { get; set; }
|
||||
//public float dead { get; set; }
|
||||
//public float sensitivity { get; set; }
|
||||
public string? gravity { get; set; }
|
||||
public string? dead { get; set; }
|
||||
public string? sensitivity { get; set; }
|
||||
|
||||
//public bool snap { get; set; }
|
||||
public int snap { get; set; }
|
||||
//public bool invert { get; set; }
|
||||
public int invert { get; set; }
|
||||
|
||||
//public AxisType type { get; set; }
|
||||
public int type { get; set; }
|
||||
|
||||
public int axis { get; set; }
|
||||
public int joyNum { get; set; }
|
||||
}
|
||||
|
||||
public class InputAxis
|
||||
{
|
||||
readonly InputAxisMapper map;
|
||||
|
||||
public InputAxis(InputAxisMapper map)
|
||||
{
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public int SerializedVersion
|
||||
{
|
||||
get { return map.serializedVersion; }
|
||||
set { map.serializedVersion = value; }
|
||||
}
|
||||
|
||||
public string? Name => map.m_Name;
|
||||
public string? descriptiveName => map.descriptiveName;
|
||||
public string? descriptiveNegativeName => map.descriptiveNegativeName;
|
||||
public string? negativeButton => map.negativeButton;
|
||||
public string? positiveButton => map.positiveButton;
|
||||
public string? altNegativeButton => map.altNegativeButton;
|
||||
public string? altPositiveButton => map.altPositiveButton;
|
||||
|
||||
public float gravity => float.Parse(map.gravity);//**--format
|
||||
public float dead => float.Parse(map.dead);//**--format
|
||||
public float sensitivity => float.Parse(map.sensitivity);//**--format
|
||||
|
||||
public bool snap => map.snap != 0;
|
||||
public bool invert => map.invert != 0;
|
||||
|
||||
public AxisType type => (AxisType)map.type;
|
||||
|
||||
public int axis => map.axis;
|
||||
public int joyNum => map.joyNum;
|
||||
}
|
||||
|
||||
public class InputManagerMapper
|
||||
{
|
||||
public int m_ObjectHideFlags { get; set; }
|
||||
public int serializedVersion { get; set; }
|
||||
public int m_UsePhysicalKeys { get; set; }
|
||||
public List<InputAxisMapper>? m_Axes { get; set; }
|
||||
}
|
||||
|
||||
#pragma warning restore IDE1006
|
||||
|
||||
public class InputManager
|
||||
{
|
||||
readonly InputManagerMapper map;
|
||||
readonly List<InputAxis> axes;
|
||||
|
||||
public InputManager(InputManagerMapper map)
|
||||
{
|
||||
this.map = map;
|
||||
this.axes = new List<InputAxis>();
|
||||
|
||||
if (map.m_Axes == null)
|
||||
{
|
||||
Serilog.Log.Warning($"map.m_Axes is null");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var a in map.m_Axes)
|
||||
this.axes.Add(new InputAxis(a));
|
||||
}
|
||||
|
||||
public int ObjectHideFlags
|
||||
{
|
||||
get { return map.m_ObjectHideFlags; }
|
||||
set { map.m_ObjectHideFlags = value; }
|
||||
}
|
||||
|
||||
public int SerializedVersion
|
||||
{
|
||||
get { return map.serializedVersion; }
|
||||
set { map.serializedVersion = value; }
|
||||
}
|
||||
|
||||
public bool UsePhysicalKeys
|
||||
{
|
||||
get { return map.m_UsePhysicalKeys != 0; }
|
||||
set { map.m_UsePhysicalKeys = value ? 1 : 0; }
|
||||
}
|
||||
|
||||
/*public List<InputAxisMapper> Axes
|
||||
{
|
||||
get { return map.m_Axes; }
|
||||
set { map.m_Axes = value; }
|
||||
}*/
|
||||
public List<InputAxis> Axes => axes;
|
||||
}
|
||||
|
||||
public class Class13Mapper
|
||||
{
|
||||
public InputManagerMapper? InputManager { get; set; }
|
||||
}
|
||||
|
||||
public class Tag13Resolver : YamlDotNet.Serialization.INodeTypeResolver
|
||||
{
|
||||
public bool Resolve(YamlDotNet.Core.Events.NodeEvent? nodeEvent, ref Type currentType)
|
||||
{
|
||||
if (nodeEvent != null && !nodeEvent.Tag.IsEmpty && !nodeEvent.Tag.IsNonSpecific)
|
||||
{
|
||||
var tagValue = nodeEvent.Tag.Value;
|
||||
if (tagValue.EndsWith(":13"))
|
||||
{
|
||||
currentType = typeof(Class13Mapper);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abdf2bf92986f1d4f802bfedb8faf551
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
|
||||
[InitializeOnLoad]
|
||||
public static class LazyInitializer
|
||||
{
|
||||
static int counter;
|
||||
|
||||
static LazyInitializer()
|
||||
{
|
||||
counter = -5;// start initializing five frames later
|
||||
EditorApplication.update += OnUpdate;
|
||||
}
|
||||
|
||||
static void OnUpdate()
|
||||
{
|
||||
counter++;
|
||||
|
||||
if (counter == 1)
|
||||
MainThreadDispatcher.Bump();
|
||||
else if (counter == 2)
|
||||
Logger.ELogger.Bump();
|
||||
else if (counter == 3)
|
||||
Monitor.Bump();
|
||||
else if (counter == 4)
|
||||
MQTTnetInitializer.Bump();
|
||||
else if (counter == 5)
|
||||
Updater.CheckUpdateSilent();
|
||||
else if (counter >= 6)
|
||||
EditorApplication.update -= OnUpdate;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc6e883c86567a34a94d52b99adf98bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.merry-yellow.code-assist/Editor/Logger.meta
Normal file
8
Packages/com.merry-yellow.code-assist/Editor/Logger.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cf68cc5d4c752c489220a20f31005f1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using static System.IO.Path;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
public static class CommonTools
|
||||
{
|
||||
public static string GetScriptPath(string script)
|
||||
{
|
||||
var projectPath = GetProjectPathRaw();
|
||||
var toolPath = Combine(projectPath, "Packages/com.merry-yellow.code-assist/Editor/", script);
|
||||
return toolPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// does NOT include the trailing slash
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetExternalReferencesPath()
|
||||
{
|
||||
var projectPath = GetProjectPathRaw();
|
||||
var extRefPath = Combine(projectPath, "Packages/com.merry-yellow.code-assist/Editor/ExternalReferences");
|
||||
return extRefPath;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static string GetToolPath(string tool)
|
||||
{
|
||||
var projectPath = GetProjectPathRaw();
|
||||
var toolPath = Combine(projectPath, "Packages/com.merry-yellow.code-assist/Tools~/", tool);
|
||||
return toolPath;
|
||||
}
|
||||
|
||||
public static string GetInstallerPath(string installer)
|
||||
{
|
||||
var projectPath = GetProjectPathRaw();
|
||||
var installerPath = Combine(projectPath, "Packages/com.merry-yellow.code-assist/Installers~/", installer);
|
||||
return installerPath;
|
||||
}
|
||||
|
||||
public static string GetTagManagerFilePath()
|
||||
{
|
||||
var projectPath = GetProjectPathRaw();
|
||||
var tagManagerPath = Combine(projectPath, "ProjectSettings/TagManager.asset");
|
||||
return tagManagerPath;
|
||||
}
|
||||
|
||||
public static string GetInputManagerFilePath()
|
||||
{
|
||||
var projectPath = GetProjectPathRaw();
|
||||
var inputManagerPath = Combine(projectPath, "ProjectSettings/InputManager.asset");
|
||||
return inputManagerPath;
|
||||
}
|
||||
|
||||
public static string GetProjectPath()
|
||||
{
|
||||
var rawPath = GetProjectPathRaw();
|
||||
//var pathWithoutWhiteSpace = rawPath.Trim(); // this is done in OSPath ctor
|
||||
var osPath = new OSPath(rawPath);
|
||||
var unixPath = osPath.Unix;
|
||||
var trimmed = unixPath.TrimEnd('\\', '/');
|
||||
var capitalized = FirstCharToUpper(trimmed); // this is required for TypeScript, so doing it here as well just in case
|
||||
return capitalized!;
|
||||
}
|
||||
|
||||
static string? FirstCharToUpper(string? input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case null: return null;
|
||||
case "": return "";
|
||||
default: return input[0].ToString().ToUpper() + input.Substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the path to the project folder.
|
||||
/// </summary>
|
||||
/// <returns>The project folder path</returns>
|
||||
static string GetProjectPathRaw()
|
||||
{
|
||||
// Application.dataPath returns the path including /Assets, which we need to strip off
|
||||
var path = UnityEngine.Application.dataPath;
|
||||
var directory = new DirectoryInfo(path);
|
||||
var parent = directory.Parent;
|
||||
if (parent != null)
|
||||
return parent.FullName;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string GetHashForLogFile(string path) => Synchronizer.Model.Utilities.GetHashForLogFile(path);
|
||||
}
|
||||
|
||||
// https://github.com/dmitrynogin/cdsf/blob/master/Cds.Folders/OSPath.cs
|
||||
internal class OSPath
|
||||
{
|
||||
public static readonly OSPath Empty = "";
|
||||
|
||||
public static bool IsWindows => DirectorySeparatorChar == '\\';
|
||||
|
||||
public OSPath(string text)
|
||||
{
|
||||
Text = text.Trim();
|
||||
}
|
||||
|
||||
public static implicit operator OSPath(string text) => new OSPath(text);
|
||||
public static implicit operator string(OSPath path) => path.Normalized;
|
||||
public override string ToString() => Normalized;
|
||||
|
||||
protected string Text { get; }
|
||||
|
||||
public string Normalized => IsWindows ? Windows : Unix;
|
||||
public string Windows => Text.Replace('/', '\\');
|
||||
//public string Unix => Simplified.Text.Replace('\\', '/');
|
||||
public string Unix => Text.Replace('\\', '/');
|
||||
|
||||
public OSPath Relative => Simplified.Text.TrimStart('/', '\\');
|
||||
public OSPath Absolute => IsAbsolute ? this : "/" + Relative;
|
||||
|
||||
public bool IsAbsolute => IsRooted || HasVolume;
|
||||
public bool IsRooted => Text.Length >= 1 && (Text[0] == '/' || Text[0] == '\\');
|
||||
public bool HasVolume => Text.Length >= 2 && Text[1] == ':';
|
||||
public OSPath Simplified => HasVolume ? Text.Substring(2) : Text;
|
||||
|
||||
public OSPath Parent => GetDirectoryName(Text);
|
||||
|
||||
public bool Contains(OSPath path) =>
|
||||
Normalized.StartsWith(path);
|
||||
|
||||
public static OSPath operator +(OSPath left, OSPath right) =>
|
||||
new OSPath(Combine(left, right.Relative));
|
||||
|
||||
public static OSPath operator -(OSPath left, OSPath right) =>
|
||||
left.Contains(right)
|
||||
? new OSPath(left.Normalized.Substring(right.Normalized.Length)).Relative
|
||||
: left;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9066c480a2fcb9940a432377a49262af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
203
Packages/com.merry-yellow.code-assist/Editor/Logger/ELogger.cs
Normal file
203
Packages/com.merry-yellow.code-assist/Editor/Logger/ELogger.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
//using Meryel.UnityCodeAssist.Serilog;
|
||||
//using Meryel.UnityCodeAssist.Serilog.Core;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
|
||||
#if ELOGGER
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Logger
|
||||
{
|
||||
|
||||
//[InitializeOnLoad]
|
||||
public static class ELogger
|
||||
{
|
||||
public static event System.Action? OnVsInternalLogChanged;
|
||||
|
||||
|
||||
// Change 'new LoggerConfiguration().MinimumLevel.Debug();' if you change these values
|
||||
const Serilog.Events.LogEventLevel fileMinLevel = Serilog.Events.LogEventLevel.Debug;
|
||||
const Serilog.Events.LogEventLevel outputWindowMinLevel = Serilog.Events.LogEventLevel.Information;
|
||||
static LoggingLevelSwitch? fileLevelSwitch, outputWindowLevelSwitch;
|
||||
|
||||
//static bool IsInitialized { get; set; }
|
||||
|
||||
static ILogEventSink? _outputWindowSink;
|
||||
static ILogEventSink? _memorySink;
|
||||
|
||||
|
||||
public static string GetInternalLogContent() => _memorySink == null ? string.Empty : ((MemorySink)_memorySink).Export();
|
||||
public static int GetErrorCountInInternalLog() => _memorySink == null ? 0 : ((MemorySink)_memorySink).ErrorCount;
|
||||
public static int GetWarningCountInInternalLog() => _memorySink == null ? 0 : ((MemorySink)_memorySink).WarningCount;
|
||||
|
||||
public static string? FilePath { get; private set; }
|
||||
public static string? VSFilePath { get; private set; }
|
||||
|
||||
//**-- make it work with multiple clients
|
||||
static string? _vsInternalLog;
|
||||
public static string? VsInternalLog
|
||||
{
|
||||
get => _vsInternalLog;
|
||||
set
|
||||
{
|
||||
_vsInternalLog = value;
|
||||
OnVsInternalLogChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ELogger()
|
||||
{
|
||||
var isFirst = false;
|
||||
const string stateName = "isFirst";
|
||||
if (!SessionState.GetBool(stateName, false))
|
||||
{
|
||||
isFirst = true;
|
||||
SessionState.SetBool(stateName, true);
|
||||
}
|
||||
|
||||
var projectPath = CommonTools.GetProjectPath();
|
||||
var outputWindowSink = new System.Lazy<ILogEventSink>(() => new UnityOutputWindowSink(null));
|
||||
|
||||
Init(isFirst, projectPath, outputWindowSink);
|
||||
|
||||
if (isFirst)
|
||||
LogHeader(Application.unityVersion, projectPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty method for invoking static class ctor
|
||||
/// </summary>
|
||||
public static void Bump() { }
|
||||
|
||||
|
||||
static void LogHeader(string unityVersion, string solutionDir)
|
||||
{
|
||||
var os = System.Runtime.InteropServices.RuntimeInformation.OSDescription;
|
||||
var assisterVersion = Assister.Version;
|
||||
var syncModel = Synchronizer.Model.Utilities.Version;
|
||||
var hash = CommonTools.GetHashForLogFile(solutionDir);
|
||||
var port = Synchronizer.Model.Utilities.GetPortForMQTTnet(solutionDir);
|
||||
Serilog.Log.Debug(
|
||||
"Beginning logging {OS}, Unity {U}, Unity Code Assist {A}, Communication Protocol {SM}, Project: '{Dir}', Project Hash: {Hash}, Port: {Port}",
|
||||
os, unityVersion, assisterVersion, syncModel, solutionDir, hash, port);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static string GetFilePath(string solutionDir)
|
||||
{
|
||||
var solutionHash = CommonTools.GetHashForLogFile(solutionDir);
|
||||
var tempDir = System.IO.Path.GetTempPath();
|
||||
var fileName = $"UnityCodeAssist_U_Log_{solutionHash}_.TXT"; // hour code will be appended to the end of file, so add a trailing '_'
|
||||
var filePath = System.IO.Path.Combine(tempDir, fileName);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
static string GetVSFilePath(string solutionDir)
|
||||
{
|
||||
var solutionHash = CommonTools.GetHashForLogFile(solutionDir);
|
||||
var tempDir = System.IO.Path.GetTempPath();
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
var fileName = $"UnityCodeAssistLite_VS_Log_{solutionHash}_.TXT"; // hour code will be appended to the end of file, so add a trailing '_'
|
||||
#else
|
||||
var fileName = $"UnityCodeAssist_VS_Log_{solutionHash}_.TXT"; // hour code will be appended to the end of file, so add a trailing '_'
|
||||
#endif
|
||||
var filePath = System.IO.Path.Combine(tempDir, fileName);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
||||
public static void Init(bool isFirst, string solutionDir, System.Lazy<ILogEventSink> outputWindowSink)
|
||||
{
|
||||
|
||||
FilePath = GetFilePath(solutionDir);
|
||||
VSFilePath = GetVSFilePath(solutionDir);
|
||||
|
||||
fileLevelSwitch = new LoggingLevelSwitch(fileMinLevel);
|
||||
outputWindowLevelSwitch = new LoggingLevelSwitch(outputWindowMinLevel);
|
||||
|
||||
var config = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.Enrich.With(new DomainHashEnricher());
|
||||
|
||||
const string outputTemplate = "{Timestamp:HH:mm:ss.fff} [U] [{Level:u3}] [{DomainHash}] {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
config = config.WriteTo.PersistentFile(FilePath
|
||||
, outputTemplate: outputTemplate
|
||||
, shared: true
|
||||
, persistentFileRollingInterval: PersistentFileRollingInterval.Day
|
||||
, preserveLogFilename: true
|
||||
, levelSwitch: fileLevelSwitch
|
||||
, rollOnEachProcessRun: isFirst
|
||||
);
|
||||
|
||||
_outputWindowSink ??= outputWindowSink.Value;
|
||||
if (_outputWindowSink != null)
|
||||
config = config.WriteTo.Sink(_outputWindowSink, outputWindowMinLevel, outputWindowLevelSwitch);
|
||||
|
||||
_memorySink ??= new MemorySink(outputTemplate);
|
||||
config = config.WriteTo.Sink(_memorySink, fileMinLevel, null);
|
||||
|
||||
config = config.Destructure.With(new MyDestructuringPolicy());
|
||||
|
||||
Serilog.Log.Logger = config.CreateLogger();
|
||||
//switchableLogger.Set(config.CreateLogger(), disposePrev: true);
|
||||
|
||||
OnOptionsChanged();
|
||||
|
||||
//IsInitialized = true;
|
||||
}
|
||||
|
||||
public static void OnOptionsChanged()
|
||||
{
|
||||
// Since we don't use LogEventLevel.Fatal, we can use it for disabling sinks
|
||||
|
||||
var isLoggingToFile = OptionsIsLoggingToFile;
|
||||
var targetFileLevel = isLoggingToFile ? fileMinLevel : Serilog.Events.LogEventLevel.Fatal;
|
||||
if (fileLevelSwitch != null)
|
||||
fileLevelSwitch.MinimumLevel = targetFileLevel;
|
||||
|
||||
var isLoggingToOutputWindow = OptionsIsLoggingToOutputWindow;
|
||||
var targetOutputWindowLevel = isLoggingToOutputWindow ? outputWindowMinLevel : Serilog.Events.LogEventLevel.Fatal;
|
||||
if (outputWindowLevelSwitch != null)
|
||||
outputWindowLevelSwitch.MinimumLevel = targetOutputWindowLevel;
|
||||
}
|
||||
|
||||
//**-- UI for these two
|
||||
static bool OptionsIsLoggingToFile => true;
|
||||
static bool OptionsIsLoggingToOutputWindow => true;
|
||||
}
|
||||
|
||||
public class MyDestructuringPolicy : IDestructuringPolicy
|
||||
{
|
||||
// serilog cannot destruct StringArrayContainer by default, so do it manually
|
||||
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Serilog.Events.LogEventPropertyValue? result)
|
||||
{
|
||||
if (value is Synchronizer.Model.StringArrayContainer sac)
|
||||
{
|
||||
var items = sac.Container.Select(item => propertyValueFactory.CreatePropertyValue(item, true));
|
||||
result = new Serilog.Events.SequenceValue(items);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca3b9ef056f0ba843936ff335d12b0a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
//using Meryel.UnityCodeAssist.Serilog;
|
||||
//using Meryel.UnityCodeAssist.Serilog.Core;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
|
||||
using Meryel.Serilog;
|
||||
using Meryel.Serilog.Core;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Logger
|
||||
{
|
||||
|
||||
//[InitializeOnLoad]
|
||||
public static class ELogger
|
||||
{
|
||||
public static event System.Action? OnVsInternalLogChanged;
|
||||
|
||||
|
||||
// Change 'new LoggerConfiguration().MinimumLevel.Debug();' if you change these values
|
||||
const Serilog.Events.LogEventLevel fileMinLevel = Serilog.Events.LogEventLevel.Debug;
|
||||
const Serilog.Events.LogEventLevel outputWindowMinLevel = Serilog.Events.LogEventLevel.Information;
|
||||
static LoggingLevelSwitch? fileLevelSwitch, outputWindowLevelSwitch;
|
||||
|
||||
//static bool IsInitialized { get; set; }
|
||||
|
||||
static ILogEventSink? _outputWindowSink;
|
||||
static ILogEventSink? _memorySink;
|
||||
|
||||
|
||||
public static string GetInternalLogContent() => _memorySink == null ? string.Empty : ((Meryel.UnityCodeAssist.Logger.MemorySink)_memorySink).Export();
|
||||
public static int GetErrorCountInInternalLog() => _memorySink == null ? 0 : ((Meryel.UnityCodeAssist.Logger.MemorySink)_memorySink).ErrorCount;
|
||||
public static int GetWarningCountInInternalLog() => _memorySink == null ? 0 : ((Meryel.UnityCodeAssist.Logger.MemorySink)_memorySink).WarningCount;
|
||||
|
||||
public static string? FilePath => Meryel.UnityCodeAssist.Logger.ELogger.UnityFilePath;
|
||||
public static string? VSFilePath => Meryel.UnityCodeAssist.Logger.ELogger.VisualStudioFilePath;
|
||||
|
||||
//**-- make it work with multiple clients
|
||||
static string? _vsInternalLog;
|
||||
public static string? VsInternalLog
|
||||
{
|
||||
get => _vsInternalLog;
|
||||
set
|
||||
{
|
||||
_vsInternalLog = value;
|
||||
OnVsInternalLogChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ELogger()
|
||||
{
|
||||
fileLevelSwitch = null;
|
||||
outputWindowLevelSwitch = null;
|
||||
_memorySink = null;
|
||||
|
||||
var isFirst = false;
|
||||
const string stateName = "isFirst";
|
||||
if (!SessionState.GetBool(stateName, false))
|
||||
{
|
||||
isFirst = true;
|
||||
SessionState.SetBool(stateName, true);
|
||||
}
|
||||
|
||||
var projectPath = CommonTools.GetProjectPath();
|
||||
var outputWindowSink = new System.Lazy<ILogEventSink>(() => new UnityOutputWindowSink(null));
|
||||
|
||||
Init(isFirst, projectPath, outputWindowSink);
|
||||
|
||||
if (isFirst)
|
||||
LogHeader(Application.unityVersion, projectPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty method for invoking static class ctor
|
||||
/// </summary>
|
||||
public static void Bump() { }
|
||||
|
||||
|
||||
static void LogHeader(string unityVersion, string solutionDir)
|
||||
{
|
||||
var os = System.Runtime.InteropServices.RuntimeInformation.OSDescription;
|
||||
var assisterVersion = Assister.Version;
|
||||
var syncModel = Synchronizer.Model.Utilities.Version;
|
||||
var hash = CommonTools.GetHashForLogFile(solutionDir);
|
||||
var port = Synchronizer.Model.Utilities.GetPortForMQTTnet(solutionDir);
|
||||
Serilog.Log.Debug(
|
||||
"Beginning logging {OS}, Unity {U}, Unity Code Assist {A}, Communication Protocol {SM}, Project: '{Dir}', Project Hash: {Hash}, Port: {Port}",
|
||||
os, unityVersion, assisterVersion, syncModel, solutionDir, hash, port);
|
||||
}
|
||||
|
||||
|
||||
public static void Init(bool isFirst, string solutionDir, System.Lazy<ILogEventSink> outputWindowSink)
|
||||
{
|
||||
//var solutionHash = Common.CommonTools.GetHashOfPath(solutionDir);
|
||||
var solutionHash = CommonTools.GetHashForLogFile(solutionDir); // dir is osSafePath
|
||||
_outputWindowSink ??= outputWindowSink.Value;
|
||||
var sinkWrapper = new System.Lazy<Meryel.Serilog.Core.ILogEventSink>(() => _outputWindowSink);
|
||||
|
||||
Meryel.UnityCodeAssist.Logger.ELogger.Init(
|
||||
UnityCodeAssist.Logger.ELogger.State.FullyInitialized,
|
||||
UnityCodeAssist.Logger.ELogger.PackagePriority.High,
|
||||
solutionDir, solutionHash, "UnityCodeAssist", ProjectData.Domain.Unity,
|
||||
sinkWrapper, null, null, null, null);
|
||||
}
|
||||
|
||||
public static void OnOptionsChanged()
|
||||
{
|
||||
// Since we don't use LogEventLevel.Fatal, we can use it for disabling sinks
|
||||
|
||||
var isLoggingToFile = OptionsIsLoggingToFile;
|
||||
var targetFileLevel = isLoggingToFile ? fileMinLevel : Serilog.Events.LogEventLevel.Fatal;
|
||||
if (fileLevelSwitch != null)
|
||||
fileLevelSwitch.MinimumLevel = targetFileLevel;
|
||||
|
||||
var isLoggingToOutputWindow = OptionsIsLoggingToOutputWindow;
|
||||
var targetOutputWindowLevel = isLoggingToOutputWindow ? outputWindowMinLevel : Serilog.Events.LogEventLevel.Fatal;
|
||||
if (outputWindowLevelSwitch != null)
|
||||
outputWindowLevelSwitch.MinimumLevel = targetOutputWindowLevel;
|
||||
}
|
||||
|
||||
//**-- UI for these two
|
||||
static bool OptionsIsLoggingToFile => true;
|
||||
static bool OptionsIsLoggingToOutputWindow => true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44a33ba3272c55d4fad27588d4bdcb2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
//using Meryel.UnityCodeAssist.Serilog;
|
||||
//using Meryel.UnityCodeAssist.Serilog.Core;
|
||||
//using Meryel.UnityCodeAssist.Serilog.Events;
|
||||
//using Meryel.UnityCodeAssist.Serilog.Configuration;
|
||||
using Meryel.Serilog;
|
||||
using Meryel.Serilog.Core;
|
||||
using Meryel.Serilog.Events;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Logger
|
||||
{
|
||||
public class UnityOutputWindowSink : ILogEventSink
|
||||
{
|
||||
private readonly IFormatProvider? _formatProvider;
|
||||
|
||||
public UnityOutputWindowSink(IFormatProvider? formatProvider)
|
||||
{
|
||||
_formatProvider = formatProvider;
|
||||
}
|
||||
|
||||
public void Emit(LogEvent? logEvent)
|
||||
{
|
||||
if (logEvent == null)
|
||||
return;
|
||||
|
||||
var message = logEvent.RenderMessage(_formatProvider, false);
|
||||
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
//case LogEventLevel.Verbose:
|
||||
//case LogEventLevel.Debug:
|
||||
case LogEventLevel.Information:
|
||||
UnityEngine.Debug.Log(message);
|
||||
break;
|
||||
case LogEventLevel.Warning:
|
||||
UnityEngine.Debug.LogWarning(message);
|
||||
break;
|
||||
case LogEventLevel.Error:
|
||||
case LogEventLevel.Fatal:
|
||||
UnityEngine.Debug.LogError(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0cc7f1339aeef54898503bd5cdc51fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
using MQTTnet = Meryel.UnityCodeAssist.MQTTnet;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
//[InitializeOnLoad]
|
||||
public static class MQTTnetInitializer
|
||||
{
|
||||
public static MQTTnetPublisher? Publisher;
|
||||
|
||||
static MQTTnetInitializer()
|
||||
{
|
||||
EditorApplication.quitting += EditorApplication_quitting;
|
||||
AssemblyReloadEvents.beforeAssemblyReload += AssemblyReloadEvents_beforeAssemblyReload;
|
||||
//AssemblyReloadEvents.afterAssemblyReload += AssemblyReloadEvents_afterAssemblyReload;
|
||||
|
||||
RunOnceOnUpdate(Initialize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty method for invoking static class ctor
|
||||
/// </summary>
|
||||
public static void Bump() { }
|
||||
|
||||
/// <summary>
|
||||
/// false for profiler standalone process
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsMainUnityEditorProcess()
|
||||
{
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
if (UnityEditor.AssetDatabase.IsAssetImportWorkerProcess())
|
||||
return false;
|
||||
#elif UNITY_2019_3_OR_NEWER
|
||||
if (UnityEditor.Experimental.AssetDatabaseExperimental.IsAssetImportWorkerProcess())
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Secondary)
|
||||
return false;
|
||||
#elif UNITY_2020_2_OR_NEWER
|
||||
if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Slave)
|
||||
return false;
|
||||
#elif UNITY_2020_1_OR_NEWER
|
||||
if (global::Unity.MPE.ProcessService.level == global::Unity.MPE.ProcessLevel.UMP_SLAVE)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (!IsMainUnityEditorProcess())
|
||||
{
|
||||
// if try to creaate NetMQ, will recieve AddressAlreadyInUseException during binding
|
||||
Serilog.Log.Debug("MQTTnet won't initialize on secondary processes");
|
||||
return;
|
||||
}
|
||||
|
||||
Serilog.Log.Debug("MQTTnet initializing");
|
||||
|
||||
//Serilog.Log.Debug("MQTTnet constructing");
|
||||
Publisher = new MQTTnetPublisher();
|
||||
|
||||
RunOnShutdown(OnShutDown);
|
||||
Serilog.Log.Debug("MQTTnet initialized");
|
||||
}
|
||||
|
||||
private static void OnShutDown()
|
||||
{
|
||||
Serilog.Log.Debug("MQTTnet OnShutDown");
|
||||
Clear();
|
||||
}
|
||||
|
||||
//private static void AssemblyReloadEvents_afterAssemblyReload()
|
||||
//{
|
||||
// Serilog.Log.Debug("MQTTnet AssemblyReloadEvents_afterAssemblyReload");
|
||||
//}
|
||||
|
||||
private static void AssemblyReloadEvents_beforeAssemblyReload()
|
||||
{
|
||||
Serilog.Log.Debug("MQTTnet AssemblyReloadEvents_beforeAssemblyReload");
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
private static void EditorApplication_quitting()
|
||||
{
|
||||
Serilog.Log.Debug("MQTTnet EditorApplication_quitting");
|
||||
|
||||
Publisher?.SendDisconnect();
|
||||
Clear();
|
||||
}
|
||||
|
||||
static void Clear() => Publisher?.Clear();
|
||||
|
||||
|
||||
private static void RunOnceOnUpdate(Action action)
|
||||
{
|
||||
void callback()
|
||||
{
|
||||
EditorApplication.update -= callback;
|
||||
action();
|
||||
}
|
||||
|
||||
EditorApplication.update += callback;
|
||||
}
|
||||
|
||||
private static void RunOnShutdown(Action action)
|
||||
{
|
||||
// Mono on OSX has all kinds of quirks on AppDomain shutdown
|
||||
//if (!VisualStudioEditor.IsWindows)
|
||||
//return;
|
||||
#if !UNITY_EDITOR_WIN
|
||||
return;
|
||||
#else
|
||||
AppDomain.CurrentDomain.DomainUnload += (_, __) => action();
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a503036d3d400042a5d9fdd5564202b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
926
Packages/com.merry-yellow.code-assist/Editor/MQTTnetPublisher.cs
Normal file
926
Packages/com.merry-yellow.code-assist/Editor/MQTTnetPublisher.cs
Normal file
@@ -0,0 +1,926 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Threading;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
using Application = UnityEngine.Application;
|
||||
|
||||
using Meryel.UnityCodeAssist.MQTTnet;
|
||||
using Meryel.UnityCodeAssist.MQTTnet.Server;
|
||||
using Meryel.UnityCodeAssist.MQTTnet.Protocol;
|
||||
using Meryel.UnityCodeAssist.MQTTnet.Adapter;
|
||||
using Meryel.UnityCodeAssist.MQTTnet.Implementations;
|
||||
using Meryel.UnityCodeAssist.MQTTnet.Diagnostics;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
using MQTTnet = Meryel.UnityCodeAssist.MQTTnet;
|
||||
using Newtonsoft = Meryel.UnityCodeAssist.Newtonsoft;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
//**--
|
||||
// can also do this for better clear, sometimes it gets locked
|
||||
// https://answers.unity.com/questions/704066/callback-before-unity-reloads-editor-assemblies.html#
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
public class MQTTnetPublisher : Synchronizer.Model.IProcessor
|
||||
{
|
||||
MqttServer? broker;
|
||||
|
||||
CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
readonly Synchronizer.Model.Manager syncMngr;
|
||||
|
||||
//public readonly List<Synchronizer.Model.Connect> clients;
|
||||
readonly System.Collections.Concurrent.ConcurrentDictionary<string, Synchronizer.Model.Connect> _clients;
|
||||
|
||||
public IEnumerable<Synchronizer.Model.Connect> Clients => _clients.Values.Where(c => c.NodeKind != Synchronizer.Model.NodeKind.SemiClient_RoslynAnalyzer.ToString());
|
||||
|
||||
Synchronizer.Model.Connect? _self;
|
||||
|
||||
Synchronizer.Model.Connect Self => _self!;
|
||||
|
||||
void InitializeSelf()
|
||||
{
|
||||
var projectPath = CommonTools.GetProjectPath();
|
||||
_self = new Synchronizer.Model.Connect()
|
||||
{
|
||||
ModelVersion = Synchronizer.Model.Utilities.Version,
|
||||
ProjectPath = projectPath,
|
||||
ProjectName = getProjectName(),
|
||||
ContactInfo = $"Unity {Application.unityVersion}",
|
||||
AssemblyVersion = Assister.Version,
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
LiteOrFull = "Lite",
|
||||
#else
|
||||
LiteOrFull = "Full",
|
||||
#endif
|
||||
NodeKind = Synchronizer.Model.NodeKind.Server.ToString(),
|
||||
ClientId = "",
|
||||
};
|
||||
|
||||
string getProjectName()
|
||||
{
|
||||
string[] s = projectPath.Split('/');
|
||||
#pragma warning disable IDE0056
|
||||
string projectName = s[s.Length - 1];
|
||||
#pragma warning restore IDE0056
|
||||
//Logg("project = " + projectName);
|
||||
return projectName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void LogContext()
|
||||
{
|
||||
}
|
||||
|
||||
public MQTTnetPublisher()
|
||||
{
|
||||
// LogContext();
|
||||
|
||||
Serilog.Log.Debug("MQTTnet server initializing, begin");
|
||||
|
||||
InitializeSelf();
|
||||
|
||||
_clients = new System.Collections.Concurrent.ConcurrentDictionary<string, Synchronizer.Model.Connect>();
|
||||
syncMngr = new Synchronizer.Model.Manager(this);
|
||||
|
||||
var port = Synchronizer.Model.Utilities.GetPortForMQTTnet(Self!.ProjectPath);
|
||||
|
||||
|
||||
// Create the options for our MQTT Broker
|
||||
MqttServerOptionsBuilder options = new MqttServerOptionsBuilder()
|
||||
// set endpoint to localhost
|
||||
.WithDefaultEndpoint()
|
||||
// port used will be 707
|
||||
.WithDefaultEndpointPort(port)
|
||||
// handler for new connections
|
||||
//.WithConnectionValidator(OnNewConnection)
|
||||
// handler for new messages
|
||||
//.WithApplicationMessageInterceptor(OnNewMessage)
|
||||
|
||||
// disable ipv6 for linux (and possibly macos too), otherwise socket exception is thrown
|
||||
.WithDefaultEndpointBoundIPV6Address(System.Net.IPAddress.None)
|
||||
|
||||
// for preventing socket ex after server restart https://github.com/dotnet/MQTTnet/issues/494
|
||||
// System.Net.Sockets.SocketException (0x80004005): Only one usage of each socket address (protocol/network address/port) is normally permitted.
|
||||
.WithTlsEndpointReuseAddress()
|
||||
;
|
||||
|
||||
IList<IMqttServerAdapter> DefaultServerAdapters = new List<IMqttServerAdapter>()
|
||||
{
|
||||
new MqttTcpServerAdapter(),
|
||||
};
|
||||
var logger = new MqttNetNullLogger();
|
||||
|
||||
|
||||
|
||||
broker = new MqttServer(options.Build(), DefaultServerAdapters, logger);
|
||||
|
||||
broker.InterceptingPublishAsync += Broker_InterceptingPublishAsync;
|
||||
broker.ClientDisconnectedAsync += Broker_ClientDisconnectedAsync;
|
||||
|
||||
Serilog.Log.Debug("MQTTnet server initializing, constructed broker, port: {Port}", port);
|
||||
|
||||
try
|
||||
{
|
||||
//broker.StartAsync().GetAwaiter().GetResult();
|
||||
|
||||
var startTask = Task.Run(() => broker.StartAsync());
|
||||
if (!startTask.Wait(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
Serilog.Log.Error("MQTTnet broker.StartAsync timed out.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Serilog.Log.Debug("MQTTnet server initializing, started broker");
|
||||
}
|
||||
catch (System.Net.Sockets.SocketException socketEx)
|
||||
{
|
||||
Serilog.Log.Error(socketEx, "Socket exception");
|
||||
LogContext();
|
||||
//Serilog.Log.Warning("Socket exception disposing pubSocket");
|
||||
//broker.Dispose();
|
||||
//broker = null;
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "MQTTnet broker.StartAsync failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//pubSocket.SendReady += PubSocket_SendReady;
|
||||
//SendConnect();
|
||||
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
//pullThread = new System.Threading.Thread(async () => await PullAsync(conn.pushPull, pullThreadCancellationTokenSource.Token));
|
||||
//pullThread = new System.Threading.Thread(() => InitPull(conn.pushPull, pullTaskCancellationTokenSource.Token));
|
||||
//pullThread.Start();
|
||||
//Task.Run(() => InitPullAsync());
|
||||
|
||||
|
||||
Serilog.Log.Debug("MQTTnet server initializing, initialized");
|
||||
|
||||
// need to sleep here, clients will take some time to start subscribing
|
||||
// https://github.com/zeromq/netmq/issues/482#issuecomment-182200323
|
||||
Thread.Sleep(1000);
|
||||
SendConnect();
|
||||
|
||||
Serilog.Log.Debug("MQTTnet server initializing, initialized at {port} with {projectPath}", port, Self!.ProjectPath);
|
||||
}
|
||||
|
||||
private Task Broker_ClientDisconnectedAsync(ClientDisconnectedEventArgs arg)
|
||||
{
|
||||
try
|
||||
{
|
||||
var removed = _clients.TryRemove(arg.ClientId, out _);
|
||||
Serilog.Log.Debug("Broker_ClientDisconnectedAsync {ClientId} {Result}", arg.ClientId, removed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "async exception at {Location}", nameof(Broker_ClientDisconnectedAsync));
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Broker_InterceptingPublishAsync(InterceptingPublishEventArgs arg)
|
||||
{
|
||||
try
|
||||
{
|
||||
// if server message
|
||||
if (string.IsNullOrEmpty(arg.ClientId))
|
||||
return Task.CompletedTask;
|
||||
|
||||
Serilog.Log.Verbose("mqttnet consume {topic} {content}", arg.ApplicationMessage.Topic, arg.ApplicationMessage.ConvertPayloadToString());
|
||||
|
||||
var topic = arg.ApplicationMessage.Topic;
|
||||
var header = topic.Substring(3); // for "cs/" prefix
|
||||
var content = arg.ApplicationMessage.ConvertPayloadToString();
|
||||
|
||||
MainThreadDispatcher.Add(() => syncMngr.ProcessMessage(header, content));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "async exception at {Location}", nameof(Broker_InterceptingPublishAsync));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// LogContext();
|
||||
|
||||
Serilog.Log.Verbose("MQTTnet clearing {HasBroker}", (broker != null));
|
||||
|
||||
var server = broker;
|
||||
if (server != null)
|
||||
{
|
||||
server.InterceptingPublishAsync -= Broker_InterceptingPublishAsync;
|
||||
Serilog.Log.Verbose("MQTTnet clearing, removed events");
|
||||
}
|
||||
|
||||
cancellationTokenSource?.Cancel();
|
||||
cancellationTokenSource = null;
|
||||
Serilog.Log.Verbose("MQTTnet clearing, cancelled async token");
|
||||
|
||||
if (server == null)
|
||||
return;
|
||||
|
||||
// broker?.StopAsync().GetAwaiter().GetResult(); // this line was freezing Unity editor, so calling Task.Run().Wait() instead
|
||||
try
|
||||
{
|
||||
var stopTask = Task.Run(() => server.StopAsync());
|
||||
if (!stopTask.Wait(TimeSpan.FromSeconds(5))) // give it five secs to complete
|
||||
{
|
||||
Serilog.Log.Error("MQTTnet broker.StopAsync timed out.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "MQTTnet broker.StopAsync failed.");
|
||||
}
|
||||
|
||||
Serilog.Log.Verbose("MQTTnet clearing, stopped broker");
|
||||
try
|
||||
{
|
||||
server.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Warning(ex, "MQTTnet broker.Dispose failed.");
|
||||
}
|
||||
|
||||
server = null;
|
||||
broker = null;
|
||||
|
||||
Serilog.Log.Debug("MQTTnet clearing, cleared");
|
||||
}
|
||||
|
||||
string SerializeObject<T>(T obj)
|
||||
where T : class
|
||||
{
|
||||
// Odin cant serialize string arrays, https://github.com/TeamSirenix/odin-serializer/issues/26
|
||||
//var buffer = OdinSerializer.SerializationUtility.SerializeValue<T>(obj, OdinSerializer.DataFormat.JSON);
|
||||
//var str = System.Text.Encoding.UTF8.GetString(buffer, 0, buffer.Length);
|
||||
|
||||
// Newtonsoft works fine, but needs package reference
|
||||
//var str = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
|
||||
|
||||
// not working
|
||||
//var str = EditorJsonUtility.ToJson(obj);
|
||||
|
||||
// needs nuget
|
||||
//System.Text.Json.JsonSerializer;
|
||||
|
||||
//var str = TinyJson.JsonWriter.ToJson(obj);
|
||||
//var str = Meryel.UnityCodeAssist.ProjectData.LitJson.JsonMapper.ToJson(obj);
|
||||
var str = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void SendAux(Synchronizer.Model.IMessage message, bool logContent = true)
|
||||
{
|
||||
if (message == null)
|
||||
return;
|
||||
|
||||
SendAux(message.GetType().Name, message, logContent);
|
||||
}
|
||||
|
||||
void SendAux(string messageType, object content, bool logContent = true)
|
||||
{
|
||||
if (logContent)
|
||||
Serilog.Log.Debug("Publishing {MessageType} {@Content}", messageType, content);
|
||||
else
|
||||
Serilog.Log.Debug("Publishing {MessageType}", messageType);
|
||||
|
||||
var publisher = broker;
|
||||
if (publisher != null)
|
||||
//publisher.SendMoreFrame(messageType).SendFrame(SerializeObject(content));
|
||||
{
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("sc/" + messageType) // sc/ => server->client message
|
||||
.WithRetainFlag(false)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
|
||||
.WithPayload(SerializeObject(content))
|
||||
.Build();
|
||||
|
||||
//broker?.InjectApplicationMessage(new InjectedMqttApplicationMessage(applicationMessage), cancellationTokenSource?.Token ?? default).GetAwaiter().GetResult();
|
||||
broker?.InjectApplicationMessage(new InjectedMqttApplicationMessage(applicationMessage), cancellationTokenSource?.Token ?? default);
|
||||
}
|
||||
else
|
||||
Serilog.Log.Error("Publisher socket is null");
|
||||
}
|
||||
|
||||
public void SendConnect()
|
||||
{
|
||||
var connect = Self;
|
||||
|
||||
SendAux(connect);
|
||||
}
|
||||
|
||||
public void SendDisconnect()
|
||||
{
|
||||
var disconnect = new Synchronizer.Model.Disconnect()
|
||||
{
|
||||
ModelVersion = Self.ModelVersion,
|
||||
ProjectPath = Self.ProjectPath,
|
||||
ProjectName = Self.ProjectName,
|
||||
ContactInfo = Self.ContactInfo,
|
||||
AssemblyVersion = Self.AssemblyVersion,
|
||||
LiteOrFull = Self.LiteOrFull,
|
||||
NodeKind = Self.NodeKind,
|
||||
ClientId = Self.ClientId,
|
||||
};
|
||||
|
||||
SendAux(disconnect);
|
||||
}
|
||||
|
||||
public void SendConnectionInfo()
|
||||
{
|
||||
var connectionInfo = new Synchronizer.Model.ConnectionInfo()
|
||||
{
|
||||
ModelVersion = Self.ModelVersion,
|
||||
ProjectPath = Self.ProjectPath,
|
||||
ProjectName = Self.ProjectName,
|
||||
ContactInfo = Self.ContactInfo,
|
||||
AssemblyVersion = Self.AssemblyVersion,
|
||||
LiteOrFull = Self.LiteOrFull,
|
||||
NodeKind = Self.NodeKind,
|
||||
ClientId = Self.ClientId,
|
||||
};
|
||||
|
||||
SendAux(connectionInfo);
|
||||
}
|
||||
|
||||
public void SendHandshake()
|
||||
{
|
||||
var handshake = new Synchronizer.Model.Handshake();
|
||||
|
||||
SendAux(handshake);
|
||||
}
|
||||
|
||||
public void SendRequestInternalLog()
|
||||
{
|
||||
var requestInternalLog = new Synchronizer.Model.RequestInternalLog();
|
||||
|
||||
SendAux(requestInternalLog);
|
||||
}
|
||||
|
||||
public void SendRequestUpdate(string app, string path, bool isInteractive)
|
||||
{
|
||||
var requestUpdate = new Synchronizer.Model.RequestUpdate()
|
||||
{
|
||||
App = app,
|
||||
Path = path,
|
||||
IsInteractive = isInteractive,
|
||||
};
|
||||
|
||||
SendAux(requestUpdate);
|
||||
}
|
||||
|
||||
public void SendInternalLog()
|
||||
{
|
||||
var internalLog = new Synchronizer.Model.InternalLog()
|
||||
{
|
||||
LogContent = Logger.ELogger.GetInternalLogContent(),
|
||||
};
|
||||
|
||||
SendAux(internalLog, logContent: false);
|
||||
}
|
||||
|
||||
|
||||
void SendStringArrayAux(string id, string[] array)
|
||||
{
|
||||
var stringArray = new Synchronizer.Model.StringArray()
|
||||
{
|
||||
Id = id,
|
||||
Array = array,
|
||||
};
|
||||
|
||||
SendAux(stringArray);
|
||||
}
|
||||
|
||||
void SendStringArrayContainerAux(params (string id, string[] array)[] container)
|
||||
{
|
||||
var stringArrayContainer = new Synchronizer.Model.StringArrayContainer()
|
||||
{
|
||||
Container = new Synchronizer.Model.StringArray[container.Length],
|
||||
};
|
||||
|
||||
for (int i = 0; i < container.Length; i++)
|
||||
{
|
||||
stringArrayContainer.Container[i] = new Synchronizer.Model.StringArray
|
||||
{
|
||||
Id = container[i].id,
|
||||
Array = container[i].array
|
||||
};
|
||||
}
|
||||
|
||||
SendAux(stringArrayContainer);
|
||||
}
|
||||
|
||||
public void SendTags(string[] tags) =>
|
||||
SendStringArrayAux(Synchronizer.Model.Ids.Tags, tags);
|
||||
|
||||
public void SendLayers(string[] layerNames, string[] layerIndices)
|
||||
{
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.Layers, layerNames),
|
||||
(Synchronizer.Model.Ids.LayerIndices, layerIndices));
|
||||
}
|
||||
|
||||
public void SendSortingLayers(string[] sortingLayers, string[] sortingLayerIds, string[] sortingLayerValues)
|
||||
{
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.SortingLayers, sortingLayers),
|
||||
(Synchronizer.Model.Ids.SortingLayerIds, sortingLayerIds),
|
||||
(Synchronizer.Model.Ids.SortingLayerValues, sortingLayerValues));
|
||||
}
|
||||
|
||||
public void SendRenderingLayers(string[] renderingLayers, string[] renderingLayerIndices)
|
||||
{
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.RenderingLayers, renderingLayers),
|
||||
(Synchronizer.Model.Ids.RenderingLayerIndices, renderingLayerIndices));
|
||||
}
|
||||
|
||||
public void SendPlayerPrefs(string[] playerPrefKeys, string[] playerPrefValues,
|
||||
string[] playerPrefStringKeys, string[] playerPrefIntegerKeys, string[] playerPrefFloatKeys)
|
||||
{
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.PlayerPrefKeys, playerPrefKeys),
|
||||
(Synchronizer.Model.Ids.PlayerPrefValues, playerPrefValues),
|
||||
(Synchronizer.Model.Ids.PlayerPrefStringKeys, playerPrefStringKeys),
|
||||
(Synchronizer.Model.Ids.PlayerPrefIntegerKeys, playerPrefIntegerKeys),
|
||||
(Synchronizer.Model.Ids.PlayerPrefFloatKeys, playerPrefFloatKeys)
|
||||
);
|
||||
}
|
||||
|
||||
public void SendEditorPrefs(string[] editorPrefKeys, string[] editorPrefValues,
|
||||
string[] editorPrefStringKeys, string[] editorPrefIntegerKeys, string[] editorPrefFloatKeys,
|
||||
string[] editorPrefBooleanKeys)
|
||||
{
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.EditorPrefKeys, editorPrefKeys),
|
||||
(Synchronizer.Model.Ids.EditorPrefValues, editorPrefValues),
|
||||
(Synchronizer.Model.Ids.EditorPrefStringKeys, editorPrefStringKeys),
|
||||
(Synchronizer.Model.Ids.EditorPrefIntegerKeys, editorPrefIntegerKeys),
|
||||
(Synchronizer.Model.Ids.EditorPrefFloatKeys, editorPrefFloatKeys),
|
||||
(Synchronizer.Model.Ids.EditorPrefBooleanKeys, editorPrefBooleanKeys)
|
||||
);
|
||||
}
|
||||
|
||||
public void SendInputManager(string[] axisNames, string[] axisInfos, string[] buttonKeys, string[] buttonAxis, string[] joystickNames)
|
||||
{
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.InputManagerAxes, axisNames),
|
||||
(Synchronizer.Model.Ids.InputManagerAxisInfos, axisInfos),
|
||||
(Synchronizer.Model.Ids.InputManagerButtonKeys, buttonKeys),
|
||||
(Synchronizer.Model.Ids.InputManagerButtonAxis, buttonAxis),
|
||||
(Synchronizer.Model.Ids.InputManagerJoystickNames, joystickNames)
|
||||
);
|
||||
}
|
||||
|
||||
public void SendSceneList(string[] sceneNames, string[] scenePaths, string[] sceneBuildIndices,
|
||||
string[] sceneNamesAndPaths, string[] scenePathsAndNames)
|
||||
{
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.SceneNames, sceneNames),
|
||||
(Synchronizer.Model.Ids.ScenePaths, scenePaths),
|
||||
(Synchronizer.Model.Ids.SceneBuildIndices, sceneBuildIndices),
|
||||
(Synchronizer.Model.Ids.SceneNamesAndPaths, sceneNamesAndPaths),
|
||||
(Synchronizer.Model.Ids.ScenePathsAndNames, scenePathsAndNames)
|
||||
);
|
||||
}
|
||||
|
||||
public void SendScriptMissing(string component)
|
||||
{
|
||||
var scriptMissing = new Synchronizer.Model.ScriptMissing()
|
||||
{
|
||||
Component = component,
|
||||
};
|
||||
|
||||
SendAux(scriptMissing);
|
||||
}
|
||||
|
||||
public void SendComponentHumanTrait(string[] bones, string[] muscles)
|
||||
{
|
||||
//var humanTrait = new Synchronizer.Model.Components.HumanTrait();
|
||||
|
||||
var boneIndices = new string[bones.Length];
|
||||
var boneNames = new string[bones.Length];
|
||||
for (int i = 0; i < bones.Length; i++)
|
||||
{
|
||||
boneIndices[i] = i.ToString();
|
||||
boneNames[i] = bones[i];
|
||||
}
|
||||
|
||||
var muscleIndices = new string[muscles.Length];
|
||||
var muscleNames = new string[muscles.Length];
|
||||
for (int i = 0; i < muscles.Length; i++)
|
||||
{
|
||||
muscleIndices[i] = i.ToString();
|
||||
muscleNames[i] = muscles[i];
|
||||
}
|
||||
SendStringArrayContainerAux(
|
||||
(Synchronizer.Model.Ids.AnimationHumanBones, boneNames),
|
||||
(Synchronizer.Model.Ids.AnimationHumanBoneIndices, boneIndices),
|
||||
(Synchronizer.Model.Ids.AnimationHumanMuscles, muscleNames),
|
||||
(Synchronizer.Model.Ids.AnimationHumanMuscleIndices, muscleIndices)
|
||||
);
|
||||
}
|
||||
|
||||
public void SendShaderGlobalKeywords()
|
||||
{
|
||||
SendStringArrayAux(Synchronizer.Model.Ids.ShaderGlobalKeywords, Shader.globalKeywords.Select(k => k.name).ToArray());
|
||||
}
|
||||
|
||||
public void SendGameObject(GameObject go)
|
||||
{
|
||||
if (!go)
|
||||
return;
|
||||
|
||||
Serilog.Log.Debug("SendGO: {GoName}", go.name);
|
||||
|
||||
var dataOfSelf = go.ToSyncModel(priority:10000);
|
||||
if (dataOfSelf != null)
|
||||
SendAux(dataOfSelf);
|
||||
|
||||
var dataOfHierarchy = go.ToSyncModelOfHierarchy();
|
||||
if (dataOfHierarchy != null)
|
||||
{
|
||||
foreach (var doh in dataOfHierarchy)
|
||||
SendAux(doh);
|
||||
}
|
||||
|
||||
var dataOfComponents = go.ToSyncModelOfComponents();
|
||||
if (dataOfComponents != null)
|
||||
{
|
||||
foreach (var doc in dataOfComponents)
|
||||
SendAux(doc);
|
||||
}
|
||||
|
||||
var dataOfComponentAnimator = go.ToSyncModelOfComponentAnimator();
|
||||
if (dataOfComponentAnimator != null)
|
||||
SendAux(dataOfComponentAnimator);
|
||||
|
||||
var dataOfComponentAnimation = go.ToSyncModelOfComponentAnimation();
|
||||
if (dataOfComponentAnimation != null)
|
||||
SendAux(dataOfComponentAnimation);
|
||||
|
||||
var dataOfComponentMaterial = go.ToSyncModelOfComponentMaterial();
|
||||
if (dataOfComponentMaterial != null)
|
||||
SendAux(dataOfComponentMaterial);
|
||||
}
|
||||
|
||||
public void SendScriptableObject(ScriptableObject so)
|
||||
{
|
||||
Serilog.Log.Debug("SendSO: {SoName}", so.name);
|
||||
|
||||
var dataOfSo = so.ToSyncModel();
|
||||
if (dataOfSo != null)
|
||||
SendAux(dataOfSo);
|
||||
}
|
||||
|
||||
public void SendAnalyticsEvent(string type, string content)
|
||||
{
|
||||
var analyticsEvent = new Synchronizer.Model.AnalyticsEvent()
|
||||
{
|
||||
EventType = type,
|
||||
EventContent = content
|
||||
};
|
||||
SendAux(analyticsEvent);
|
||||
}
|
||||
|
||||
public void SendErrorReport(string errorMessage, string stack, string type)
|
||||
{
|
||||
var errorReport = new Synchronizer.Model.ErrorReport()
|
||||
{
|
||||
ErrorMessage = errorMessage,
|
||||
ErrorStack = stack,
|
||||
ErrorType = type,
|
||||
};
|
||||
SendAux(errorReport);
|
||||
}
|
||||
|
||||
public void SendRequestVerboseType(string type, string docPath)
|
||||
{
|
||||
var requestVerboseType = new Synchronizer.Model.RequestVerboseType()
|
||||
{
|
||||
Type = type,
|
||||
DocPath = docPath,
|
||||
};
|
||||
SendAux(requestVerboseType);
|
||||
}
|
||||
|
||||
public void ForwardRelayMessage(Synchronizer.Model.IRelayMessage relayMessage)
|
||||
{
|
||||
SendAux(relayMessage);
|
||||
}
|
||||
|
||||
|
||||
string Synchronizer.Model.IProcessor.Serialize<T>(T value)
|
||||
{
|
||||
//return System.Text.Json.JsonSerializer.Serialize<T>(value);
|
||||
//return Newtonsoft.Json.JsonConvert.SerializeObject(value);
|
||||
return SerializeObject(value);
|
||||
}
|
||||
T Synchronizer.Model.IProcessor.Deserialize<T>(string data)
|
||||
{
|
||||
//return System.Text.Json.JsonSerializer.Deserialize<T>(data)!;
|
||||
//return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(data)!;
|
||||
//return TinyJson.JsonParser.FromJson<T>(data)!;
|
||||
//return Meryel.UnityCodeAssist.ProjectData.LitJson.JsonMapper.ToObject<T>(data);
|
||||
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(data)!;
|
||||
|
||||
//byte[] buffer = System.Text.Encoding.UTF8.GetBytes(data);
|
||||
//T val = OdinSerializer.SerializationUtility.DeserializeValue<T>(buffer, OdinSerializer.DataFormat.JSON);
|
||||
//return val;
|
||||
}
|
||||
|
||||
//**--make sure all Synchronizer.Model.IProcessor.Process methods are thread-safe
|
||||
|
||||
// a new client has connected
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Connect connect)
|
||||
{
|
||||
if (connect.ModelVersion != Self.ModelVersion)
|
||||
{
|
||||
Serilog.Log.Error("Version mismatch with {ContactInfo}. Please update your Unity asset and reinstall the Visual Studio/VS Code extension. {ContactModel} != {SelfModel}", connect.ContactInfo, connect.ModelVersion, Self.ModelVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connect.ProjectPath != Self.ProjectPath)
|
||||
{
|
||||
Serilog.Log.Error("Project mismatch with {ProjectName}. '{ConnectPath}' != '{SelfPath}'", connect.ProjectName, connect.ProjectPath, Self.ProjectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(connect.LiteOrFull) && connect.LiteOrFull != Self.LiteOrFull)
|
||||
{
|
||||
if (connect.LiteOrFull == "Lite")
|
||||
{
|
||||
//**-- upgrade vsix to full here //**--//**--
|
||||
}
|
||||
}
|
||||
|
||||
var hasClient = _clients.TryGetValue(connect.ClientId, out var client);
|
||||
if (!hasClient)
|
||||
_clients[connect.ClientId] = connect;
|
||||
else
|
||||
{
|
||||
// LiteOrFull field might be updated
|
||||
client.ModelVersion = connect.ModelVersion;
|
||||
client.ProjectPath = connect.ProjectPath;
|
||||
client.ProjectName = connect.ProjectName;
|
||||
client.ContactInfo = connect.ContactInfo;
|
||||
client.AssemblyVersion = connect.AssemblyVersion;
|
||||
client.LiteOrFull = connect.LiteOrFull;
|
||||
client.NodeKind = connect.NodeKind;
|
||||
client.ClientId = connect.ClientId;
|
||||
}
|
||||
|
||||
SendHandshake();
|
||||
if (ScriptFinder.GetActiveGameObject(out var activeGO))
|
||||
SendGameObject(activeGO);
|
||||
Assister.SendTagsAndLayers();
|
||||
}
|
||||
|
||||
// a new client is online and requesting connection
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestConnect requestConnect)
|
||||
{
|
||||
SendConnect();
|
||||
}
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Disconnect disconnect)
|
||||
{
|
||||
var removed = _clients.TryRemove(disconnect.ClientId, out var client);
|
||||
Serilog.Log.Debug("Synchronizer.Model.Disconnect {ClientId} {Removed}", disconnect.ClientId, removed);
|
||||
}
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.ConnectionInfo connectionInfo)
|
||||
{
|
||||
if (connectionInfo.ModelVersion != Self.ModelVersion)
|
||||
{
|
||||
Serilog.Log.Error("Version mismatch with {ContactInfo}. Please update your Unity asset and reinstall the Visual Studio/VS Code extension. {ContactModel} != {SelfModel}", connectionInfo.ContactInfo, connectionInfo.ModelVersion, Self.ModelVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectionInfo.ProjectPath != Self.ProjectPath)
|
||||
{
|
||||
Serilog.Log.Error("Project mismatch with {ProjectName}. '{ConnectPath}' != '{SelfPath}'", connectionInfo.ProjectName, connectionInfo.ProjectPath, Self.ProjectPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_clients.TryGetValue(connectionInfo.ClientId, out _))
|
||||
{
|
||||
SendConnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
SendHandshake();
|
||||
if (ScriptFinder.GetActiveGameObject(out var activeGO))
|
||||
SendGameObject(activeGO);
|
||||
Assister.SendTagsAndLayers();
|
||||
}
|
||||
}
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestConnectionInfo requestConnectionInfo)
|
||||
{
|
||||
SendConnectionInfo();
|
||||
}
|
||||
/*
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Layers layers)
|
||||
{
|
||||
|
||||
}
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Tags tags)
|
||||
{
|
||||
|
||||
}
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.SortingLayers sortingLayers)
|
||||
{
|
||||
|
||||
}*/
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.StringArray stringArray)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.StringArray)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.StringArrayContainer stringArrayContainer)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.StringArrayContainer)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.GameObject gameObject)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.GameObject)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.ComponentData component)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.ComponentData)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Component_Animator component_Animator)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Component_Animator)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Component_Animation component_Animation)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Component_Animation)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Component_Material component_Material)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Component_Material)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestScript requestScript)
|
||||
{
|
||||
if (requestScript.DeclaredTypes == null || requestScript.DeclaredTypes.Length == 0)
|
||||
return;
|
||||
|
||||
var documentPath = requestScript.DocumentPath;
|
||||
|
||||
foreach (var declaredType in requestScript.DeclaredTypes)
|
||||
{
|
||||
if (ScriptFinder.FindInstanceOfType(declaredType, documentPath, out var go, out var so))
|
||||
{
|
||||
if (go != null)
|
||||
SendGameObject(go);
|
||||
else if (so != null)
|
||||
SendScriptableObject(so);
|
||||
else
|
||||
Serilog.Log.Warning("Invalid instance of type");
|
||||
}
|
||||
else
|
||||
{
|
||||
SendScriptMissing(declaredType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestScriptFast requestScriptFast)
|
||||
{
|
||||
var documentPath = requestScriptFast.DocumentPath;
|
||||
|
||||
//**--namespace?
|
||||
var possiblyDeclaredType = Path.GetFileNameWithoutExtension(documentPath);
|
||||
|
||||
if (ScriptFinder.FindInstanceOfType(possiblyDeclaredType, documentPath, out var go, out var so))
|
||||
{
|
||||
if (go != null)
|
||||
SendGameObject(go);
|
||||
else if (so != null)
|
||||
SendScriptableObject(so);
|
||||
else
|
||||
Serilog.Log.Warning("Invalid instance of type");
|
||||
}
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.ScriptMissing scriptMissing)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.ScriptMissing)");
|
||||
}
|
||||
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.Handshake handshake)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestInternalLog requestInternalLog)
|
||||
{
|
||||
SendInternalLog();
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.InternalLog internalLog)
|
||||
{
|
||||
Logger.ELogger.VsInternalLog = internalLog.LogContent;
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.AnalyticsEvent analyticsEvent)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.AnalyticsEvent)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.ErrorReport errorReport)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.ErrorReport)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestVerboseType requestVerboseType)
|
||||
{
|
||||
Serilog.Log.Warning("Unity/Server shouldn't call Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestVerboseType)");
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestLazyLoad requestLazyLoad)
|
||||
{
|
||||
Monitor.LazyLoad(requestLazyLoad.Category);
|
||||
}
|
||||
|
||||
internal Synchronizer.Model.RequestUpdate? DelayedRequestUpdate { get; private set; }
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RequestUpdate requestUpdate)
|
||||
{
|
||||
if (requestUpdate.App != "Unity" && requestUpdate.App != "SystemBinariesForDotNetStandard20")
|
||||
return;
|
||||
|
||||
// cannot import package in play mode, so delay it
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
Serilog.Log.Information("Cannot import package in play mode, please exit play mode to update");
|
||||
DelayedRequestUpdate = requestUpdate;
|
||||
return;
|
||||
}
|
||||
DelayedRequestUpdate = null;
|
||||
|
||||
// let unity update the package, don't unzip it, to prevent file already in use and other issues
|
||||
AssetDatabase.ImportPackage(requestUpdate.Path, requestUpdate.IsInteractive);
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RelayDocumentShow relayDocumentShow)
|
||||
{
|
||||
ForwardRelayMessage(relayDocumentShow);
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RelayDocumentSave relayDocumentSave)
|
||||
{
|
||||
ForwardRelayMessage(relayDocumentSave);
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RelayDocumentViewportChanged relayDocumentViewportChanged)
|
||||
{
|
||||
ForwardRelayMessage(relayDocumentViewportChanged);
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RelayLogMessage relayLogMessage)
|
||||
{
|
||||
ForwardRelayMessage(relayLogMessage);
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RelayUpdateExport relayUpdateExport)
|
||||
{
|
||||
ForwardRelayMessage(relayUpdateExport);
|
||||
}
|
||||
|
||||
void Synchronizer.Model.IProcessor.Process(Synchronizer.Model.RelayAdornmentText relayAdornmentText)
|
||||
{
|
||||
ForwardRelayMessage(relayAdornmentText);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e6bf8bab3f6a9a439b3992693b1fc34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
|
||||
//[InitializeOnLoad]
|
||||
public static class MainThreadDispatcher
|
||||
{
|
||||
readonly static ConcurrentBag<System.Action> actions;
|
||||
|
||||
static MainThreadDispatcher()
|
||||
{
|
||||
actions = new ConcurrentBag<System.Action>();
|
||||
EditorApplication.update += Update;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty method for invoking static class ctor
|
||||
/// </summary>
|
||||
public static void Bump() {}
|
||||
|
||||
static void Update()
|
||||
{
|
||||
while (actions.TryTake(out var action))
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(System.Action action) => actions.Add(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 238ff68bac1bd2a44ad831a256db9cae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "MerryYellow.CodeAssist.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82ec9ead3f2d6e8488c5717eed8b7cf5
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
332
Packages/com.merry-yellow.code-assist/Editor/Monitor.cs
Normal file
332
Packages/com.merry-yellow.code-assist/Editor/Monitor.cs
Normal file
@@ -0,0 +1,332 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
|
||||
//[InitializeOnLoad]
|
||||
public static class Monitor
|
||||
{
|
||||
private readonly static string tagManagerFilePath;
|
||||
private static System.DateTime previousTagManagerLastWrite;
|
||||
|
||||
private static bool isAppFocused;
|
||||
private static bool isAppFocusedOnTagManager;
|
||||
|
||||
private static int dirtyCounter;
|
||||
private static readonly Dictionary<GameObject, int> dirtyDict;
|
||||
|
||||
static Monitor()
|
||||
{
|
||||
tagManagerFilePath = CommonTools.GetTagManagerFilePath();
|
||||
try
|
||||
{
|
||||
previousTagManagerLastWrite = System.IO.File.GetLastWriteTime(tagManagerFilePath);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Serilog.Log.Debug(ex, "Exception at {Location}", nameof(System.IO.File.GetLastWriteTime));
|
||||
}
|
||||
dirtyDict = new Dictionary<GameObject, int>();
|
||||
dirtyCounter = 0;
|
||||
|
||||
EditorApplication.hierarchyChanged += OnHierarchyChanged;
|
||||
EditorApplication.update += OnUpdate;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
Undo.postprocessModifications += MyPostprocessModificationsCallback;
|
||||
//Undo.undoRedoPerformed += MyUndoCallback;
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
//EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened;
|
||||
EditorSceneManager.activeSceneChangedInEditMode += EditorSceneManager_activeSceneChangedInEditMode;
|
||||
EditorBuildSettings.sceneListChanged += EditorBuildSettings_sceneListChanged;
|
||||
|
||||
Application.logMessageReceived += Application_logMessageReceived;
|
||||
//System.Threading.Tasks.TaskScheduler.UnobservedTaskException +=
|
||||
}
|
||||
|
||||
private static void EditorBuildSettings_sceneListChanged()
|
||||
{
|
||||
OnSceneListChanged();
|
||||
}
|
||||
|
||||
private static void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange)
|
||||
{
|
||||
if (playModeStateChange != PlayModeStateChange.EnteredEditMode)
|
||||
return;
|
||||
|
||||
var delayedRequestUpdate = MQTTnetInitializer.Publisher?.DelayedRequestUpdate;
|
||||
if (delayedRequestUpdate == null)
|
||||
return;
|
||||
|
||||
var processor = MQTTnetInitializer.Publisher as Synchronizer.Model.IProcessor;
|
||||
processor?.Process(delayedRequestUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty method for invoking static class ctor
|
||||
/// </summary>
|
||||
public static void Bump() { }
|
||||
|
||||
private static void EditorSceneManager_activeSceneChangedInEditMode(Scene arg0, Scene arg1)
|
||||
{
|
||||
//Debug.Log("EditorSceneManager_activeSceneChangedInEditMode");
|
||||
OnHierarchyChanged();
|
||||
}
|
||||
|
||||
private static void EditorSceneManager_sceneOpened(Scene scene, OpenSceneMode mode)
|
||||
{
|
||||
Serilog.Log.Debug("Monitor {Event} scene:{Scene} mode:{Mode}", nameof(EditorSceneManager_sceneOpened), scene.name, mode);
|
||||
//Debug.Log("EditorSceneManager_sceneOpened");
|
||||
OnHierarchyChanged();
|
||||
}
|
||||
|
||||
static void OnUpdate()
|
||||
{
|
||||
string? currentEditorFocus = null;
|
||||
if (Selection.activeObject)
|
||||
currentEditorFocus = Selection.activeObject.GetType().ToString();
|
||||
|
||||
//**-- use this instead? https://learn.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher?view=net-8.0
|
||||
// there is also this approach, but need to check OnUpdate anyways for focus checking, https://github.com/AlkimeeGames/TagLayerTypeGenerator/blob/main/Editor/TypeGenerator.cs#L36
|
||||
var currentTagManagerLastWrite = previousTagManagerLastWrite;
|
||||
try
|
||||
{
|
||||
currentTagManagerLastWrite = System.IO.File.GetLastWriteTime(tagManagerFilePath);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Serilog.Log.Debug(ex, "Exception at {Location}", nameof(System.IO.File.GetLastWriteTime));
|
||||
}
|
||||
if (currentTagManagerLastWrite != previousTagManagerLastWrite)
|
||||
{
|
||||
previousTagManagerLastWrite = currentTagManagerLastWrite;
|
||||
OnTagsOrLayersModified();
|
||||
}
|
||||
else if (currentEditorFocus == "UnityEditor.TagManager")
|
||||
{
|
||||
// since unity does not commit changes to the file immediately, checking if user is displaying and focusing on tag manager (tags & layers) inspector
|
||||
isAppFocusedOnTagManager = true;
|
||||
}
|
||||
|
||||
|
||||
if (isAppFocused != UnityEditorInternal.InternalEditorUtility.isApplicationActive)
|
||||
{
|
||||
isAppFocused = UnityEditorInternal.InternalEditorUtility.isApplicationActive;
|
||||
OnOnUnityEditorFocusChanged(isAppFocused);
|
||||
//Serilog.Log.Debug("On focus {State}", isAppFocused);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnTagsOrLayersModified()
|
||||
{
|
||||
Serilog.Log.Debug("Monitor {Event}", nameof(OnTagsOrLayersModified));
|
||||
|
||||
Assister.SendTagsAndLayers();
|
||||
}
|
||||
|
||||
static void OnHierarchyChanged()
|
||||
{
|
||||
Serilog.Log.Debug("Monitor {Event}", nameof(OnHierarchyChanged));
|
||||
|
||||
// For requesting active doc's GO
|
||||
MQTTnetInitializer.Publisher?.SendHandshake();
|
||||
|
||||
if (ScriptFinder.GetActiveGameObject(out var activeGO))
|
||||
MQTTnetInitializer.Publisher?.SendGameObject(activeGO);
|
||||
//Assister.SendTagsAndLayers(); Don't send tags & layers here
|
||||
}
|
||||
|
||||
static void OnSceneListChanged()
|
||||
{
|
||||
// link below for scenes which are not on build list, but we can skip this for now, because of performance and needlessness (user probably wont work with it if its not on build list)
|
||||
// https://gist.github.com/xfleckx/2527f0420fbcc428a8b86be191d8ad96
|
||||
|
||||
var scenes = EditorBuildSettings.scenes;
|
||||
var count = scenes.Length;
|
||||
|
||||
string[] buildIndicies = new string[count];
|
||||
string[] names = new string[count];
|
||||
string[] paths = new string[count];
|
||||
string[] namesAndPaths = new string[count * 2];
|
||||
string[] pathsAndNames = new string[count * 2];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var scene = scenes[i];
|
||||
var path = scene.path;
|
||||
var name = System.IO.Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
buildIndicies[i] = i.ToString();
|
||||
names[i] = name;
|
||||
paths[i] = path;
|
||||
namesAndPaths[i] = name;
|
||||
namesAndPaths[i + count] = path;
|
||||
pathsAndNames[i] = path;
|
||||
pathsAndNames[i + count] = name;
|
||||
}
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendSceneList(names, paths, buildIndicies, namesAndPaths, pathsAndNames);
|
||||
}
|
||||
|
||||
static UndoPropertyModification[] MyPostprocessModificationsCallback(UndoPropertyModification[] modifications)
|
||||
{
|
||||
Serilog.Log.Debug("Monitor {Event}", nameof(MyPostprocessModificationsCallback));
|
||||
|
||||
foreach (var modification in modifications)
|
||||
{
|
||||
var target = modification.currentValue?.target;
|
||||
SetDirty(target);
|
||||
}
|
||||
|
||||
// here, you can perform processing of the recorded modifications before returning them
|
||||
return modifications;
|
||||
}
|
||||
|
||||
//static void MyUndoCallback()
|
||||
//{
|
||||
// Serilog.Log.Debug("Monitor {Event}", nameof(MyUndoCallback));
|
||||
// // code for the action to take on Undo
|
||||
//}
|
||||
|
||||
static void OnOnUnityEditorFocusChanged(bool isFocused)
|
||||
{
|
||||
if (!isFocused)
|
||||
{
|
||||
if (isAppFocusedOnTagManager)
|
||||
{
|
||||
isAppFocusedOnTagManager = false;
|
||||
OnTagsOrLayersModified();
|
||||
}
|
||||
|
||||
OnSelectionChanged();
|
||||
FlushAllDirty();
|
||||
/*
|
||||
Serilog.Log.Debug("exporting {Count} objects", selectedObjects.Count);
|
||||
|
||||
//**--if too many
|
||||
foreach (var obj in selectedObjects)
|
||||
{
|
||||
if (obj is GameObject go)
|
||||
MQTTnetInitializer.Publisher.SendGameObject(go);
|
||||
}
|
||||
|
||||
selectedObjects.Clear();
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
static void OnSelectionChanged()
|
||||
{
|
||||
|
||||
//**--check order, last selected should be sent last as well
|
||||
//**--limit here, what if too many?
|
||||
//selectedObjects.UnionWith(Selection.objects);
|
||||
foreach(var so in Selection.objects)
|
||||
{
|
||||
SetDirty(so);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDirty(Object? obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return;
|
||||
else if (obj is GameObject go && go)
|
||||
SetDirty(go);
|
||||
else if (obj is Component component && component)
|
||||
//SetDirty(component.gameObject);
|
||||
{
|
||||
var componentGo = component.gameObject;
|
||||
if (componentGo)
|
||||
SetDirty(componentGo);
|
||||
}
|
||||
//else
|
||||
//;//**--scriptable obj
|
||||
}
|
||||
|
||||
public static void SetDirty(GameObject go)
|
||||
{
|
||||
dirtyCounter++;
|
||||
dirtyDict[go] = dirtyCounter;
|
||||
}
|
||||
|
||||
static void FlushAllDirty()
|
||||
{
|
||||
// Sending order is important, must send them in the same order as they are added to/modified in the collection
|
||||
// Using dict instead of hashset because of that. Dict value is used as add/modify order
|
||||
|
||||
var sortedDict = from entry in dirtyDict orderby entry.Value descending select entry;
|
||||
|
||||
foreach (var entry in sortedDict)
|
||||
{
|
||||
var go = entry.Key;
|
||||
MQTTnetInitializer.Publisher?.SendGameObject(go);
|
||||
}
|
||||
|
||||
dirtyDict.Clear();
|
||||
dirtyCounter = 0;
|
||||
}
|
||||
|
||||
|
||||
private static void Application_logMessageReceived(string condition, string stackTrace, LogType type)
|
||||
{
|
||||
if (type != LogType.Exception && type != LogType.Error && type != LogType.Warning)
|
||||
return;
|
||||
|
||||
if (!stackTrace.Contains("Meryel.UnityCodeAssist.Editor"))
|
||||
return;
|
||||
|
||||
var typeStr = type.ToString();
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendErrorReport(condition, stackTrace, typeStr);
|
||||
}
|
||||
|
||||
|
||||
public static void LazyLoad(string category)
|
||||
{
|
||||
if (category == "PlayerPrefs")
|
||||
{
|
||||
Preferences.PreferenceMonitor.InstanceOfPlayerPrefs.Bump();
|
||||
}
|
||||
else if (category == "EditorPrefs")
|
||||
{
|
||||
Preferences.PreferenceMonitor.InstanceOfEditorPrefs.Bump();
|
||||
}
|
||||
else if (category == "InputManager")
|
||||
{
|
||||
Input.InputManagerMonitor.Instance.Bump();
|
||||
}
|
||||
else if (category == "AnimationHuman")
|
||||
{
|
||||
MQTTnetInitializer.Publisher?.SendComponentHumanTrait(HumanTrait.BoneName, HumanTrait.MuscleName);
|
||||
}
|
||||
else if (category == "Scene")
|
||||
{
|
||||
OnSceneListChanged();
|
||||
}
|
||||
else if (category == "ShaderGlobalKeywords")
|
||||
{
|
||||
MQTTnetInitializer.Publisher?.SendShaderGlobalKeywords();
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Error("Invalid LazyLoad category {Category}", category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
11
Packages/com.merry-yellow.code-assist/Editor/Monitor.cs.meta
Normal file
11
Packages/com.merry-yellow.code-assist/Editor/Monitor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 654d0913b44b86a40bee8394ea8c14cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a9386f468b6268479944ca0c56f8d07
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
//namespace BgTools.PlayerPrefsEditor
|
||||
namespace Meryel.UnityCodeAssist.Editor.Preferences
|
||||
{
|
||||
[System.Serializable]
|
||||
public class PreferenceEntryHolder : ScriptableObject
|
||||
{
|
||||
public List<PreferenceEntry>? userDefList;
|
||||
public List<PreferenceEntry>? unityDefList;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
hideFlags = HideFlags.DontSave;
|
||||
userDefList ??= new List<PreferenceEntry>();
|
||||
unityDefList ??= new List<PreferenceEntry>();
|
||||
}
|
||||
|
||||
public void ClearLists()
|
||||
{
|
||||
userDefList?.Clear();
|
||||
unityDefList?.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class PreferenceEntry
|
||||
{
|
||||
public enum PrefTypes
|
||||
{
|
||||
String = 0,
|
||||
Int = 1,
|
||||
Float = 2
|
||||
}
|
||||
|
||||
public PrefTypes m_typeSelection;
|
||||
public string? m_key;
|
||||
|
||||
// Need diffrend ones for auto type selection of serilizedProerty
|
||||
public string? m_strValue;
|
||||
public int m_intValue;
|
||||
public float m_floatValue;
|
||||
|
||||
public string? ValueAsString()
|
||||
{
|
||||
return m_typeSelection switch
|
||||
{
|
||||
PrefTypes.String => m_strValue,
|
||||
PrefTypes.Int => m_intValue.ToString(),
|
||||
PrefTypes.Float => m_floatValue.ToString(),
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b686b7831cfe0ec48b01473fa8a29772
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,375 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Preferences
|
||||
{
|
||||
public class PreferenceMonitor
|
||||
{
|
||||
private static readonly Lazy<PreferenceMonitor> _instanceOfPlayerPrefs = new Lazy<PreferenceMonitor>(() => new PreferenceMonitor(true));
|
||||
private static readonly Lazy<PreferenceMonitor> _instanceOfEditorPrefs = new Lazy<PreferenceMonitor>(() => new PreferenceMonitor(false));
|
||||
public static PreferenceMonitor InstanceOfPlayerPrefs => _instanceOfPlayerPrefs.Value;
|
||||
public static PreferenceMonitor InstanceOfEditorPrefs => _instanceOfEditorPrefs.Value;
|
||||
|
||||
//const int Limit = 128;
|
||||
const int Limit = 8192;
|
||||
|
||||
/// <summary>
|
||||
/// PlayerPrefs or EditorPrefs
|
||||
/// </summary>
|
||||
readonly bool isPlayerPrefs;
|
||||
|
||||
#region ErrorValues
|
||||
private readonly int ERROR_VALUE_INT = int.MinValue;
|
||||
private readonly string ERROR_VALUE_STR = "<UCA_ERR_2407201713>";
|
||||
#endregion //ErrorValues
|
||||
|
||||
#pragma warning disable CS0414
|
||||
private static string pathToPrefs = String.Empty;
|
||||
private static string platformPathPrefix = @"~";
|
||||
#pragma warning restore CS0414
|
||||
|
||||
//private string[] userDef;
|
||||
//private string[] unityDef;
|
||||
//private bool showSystemGroup = false;
|
||||
|
||||
private SerializedObject? serializedObject;
|
||||
private ReorderableList? userDefList;
|
||||
private ReorderableList? unityDefList;
|
||||
|
||||
private PreferenceEntryHolder? prefEntryHolder;
|
||||
|
||||
private PreferanceStorageAccessor? entryAccessor;
|
||||
|
||||
|
||||
private bool updateView = false;
|
||||
//private bool monitoring = false;
|
||||
//private bool showLoadingIndicatorOverlay = false;
|
||||
|
||||
|
||||
#if UNITY_EDITOR_LINUX
|
||||
private readonly char[] invalidFilenameChars = { '"', '\\', '*', '/', ':', '<', '>', '?', '|' };
|
||||
#elif UNITY_EDITOR_OSX
|
||||
private readonly char[] invalidFilenameChars = { '$', '%', '&', '\\', '/', ':', '<', '>', '|', '~' };
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
PreferenceMonitor(bool isPlayerPrefs)
|
||||
{
|
||||
this.isPlayerPrefs = isPlayerPrefs;
|
||||
OnEnable();
|
||||
EditorApplication.update += Update;
|
||||
}
|
||||
|
||||
~PreferenceMonitor()
|
||||
{
|
||||
OnDisable();
|
||||
}
|
||||
|
||||
public void Bump()
|
||||
{
|
||||
Serilog.Log.Debug("Bumping preference {IsPlayerPrefs}", isPlayerPrefs);
|
||||
|
||||
RetrieveAndSendKeysAndValues(false);
|
||||
}
|
||||
|
||||
private void RetrieveAndSendKeysAndValues(bool reloadKeys)
|
||||
{
|
||||
string[]? keys = GetKeys(reloadKeys);
|
||||
if (keys == null)
|
||||
return;
|
||||
string[] values = GetKeyValues(reloadKeys, keys, out var stringKeys, out var integerKeys, out var floatKeys, out var booleanKeys);
|
||||
|
||||
if (isPlayerPrefs)
|
||||
MQTTnetInitializer.Publisher?.SendPlayerPrefs(keys, values, stringKeys, integerKeys, floatKeys);
|
||||
else
|
||||
MQTTnetInitializer.Publisher?.SendEditorPrefs(keys, values, stringKeys, integerKeys, floatKeys, booleanKeys);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
#if UNITY_EDITOR_WIN
|
||||
if (isPlayerPrefs)
|
||||
pathToPrefs = @"SOFTWARE\Unity\UnityEditor\" + PlayerSettings.companyName + @"\" + PlayerSettings.productName;
|
||||
else
|
||||
pathToPrefs = @"Software\Unity Technologies\Unity Editor 5.x";
|
||||
|
||||
platformPathPrefix = @"<CurrentUser>";
|
||||
entryAccessor = new WindowsPrefStorage(pathToPrefs);
|
||||
#elif UNITY_EDITOR_OSX
|
||||
if (isPlayerPrefs)
|
||||
pathToPrefs = @"Library/Preferences/com." + MakeValidFileName(PlayerSettings.companyName) + "." + MakeValidFileName(PlayerSettings.productName) + ".plist";
|
||||
else
|
||||
pathToPrefs = @"Library/Preferences/com.unity3d.UnityEditor5.x.plist";
|
||||
|
||||
platformPathPrefix = @"~";
|
||||
entryAccessor = new MacPrefStorage(pathToPrefs);
|
||||
//entryAccessor.StartLoadingDelegate = () => { showLoadingIndicatorOverlay = true; };
|
||||
//entryAccessor.StopLoadingDelegate = () => { showLoadingIndicatorOverlay = false; };
|
||||
#elif UNITY_EDITOR_LINUX
|
||||
if (isPlayerPrefs)
|
||||
pathToPrefs = @".config/unity3d/" + MakeValidFileName(PlayerSettings.companyName) + "/" + MakeValidFileName(PlayerSettings.productName) + "/prefs";
|
||||
else
|
||||
pathToPrefs = @".local/share/unity3d/prefs";
|
||||
|
||||
platformPathPrefix = @"~";
|
||||
entryAccessor = new LinuxPrefStorage(pathToPrefs);
|
||||
#else
|
||||
Serilog.Log.Warning("Undefined Unity Editor platform");
|
||||
pathToPrefs = String.Empty;
|
||||
platformPathPrefix = @"~";
|
||||
entryAccessor = null;
|
||||
#endif
|
||||
|
||||
if (entryAccessor != null)
|
||||
{
|
||||
entryAccessor.PrefEntryChangedDelegate = () => { updateView = true; };
|
||||
entryAccessor.StartMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
// Handel view updates for monitored changes
|
||||
// Necessary to avoid main thread access issue
|
||||
private void Update()
|
||||
{
|
||||
if (updateView)
|
||||
{
|
||||
updateView = false;
|
||||
//PrepareData();
|
||||
//Repaint();
|
||||
|
||||
Serilog.Log.Debug("Updating preference {IsPlayerPrefs}", isPlayerPrefs);
|
||||
|
||||
RetrieveAndSendKeysAndValues(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
entryAccessor?.StopMonitoring();
|
||||
}
|
||||
|
||||
private void InitReorderedList()
|
||||
{
|
||||
if (prefEntryHolder == null)
|
||||
{
|
||||
var tmp = Resources.FindObjectsOfTypeAll<PreferenceEntryHolder>();
|
||||
if (tmp.Length > 0)
|
||||
{
|
||||
prefEntryHolder = tmp[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
prefEntryHolder = ScriptableObject.CreateInstance<PreferenceEntryHolder>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
serializedObject ??= new SerializedObject(prefEntryHolder);
|
||||
|
||||
userDefList = new ReorderableList(serializedObject, serializedObject.FindProperty("userDefList"), false, true, true, true);
|
||||
unityDefList = new ReorderableList(serializedObject, serializedObject.FindProperty("unityDefList"), false, true, false, false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private string[]? GetKeys(bool reloadKeys)
|
||||
{
|
||||
if (entryAccessor == null)
|
||||
{
|
||||
Serilog.Log.Warning($"{nameof(entryAccessor)} is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] keys = entryAccessor.GetKeys(reloadKeys);
|
||||
|
||||
if (keys.Length > Limit)
|
||||
keys = keys.Where(k => !k.StartsWith("unity.") && !k.StartsWith("UnityGraphicsQuality")).Take(Limit).ToArray();
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
string[]? _cachedKeyValues = null;
|
||||
|
||||
string[]? _cachedStringKeys = null;
|
||||
string[]? _cachedIntegerKeys = null;
|
||||
string[]? _cachedFloatKeys = null;
|
||||
string[]? _cachedBooleanKeys = null;
|
||||
|
||||
private string[] GetKeyValues(bool reloadData, string[] keys,
|
||||
out string[] stringKeys, out string[] integerKeys, out string[] floatKeys, out string[] booleanKeys)
|
||||
{
|
||||
if (!reloadData && _cachedKeyValues != null && _cachedKeyValues.Length == keys.Length)
|
||||
{
|
||||
stringKeys = _cachedStringKeys!;
|
||||
integerKeys = _cachedIntegerKeys!;
|
||||
floatKeys = _cachedFloatKeys!;
|
||||
booleanKeys = _cachedBooleanKeys!;
|
||||
return _cachedKeyValues;
|
||||
}
|
||||
|
||||
string[] values = new string[keys.Length];
|
||||
var stringKeyList = new List<string>();
|
||||
var integerKeyList = new List<string>();
|
||||
var floatKeyList = new List<string>();
|
||||
var boolenKeyList = new List<string>();
|
||||
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
var key = keys[i];
|
||||
|
||||
string stringValue;
|
||||
if (isPlayerPrefs)
|
||||
stringValue = PlayerPrefs.GetString(key, ERROR_VALUE_STR);
|
||||
else
|
||||
stringValue = EditorPrefs.GetString(key, ERROR_VALUE_STR);
|
||||
|
||||
if (stringValue != ERROR_VALUE_STR)
|
||||
{
|
||||
values[i] = stringValue;
|
||||
stringKeyList.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
float floatValue;
|
||||
if (isPlayerPrefs)
|
||||
floatValue = PlayerPrefs.GetFloat(key, float.NaN);
|
||||
else
|
||||
floatValue = EditorPrefs.GetFloat(key, float.NaN);
|
||||
|
||||
if (!float.IsNaN(floatValue))
|
||||
{
|
||||
values[i] = floatValue.ToString();
|
||||
floatKeyList.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
int intValue;
|
||||
if (isPlayerPrefs)
|
||||
intValue = PlayerPrefs.GetInt(key, ERROR_VALUE_INT);
|
||||
else
|
||||
intValue = EditorPrefs.GetInt(key, ERROR_VALUE_INT);
|
||||
|
||||
if (intValue != ERROR_VALUE_INT)
|
||||
{
|
||||
values[i] = intValue.ToString();
|
||||
integerKeyList.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool boolValue = false;
|
||||
if (!isPlayerPrefs)
|
||||
{
|
||||
bool boolValueTrue = EditorPrefs.GetBool(key, true);
|
||||
bool boolValueFalse = EditorPrefs.GetBool(key, false);
|
||||
|
||||
boolValue = boolValueFalse;
|
||||
if (boolValueTrue == boolValueFalse)
|
||||
{
|
||||
values[i] = boolValueTrue.ToString();
|
||||
boolenKeyList.Add(key);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
values[i] = string.Empty;
|
||||
if (isPlayerPrefs)
|
||||
{
|
||||
// Keys with ? causing problems, just ignore them
|
||||
if (key.Contains("?"))
|
||||
Serilog.Log.Debug("Invalid {PreferenceType} KEY WITH '?', '{Key}' at {Location}, str:{StringValue}, int:{IntegerValue}, float:{FloatValue}, bool:{BooleanValue}",
|
||||
(isPlayerPrefs ? "PlayerPrefs" : "EditorPrefs"), key, nameof(GetKeyValues),
|
||||
stringValue, intValue, floatValue, boolValue);
|
||||
|
||||
else
|
||||
// EditorPrefs gives error for some keys
|
||||
Serilog.Log.Error("Invalid {PreferenceType} '{Key}' at {Location}, str:{StringValue}, int:{IntegerValue}, float:{FloatValue}, bool:{BooleanValue}",
|
||||
(isPlayerPrefs ? "PlayerPrefs" : "EditorPrefs"), key, nameof(GetKeyValues),
|
||||
stringValue, intValue, floatValue, boolValue);
|
||||
}
|
||||
}
|
||||
|
||||
stringKeys = stringKeyList.ToArray();
|
||||
integerKeys = integerKeyList.ToArray();
|
||||
floatKeys = floatKeyList.ToArray();
|
||||
booleanKeys = boolenKeyList.ToArray();
|
||||
|
||||
_cachedKeyValues = values;
|
||||
|
||||
_cachedStringKeys = stringKeys;
|
||||
_cachedIntegerKeys = integerKeys;
|
||||
_cachedFloatKeys = floatKeys;
|
||||
_cachedBooleanKeys = booleanKeys;
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private void LoadKeys(out string[]? userDef, out string[]? unityDef, bool reloadKeys)
|
||||
{
|
||||
if(entryAccessor == null)
|
||||
{
|
||||
userDef = null;
|
||||
unityDef = null;
|
||||
return;
|
||||
}
|
||||
|
||||
string[] keys = entryAccessor.GetKeys(reloadKeys);
|
||||
|
||||
//keys.ToList().ForEach( e => { Debug.Log(e); } );
|
||||
|
||||
// Seperate keys int unity defined and user defined
|
||||
Dictionary<bool, List<string>> groups = keys
|
||||
.GroupBy((key) => key.StartsWith("unity.") || key.StartsWith("UnityGraphicsQuality"))
|
||||
.ToDictionary((g) => g.Key, (g) => g.ToList());
|
||||
|
||||
unityDef = (groups.ContainsKey(true)) ? groups[true].ToArray() : new string[0];
|
||||
userDef = (groups.ContainsKey(false)) ? groups[false].ToArray() : new string[0];
|
||||
}
|
||||
|
||||
|
||||
#if (UNITY_EDITOR_LINUX || UNITY_EDITOR_OSX)
|
||||
private string MakeValidFileName(string unsafeFileName)
|
||||
{
|
||||
string normalizedFileName = unsafeFileName.Trim().Normalize(NormalizationForm.FormD);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
// We need to use a TextElementEmumerator in order to support UTF16 characters that may take up more than one char(case 1169358)
|
||||
TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(normalizedFileName);
|
||||
while (charEnum.MoveNext())
|
||||
{
|
||||
string c = charEnum.GetTextElement();
|
||||
if (c.Length == 1 && invalidFilenameChars.Contains(c[0]))
|
||||
{
|
||||
stringBuilder.Append('_');
|
||||
continue;
|
||||
}
|
||||
UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c, 0);
|
||||
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d848223c4ab636544a7b13aeffa72434
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
#if UNITY_EDITOR_WIN
|
||||
using Microsoft.Win32;
|
||||
using System.Text;
|
||||
#elif UNITY_EDITOR_OSX
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
#elif UNITY_EDITOR_LINUX
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
#endif
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
//namespace BgTools.PlayerPrefsEditor
|
||||
namespace Meryel.UnityCodeAssist.Editor.Preferences
|
||||
{
|
||||
public abstract class PreferanceStorageAccessor
|
||||
{
|
||||
protected string prefPath;
|
||||
protected string[] cachedData = new string[0];
|
||||
|
||||
protected abstract void FetchKeysFromSystem();
|
||||
|
||||
protected PreferanceStorageAccessor(string pathToPrefs)
|
||||
{
|
||||
prefPath = pathToPrefs;
|
||||
}
|
||||
|
||||
public string[] GetKeys(bool reloadData = true)
|
||||
{
|
||||
if (reloadData || cachedData.Length == 0)
|
||||
{
|
||||
FetchKeysFromSystem();
|
||||
}
|
||||
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
public Action? PrefEntryChangedDelegate;
|
||||
protected bool ignoreNextChange = false;
|
||||
|
||||
public void IgnoreNextChange()
|
||||
{
|
||||
ignoreNextChange = true;
|
||||
}
|
||||
|
||||
protected virtual void OnPrefEntryChanged()
|
||||
{
|
||||
if (ignoreNextChange)
|
||||
{
|
||||
ignoreNextChange = false;
|
||||
return;
|
||||
}
|
||||
|
||||
PrefEntryChangedDelegate?.Invoke();
|
||||
}
|
||||
|
||||
public Action? StartLoadingDelegate;
|
||||
public Action? StopLoadingDelegate;
|
||||
|
||||
public abstract void StartMonitoring();
|
||||
public abstract void StopMonitoring();
|
||||
public abstract bool IsMonitoring();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR_WIN
|
||||
|
||||
public class WindowsPrefStorage : PreferanceStorageAccessor
|
||||
{
|
||||
readonly RegistryMonitor monitor;
|
||||
|
||||
public WindowsPrefStorage(string pathToPrefs) : base(pathToPrefs)
|
||||
{
|
||||
monitor = new RegistryMonitor(RegistryHive.CurrentUser, prefPath);
|
||||
monitor.RegChanged += new EventHandler(OnRegChanged);
|
||||
}
|
||||
|
||||
private void OnRegChanged(object sender, EventArgs e)
|
||||
{
|
||||
OnPrefEntryChanged();
|
||||
}
|
||||
|
||||
protected override void FetchKeysFromSystem()
|
||||
{
|
||||
cachedData = new string[0];
|
||||
|
||||
using (RegistryKey rootKey = Registry.CurrentUser.OpenSubKey(prefPath))
|
||||
{
|
||||
if (rootKey != null)
|
||||
{
|
||||
cachedData = rootKey.GetValueNames();
|
||||
rootKey.Close();
|
||||
}
|
||||
}
|
||||
|
||||
// Clean <key>_h3320113488 nameing
|
||||
//cachedData = cachedData.Select((key) => { return key.Substring(0, key.LastIndexOf("_h", StringComparison.Ordinal)); }).ToArray();
|
||||
for (int i = 0; i < cachedData.Length; i++)
|
||||
{
|
||||
var indexOfSuffix = cachedData[i].LastIndexOf("_h", StringComparison.Ordinal);
|
||||
if (indexOfSuffix >= 0)
|
||||
cachedData[i] = cachedData[i].Substring(0, indexOfSuffix);
|
||||
}
|
||||
|
||||
EncodeAnsiInPlace();
|
||||
}
|
||||
|
||||
public override void StartMonitoring()
|
||||
{
|
||||
monitor.Start();
|
||||
}
|
||||
|
||||
public override void StopMonitoring()
|
||||
{
|
||||
monitor.Stop();
|
||||
}
|
||||
|
||||
public override bool IsMonitoring()
|
||||
{
|
||||
return monitor.IsMonitoring;
|
||||
}
|
||||
|
||||
private void EncodeAnsiInPlace()
|
||||
{
|
||||
Encoding utf8 = Encoding.UTF8;
|
||||
Encoding ansi = Encoding.GetEncoding(1252);
|
||||
|
||||
for (int i = 0; i < cachedData.Length; i++)
|
||||
{
|
||||
cachedData[i] = utf8.GetString(ansi.GetBytes(cachedData[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif UNITY_EDITOR_LINUX
|
||||
|
||||
public class LinuxPrefStorage : PreferanceStorageAccessor
|
||||
{
|
||||
readonly FileSystemWatcher fileWatcher;
|
||||
|
||||
public LinuxPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs))
|
||||
{
|
||||
fileWatcher = new FileSystemWatcher
|
||||
{
|
||||
Path = Path.GetDirectoryName(prefPath),
|
||||
NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite,
|
||||
Filter = "prefs"
|
||||
};
|
||||
|
||||
fileWatcher.Changed += OnWatchedFileChanged;
|
||||
}
|
||||
|
||||
protected override void FetchKeysFromSystem()
|
||||
{
|
||||
cachedData = new string[0];
|
||||
|
||||
if (File.Exists(prefPath))
|
||||
{
|
||||
XmlReaderSettings settings = new XmlReaderSettings();
|
||||
XmlReader reader = XmlReader.Create(prefPath, settings);
|
||||
|
||||
XDocument doc = XDocument.Load(reader);
|
||||
|
||||
cachedData = doc.Element("unity_prefs").Elements().Select((e) => e.Attribute("name").Value).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public override void StartMonitoring()
|
||||
{
|
||||
fileWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
public override void StopMonitoring()
|
||||
{
|
||||
fileWatcher.EnableRaisingEvents = false;
|
||||
}
|
||||
|
||||
public override bool IsMonitoring()
|
||||
{
|
||||
return fileWatcher.EnableRaisingEvents;
|
||||
}
|
||||
|
||||
private void OnWatchedFileChanged(object source, FileSystemEventArgs e)
|
||||
{
|
||||
OnPrefEntryChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#elif UNITY_EDITOR_OSX
|
||||
|
||||
public class MacPrefStorage : PreferanceStorageAccessor
|
||||
{
|
||||
private readonly FileSystemWatcher fileWatcher;
|
||||
private readonly DirectoryInfo prefsDirInfo;
|
||||
private readonly String prefsFileNameWithoutExtension;
|
||||
|
||||
public MacPrefStorage(string pathToPrefs) : base(Path.Combine(Environment.GetEnvironmentVariable("HOME"), pathToPrefs))
|
||||
{
|
||||
prefsDirInfo = new DirectoryInfo(Path.GetDirectoryName(prefPath));
|
||||
prefsFileNameWithoutExtension = Path.GetFileNameWithoutExtension(prefPath);
|
||||
|
||||
fileWatcher = new FileSystemWatcher
|
||||
{
|
||||
Path = Path.GetDirectoryName(prefPath),
|
||||
NotifyFilter = NotifyFilters.LastWrite,
|
||||
Filter = Path.GetFileName(prefPath)
|
||||
};
|
||||
|
||||
// MAC delete the old and create a new file instead of updating
|
||||
fileWatcher.Created += OnWatchedFileChanged;
|
||||
}
|
||||
|
||||
protected override void FetchKeysFromSystem()
|
||||
{
|
||||
// Workaround to avoid incomplete tmp phase from MAC OS
|
||||
foreach (FileInfo info in prefsDirInfo.GetFiles())
|
||||
{
|
||||
// Check if tmp PlayerPrefs file exist
|
||||
if (info.FullName.Contains(prefsFileNameWithoutExtension) && !info.FullName.EndsWith(".plist"))
|
||||
{
|
||||
StartLoadingDelegate?.Invoke();
|
||||
return;
|
||||
}
|
||||
}
|
||||
StopLoadingDelegate?.Invoke();
|
||||
|
||||
cachedData = new string[0];
|
||||
|
||||
if (File.Exists(prefPath))
|
||||
{
|
||||
string fixedPrefsPath = prefPath.Replace("\"", "\\\"").Replace("'", "\\'").Replace("`", "\\`");
|
||||
var cmdStr = string.Format(@"-p '{0}'", fixedPrefsPath);
|
||||
|
||||
string stdOut = String.Empty;
|
||||
string errOut = String.Empty;
|
||||
|
||||
var process = new System.Diagnostics.Process();
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.FileName = "plutil";
|
||||
process.StartInfo.Arguments = cmdStr;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.OutputDataReceived += new DataReceivedEventHandler((sender, evt) => { stdOut += evt.Data + "\n"; });
|
||||
process.ErrorDataReceived += new DataReceivedEventHandler((sender, evt) => { errOut += evt.Data + "\n"; });
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
MatchCollection matches = Regex.Matches(stdOut, @"(?: "")(.*)(?:"" =>.*)");
|
||||
cachedData = matches.Cast<Match>().Select((e) => e.Groups[1].Value).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public override void StartMonitoring()
|
||||
{
|
||||
fileWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
public override void StopMonitoring()
|
||||
{
|
||||
fileWatcher.EnableRaisingEvents = false;
|
||||
}
|
||||
|
||||
public override bool IsMonitoring()
|
||||
{
|
||||
return fileWatcher.EnableRaisingEvents;
|
||||
}
|
||||
|
||||
private void OnWatchedFileChanged(object source, FileSystemEventArgs e)
|
||||
{
|
||||
OnPrefEntryChanged();
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca73981befbec034a8f1b31535ef8b7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,339 @@
|
||||
|
||||
/*
|
||||
* Thanks to gr0ss for the inspiration.
|
||||
*
|
||||
* https://github.com/gr0ss/RegistryMonitor
|
||||
*
|
||||
* 11/08/2019
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
//namespace BgTools.PlayerPrefsEditor
|
||||
namespace Meryel.UnityCodeAssist.Editor.Preferences
|
||||
{
|
||||
public class RegistryMonitor : IDisposable
|
||||
{
|
||||
#region P/Invoke
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int samDesired, out IntPtr phkResult);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
private static extern int RegNotifyChangeKeyValue(IntPtr hKey, bool bWatchSubtree, RegChangeNotifyFilter dwNotifyFilter, IntPtr hEvent, bool fAsynchronous);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
private static extern int RegCloseKey(IntPtr hKey);
|
||||
|
||||
private const int KEY_QUERY_VALUE = 0x0001;
|
||||
private const int KEY_NOTIFY = 0x0010;
|
||||
private const int STANDARD_RIGHTS_READ = 0x00020000;
|
||||
|
||||
private static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000));
|
||||
private static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001));
|
||||
private static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002));
|
||||
private static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003));
|
||||
private static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004));
|
||||
private static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005));
|
||||
private static readonly IntPtr HKEY_DYN_DATA = new IntPtr(unchecked((int)0x80000006));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handling
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the specified registry key has changed.
|
||||
/// </summary>
|
||||
public event EventHandler RegChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="RegChanged"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <p>
|
||||
/// <b>OnRegChanged</b> is called when the specified registry key has changed.
|
||||
/// </p>
|
||||
/// <note type="inheritinfo">
|
||||
/// When overriding <see cref="OnRegChanged"/> in a derived class, be sure to call
|
||||
/// the base class's <see cref="OnRegChanged"/> method.
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
protected virtual void OnRegChanged()
|
||||
{
|
||||
RegChanged?.Invoke(this, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the access to the registry fails.
|
||||
/// </summary>
|
||||
public event ErrorEventHandler Error;
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="Error"/> event.
|
||||
/// </summary>
|
||||
/// <param name="e">The <see cref="Exception"/> which occured while watching the registry.</param>
|
||||
/// <remarks>
|
||||
/// <p>
|
||||
/// <b>OnError</b> is called when an exception occurs while watching the registry.
|
||||
/// </p>
|
||||
/// <note type="inheritinfo">
|
||||
/// When overriding <see cref="OnError"/> in a derived class, be sure to call
|
||||
/// the base class's <see cref="OnError"/> method.
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
protected virtual void OnError(Exception e)
|
||||
{
|
||||
Error?.Invoke(this, new ErrorEventArgs(e));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private member variables
|
||||
|
||||
private IntPtr _registryHive;
|
||||
private string _registrySubName;
|
||||
private readonly object _threadLock = new object();
|
||||
private Thread _thread;
|
||||
private bool _disposed = false;
|
||||
private readonly ManualResetEvent _eventTerminate = new ManualResetEvent(false);
|
||||
|
||||
private RegChangeNotifyFilter _regFilter = RegChangeNotifyFilter.Key | RegChangeNotifyFilter.Attribute | RegChangeNotifyFilter.Value | RegChangeNotifyFilter.Security;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="registryKey">The registry key to monitor.</param>
|
||||
public RegistryMonitor(RegistryKey registryKey)
|
||||
{
|
||||
InitRegistryKey(registryKey.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
public RegistryMonitor(string name)
|
||||
{
|
||||
if (name == null || name.Length == 0)
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
InitRegistryKey(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegistryMonitor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="registryHive">The registry hive.</param>
|
||||
/// <param name="subKey">The sub key.</param>
|
||||
public RegistryMonitor(RegistryHive registryHive, string subKey)
|
||||
{
|
||||
InitRegistryKey(registryHive, subKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this object.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="RegChangeNotifyFilter">RegChangeNotifyFilter</see>.
|
||||
/// </summary>
|
||||
public RegChangeNotifyFilter RegChangeNotifyFilter
|
||||
{
|
||||
get { return _regFilter; }
|
||||
set
|
||||
{
|
||||
lock (_threadLock)
|
||||
{
|
||||
if (IsMonitoring)
|
||||
throw new InvalidOperationException("Monitoring thread is already running");
|
||||
|
||||
_regFilter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
private void InitRegistryKey(RegistryHive hive, string name)
|
||||
{
|
||||
_registryHive = hive switch
|
||||
{
|
||||
RegistryHive.ClassesRoot => HKEY_CLASSES_ROOT,
|
||||
RegistryHive.CurrentConfig => HKEY_CURRENT_CONFIG,
|
||||
RegistryHive.CurrentUser => HKEY_CURRENT_USER,
|
||||
RegistryHive.DynData => HKEY_DYN_DATA,
|
||||
RegistryHive.LocalMachine => HKEY_LOCAL_MACHINE,
|
||||
RegistryHive.PerformanceData => HKEY_PERFORMANCE_DATA,
|
||||
RegistryHive.Users => HKEY_USERS,
|
||||
_ => throw new InvalidEnumArgumentException("hive", (int)hive, typeof(RegistryHive)),
|
||||
};
|
||||
_registrySubName = name;
|
||||
}
|
||||
|
||||
private void InitRegistryKey(string name)
|
||||
{
|
||||
string[] nameParts = name.Split('\\');
|
||||
|
||||
switch (nameParts[0])
|
||||
{
|
||||
case "HKEY_CLASSES_ROOT":
|
||||
case "HKCR":
|
||||
_registryHive = HKEY_CLASSES_ROOT;
|
||||
break;
|
||||
|
||||
case "HKEY_CURRENT_USER":
|
||||
case "HKCU":
|
||||
_registryHive = HKEY_CURRENT_USER;
|
||||
break;
|
||||
|
||||
case "HKEY_LOCAL_MACHINE":
|
||||
case "HKLM":
|
||||
_registryHive = HKEY_LOCAL_MACHINE;
|
||||
break;
|
||||
|
||||
case "HKEY_USERS":
|
||||
_registryHive = HKEY_USERS;
|
||||
break;
|
||||
|
||||
case "HKEY_CURRENT_CONFIG":
|
||||
_registryHive = HKEY_CURRENT_CONFIG;
|
||||
break;
|
||||
|
||||
default:
|
||||
_registryHive = IntPtr.Zero;
|
||||
throw new ArgumentException("The registry hive '" + nameParts[0] + "' is not supported", "value");
|
||||
}
|
||||
|
||||
_registrySubName = String.Join("\\", nameParts, 1, nameParts.Length - 1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// <b>true</b> if this <see cref="RegistryMonitor"/> object is currently monitoring;
|
||||
/// otherwise, <b>false</b>.
|
||||
/// </summary>
|
||||
public bool IsMonitoring
|
||||
{
|
||||
get { return _thread != null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start monitoring.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(null, "This instance is already disposed");
|
||||
|
||||
lock (_threadLock)
|
||||
{
|
||||
if (!IsMonitoring)
|
||||
{
|
||||
_eventTerminate.Reset();
|
||||
_thread = new Thread(new ThreadStart(MonitorThread)) { IsBackground = true };
|
||||
_thread.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the monitoring thread.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(null, "This instance is already disposed");
|
||||
|
||||
lock (_threadLock)
|
||||
{
|
||||
Thread thread = _thread;
|
||||
if (thread != null)
|
||||
{
|
||||
_eventTerminate.Set();
|
||||
thread.Join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MonitorThread()
|
||||
{
|
||||
try
|
||||
{
|
||||
ThreadLoop();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnError(e);
|
||||
}
|
||||
_thread = null;
|
||||
}
|
||||
|
||||
private void ThreadLoop()
|
||||
{
|
||||
int result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY, out IntPtr registryKey);
|
||||
if (result != 0)
|
||||
{
|
||||
throw new Win32Exception(result);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AutoResetEvent _eventNotify = new AutoResetEvent(false);
|
||||
WaitHandle[] waitHandles = new WaitHandle[] { _eventNotify, _eventTerminate };
|
||||
while (!_eventTerminate.WaitOne(0, true))
|
||||
{
|
||||
result = RegNotifyChangeKeyValue(registryKey, true, _regFilter, _eventNotify.SafeWaitHandle.DangerousGetHandle(), true);
|
||||
if (result != 0)
|
||||
{
|
||||
throw new Win32Exception(result);
|
||||
}
|
||||
|
||||
if (WaitHandle.WaitAny(waitHandles) == 0)
|
||||
{
|
||||
OnRegChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (registryKey != IntPtr.Zero)
|
||||
{
|
||||
RegCloseKey(registryKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter for notifications reported by <see cref="RegistryMonitor"/>.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum RegChangeNotifyFilter
|
||||
{
|
||||
/// <summary>Notify the caller if a subkey is added or deleted.</summary>
|
||||
Key = 1,
|
||||
/// <summary>Notify the caller of changes to the attributes of the key,
|
||||
/// such as the security descriptor information.</summary>
|
||||
Attribute = 2,
|
||||
/// <summary>Notify the caller of changes to a value of the key. This can
|
||||
/// include adding or deleting a value, or changing an existing value.</summary>
|
||||
Value = 4,
|
||||
/// <summary>Notify the caller of changes to the security descriptor
|
||||
/// of the key.</summary>
|
||||
Security = 8,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe045212b5ab43f4d9860e1fb03b686b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
309
Packages/com.merry-yellow.code-assist/Editor/ScriptFinder.cs
Normal file
309
Packages/com.merry-yellow.code-assist/Editor/ScriptFinder.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
|
||||
public class ScriptFinder //: MonoBehaviour
|
||||
{
|
||||
|
||||
internal static Type? GetType123(string typeName)
|
||||
{
|
||||
//**--
|
||||
//**--
|
||||
/*
|
||||
* for performance,
|
||||
* check assembly-csharp, assembly-csharp-editor, assembly-csharp-first-pass,assembly-csharp-editor-first-pass
|
||||
* first, (then maybe asmdef dlls), then check mscorlib and other referenced dlls
|
||||
*/
|
||||
|
||||
|
||||
//**--use typecache???
|
||||
//TypeCache
|
||||
|
||||
//**--check this again
|
||||
//https://github.com/Unity-Technologies/SuperScience/blob/main/Editor/GlobalNamespaceWatcher.cs
|
||||
|
||||
// Try Type.GetType() first. This will work with types defined
|
||||
// by the Mono runtime, in the same assembly as the caller, etc.
|
||||
Type type = Type.GetType(typeName);
|
||||
|
||||
// If it worked, then we're done here
|
||||
if (type != null)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
// Attempt to search for type on the loaded assemblies
|
||||
Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach (Assembly assembly in currentAssemblies)
|
||||
{
|
||||
type = assembly.GetType(typeName);
|
||||
if (type != null)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// If we still haven't found the proper type, we can enumerate all of the
|
||||
// loaded assemblies and see if any of them define the type
|
||||
var currentAssembly = Assembly.GetExecutingAssembly();
|
||||
var referencedAssemblies = currentAssembly.GetReferencedAssemblies();
|
||||
foreach (var assemblyName in referencedAssemblies)
|
||||
{
|
||||
// Load the referenced assembly
|
||||
var assembly = Assembly.Load(assemblyName);
|
||||
if (assembly != null)
|
||||
{
|
||||
// See if that assembly defines the named type
|
||||
type = assembly.GetType(typeName);
|
||||
if (type != null)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The type just couldn't be found...
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool FindInstanceOfType(string typeName, string docPath, out GameObject? gameObjectInstanceOfType, out ScriptableObject? scriptableObjectInstanceOfType)
|
||||
{
|
||||
gameObjectInstanceOfType = null;
|
||||
scriptableObjectInstanceOfType = null;
|
||||
|
||||
var type = GetType123(typeName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
// Possibly a class has been created in Visual Studio, and these changes are not reflected in Unity domain yet
|
||||
// We can force Unity to recompile and get the type, but since there will be no instance of that type, it won't be of any use, will be just a performance burden
|
||||
Serilog.Log.Debug("{Type} type couldn't be found", typeName);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var obj = GetObjectOfType(type, out var requestVerboseType);
|
||||
if (requestVerboseType)
|
||||
MQTTnetInitializer.Publisher?.SendRequestVerboseType(typeName, docPath);
|
||||
|
||||
if (obj != null && obj is GameObject go)
|
||||
{
|
||||
gameObjectInstanceOfType = go;
|
||||
return true;
|
||||
}
|
||||
else if (obj != null && obj is ScriptableObject so)
|
||||
{
|
||||
scriptableObjectInstanceOfType = so;
|
||||
return true;
|
||||
}
|
||||
|
||||
Serilog.Log.Debug("Instance of {Type} type couldn't be found", typeName);
|
||||
return false;
|
||||
}
|
||||
|
||||
static UnityEngine.Object? GetObjectOfType(Type type, out bool requestVerboseType)
|
||||
{
|
||||
requestVerboseType = false;
|
||||
var isMonoBehaviour = type.IsSubclassOf(typeof(MonoBehaviour));
|
||||
var isScriptableObject = type.IsSubclassOf(typeof(ScriptableObject));
|
||||
|
||||
if (!isMonoBehaviour && !isScriptableObject)
|
||||
{
|
||||
// Possibly a class's base class changed from none to MonoBehaviour in Visual Studio, and these changes are not reflected in Unity domain yet
|
||||
// We can force Unity to recompile and get the type correctly, but since there will be no instance of that type, it won't be of any use, will be just a performance burden
|
||||
Serilog.Log.Debug("{Type} is not a valid Unity object", type.ToString());
|
||||
//requestVerboseType = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
UnityEngine.Object? obj;
|
||||
|
||||
obj = getObjectToSend(Selection.activeGameObject, type);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
|
||||
|
||||
obj = getObjectToSend(Selection.activeTransform, type);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
|
||||
|
||||
obj = getObjectToSend(Selection.activeObject, type);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
|
||||
|
||||
//**--check source code of this, for sorting
|
||||
var filteredArray = Selection.GetFiltered(type, SelectionMode.Unfiltered);
|
||||
if (filteredArray != null)
|
||||
{
|
||||
//**--sort
|
||||
foreach (var filtered in filteredArray)
|
||||
{
|
||||
obj = getObjectToSend(filtered, type);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//**--rest can be slow, try avoiding them, make own db etc
|
||||
//**--can add a stop-wacher and add warning if slow as well
|
||||
//**--can also cache the result
|
||||
|
||||
try
|
||||
{
|
||||
// UnityEngine.Object.FindObjectOfType is deprecated in new versions of Unity
|
||||
#if UNITY_2022_3 || UNITY_2023_1_OR_NEWER
|
||||
// Object.FindAnyObjectOfType doesn't return Assets (for example meshes, textures, or prefabs), or inactive objects. It also doesn't return objects that have HideFlags.DontSave set.
|
||||
obj = UnityEngine.Object.FindAnyObjectByType(type);
|
||||
#else
|
||||
// Object.FindObjectOfType will not return Assets (meshes, textures, prefabs, ...) or inactive objects. It will not return an object that has HideFlags.DontSave set.
|
||||
obj = UnityEngine.Object.FindObjectOfType(type);
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Warning(ex, "FindObjectOfType/FindAnyObjectByType failed for {Type}, mb:{isMB}, so:{isSO}", type.ToString(), isMonoBehaviour, isScriptableObject);
|
||||
}
|
||||
|
||||
obj = getObjectToSend(obj, type);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
|
||||
UnityEngine.Object[]? arr = null;
|
||||
try
|
||||
{
|
||||
// This function can return any type of Unity object that is loaded, including game objects, prefabs, materials, meshes, textures, etc.
|
||||
// Contrary to Object.FindObjectsOfType this function will also list disabled objects.
|
||||
arr = Resources.FindObjectsOfTypeAll(type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//var isMonoBehaviour = type.IsSubclassOf(typeof(MonoBehaviour));
|
||||
//var isScriptableObject = type.IsSubclassOf(typeof(ScriptableObject));
|
||||
Serilog.Log.Warning(ex, "FindObjectsOfTypeAll failed for {Type}, mb:{isMB}, so:{isSO}", type.ToString(), isMonoBehaviour, isScriptableObject);
|
||||
}
|
||||
|
||||
if (arr != null)
|
||||
{
|
||||
//**--sort
|
||||
foreach (var item in arr)
|
||||
{
|
||||
obj = getObjectToSend(item, type);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
|
||||
|
||||
static UnityEngine.Object? getObjectToSend(UnityEngine.Object? obj, Type type)
|
||||
{
|
||||
if (obj == null || !obj)
|
||||
return null;
|
||||
|
||||
if (obj is GameObject go)
|
||||
{
|
||||
if (!go)
|
||||
return null;
|
||||
if (isTypeComponent(type) && go.GetComponent(type) != null)
|
||||
return go;
|
||||
}
|
||||
else if (obj is Transform transform)
|
||||
{
|
||||
go = transform.gameObject;
|
||||
if (!go)
|
||||
return null;
|
||||
if (isTypeComponent(type) && go.GetComponent(type) != null)
|
||||
return go;
|
||||
}
|
||||
else if (obj is Component comp)
|
||||
{
|
||||
go = comp.gameObject;
|
||||
if (!go)
|
||||
return null;
|
||||
else
|
||||
return go;
|
||||
}
|
||||
else if (obj is ScriptableObject so)
|
||||
{
|
||||
if (!so)
|
||||
return null;
|
||||
else
|
||||
return so;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool isTypeComponent(Type type)
|
||||
{
|
||||
var componentType = typeof(Component);//**--cache these types
|
||||
if (type == componentType || type.IsSubclassOf(componentType))
|
||||
return true;
|
||||
|
||||
// MonoBehaviour is Component, so below is unnecessary
|
||||
//var monoBehaviourType = typeof(MonoBehaviour);
|
||||
//if (type == monoBehaviourType || type.IsSubclassOf(monoBehaviourType))
|
||||
// return true;
|
||||
|
||||
//else if(type is interface)//**--
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DENEMEEEE()
|
||||
{
|
||||
//UnityEditor.SceneManagement.EditorSceneManager.all
|
||||
//AssetDatabase.get
|
||||
|
||||
foreach (var sceneGUID in AssetDatabase.FindAssets("t:Scene", new string[] { "Assets" }))
|
||||
{
|
||||
var scenePath = AssetDatabase.GUIDToAssetPath(sceneGUID);
|
||||
Debug.Log("scenePath: " + scenePath);
|
||||
|
||||
//EditorSceneManager.OpenScene(scenePath);
|
||||
//var scene = EditorSceneManager.GetActiveScene();
|
||||
}
|
||||
|
||||
var assets = AssetDatabase.FindAssets("Deneme_OtherScene");
|
||||
Debug.Log("Assets: " + assets.Length);
|
||||
|
||||
foreach (var assetGuid in assets)
|
||||
{
|
||||
var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
|
||||
Debug.Log("Asset: " + assetGuid + " " + assetPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetActiveGameObject(out GameObject activeGameObject)
|
||||
{
|
||||
activeGameObject = Selection.activeGameObject;
|
||||
return activeGameObject ? true : false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11ac3f66eb86de34cb65aa46b691b9ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
Packages/com.merry-yellow.code-assist/Editor/SetupManager.cs
Normal file
43
Packages/com.merry-yellow.code-assist/Editor/SetupManager.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
//using Serilog = Meryel.UnityCodeAssist.Serilog;
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Setup
|
||||
{
|
||||
|
||||
#if !MERYEL_UCA_LITE_VERSION
|
||||
[InitializeOnLoad]
|
||||
public static class SetupManager
|
||||
{
|
||||
static SetupManager()
|
||||
{
|
||||
//var cleanupPath = CommonTools.GetToolPath("CleanupObsoleteFiles.bat");
|
||||
//Assister.Execute(cleanupPath);
|
||||
Cleanup.DoCleanup();
|
||||
|
||||
//var installerPath = CommonTools.GetToolPath("InstallFullVersionOfVsix.bat");
|
||||
//Assister.Execute(installerPath);
|
||||
Assister.Upgrade();
|
||||
|
||||
// delete itself (file), so these cleanup and install only called once
|
||||
var scriptMeta = CommonTools.GetScriptPath("SetupManager.cs.meta");
|
||||
System.IO.File.Delete(scriptMeta);
|
||||
var script = CommonTools.GetScriptPath("SetupManager.cs");
|
||||
System.IO.File.Delete(script);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff81bd2adb6cbc242b782ff821e5f58b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
86
Packages/com.merry-yellow.code-assist/Editor/StatusWindow.cs
Normal file
86
Packages/com.merry-yellow.code-assist/Editor/StatusWindow.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
public class StatusWindow : EditorWindow
|
||||
{
|
||||
GUIStyle? styleLabel;
|
||||
|
||||
public static void Display()
|
||||
{
|
||||
// Get existing open window or if none, make a new one:
|
||||
var window = GetWindow<StatusWindow>();
|
||||
window.Show();
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendConnectionInfo();
|
||||
|
||||
Serilog.Log.Debug("Displaying status window");
|
||||
|
||||
MQTTnetInitializer.Publisher?.SendAnalyticsEvent("Gui", "StatusWindow_Display");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
//**--icon
|
||||
//var icon = AssetDatabase.LoadAssetAtPath<Texture>("Assets/Sprites/Gear.png");
|
||||
//titleContent = new GUIContent("Code Assist", icon);
|
||||
titleContent = new GUIContent(Assister.Title);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
var hasAnyClient = MQTTnetInitializer.Publisher?.Clients.Any() == true;
|
||||
|
||||
styleLabel ??= new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
wordWrap = true,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
};
|
||||
|
||||
if (hasAnyClient)
|
||||
{
|
||||
EditorGUILayout.LabelField($"Code Assist is working!", styleLabel, GUILayout.ExpandWidth(true));
|
||||
|
||||
foreach (var client in MQTTnetInitializer.Publisher!.Clients)
|
||||
{
|
||||
EditorGUILayout.LabelField($"Connected to {client.ContactInfo}", styleLabel, GUILayout.ExpandWidth(true));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField($"Code Assist isn't working!", styleLabel, GUILayout.ExpandWidth(true));
|
||||
|
||||
EditorGUILayout.LabelField($"No IDE found", styleLabel, GUILayout.ExpandWidth(true));
|
||||
}
|
||||
|
||||
#if MERYEL_UCA_LITE_VERSION
|
||||
|
||||
EditorGUILayout.LabelField($"", styleLabel, GUILayout.ExpandWidth(true));
|
||||
EditorGUILayout.LabelField($"This is the lite version of Code Assist with limited features.", styleLabel, GUILayout.ExpandWidth(true));
|
||||
EditorGUILayout.LabelField($"To unlock all of the features, get the full version.", styleLabel, GUILayout.ExpandWidth(true));
|
||||
|
||||
if (GUILayout.Button("Get full version"))
|
||||
{
|
||||
Application.OpenURL("https://unitycodeassist.netlify.app/purchase?utm_source=unity_getfullbutton");
|
||||
}
|
||||
|
||||
#endif // MERYEL_UCA_LITE_VERSION
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf49614de8ad81c45828cc9e98f0c174
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,683 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor
|
||||
{
|
||||
internal static partial class UnityClassExtensions
|
||||
{
|
||||
static GameObject? GetParentGO(GameObject go)
|
||||
{
|
||||
if (!go)
|
||||
return null;
|
||||
|
||||
var parentTransform = go.transform.parent;
|
||||
|
||||
if (parentTransform && parentTransform.gameObject)
|
||||
return parentTransform.gameObject;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
static string GetId(UnityEngine.Object? obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
// obj can be null
|
||||
|
||||
var globalObjectId = GlobalObjectId.GetGlobalObjectIdSlow(obj);
|
||||
var objectGuid = globalObjectId.ToString();
|
||||
return objectGuid;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// OnBeforeSerialize of user scripts may raise exception
|
||||
Serilog.Log.Warning(ex, "GetGlobalObjectIdSlow failed for obj {Obj}", obj);
|
||||
return "GlobalObjectId_V1-0-00000000000000000000000000000000-0-0";
|
||||
}
|
||||
}
|
||||
|
||||
public static Synchronizer.Model.Component_Material? ToSyncModelOfComponentMaterial(this GameObject go)
|
||||
{
|
||||
if (!go.TryGetComponent<Renderer>(out var renderer))
|
||||
return null;
|
||||
|
||||
if (!renderer)
|
||||
return null;
|
||||
|
||||
var propertyNames = new List<string>();
|
||||
var propertyIndices = new List<string>();
|
||||
var propertyTypes = new List<int>();
|
||||
var propertyValues = new List<string>();
|
||||
|
||||
var processedShaders = new HashSet<Shader>();
|
||||
|
||||
// most of the time, there will be a single material, so initiate with capacity of 1
|
||||
var keywordsContainer = new List<string[]>(1);
|
||||
var passNamesContainer = new List<string[]>(1);
|
||||
var passIndicesContainer = new List<string[]>(1);
|
||||
|
||||
foreach (var material in renderer.sharedMaterials)
|
||||
{
|
||||
if (!material)
|
||||
continue;
|
||||
|
||||
var shader = material.shader;
|
||||
if (!shader)
|
||||
continue;
|
||||
|
||||
if (processedShaders.Contains(shader))
|
||||
continue;
|
||||
processedShaders.Add(shader);
|
||||
|
||||
int propertyCount = shader.GetPropertyCount();
|
||||
|
||||
for (int i = 0; i < propertyCount; i++)
|
||||
{
|
||||
var propertyName = shader.GetPropertyName(i);
|
||||
var propertyId = Shader.PropertyToID(propertyName);
|
||||
|
||||
if (!material.HasProperty(propertyId))
|
||||
continue;
|
||||
|
||||
var propertyTypeRaw = shader.GetPropertyType(i);
|
||||
GetExtendedTypeAndValue(propertyId, propertyTypeRaw, material, out var propertyTypeExtended, out var propertyValue);
|
||||
|
||||
propertyNames.Add(propertyName);
|
||||
propertyIndices.Add(propertyId.ToString());
|
||||
propertyTypes.Add((int)propertyTypeExtended);
|
||||
propertyValues.Add(propertyValue);
|
||||
}
|
||||
|
||||
keywordsContainer.Add(shader.keywordSpace.keywordNames);
|
||||
|
||||
var passCount = material.passCount;
|
||||
var passNames = new string[passCount];
|
||||
var passIndices = new string[passCount];
|
||||
for (int i = 0; i < passCount; i++)
|
||||
{
|
||||
passNames[i] = material.GetPassName(i);
|
||||
passIndices[i] = i.ToString();
|
||||
}
|
||||
passNamesContainer.Add(passNames);
|
||||
passIndicesContainer.Add(passIndices);
|
||||
}
|
||||
|
||||
var data = new Synchronizer.Model.Component_Material
|
||||
{
|
||||
GameObjectId = GetId(go),
|
||||
PropertyNames = propertyNames.ToArray(),
|
||||
PropertyIndices = propertyIndices.ToArray(),
|
||||
PropertyTypes = propertyTypes.ToArray(),
|
||||
PropertyValues = propertyValues.ToArray(),
|
||||
Keywords = ConcatenateListOfArrays(keywordsContainer),
|
||||
PassNames = ConcatenateListOfArrays(passNamesContainer),
|
||||
PassIndices = ConcatenateListOfArrays(passIndicesContainer),
|
||||
};
|
||||
return data;
|
||||
|
||||
|
||||
static void GetExtendedTypeAndValue(int propertyId, UnityEngine.Rendering.ShaderPropertyType typeRaw, Material material, out Synchronizer.Model.Component_Material.MaterialPropertyType typeExtended, out string value)
|
||||
{
|
||||
// Handle scalar types based on shader declaration
|
||||
switch (typeRaw)
|
||||
{
|
||||
case UnityEngine.Rendering.ShaderPropertyType.Color:
|
||||
typeExtended = Synchronizer.Model.Component_Material.MaterialPropertyType.Color;
|
||||
value = material.GetColor(propertyId).ToString();
|
||||
break;
|
||||
|
||||
case UnityEngine.Rendering.ShaderPropertyType.Vector:
|
||||
typeExtended = Synchronizer.Model.Component_Material.MaterialPropertyType.Vector;
|
||||
value = material.GetVector(propertyId).ToString();
|
||||
break;
|
||||
|
||||
case UnityEngine.Rendering.ShaderPropertyType.Float:
|
||||
typeExtended = Synchronizer.Model.Component_Material.MaterialPropertyType.Float;
|
||||
value = material.GetFloat(propertyId).ToString();
|
||||
break;
|
||||
|
||||
case UnityEngine.Rendering.ShaderPropertyType.Range:
|
||||
typeExtended = Synchronizer.Model.Component_Material.MaterialPropertyType.Range;
|
||||
value = material.GetFloat(propertyId).ToString();
|
||||
break;
|
||||
|
||||
case UnityEngine.Rendering.ShaderPropertyType.Texture:
|
||||
{
|
||||
typeExtended = Synchronizer.Model.Component_Material.MaterialPropertyType.Texture;
|
||||
var texture = material.GetTexture(propertyId);
|
||||
if (texture)
|
||||
value = texture.name;
|
||||
else
|
||||
value = string.Empty;
|
||||
}
|
||||
break;
|
||||
|
||||
case UnityEngine.Rendering.ShaderPropertyType.Int:
|
||||
typeExtended = Synchronizer.Model.Component_Material.MaterialPropertyType.Integer;
|
||||
value = material.GetInteger(propertyId).ToString();
|
||||
break;
|
||||
|
||||
default:
|
||||
typeExtended = Synchronizer.Model.Component_Material.MaterialPropertyType.Invalid;
|
||||
value = string.Empty;
|
||||
Serilog.Log.Error("invalid material type {TypeRaw}", typeRaw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static string[] ConcatenateListOfArrays(List<string[]> listOfArrays)
|
||||
{
|
||||
if (listOfArrays.Count == 0)
|
||||
return new string[0];
|
||||
else if (listOfArrays.Count == 1)
|
||||
return listOfArrays[0];
|
||||
|
||||
int totalLength = 0;
|
||||
foreach (var arr in listOfArrays)
|
||||
totalLength += arr.Length;
|
||||
|
||||
string[] result = new string[totalLength];
|
||||
Span<string> span = result.AsSpan();
|
||||
|
||||
int offset = 0;
|
||||
foreach (var arr in listOfArrays)
|
||||
{
|
||||
arr.AsSpan().CopyTo(span.Slice(offset));
|
||||
offset += arr.Length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static Synchronizer.Model.Component_Animation? ToSyncModelOfComponentAnimation(this GameObject go)
|
||||
{
|
||||
if (!go.TryGetComponent<Animation>(out var animation))
|
||||
return null;
|
||||
|
||||
if (!animation.isActiveAndEnabled)
|
||||
return null;
|
||||
|
||||
var data = new Synchronizer.Model.Component_Animation
|
||||
{
|
||||
GameObjectId = GetId(go)
|
||||
};
|
||||
|
||||
/*
|
||||
var clips = AnimationUtility.GetAnimationClips(go);
|
||||
data.Clips = new string[clips.Length];
|
||||
for (int i = 0; i < clips.Length; i++)
|
||||
{
|
||||
data.Clips[i] = clips[i].name;
|
||||
}
|
||||
*/
|
||||
|
||||
var states = new List<string>();
|
||||
foreach (AnimationState state in animation)
|
||||
{
|
||||
states.Add(state.name);
|
||||
}
|
||||
data.States = states.ToArray();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static Synchronizer.Model.Component_Animator? ToSyncModelOfComponentAnimator(this GameObject go)
|
||||
{
|
||||
if (!go.TryGetComponent<Animator>(out var animator))
|
||||
return null;
|
||||
|
||||
if (!animator.isActiveAndEnabled)
|
||||
return null;
|
||||
|
||||
if (!animator.runtimeAnimatorController)
|
||||
return null;
|
||||
|
||||
var data = new Synchronizer.Model.Component_Animator
|
||||
{
|
||||
GameObjectId = GetId(go)
|
||||
};
|
||||
|
||||
var layerCount = animator.layerCount;
|
||||
data.LayerIndices = new string[layerCount];
|
||||
data.LayerNames = new string[layerCount];
|
||||
for (int i = 0; i < layerCount; i++)
|
||||
{
|
||||
data.LayerIndices[i] = i.ToString();
|
||||
data.LayerNames[i] = animator.GetLayerName(i);
|
||||
}
|
||||
|
||||
int curParameterIndex = 0;
|
||||
try
|
||||
{
|
||||
var parameterCount = animator.parameterCount;
|
||||
data.ParameterIndices = new string[parameterCount];
|
||||
data.ParameterNames = new string[parameterCount];
|
||||
data.ParameterHashes = new string[parameterCount];
|
||||
data.ParameterTypes = new int[parameterCount];
|
||||
for (var i = 0; i < parameterCount; i++)
|
||||
{
|
||||
curParameterIndex = i;
|
||||
// receiving error here, like "IndexOutOfRangeException: Index must be between 0 and 3",
|
||||
// probably user edits it while retrieving data
|
||||
var parameter = animator.GetParameter(i);
|
||||
data.ParameterIndices[i] = i.ToString();
|
||||
data.ParameterNames[i] = parameter.name;
|
||||
data.ParameterHashes[i] = parameter.nameHash.ToString();
|
||||
data.ParameterTypes[i] = (int)parameter.type;
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfRangeException indexOutOfRangeException)
|
||||
{
|
||||
Serilog.Log.Debug(indexOutOfRangeException, "handling IndexOutOfRangeException of animator.GetParameter(i)");
|
||||
|
||||
var parameterCount = curParameterIndex;
|
||||
|
||||
data.ParameterIndices = ResizeArray(data.ParameterIndices, parameterCount);
|
||||
data.ParameterNames = ResizeArray(data.ParameterNames, parameterCount);
|
||||
data.ParameterHashes = ResizeArray(data.ParameterHashes, parameterCount);
|
||||
data.ParameterTypes = ResizeArray(data.ParameterTypes, parameterCount);
|
||||
}
|
||||
|
||||
// When you specify a state name, or the string used to generate a hash, it should include the name of the parent layer. For example, if you have a Bounce state in the Base Layer, the name is Base Layer.Bounce
|
||||
// The name should be in the form Layer.Name or Layer.SubStateMachine.Name
|
||||
if (!GetAnimatorStateInfo(animator, out var states, out var transitions) ||
|
||||
states == null || transitions == null) //for nullables
|
||||
return data;
|
||||
|
||||
var stateCount = states.Count;
|
||||
data.StateNames = new string[stateCount];
|
||||
data.StateNameHashes = new string[stateCount];
|
||||
data.StateTags = new string[stateCount];
|
||||
data.StateTagHashes = new string[stateCount];
|
||||
data.StateFullPaths = new string[stateCount];
|
||||
data.StateFullPathHashes = new string[stateCount];
|
||||
data.StateMotionNames = new string[stateCount];
|
||||
for (int i = 0; i < stateCount; i++)
|
||||
{
|
||||
var state = states[i].state;
|
||||
var fullPath = states[i].fullPath;
|
||||
data.StateNames[i] = state.name;
|
||||
data.StateNameHashes[i] = state.nameHash.ToString();
|
||||
data.StateTags[i] = state.tag;
|
||||
data.StateTagHashes[i] = Animator.StringToHash(state.tag).ToString();
|
||||
data.StateFullPaths[i] = fullPath;
|
||||
data.StateFullPathHashes[i] = Animator.StringToHash(fullPath).ToString();
|
||||
var motion = state.motion;
|
||||
if (motion)
|
||||
data.StateMotionNames[i] = motion.name;
|
||||
else
|
||||
data.StateMotionNames[i] = string.Empty;
|
||||
}
|
||||
|
||||
var transitionCount = transitions.Count;
|
||||
data.TransitionNames = new string[transitionCount];
|
||||
data.TransitionNameHashes = new string[transitionCount];
|
||||
data.TransitionUsernames = new string[transitionCount];
|
||||
data.TransitionUsernameHashes = new string[transitionCount];
|
||||
data.TransitionFullPaths = new string[transitionCount];
|
||||
data.TransitionFullPathHashes = new string[transitionCount];
|
||||
for (int i = 0; i < transitionCount; i++)
|
||||
{
|
||||
var transition = transitions[i].transition;
|
||||
var fullPath = transitions[i].fullPath;
|
||||
data.TransitionNames[i] = transition.name;
|
||||
data.TransitionNameHashes[i] = Animator.StringToHash(transition.name).ToString();
|
||||
data.TransitionUsernames[i] = transition.GetDisplayName(transition.destinationState);
|
||||
data.TransitionUsernameHashes[i] = Animator.StringToHash(data.TransitionUsernames[i]).ToString();
|
||||
data.TransitionFullPaths[i] = fullPath;
|
||||
data.TransitionFullPathHashes[i] = Animator.StringToHash(fullPath).ToString();
|
||||
}
|
||||
|
||||
var clips = animator.runtimeAnimatorController.animationClips;
|
||||
data.Clips = new string[clips.Length];
|
||||
for (int i = 0; i < clips.Length; i++)
|
||||
data.Clips[i] = clips[i].name;
|
||||
|
||||
return data;
|
||||
|
||||
//var events = clips.SelectMany(c => c.events);
|
||||
|
||||
static T[] ResizeArray<T>(T[] array, int size)
|
||||
{
|
||||
Array.Resize(ref array, size);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool GetAnimatorStateInfo(Animator animator, out List<(AnimatorState state, string fullPath)>? states, out List<(AnimatorTransition transition, string fullPath)>? transitions)
|
||||
{
|
||||
AnimatorController? controller = animator.runtimeAnimatorController as AnimatorController;
|
||||
if (!controller || controller == null)
|
||||
{
|
||||
states = null;
|
||||
transitions = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimatorControllerLayer[] layers = controller.layers;
|
||||
states = new List<(AnimatorState, string)>();
|
||||
transitions = new List<(AnimatorTransition, string)>();
|
||||
foreach (AnimatorControllerLayer layer in layers)
|
||||
{
|
||||
if (layer == null || layer.stateMachine == null)
|
||||
continue;
|
||||
|
||||
ChildAnimatorState[] animStates = layer.stateMachine.states;
|
||||
getStateMachineInfo(layer.stateMachine, 0, layer.name, states, transitions);
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
static void getStateMachineInfo(AnimatorStateMachine stateMachine, int depth, string curPath,
|
||||
List<(AnimatorState state, string fullPath)> states,
|
||||
List<(AnimatorTransition transition, string fullPath)> transitions)
|
||||
{
|
||||
// for performance
|
||||
if (depth > 4 || states.Count > 128)
|
||||
return;
|
||||
|
||||
states.AddRange(stateMachine.states.Select(s => (s.state, curPath + "." + s.state.name)));
|
||||
|
||||
//var transitions = stateMachine.GetStateMachineTransitions(stateMachine);
|
||||
transitions.AddRange(stateMachine.GetStateMachineTransitions(stateMachine).Select(t => (t, curPath + "." + t.name)));
|
||||
|
||||
foreach (var subStateMachine in stateMachine.stateMachines)
|
||||
getStateMachineInfo(subStateMachine.stateMachine, depth + 1, curPath + "." + subStateMachine.stateMachine.name, states, transitions);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Synchronizer.Model.GameObject? ToSyncModel(this GameObject go, int priority = 0)
|
||||
{
|
||||
if (!go)
|
||||
return null;
|
||||
|
||||
var data = new Synchronizer.Model.GameObject()
|
||||
{
|
||||
Id = GetId(go),
|
||||
|
||||
Name = go.name,
|
||||
Layer = go.layer.ToString(),
|
||||
Tag = go.tag,
|
||||
Scene = go.scene.name,
|
||||
|
||||
ParentId = GetId(GetParentGO(go)),
|
||||
ChildrenIds = getChildrenIds(go),
|
||||
|
||||
Components = getComponents(go),
|
||||
|
||||
Priority = priority,
|
||||
};
|
||||
return data;
|
||||
|
||||
static string[] getChildrenIds(GameObject g)
|
||||
{
|
||||
var ids = new List<string>();
|
||||
var limit = 10;//**--
|
||||
foreach (Transform child in g.transform)
|
||||
{
|
||||
if (!child || !child.gameObject)
|
||||
continue;
|
||||
|
||||
ids.Add(GetId(child.gameObject));
|
||||
|
||||
if (--limit <= 0)
|
||||
break;
|
||||
}
|
||||
return ids.ToArray();
|
||||
}
|
||||
|
||||
//**--limit/10
|
||||
static string[] getComponents(GameObject g) =>
|
||||
g.GetComponents<Component>().Where(c => c).Select(c => c.GetType().FullName).Take(10).ToArray();
|
||||
/*(string[] componentNames, Synchronizer.Model.ComponentData[] componentData) getComponents(GameObject g)
|
||||
{
|
||||
var components = g.GetComponents<Component>();
|
||||
var names = components.Select(c => c.name).ToArray();
|
||||
|
||||
var data = new List<Synchronizer.Model.ComponentData>();
|
||||
foreach (var comp in components)
|
||||
{
|
||||
var name = comp.name;
|
||||
|
||||
|
||||
}
|
||||
|
||||
return (names, data.ToArray());
|
||||
}*/
|
||||
}
|
||||
|
||||
internal static Synchronizer.Model.GameObject[]? ToSyncModelOfHierarchy(this GameObject go)
|
||||
{
|
||||
if (!go)
|
||||
return null;
|
||||
|
||||
var list = new List<Synchronizer.Model.GameObject>();
|
||||
|
||||
var parent = GetParentGO(go);
|
||||
if (parent != null && parent)
|
||||
{
|
||||
var parentModel = parent.ToSyncModel();
|
||||
if (parentModel != null)
|
||||
list.Add(parentModel);
|
||||
}
|
||||
|
||||
int limit = 10;
|
||||
foreach (Transform child in go.transform)
|
||||
{
|
||||
if (!child || !child.gameObject)
|
||||
continue;
|
||||
|
||||
var childModel = child.gameObject.ToSyncModel();
|
||||
if (childModel == null)
|
||||
continue;
|
||||
|
||||
list.Add(childModel);
|
||||
|
||||
if (--limit <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
internal static Synchronizer.Model.ComponentData[]? ToSyncModelOfComponents(this GameObject go)
|
||||
{
|
||||
if (!go)
|
||||
return null;
|
||||
|
||||
var limit = 10;//**--
|
||||
return go.GetComponents<Component>().Where(c => c).Select(c => c.ToSyncModel(go)).Where(cd => cd != null).Take(limit).ToArray()!;
|
||||
|
||||
/*
|
||||
var components = go.GetComponents<Component>();
|
||||
var len = components.Count(c => c != null);
|
||||
len = Math.Min(len, limit);//**--limit
|
||||
|
||||
var array = new Synchronizer.Model.ComponentData[len];
|
||||
|
||||
var arrayIndex = 0;
|
||||
foreach (var component in components)
|
||||
{
|
||||
if (component == null)
|
||||
continue;
|
||||
|
||||
array[arrayIndex++] = component.ToSyncModel(go);
|
||||
|
||||
if (arrayIndex >= len)
|
||||
break;
|
||||
}
|
||||
|
||||
return array;
|
||||
*/
|
||||
}
|
||||
|
||||
internal static Synchronizer.Model.ComponentData? ToSyncModel(this Component component, GameObject go)
|
||||
{
|
||||
if (!component || !go)
|
||||
return null;
|
||||
|
||||
Type type = component.GetType();
|
||||
var list = new List<(string, string)>();
|
||||
ShowFieldInfo(type, component, list);
|
||||
|
||||
var data = new Synchronizer.Model.ComponentData()
|
||||
{
|
||||
GameObjectId = GetId(go),
|
||||
Component = component.GetType().FullName,
|
||||
Type = Synchronizer.Model.ComponentData.DataType.Component,
|
||||
Data = list.ToArray(),
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
internal static Synchronizer.Model.ComponentData? ToSyncModel(this ScriptableObject so)
|
||||
{
|
||||
if (!so)
|
||||
return null;
|
||||
|
||||
Type type = so.GetType();
|
||||
var list = new List<(string, string)>();
|
||||
ShowFieldInfo(type, so, list);
|
||||
|
||||
var data = new Synchronizer.Model.ComponentData()
|
||||
{
|
||||
GameObjectId = GetId(so),
|
||||
Component = so.GetType().FullName,
|
||||
Type = Synchronizer.Model.ComponentData.DataType.ScriptableObject,
|
||||
Data = list.ToArray(),
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
static bool IsTypeCompatible(Type type)
|
||||
{
|
||||
if (type == null || !(type.IsSubclassOf(typeof(MonoBehaviour)) || type.IsSubclassOf(typeof(ScriptableObject))))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ShowFieldInfo(Type type)//, MonoImporter importer, List<string> names, List<Object> objects, ref bool didModify)
|
||||
{
|
||||
// Only show default properties for types that support it (so far only MonoBehaviour derived types)
|
||||
if (!IsTypeCompatible(type))
|
||||
return;
|
||||
|
||||
ShowFieldInfo(type.BaseType);//, importer, names, objects, ref didModify);
|
||||
|
||||
FieldInfo[] infos = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
||||
foreach (FieldInfo field in infos)
|
||||
{
|
||||
if (!field.IsPublic)
|
||||
{
|
||||
object[] attr = field.GetCustomAttributes(typeof(SerializeField), true);
|
||||
if (attr == null || attr.Length == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
if (field.FieldType.IsSubclassOf(typeof(Object)) || field.FieldType == typeof(Object))
|
||||
{
|
||||
Object oldTarget = importer.GetDefaultReference(field.Name);
|
||||
Object newTarget = EditorGUILayout.ObjectField(ObjectNames.NicifyVariableName(field.Name), oldTarget, field.FieldType, false);
|
||||
|
||||
names.Add(field.Name);
|
||||
objects.Add(newTarget);
|
||||
|
||||
if (oldTarget != newTarget)
|
||||
didModify = true;
|
||||
}
|
||||
*/
|
||||
|
||||
if (field.FieldType.IsValueType && field.FieldType.IsPrimitive && !field.FieldType.IsEnum)
|
||||
{
|
||||
|
||||
}
|
||||
else if (field.FieldType == typeof(string))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ShowFieldInfo(Type type, UnityEngine.Object unityObjectInstance, List<(string, string)> fields)//, MonoImporter importer, List<string> names, List<Object> objects, ref bool didModify)
|
||||
{
|
||||
// Only show default properties for types that support it (so far only MonoBehaviour derived types)
|
||||
if (!IsTypeCompatible(type))
|
||||
return;
|
||||
|
||||
if (!unityObjectInstance)
|
||||
return;
|
||||
|
||||
ShowFieldInfo(type.BaseType, unityObjectInstance, fields);//, importer, names, objects, ref didModify);
|
||||
|
||||
FieldInfo[] infos = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
||||
foreach (FieldInfo field in infos)
|
||||
{
|
||||
if (!field.IsPublic)
|
||||
{
|
||||
object[] attr = field.GetCustomAttributes(typeof(SerializeField), true);
|
||||
if (attr == null || attr.Length == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
// check attribute [HideInInspector]
|
||||
{
|
||||
object[] attr = field.GetCustomAttributes(typeof(HideInInspector), true);
|
||||
if (attr != null && attr.Length > 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
// readonly
|
||||
if (field.IsInitOnly)
|
||||
continue;
|
||||
|
||||
|
||||
/*
|
||||
if (field.FieldType.IsSubclassOf(typeof(Object)) || field.FieldType == typeof(Object))
|
||||
{
|
||||
Object oldTarget = importer.GetDefaultReference(field.Name);
|
||||
Object newTarget = EditorGUILayout.ObjectField(ObjectNames.NicifyVariableName(field.Name), oldTarget, field.FieldType, false);
|
||||
|
||||
names.Add(field.Name);
|
||||
objects.Add(newTarget);
|
||||
|
||||
if (oldTarget != newTarget)
|
||||
didModify = true;
|
||||
}
|
||||
*/
|
||||
|
||||
if (field.FieldType.IsValueType && field.FieldType.IsPrimitive && !field.FieldType.IsEnum)
|
||||
{
|
||||
var val = field.GetValue(unityObjectInstance);
|
||||
fields.Add((field.Name, val.ToString()));//**--culture
|
||||
}
|
||||
else if (field.FieldType == typeof(string))
|
||||
{
|
||||
var val = (string)field.GetValue(unityObjectInstance);
|
||||
fields.Add((field.Name, val));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3839ff35a8db8d4449c565bdf10551f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
305
Packages/com.merry-yellow.code-assist/Editor/UnityEditorShell.cs
Normal file
305
Packages/com.merry-yellow.code-assist/Editor/UnityEditorShell.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Shell
|
||||
{
|
||||
public class UnityEditorShell
|
||||
{
|
||||
public static string DefaultShellApp
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_EDITOR_WIN
|
||||
return "cmd.exe";
|
||||
#elif UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
//return "bash";
|
||||
return System.IO.File.Exists("/bin/zsh") ? "/bin/zsh" : "/bin/bash";
|
||||
#else
|
||||
Serilog.Log.Error("invalid platform");
|
||||
return "invalid-platform";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// we are using unity actions for posterity in case we want to inspect those in-editor someday
|
||||
private static readonly List<UnityAction> ActionsQueue;
|
||||
|
||||
static UnityEditorShell()
|
||||
{
|
||||
ActionsQueue = new List<UnityAction>();
|
||||
EditorApplication.update += OnUpdate;
|
||||
}
|
||||
|
||||
// while running the Unity Editor update loop, we'll unqueue any tasks if such exist.
|
||||
// actions can be
|
||||
private static void OnUpdate()
|
||||
{
|
||||
while (ActionsQueue.Count > 0)
|
||||
{
|
||||
lock (ActionsQueue)
|
||||
{
|
||||
var action = ActionsQueue[0];
|
||||
try
|
||||
{
|
||||
action?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Serilog.Log.Error(e, "error invoking shell action");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActionsQueue.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Enqueue(UnityAction action)
|
||||
{
|
||||
lock (ActionsQueue)
|
||||
{
|
||||
ActionsQueue.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
public static ShellCommandEditorToken Execute(string cmd)
|
||||
{
|
||||
var shellCommandEditorToken = new ShellCommandEditorToken();
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(delegate (object state)
|
||||
{
|
||||
Process? process = null;
|
||||
|
||||
try
|
||||
{
|
||||
var processStartInfo = CreateProcessStartInfo(cmd);
|
||||
|
||||
// in case the command was already killed from the editor when the thread was queued
|
||||
if (shellCommandEditorToken.IsKillRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
process = Process.Start(processStartInfo);
|
||||
SetupProcessCallbacks(process, processStartInfo, shellCommandEditorToken);
|
||||
ReadProcessOutput(process, shellCommandEditorToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Serilog.Log.Error(e, "error starting shell");
|
||||
process?.Close();
|
||||
|
||||
Enqueue(() =>
|
||||
{
|
||||
shellCommandEditorToken.FeedLog(UnityShellLogType.Error, e.ToString());
|
||||
shellCommandEditorToken.MarkAsDone(-1);
|
||||
});
|
||||
}
|
||||
});
|
||||
return shellCommandEditorToken;
|
||||
}
|
||||
|
||||
private static ProcessStartInfo CreateProcessStartInfo(string cmd)
|
||||
{
|
||||
var processStartInfo = new ProcessStartInfo(DefaultShellApp);
|
||||
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
processStartInfo.Arguments = "-c";
|
||||
#elif UNITY_EDITOR_WIN
|
||||
processStartInfo.Arguments = "/c";
|
||||
#endif
|
||||
|
||||
processStartInfo.Arguments += (" \"" + cmd + " \"");
|
||||
processStartInfo.CreateNoWindow = true;
|
||||
processStartInfo.ErrorDialog = true;
|
||||
processStartInfo.UseShellExecute = false;
|
||||
//processStartInfo.WorkingDirectory = options.WorkingDirectory == null ? "./" : options.WorkingDirectory;
|
||||
processStartInfo.RedirectStandardOutput = true;
|
||||
processStartInfo.RedirectStandardError = true;
|
||||
processStartInfo.RedirectStandardInput = true;
|
||||
processStartInfo.StandardOutputEncoding = Encoding.UTF8;
|
||||
processStartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
return processStartInfo;
|
||||
}
|
||||
|
||||
private static void SetupProcessCallbacks(Process process, ProcessStartInfo processStartInfo, ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
shellCommandEditorToken.BindProcess(process);
|
||||
|
||||
process.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
Serilog.Log.Error("error on shell.ErrorDataReceived: {data}", e.Data);
|
||||
};
|
||||
process.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
Serilog.Log.Debug("shell.OutputDataReceived: {data}", e.Data);
|
||||
};
|
||||
process.Exited += delegate (object sender, System.EventArgs e)
|
||||
{
|
||||
Serilog.Log.Debug("shell.Exited: {data}", e.ToString());
|
||||
};
|
||||
}
|
||||
|
||||
private static void ReadProcessOutput(Process process, ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
do
|
||||
{
|
||||
var line = process.StandardOutput.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
line = line.Replace("\\", "/");
|
||||
Enqueue(delegate () { shellCommandEditorToken.FeedLog(UnityShellLogType.Log, line); });
|
||||
} while (true);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var error = process.StandardError.ReadLine();
|
||||
if (string.IsNullOrEmpty(error))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Enqueue(delegate () { shellCommandEditorToken.FeedLog(UnityShellLogType.Error, error); });
|
||||
}
|
||||
|
||||
process.WaitForExit();
|
||||
var exitCode = process.ExitCode;
|
||||
process.Close();
|
||||
Enqueue(() => { shellCommandEditorToken.MarkAsDone(exitCode); });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ShellCommandEditorToken
|
||||
{
|
||||
public event UnityAction<UnityShellLogType, string>? OnLog;
|
||||
public event UnityAction<int>? OnExit;
|
||||
|
||||
private Process? _process;
|
||||
|
||||
internal void BindProcess(Process process)
|
||||
{
|
||||
_process = process;
|
||||
}
|
||||
|
||||
internal void FeedLog(UnityShellLogType unityShellLogType, string log)
|
||||
{
|
||||
OnLog?.Invoke(unityShellLogType, log);
|
||||
|
||||
if (unityShellLogType == UnityShellLogType.Error)
|
||||
{
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsKillRequested { get; private set; }
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
if (IsKillRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsKillRequested = true;
|
||||
if (_process != null)
|
||||
{
|
||||
_process.Kill();
|
||||
_process = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
MarkAsDone(137);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasError { get; private set; }
|
||||
|
||||
public int ExitCode { get; private set; }
|
||||
|
||||
public bool IsDone { get; private set; }
|
||||
|
||||
internal void MarkAsDone(int exitCode)
|
||||
{
|
||||
ExitCode = exitCode;
|
||||
IsDone = true;
|
||||
OnExit?.Invoke(exitCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is intended for compiler use. Don't call it in your code.
|
||||
/// </summary>
|
||||
public ShellCommandAwaiter GetAwaiter()
|
||||
{
|
||||
return new ShellCommandAwaiter(this);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ShellCommandAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
|
||||
{
|
||||
private readonly ShellCommandEditorToken _shellCommandEditorToken;
|
||||
|
||||
public ShellCommandAwaiter(ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
_shellCommandEditorToken = shellCommandEditorToken;
|
||||
}
|
||||
|
||||
public int GetResult()
|
||||
{
|
||||
return _shellCommandEditorToken.ExitCode;
|
||||
}
|
||||
|
||||
public bool IsCompleted => _shellCommandEditorToken.IsDone;
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
if (IsCompleted)
|
||||
{
|
||||
continuation();
|
||||
}
|
||||
else
|
||||
{
|
||||
_shellCommandEditorToken.OnExit += (_) => { continuation(); };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum UnityShellLogType
|
||||
{
|
||||
Log,
|
||||
Error
|
||||
}
|
||||
|
||||
public class ShellCommandYieldable : CustomYieldInstruction
|
||||
{
|
||||
private readonly ShellCommandEditorToken _shellCommandEditorToken;
|
||||
|
||||
public ShellCommandYieldable(ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
_shellCommandEditorToken = shellCommandEditorToken;
|
||||
}
|
||||
|
||||
public override bool keepWaiting => !_shellCommandEditorToken.IsDone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8a1c8d4b7d7fcc41bc2bdb8aa27522e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user