diff options
Diffstat (limited to 'modules/mono')
38 files changed, 676 insertions, 106 deletions
diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index 7391e8790d..3bbbf29d3b 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -15,7 +15,9 @@ def build_godot_tools(source, target, env): from .solution_builder import build_solution - build_solution(env, solution_path, build_config) + extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]] + + build_solution(env, solution_path, build_config, extra_msbuild_args) # No need to copy targets. The GodotTools csproj takes care of copying them. diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 23f01b3cca..80e3b59325 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -191,17 +191,16 @@ def configure(env, env_mono): env.Append(LIBS=["psapi"]) env.Append(LIBS=["version"]) else: - mono_lib_name = find_name_in_dir_files( - mono_lib_path, mono_lib_names, prefixes=["", "lib"], extensions=lib_suffixes - ) + mono_lib_file = find_file_in_dir(mono_lib_path, mono_lib_names, extensions=lib_suffixes) - if not mono_lib_name: + if not mono_lib_file: raise RuntimeError("Could not find mono library in: " + mono_lib_path) if env.msvc: - env.Append(LINKFLAGS=mono_lib_name + ".lib") + env.Append(LINKFLAGS=mono_lib_file) else: - env.Append(LIBS=[mono_lib_name]) + mono_lib_file_path = os.path.join(mono_lib_path, mono_lib_file) + env.Append(LINKFLAGS=mono_lib_file_path) mono_bin_path = os.path.join(mono_root, "bin") diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py index 371819fd72..03f4e57f02 100644 --- a/modules/mono/build_scripts/solution_builder.py +++ b/modules/mono/build_scripts/solution_builder.py @@ -142,9 +142,7 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): # Build solution - targets = ["Restore", "Build"] - - msbuild_args += [solution_path, "/t:%s" % ",".join(targets), "/p:Configuration=" + build_config] + msbuild_args += [solution_path, "/restore", "/t:Build", "/p:Configuration=" + build_config] msbuild_args += extra_msbuild_args run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild") diff --git a/modules/mono/config.py b/modules/mono/config.py index 7980a86cb3..cd659057ef 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -57,7 +57,6 @@ def configure(env): def get_doc_classes(): return [ - "@C#", "CSharpScript", "GodotSharp", ] diff --git a/modules/mono/doc_classes/@C#.xml b/modules/mono/doc_classes/@C#.xml deleted file mode 100644 index 83a7fbf02c..0000000000 --- a/modules/mono/doc_classes/@C#.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="@C#" version="4.0"> - <brief_description> - </brief_description> - <description> - </description> - <tutorials> - </tutorials> - <methods> - </methods> - <constants> - </constants> -</class> diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml index 1eb3404f9e..e1e9d1381f 100644 --- a/modules/mono/doc_classes/CSharpScript.xml +++ b/modules/mono/doc_classes/CSharpScript.xml @@ -1,16 +1,21 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CSharpScript" inherits="Script" version="4.0"> <brief_description> + A script implemented in the C# programming language (Mono-enabled builds only). </brief_description> <description> + This class represents a C# script. It is the C# equivalent of the [GDScript] class and is only available in Mono-enabled Godot builds. + See also [GodotSharp]. </description> <tutorials> + <link>https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html</link> </tutorials> <methods> <method name="new" qualifiers="vararg"> <return type="Variant"> </return> <description> + Returns a new instance of the script. </description> </method> </methods> diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml index 19a08d2036..417f8ac704 100644 --- a/modules/mono/doc_classes/GodotSharp.xml +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GodotSharp" inherits="Object" version="4.0"> <brief_description> + Bridge between Godot and the Mono runtime (Mono-enabled builds only). </brief_description> <description> + This class is a bridge between Godot and the Mono runtime. It exposes several low-level operations and is only available in Mono-enabled Godot builds. + See also [CSharpScript]. </description> <tutorials> </tutorials> @@ -11,26 +14,30 @@ <return type="void"> </return> <description> - Attaches the current thread to the mono runtime. + Attaches the current thread to the Mono runtime. </description> </method> <method name="detach_thread"> <return type="void"> </return> <description> - Detaches the current thread from the mono runtime. + Detaches the current thread from the Mono runtime. </description> </method> <method name="get_domain_id"> <return type="int"> </return> <description> + Returns the current MonoDomain ID. + [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash. </description> </method> <method name="get_scripts_domain_id"> <return type="int"> </return> <description> + Returns the scripts MonoDomain's ID. This will be the same MonoDomain ID as [method get_domain_id], unless the scripts domain isn't loaded. + [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash. </description> </method> <method name="is_domain_finalizing_for_unload"> @@ -39,26 +46,28 @@ <argument index="0" name="domain_id" type="int"> </argument> <description> - Returns whether the domain is being finalized. + Returns [code]true[/code] if the domain is being finalized, [code]false[/code] otherwise. </description> </method> <method name="is_runtime_initialized"> <return type="bool"> </return> <description> + Returns [code]true[/code] if the Mono runtime is initialized, [code]false[/code] otherwise. </description> </method> <method name="is_runtime_shutting_down"> <return type="bool"> </return> <description> + Returns [code]true[/code] if the Mono runtime is shutting down, [code]false[/code] otherwise. </description> </method> <method name="is_scripts_domain_loaded"> <return type="bool"> </return> <description> - Returns whether the scripts domain is loaded. + Returns [code]true[/code] if the scripts domain is loaded, [code]false[/code] otherwise. </description> </method> </methods> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index 7ab5c5fc59..012b69032e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -25,8 +25,9 @@ namespace GodotTools.Core bool rooted = path.IsAbsolutePath(); path = path.Replace('\\', '/'); + path = path[path.Length - 1] == '/' ? path.Substring(0, path.Length - 1) : path; - string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs index d069651dd3..572c541412 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs @@ -19,9 +19,12 @@ namespace GodotTools.IdeMessaging private readonly string identity; private string MetaFilePath { get; } + private DateTime? metaFileModifiedTime; private GodotIdeMetadata godotIdeMetadata; private readonly FileSystemWatcher fsWatcher; + public string GodotEditorExecutablePath => godotIdeMetadata.EditorExecutablePath; + private readonly IMessageHandler messageHandler; private Peer peer; @@ -123,7 +126,7 @@ namespace GodotTools.IdeMessaging MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); // FileSystemWatcher requires an existing directory - if (!File.Exists(projectMetadataDir)) + if (!Directory.Exists(projectMetadataDir)) Directory.CreateDirectory(projectMetadataDir); fsWatcher = new FileSystemWatcher(projectMetadataDir, GodotIdeMetadata.DefaultFileName); @@ -142,6 +145,13 @@ namespace GodotTools.IdeMessaging if (!File.Exists(MetaFilePath)) return; + var lastWriteTime = File.GetLastWriteTime(MetaFilePath); + + if (lastWriteTime == metaFileModifiedTime) + return; + + metaFileModifiedTime = lastWriteTime; + var metadata = ReadMetadataFile(); if (metadata != null && metadata != godotIdeMetadata) @@ -173,6 +183,13 @@ namespace GodotTools.IdeMessaging if (IsConnected || !File.Exists(MetaFilePath)) return; + var lastWriteTime = File.GetLastWriteTime(MetaFilePath); + + if (lastWriteTime == metaFileModifiedTime) + return; + + metaFileModifiedTime = lastWriteTime; + var metadata = ReadMetadataFile(); if (metadata != null) @@ -185,7 +202,8 @@ namespace GodotTools.IdeMessaging private GodotIdeMetadata? ReadMetadataFile() { - using (var reader = File.OpenText(MetaFilePath)) + using (var fileStream = new FileStream(MetaFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var reader = new StreamReader(fileStream)) { string portStr = reader.ReadLine(); @@ -272,6 +290,7 @@ namespace GodotTools.IdeMessaging // ReSharper disable once UnusedMember.Global public async void Start() { + fsWatcher.Created += OnMetaFileChanged; fsWatcher.Changed += OnMetaFileChanged; fsWatcher.Deleted += OnMetaFileDeleted; fsWatcher.EnableRaisingEvents = true; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj index 67815959a6..dad6b9ae7a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj @@ -4,7 +4,7 @@ <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>7.2</LangVersion> <PackageId>GodotTools.IdeMessaging</PackageId> - <Version>1.1.0</Version> + <Version>1.1.1</Version> <AssemblyVersion>$(Version)</AssemblyVersion> <Authors>Godot Engine contributors</Authors> <Company /> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs index a4e86d6177..10d7e1898e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs @@ -105,49 +105,45 @@ namespace GodotTools.IdeMessaging try { - try + if (msg.Kind == MessageKind.Request) { - if (msg.Kind == MessageKind.Request) - { - var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger); - await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent)); - } - else if (msg.Kind == MessageKind.Response) - { - ResponseAwaiter responseAwaiter; + var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger); + await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent)); + } + else if (msg.Kind == MessageKind.Response) + { + ResponseAwaiter responseAwaiter; - using (await requestsSem.UseAsync()) + using (await requestsSem.UseAsync()) + { + if (!requestAwaiterQueues.TryGetValue(msg.Id, out var queue) || queue.Count <= 0) { - if (!requestAwaiterQueues.TryGetValue(msg.Id, out var queue) || queue.Count <= 0) - { - Logger.LogError($"Received unexpected response: {msg.Id}"); - return; - } - - responseAwaiter = queue.Dequeue(); + Logger.LogError($"Received unexpected response: {msg.Id}"); + return; } - responseAwaiter.SetResult(msg.Content); - } - else - { - throw new IndexOutOfRangeException($"Invalid message kind {msg.Kind}"); + responseAwaiter = queue.Dequeue(); } + + responseAwaiter.SetResult(msg.Content); } - catch (Exception e) + else { - Logger.LogError($"Message handler for '{msg}' failed with exception", e); + throw new IndexOutOfRangeException($"Invalid message kind {msg.Kind}"); } } catch (Exception e) { - Logger.LogError($"Exception thrown from message handler. Message: {msg}", e); + Logger.LogError($"Message handler for '{msg}' failed with exception", e); } } } catch (Exception e) { - Logger.LogError("Unhandled exception in the peer loop", e); + if (!IsDisposed || !(e is SocketException || e.InnerException is SocketException)) + { + Logger.LogError("Unhandled exception in the peer loop", e); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs index 1dd4f852e5..e93db9377b 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs @@ -67,6 +67,19 @@ namespace GodotTools.IdeMessaging.Requests { } + public sealed class StopPlayRequest : Request + { + public new const string Id = "StopPlay"; + + public StopPlayRequest() : base(Id) + { + } + } + + public sealed class StopPlayResponse : Response + { + } + public sealed class DebugPlayRequest : Request { public string DebuggerHost { get; set; } diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj new file mode 100644 index 0000000000..5b3ed0b1b7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid> + <OutputType>Exe</OutputType> + <TargetFramework>net472</TargetFramework> + <LangVersion>7.2</LangVersion> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> + <PackageReference Include="EnvDTE" Version="8.0.2" /> + </ItemGroup> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs new file mode 100644 index 0000000000..affb2a47e7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -0,0 +1,270 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text.RegularExpressions; +using EnvDTE; + +namespace GodotTools.OpenVisualStudio +{ + internal static class Program + { + [DllImport("ole32.dll")] + private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable pprot); + + [DllImport("ole32.dll")] + private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc); + + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + private static void ShowHelp() + { + Console.WriteLine("Opens the file(s) in a Visual Studio instance that is editing the specified solution."); + Console.WriteLine("If an existing instance for the solution is not found, a new one is created."); + Console.WriteLine(); + Console.WriteLine("Usage:"); + Console.WriteLine(@" GodotTools.OpenVisualStudio.exe solution [file[;line[;col]]...]"); + Console.WriteLine(); + Console.WriteLine("Lines and columns begin at one. Zero or lower will result in an error."); + Console.WriteLine("If a line is specified but a column is not, the line is selected in the text editor."); + } + + // STAThread needed, otherwise CoRegisterMessageFilter may return CO_E_NOT_SUPPORTED. + [STAThread] + private static int Main(string[] args) + { + if (args.Length == 0 || args[0] == "--help" || args[0] == "-h") + { + ShowHelp(); + return 0; + } + + string solutionFile = NormalizePath(args[0]); + + var dte = FindInstanceEditingSolution(solutionFile); + + if (dte == null) + { + // Open a new instance + + var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true); + dte = (DTE)Activator.CreateInstance(visualStudioDteType); + + dte.UserControl = true; + + try + { + dte.Solution.Open(solutionFile); + } + catch (ArgumentException) + { + Console.Error.WriteLine("Solution.Open: Invalid path or file not found"); + return 1; + } + + dte.MainWindow.Visible = true; + } + + MessageFilter.Register(); + + try + { + // Open files + + for (int i = 1; i < args.Length; i++) + { + // Both the line number and the column begin at one + + string[] fileArgumentParts = args[i].Split(';'); + + string filePath = NormalizePath(fileArgumentParts[0]); + + try + { + dte.ItemOperations.OpenFile(filePath); + } + catch (ArgumentException) + { + Console.Error.WriteLine("ItemOperations.OpenFile: Invalid path or file not found"); + return 1; + } + + if (fileArgumentParts.Length > 1) + { + if (int.TryParse(fileArgumentParts[1], out int line)) + { + var textSelection = (TextSelection)dte.ActiveDocument.Selection; + + if (fileArgumentParts.Length > 2) + { + if (int.TryParse(fileArgumentParts[2], out int column)) + { + textSelection.MoveToLineAndOffset(line, column); + } + else + { + Console.Error.WriteLine("The column part of the argument must be a valid integer"); + return 1; + } + } + else + { + textSelection.GotoLine(line, Select: true); + } + } + else + { + Console.Error.WriteLine("The line part of the argument must be a valid integer"); + return 1; + } + } + } + } + finally + { + var mainWindow = dte.MainWindow; + mainWindow.Activate(); + SetForegroundWindow(new IntPtr(mainWindow.HWnd)); + + MessageFilter.Revoke(); + } + + return 0; + } + + private static DTE FindInstanceEditingSolution(string solutionPath) + { + if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0) + return null; + + try + { + pprot.EnumRunning(out IEnumMoniker ppenumMoniker); + ppenumMoniker.Reset(); + + var moniker = new IMoniker[1]; + + while (ppenumMoniker.Next(1, moniker, IntPtr.Zero) == 0) + { + string ppszDisplayName; + + CreateBindCtx(0, out IBindCtx ppbc); + + try + { + moniker[0].GetDisplayName(ppbc, null, out ppszDisplayName); + } + finally + { + Marshal.ReleaseComObject(ppbc); + } + + if (ppszDisplayName == null) + continue; + + // The digits after the colon are the process ID + if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]")) + continue; + + if (pprot.GetObject(moniker[0], out object ppunkObject) == 0) + { + if (ppunkObject is DTE dte && dte.Solution.FullName.Length > 0) + { + if (NormalizePath(dte.Solution.FullName) == solutionPath) + return dte; + } + } + } + } + finally + { + Marshal.ReleaseComObject(pprot); + } + + return null; + } + + static string NormalizePath(string path) + { + return new Uri(Path.GetFullPath(path)).LocalPath + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .ToUpperInvariant(); + } + + #region MessageFilter. See: http: //msdn.microsoft.com/en-us/library/ms228772.aspx + + private class MessageFilter : IOleMessageFilter + { + // Class containing the IOleMessageFilter + // thread error-handling functions + + private static IOleMessageFilter _oldFilter; + + // Start the filter + public static void Register() + { + IOleMessageFilter newFilter = new MessageFilter(); + int ret = CoRegisterMessageFilter(newFilter, out _oldFilter); + if (ret != 0) + Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}"); + } + + // Done with the filter, close it + public static void Revoke() + { + int ret = CoRegisterMessageFilter(_oldFilter, out _); + if (ret != 0) + Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}"); + } + + // + // IOleMessageFilter functions + // Handle incoming thread requests + int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo) + { + // Return the flag SERVERCALL_ISHANDLED + return 0; + } + + // Thread call was rejected, so try again. + int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) + { + if (dwRejectType == 2) + // flag = SERVERCALL_RETRYLATER + { + // Retry the thread call immediately if return >= 0 & < 100 + return 99; + } + + // Too busy; cancel call + return -1; + } + + int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType) + { + // Return the flag PENDINGMSG_WAITDEFPROCESS + return 2; + } + + // Implement the IOleMessageFilter interface + [DllImport("ole32.dll")] + private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter); + } + + [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IOleMessageFilter + { + [PreserveSig] + int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); + + [PreserveSig] + int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType); + + [PreserveSig] + int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType); + } + + #endregion + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index fb2beb6995..679d5bb444 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -12,6 +12,11 @@ namespace GodotTools.ProjectEditor private const string CoreApiProjectName = "GodotSharp"; private const string EditorApiProjectName = "GodotSharpEditor"; + public const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; + public const string GodotProjectTypeGuid = "{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}"; + + public static readonly string GodotDefaultProjectTypeGuids = $"{GodotProjectTypeGuid};{CSharpProjectTypeGuid}"; + public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems) { string path = Path.Combine(dir, name + ".csproj"); @@ -19,6 +24,7 @@ namespace GodotTools.ProjectEditor ProjectPropertyGroupElement mainGroup; var root = CreateLibraryProject(name, "Debug", out mainGroup); + mainGroup.SetProperty("ProjectTypeGuids", GodotDefaultProjectTypeGuids); mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 069a1edaa3..8774b4ee31 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -165,6 +165,21 @@ namespace GodotTools.ProjectEditor return result.ToArray(); } + public static void EnsureHasProjectTypeGuids(MSBuildProject project) + { + var root = project.Root; + + bool found = root.PropertyGroups.Any(pg => + string.IsNullOrEmpty(pg.Condition) && pg.Properties.Any(p => p.Name == "ProjectTypeGuids")); + + if (found) + return; + + root.AddProperty("ProjectTypeGuids", ProjectGenerator.GodotDefaultProjectTypeGuids); + + project.HasUnsavedChanges = true; + } + /// Simple function to make sure the Api assembly references are configured correctly public static void FixApiHintPath(MSBuildProject project) { diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index f6147eb5bb..ba5379e562 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "G EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeMessaging", "GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio", "GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj", "{EAFFF236-FA96-4A4D-BD23-0E51EF988277}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,5 +39,9 @@ Global {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index 3cf495f025..3de3d8d318 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -20,8 +20,8 @@ namespace GodotTools private ItemList buildTabsList; private TabContainer buildTabs; - private ToolButton warningsBtn; - private ToolButton errorsBtn; + private Button warningsBtn; + private Button errorsBtn; private Button viewLogBtn; private void _UpdateBuildTabsList() @@ -285,7 +285,7 @@ namespace GodotTools toolBarHBox.AddSpacer(begin: false); - warningsBtn = new ToolButton + warningsBtn = new Button { Text = "Warnings".TTR(), ToggleMode = true, @@ -296,7 +296,7 @@ namespace GodotTools warningsBtn.Toggled += _WarningsToggled; toolBarHBox.AddChild(warningsBtn); - errorsBtn = new ToolButton + errorsBtn = new Button { Text = "Errors".TTR(), ToggleMode = true, diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index e55558c100..34e42489eb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -47,19 +47,14 @@ namespace GodotTools.Build private static bool PrintBuildOutput => (bool)EditorSettings.GetSetting("mono/builds/print_build_output"); - private static Process LaunchBuild(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + private static Process LaunchBuild(BuildInfo buildInfo) { (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild(); if (msbuildPath == null) throw new FileNotFoundException("Cannot find the MSBuild executable."); - var customPropertiesList = new List<string>(); - - if (customProperties != null) - customPropertiesList.AddRange(customProperties); - - string compilerArgs = BuildArguments(buildTool, solution, targets, config, loggerOutputDir, customPropertiesList); + string compilerArgs = BuildArguments(buildTool, buildInfo); var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs); @@ -100,19 +95,7 @@ namespace GodotTools.Build public static int Build(BuildInfo buildInfo) { - return Build(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration, - buildInfo.LogsDirPath, buildInfo.CustomProperties); - } - - public static Task<int> BuildAsync(BuildInfo buildInfo) - { - return BuildAsync(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration, - buildInfo.LogsDirPath, buildInfo.CustomProperties); - } - - public static int Build(string solution, string[] targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) - { - using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties)) + using (var process = LaunchBuild(buildInfo)) { process.WaitForExit(); @@ -120,9 +103,9 @@ namespace GodotTools.Build } } - public static async Task<int> BuildAsync(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + public static async Task<int> BuildAsync(BuildInfo buildInfo) { - using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties)) + using (var process = LaunchBuild(buildInfo)) { await process.WaitForExitAsync(); @@ -130,17 +113,18 @@ namespace GodotTools.Build } } - private static string BuildArguments(BuildTool buildTool, string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties) + private static string BuildArguments(BuildTool buildTool, BuildInfo buildInfo) { string arguments = string.Empty; if (buildTool == BuildTool.DotnetCli) arguments += "msbuild "; // `dotnet msbuild` command - arguments += $@"""{solution}"" /v:normal /t:{string.Join(",", targets)} ""/p:{"Configuration=" + config}"" " + - $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}"""; + arguments += $@"""{buildInfo.Solution}"" /t:{string.Join(",", buildInfo.Targets)} " + + $@"""/p:{"Configuration=" + buildInfo.Configuration}"" /v:normal " + + $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{buildInfo.LogsDirPath}"""; - foreach (string customProperty in customProperties) + foreach (string customProperty in buildInfo.CustomProperties) { arguments += " /p:" + customProperty; } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs index a1a69334e3..837c8adddb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs @@ -1,6 +1,6 @@ namespace GodotTools.Build { - public enum BuildTool + public enum BuildTool : long { MsBuildMono, MsBuildVs, diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs index cca0983c01..ab090c46e7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs @@ -12,6 +12,7 @@ namespace GodotTools public string Solution { get; } public string[] Targets { get; } public string Configuration { get; } + public bool Restore { get; } public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); @@ -39,11 +40,12 @@ namespace GodotTools { } - public BuildInfo(string solution, string[] targets, string configuration) + public BuildInfo(string solution, string[] targets, string configuration, bool restore) { Solution = solution; Targets = targets; Configuration = configuration; + Restore = restore; } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index 598787ba03..0974d23176 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -175,7 +175,7 @@ namespace GodotTools { pr.Step("Building project solution", 0); - var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Restore", "Build"}, config); + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Build"}, config, restore: true); bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli; diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs index 0106e1f1ac..8596cd24af 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs @@ -113,7 +113,7 @@ namespace GodotTools throw new IndexOutOfRangeException("Item list index out of range"); // Get correct issue idx from issue list - int issueIndex = (int)issuesList.GetItemMetadata(idx); + int issueIndex = (int)(long)issuesList.GetItemMetadata(idx); if (issueIndex < 0 || issueIndex >= issues.Count) throw new IndexOutOfRangeException("Issue index out of range"); diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs index bb218c2f19..90d6eb960e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs +++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs @@ -1,6 +1,6 @@ namespace GodotTools { - public enum ExternalEditorId + public enum ExternalEditorId : long { None, VisualStudio, // TODO (Windows-only) diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index c874025be0..f330f9ed2c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using GodotTools.Ides; using GodotTools.Ides.Rider; using GodotTools.Internals; @@ -29,7 +30,7 @@ namespace GodotTools private AcceptDialog aboutDialog; private CheckBox aboutDialogCheckBox; - private ToolButton bottomPanelBtn; + private Button bottomPanelBtn; public GodotIdeManager GodotIdeManager { get; private set; } @@ -238,7 +239,31 @@ namespace GodotTools // Not an error. Tells the caller to fallback to the global external editor settings or the built-in editor. return Error.Unavailable; case ExternalEditorId.VisualStudio: - throw new NotSupportedException(); + { + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + var args = new List<string> + { + GodotSharpDirs.ProjectSlnPath, + line >= 0 ? $"{scriptPath};{line + 1};{col + 1}" : scriptPath + }; + + string command = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.exe"); + + try + { + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}"); + + OS.RunProcess(command, args); + } + catch (Exception e) + { + GD.PushError($"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'"); + } + + break; + } case ExternalEditorId.VisualStudioForMac: goto case ExternalEditorId.MonoDevelop; case ExternalEditorId.Rider: @@ -458,6 +483,9 @@ namespace GodotTools // Apply the other fixes only after configurations have been migrated + // Make sure the existing project has the ProjectTypeGuids property (for VisualStudio) + ProjectUtils.EnsureHasProjectTypeGuids(msbuildProject); + // Make sure the existing project has Api assembly references configured correctly ProjectUtils.FixApiHintPath(msbuildProject); @@ -485,7 +513,7 @@ namespace GodotTools menuPopup.IdPressed += _MenuOptionPressed; - var buildButton = new ToolButton + var buildButton = new Button { Text = "Build", HintTooltip = "Build solution", @@ -501,7 +529,8 @@ namespace GodotTools if (OS.IsWindows) { - settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + + settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" + + $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index ba527ca3b5..3f14629b11 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -33,5 +33,7 @@ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" /> <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> + <!-- Include it if this is an SCons build targeting Windows, or if it's not an SCons build but we're on Windows --> + <ProjectReference Include="..\GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) " /> </ItemGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index ae05710f4f..b30c857c64 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -10,7 +10,7 @@ namespace GodotTools public override void _Notification(int what) { - if (what == Node.NotificationWmFocusIn) + if (what == Node.NotificationWmWindowFocusIn) { RestartTimer(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index 32f264d100..17f3339560 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -12,6 +12,7 @@ using GodotTools.IdeMessaging; using GodotTools.IdeMessaging.Requests; using GodotTools.IdeMessaging.Utils; using GodotTools.Internals; +using GodotTools.Utils; using Newtonsoft.Json; using Directory = System.IO.Directory; using File = System.IO.File; @@ -307,6 +308,11 @@ namespace GodotTools.Ides var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body); return await HandleDebugPlay(request); }, + [StopPlayRequest.Id] = async (peer, content) => + { + var request = JsonConvert.DeserializeObject<StopPlayRequest>(content.Body); + return await HandleStopPlay(request); + }, [ReloadScriptsRequest.Id] = async (peer, content) => { _ = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body); @@ -343,6 +349,12 @@ namespace GodotTools.Ides return Task.FromResult<Response>(new DebugPlayResponse()); } + private static Task<Response> HandleStopPlay(StopPlayRequest request) + { + DispatchToMainThread(Internal.EditorRunStop); + return Task.FromResult<Response>(new StopPlayResponse()); + } + private static Task<Response> HandleReloadScripts() { DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts); @@ -351,8 +363,13 @@ namespace GodotTools.Ides private static async Task<Response> HandleCodeCompletionRequest(CodeCompletionRequest request) { + // This is needed if the "resource path" part of the path is case insensitive. + // However, it doesn't fix resource loading if the rest of the path is also case insensitive. + string scriptFileLocalized = FsPathUtils.LocalizePathWithCaseChecked(request.ScriptFile); + var response = new CodeCompletionResponse {Kind = request.Kind, ScriptFile = request.ScriptFile}; - response.Suggestions = await Task.Run(() => Internal.CodeCompletionRequest(response.Kind, response.ScriptFile)); + response.Suggestions = await Task.Run(() => + Internal.CodeCompletionRequest(response.Kind, scriptFileLocalized ?? request.ScriptFile)); return response; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs index 7fb087467f..569f27649f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs @@ -13,9 +13,9 @@ namespace GodotTools.Internals public string Name { get; } public string Namespace { get; } public bool Nested { get; } - public int BaseCount { get; } + public long BaseCount { get; } - public ClassDecl(string name, string @namespace, bool nested, int baseCount) + public ClassDecl(string name, string @namespace, bool nested, long baseCount) { Name = name; Namespace = @namespace; @@ -45,7 +45,7 @@ namespace GodotTools.Internals (string)classDeclDict["name"], (string)classDeclDict["namespace"], (bool)classDeclDict["nested"], - (int)classDeclDict["base_count"] + (long)classDeclDict["base_count"] )); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs new file mode 100644 index 0000000000..c6724ccaf7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using Godot; +using GodotTools.Core; +using JetBrains.Annotations; + +namespace GodotTools.Utils +{ + public static class FsPathUtils + { + private static readonly string ResourcePath = ProjectSettings.GlobalizePath("res://"); + + private static bool PathStartsWithAlreadyNorm(this string childPath, string parentPath) + { + // This won't work for Linux/macOS case insensitive file systems, but it's enough for our current problems + bool caseSensitive = !OS.IsWindows; + + string parentPathNorm = parentPath.NormalizePath() + Path.DirectorySeparatorChar; + string childPathNorm = childPath.NormalizePath() + Path.DirectorySeparatorChar; + + return childPathNorm.StartsWith(parentPathNorm, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + } + + public static bool PathStartsWith(this string childPath, string parentPath) + { + string childPathNorm = childPath.NormalizePath() + Path.DirectorySeparatorChar; + string parentPathNorm = parentPath.NormalizePath() + Path.DirectorySeparatorChar; + + return childPathNorm.PathStartsWithAlreadyNorm(parentPathNorm); + } + + [CanBeNull] + public static string LocalizePathWithCaseChecked(string path) + { + string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar; + string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar; + + if (!pathNorm.PathStartsWithAlreadyNorm(resourcePathNorm)) + return null; + + string result = "res://" + pathNorm.Substring(resourcePathNorm.Length); + + // Remove the last separator we added + return result.Substring(0, result.Length - 1); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs new file mode 100644 index 0000000000..20b11a48dd --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.CompilerServices; +using Godot.Collections; + +namespace Godot +{ + public partial class SceneTree + { + public Array<T> GetNodesInGroup<T>(StringName group) where T : class + { + return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(Object.GetPtr(this), StringName.GetPtr(group), typeof(T))); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index b5ac124c9a..06ec2483c8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -53,6 +53,7 @@ <Compile Include="Core\Extensions\NodeExtensions.cs" /> <Compile Include="Core\Extensions\ObjectExtensions.cs" /> <Compile Include="Core\Extensions\ResourceLoaderExtensions.cs" /> + <Compile Include="Core\Extensions\SceneTreeExtensions.cs" /> <Compile Include="Core\GD.cs" /> <Compile Include="Core\GodotSynchronizationContext.cs" /> <Compile Include="Core\GodotTaskScheduler.cs" /> diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h index ee99a300b9..f6999d01fb 100644 --- a/modules/mono/glue/glue_header.h +++ b/modules/mono/glue/glue_header.h @@ -35,6 +35,7 @@ #include "gd_glue.h" #include "nodepath_glue.h" #include "rid_glue.h" +#include "scene_tree_glue.h" #include "string_glue.h" #include "string_name_glue.h" @@ -50,6 +51,7 @@ void godot_register_glue_header_icalls() { godot_register_object_icalls(); godot_register_rid_icalls(); godot_register_string_icalls(); + godot_register_scene_tree_icalls(); } // Used by the generated glue diff --git a/modules/mono/glue/scene_tree_glue.cpp b/modules/mono/glue/scene_tree_glue.cpp new file mode 100644 index 0000000000..bea9544b08 --- /dev/null +++ b/modules/mono/glue/scene_tree_glue.cpp @@ -0,0 +1,82 @@ +/*************************************************************************/ +/* scene_tree_glue.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene_tree_glue.h" + +#ifdef MONO_GLUE_ENABLED + +#include "core/class_db.h" +#include "modules/mono/csharp_script.h" +#include "modules/mono/mono_gd/gd_mono_utils.h" +#include "scene/main/node.h" + +Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringName *group, MonoReflectionType *refltype) { + List<Node *> nodes; + Array ret; + + // Retrieve all the nodes in the group + ptr->get_nodes_in_group(*group, &nodes); + + // No need to bother if the group is empty + if (!nodes.empty()) { + MonoType *elem_type = mono_reflection_type_get_type(refltype); + MonoClass *mono_class = mono_class_from_mono_type(elem_type); + GDMonoClass *klass = GDMono::get_singleton()->get_class(mono_class); + + if (klass == GDMonoUtils::get_class_native_base(klass)) { + // If we're trying to get native objects, just check the inheritance list + StringName native_class_name = GDMonoUtils::get_native_godot_class_name(klass); + for (int i = 0; i < nodes.size(); ++i) { + if (ClassDB::is_parent_class(nodes[i]->get_class(), native_class_name)) + ret.push_back(nodes[i]); + } + } else { + // If we're trying to get csharpscript instances, get the mono object and compare the classes + for (int i = 0; i < nodes.size(); ++i) { + CSharpInstance *si = CAST_CSHARP_INSTANCE(nodes[i]->get_script_instance()); + + if (si != nullptr) { + MonoObject *obj = si->get_mono_object(); + if (obj != nullptr && mono_object_get_class(obj) == mono_class) { + ret.push_back(nodes[i]); + } + } + } + } + } + + return memnew(Array(ret)); +} + +void godot_register_scene_tree_icalls() { + mono_add_internal_call("Godot.SceneTree::godot_icall_SceneTree_get_nodes_in_group_Generic", (void *)godot_icall_SceneTree_get_nodes_in_group_Generic); +} + +#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/scene_tree_glue.h b/modules/mono/glue/scene_tree_glue.h new file mode 100644 index 0000000000..e9af35a30b --- /dev/null +++ b/modules/mono/glue/scene_tree_glue.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* scene_tree_glue.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_TREE_GLUE_H +#define SCENE_TREE_GLUE_H + +#ifdef MONO_GLUE_ENABLED + +#include "core/array.h" +#include "core/string_name.h" +#include "scene/main/scene_tree.h" + +#include "../mono_gd/gd_mono_marshal.h" + +Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringName *group, MonoReflectionType *refltype); + +// Register internal calls + +void godot_register_scene_tree_icalls(); + +#endif // MONO_GLUE_ENABLED + +#endif // SCENE_TREE_GLUE_H diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index 27e402d777..abb2761909 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -114,7 +114,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { } void unhandled_exception(MonoException *p_exc) { - mono_unhandled_exception((MonoObject *)p_exc); // prints the exception as well + mono_print_unhandled_exception((MonoObject *)p_exc); if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) { // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 085062261d..158742846b 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -624,8 +624,8 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return BOX_BOOLEAN(val); } case Variant::INT: { - int32_t val = p_var->operator signed int(); - return BOX_INT32(val); + int64_t val = p_var->operator int64_t(); + return BOX_INT64(val); } case Variant::FLOAT: { #ifdef REAL_T_IS_DOUBLE diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp index 1b4fe68582..e0cf916a01 100644 --- a/modules/mono/utils/mono_reg_utils.cpp +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -75,7 +75,6 @@ LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) if (res == ERROR_MORE_DATA) { // dwBufferSize now contains the actual size - Vector<WCHAR> buffer; buffer.resize(dwBufferSize); res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); } |