mirror of
https://github.com/godotengine/godot.git
synced 2024-11-10 14:12:51 +00:00
C#: Add Ide Connection library and server for the editor
This will be used for communicating between the Godot editor and external IDEs/editors, for things like opening files, triggering hot-reload and running the game with a debugger attached.
This commit is contained in:
parent
4b7b1b0d4a
commit
0b94203a79
@ -4339,6 +4339,15 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorNode::run_play() {
|
||||
_menu_option_confirm(RUN_STOP, true);
|
||||
_run(false);
|
||||
}
|
||||
|
||||
void EditorNode::run_stop() {
|
||||
_menu_option_confirm(RUN_STOP, false);
|
||||
}
|
||||
|
||||
int EditorNode::get_current_tab() {
|
||||
return scene_tabs->get_current_tab();
|
||||
}
|
||||
|
@ -867,6 +867,9 @@ public:
|
||||
static void add_build_callback(EditorBuildCallback p_callback);
|
||||
|
||||
bool ensure_main_scene(bool p_from_native);
|
||||
|
||||
void run_play();
|
||||
void run_stop();
|
||||
};
|
||||
|
||||
struct EditorProgress {
|
||||
|
@ -84,10 +84,16 @@ def build(env_mono):
|
||||
source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll']
|
||||
sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames]
|
||||
|
||||
target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll']
|
||||
target_filenames = [
|
||||
'GodotTools.dll', 'GodotTools.IdeConnection.dll', 'GodotTools.BuildLogger.dll',
|
||||
'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'
|
||||
]
|
||||
|
||||
if env_mono['target'] == 'debug':
|
||||
target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.pdb', 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb']
|
||||
target_filenames += [
|
||||
'GodotTools.pdb', 'GodotTools.IdeConnection.pdb', 'GodotTools.BuildLogger.pdb',
|
||||
'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'
|
||||
]
|
||||
|
||||
targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class ConsoleLogger : ILogger
|
||||
{
|
||||
public void LogDebug(string message)
|
||||
{
|
||||
Console.WriteLine("DEBUG: " + message);
|
||||
}
|
||||
|
||||
public void LogInfo(string message)
|
||||
{
|
||||
Console.WriteLine("INFO: " + message);
|
||||
}
|
||||
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
Console.WriteLine("WARN: " + message);
|
||||
}
|
||||
|
||||
public void LogError(string message)
|
||||
{
|
||||
Console.WriteLine("ERROR: " + message);
|
||||
}
|
||||
|
||||
public void LogError(string message, Exception e)
|
||||
{
|
||||
Console.WriteLine("EXCEPTION: " + message);
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class GodotIdeBase : IDisposable
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public ILogger Logger
|
||||
{
|
||||
get => logger ?? (logger = new ConsoleLogger());
|
||||
set => logger = value;
|
||||
}
|
||||
|
||||
private readonly string projectMetadataDir;
|
||||
|
||||
protected const string MetaFileName = "ide_server_meta.txt";
|
||||
protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName);
|
||||
|
||||
private GodotIdeConnection connection;
|
||||
protected readonly object ConnectionLock = new object();
|
||||
|
||||
public bool IsDisposed { get; private set; } = false;
|
||||
|
||||
public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected;
|
||||
|
||||
public event Action Connected
|
||||
{
|
||||
add
|
||||
{
|
||||
if (connection != null && !connection.IsDisposed)
|
||||
connection.Connected += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
if (connection != null && !connection.IsDisposed)
|
||||
connection.Connected -= value;
|
||||
}
|
||||
}
|
||||
|
||||
protected GodotIdeConnection Connection
|
||||
{
|
||||
get => connection;
|
||||
set
|
||||
{
|
||||
connection?.Dispose();
|
||||
connection = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected GodotIdeBase(string projectMetadataDir)
|
||||
{
|
||||
this.projectMetadataDir = projectMetadataDir;
|
||||
}
|
||||
|
||||
protected void DisposeConnection()
|
||||
{
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
connection?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
~GodotIdeBase()
|
||||
{
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed) // lock may not be fair
|
||||
return;
|
||||
IsDisposed = true;
|
||||
}
|
||||
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
connection?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public abstract class GodotIdeClient : GodotIdeBase
|
||||
{
|
||||
protected GodotIdeMetadata GodotIdeMetadata;
|
||||
|
||||
private readonly FileSystemWatcher fsWatcher;
|
||||
|
||||
protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir)
|
||||
{
|
||||
messageHandlers = InitializeMessageHandlers();
|
||||
|
||||
// FileSystemWatcher requires an existing directory
|
||||
if (!File.Exists(projectMetadataDir))
|
||||
Directory.CreateDirectory(projectMetadataDir);
|
||||
|
||||
fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName);
|
||||
}
|
||||
|
||||
private void OnMetaFileChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (!File.Exists(MetaFilePath))
|
||||
return;
|
||||
|
||||
var metadata = ReadMetadataFile();
|
||||
|
||||
if (metadata != null && metadata != GodotIdeMetadata)
|
||||
{
|
||||
GodotIdeMetadata = metadata.Value;
|
||||
ConnectToServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMetaFileDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (IsConnected)
|
||||
DisposeConnection();
|
||||
|
||||
// The file may have been re-created
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (IsConnected || !File.Exists(MetaFilePath))
|
||||
return;
|
||||
|
||||
var metadata = ReadMetadataFile();
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
GodotIdeMetadata = metadata.Value;
|
||||
ConnectToServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GodotIdeMetadata? ReadMetadataFile()
|
||||
{
|
||||
using (var reader = File.OpenText(MetaFilePath))
|
||||
{
|
||||
string portStr = reader.ReadLine();
|
||||
|
||||
if (portStr == null)
|
||||
return null;
|
||||
|
||||
string editorExecutablePath = reader.ReadLine();
|
||||
|
||||
if (editorExecutablePath == null)
|
||||
return null;
|
||||
|
||||
if (!int.TryParse(portStr, out int port))
|
||||
return null;
|
||||
|
||||
return new GodotIdeMetadata(port, editorExecutablePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectToServer()
|
||||
{
|
||||
var tcpClient = new TcpClient();
|
||||
|
||||
Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage);
|
||||
Connection.Logger = Logger;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.LogInfo("Connecting to Godot Ide Server");
|
||||
|
||||
tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port);
|
||||
|
||||
Logger.LogInfo("Connection open with Godot Ide Server");
|
||||
|
||||
var clientThread = new Thread(Connection.Start)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "Godot Ide Connection Client"
|
||||
};
|
||||
clientThread.Start();
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
if (e.SocketErrorCode == SocketError.ConnectionRefused)
|
||||
Logger.LogError("The connection to the Godot Ide Server was refused");
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Logger.LogInfo("Starting Godot Ide Client");
|
||||
|
||||
fsWatcher.Changed += OnMetaFileChanged;
|
||||
fsWatcher.Deleted += OnMetaFileDeleted;
|
||||
fsWatcher.EnableRaisingEvents = true;
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (!File.Exists(MetaFilePath))
|
||||
{
|
||||
Logger.LogInfo("There is no Godot Ide Server running");
|
||||
return;
|
||||
}
|
||||
|
||||
var metadata = ReadMetadataFile();
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
GodotIdeMetadata = metadata.Value;
|
||||
ConnectToServer();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("Failed to read Godot Ide metadata file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool WriteMessage(Message message)
|
||||
{
|
||||
return Connection.WriteMessage(message);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
fsWatcher?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool HandleMessage(Message message)
|
||||
{
|
||||
if (messageHandlers.TryGetValue(message.Id, out var action))
|
||||
{
|
||||
action(message.Arguments);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, Action<string[]>> messageHandlers;
|
||||
|
||||
private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
|
||||
{
|
||||
return new Dictionary<string, Action<string[]>>
|
||||
{
|
||||
["OpenFile"] = args =>
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
OpenFile(file: args[0]);
|
||||
return;
|
||||
case 2:
|
||||
OpenFile(file: args[0], line: int.Parse(args[1]));
|
||||
return;
|
||||
case 3:
|
||||
OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2]));
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract void OpenFile(string file);
|
||||
protected abstract void OpenFile(string file, int line);
|
||||
protected abstract void OpenFile(string file, int line, int column);
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public abstract class GodotIdeConnection : IDisposable
|
||||
{
|
||||
protected const string Version = "1.0";
|
||||
|
||||
protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}";
|
||||
protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}";
|
||||
|
||||
private const int ClientWriteTimeout = 8000;
|
||||
private readonly TcpClient tcpClient;
|
||||
|
||||
private TextReader clientReader;
|
||||
private TextWriter clientWriter;
|
||||
|
||||
private readonly object writeLock = new object();
|
||||
|
||||
private readonly Func<Message, bool> messageHandler;
|
||||
|
||||
public event Action Connected;
|
||||
|
||||
private ILogger logger;
|
||||
|
||||
public ILogger Logger
|
||||
{
|
||||
get => logger ?? (logger = new ConsoleLogger());
|
||||
set => logger = value;
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; private set; } = false;
|
||||
|
||||
public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected;
|
||||
|
||||
protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler)
|
||||
{
|
||||
this.tcpClient = tcpClient;
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!StartConnection())
|
||||
return;
|
||||
|
||||
string messageLine;
|
||||
while ((messageLine = ReadLine()) != null)
|
||||
{
|
||||
if (!MessageParser.TryParse(messageLine, out Message msg))
|
||||
{
|
||||
Logger.LogError($"Received message with invalid format: {messageLine}");
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Received message: {msg}");
|
||||
|
||||
if (msg.Id == "close")
|
||||
{
|
||||
Logger.LogInfo("Closing connection");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Assert(messageHandler != null);
|
||||
|
||||
if (!messageHandler(msg))
|
||||
Logger.LogError($"Received unknown message: {msg}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Message handler for '{msg}' failed with exception", e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Exception thrown from message handler. Message: {msg}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool StartConnection()
|
||||
{
|
||||
NetworkStream clientStream = tcpClient.GetStream();
|
||||
|
||||
clientReader = new StreamReader(clientStream, Encoding.UTF8);
|
||||
|
||||
lock (writeLock)
|
||||
clientWriter = new StreamWriter(clientStream, Encoding.UTF8);
|
||||
|
||||
clientStream.WriteTimeout = ClientWriteTimeout;
|
||||
|
||||
if (!WriteHandshake())
|
||||
{
|
||||
Logger.LogError("Could not write handshake");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidResponseHandshake(ReadLine()))
|
||||
{
|
||||
Logger.LogError("Received invalid handshake");
|
||||
return false;
|
||||
}
|
||||
|
||||
Connected?.Invoke();
|
||||
|
||||
Logger.LogInfo("Godot Ide connection started");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string ReadLine()
|
||||
{
|
||||
try
|
||||
{
|
||||
return clientReader?.ReadLine();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
var se = e as SocketException ?? e.InnerException as SocketException;
|
||||
if (se != null && se.SocketErrorCode == SocketError.Interrupted)
|
||||
return null;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool WriteMessage(Message message)
|
||||
{
|
||||
Logger.LogDebug($"Sending message {message}");
|
||||
|
||||
var messageComposer = new MessageComposer();
|
||||
|
||||
messageComposer.AddArgument(message.Id);
|
||||
foreach (string argument in message.Arguments)
|
||||
messageComposer.AddArgument(argument);
|
||||
|
||||
return WriteLine(messageComposer.ToString());
|
||||
}
|
||||
|
||||
protected bool WriteLine(string text)
|
||||
{
|
||||
if (clientWriter == null || IsDisposed || !IsConnected)
|
||||
return false;
|
||||
|
||||
lock (writeLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
clientWriter.WriteLine(text);
|
||||
clientWriter.Flush();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
var se = e as SocketException ?? e.InnerException as SocketException;
|
||||
if (se != null && se.SocketErrorCode == SocketError.Shutdown)
|
||||
Logger.LogInfo("Client disconnected ungracefully");
|
||||
else
|
||||
Logger.LogError("Exception thrown when trying to write to client", e);
|
||||
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract bool WriteHandshake();
|
||||
protected abstract bool IsValidResponseHandshake(string handshakeLine);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
IsDisposed = true;
|
||||
|
||||
clientReader?.Dispose();
|
||||
clientWriter?.Dispose();
|
||||
((IDisposable) tcpClient)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class GodotIdeConnectionClient : GodotIdeConnection
|
||||
{
|
||||
public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler)
|
||||
: base(tcpClient, messageHandler)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool WriteHandshake()
|
||||
{
|
||||
return WriteLine(ClientHandshake);
|
||||
}
|
||||
|
||||
protected override bool IsValidResponseHandshake(string handshakeLine)
|
||||
{
|
||||
return handshakeLine == ServerHandshake;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class GodotIdeConnectionServer : GodotIdeConnection
|
||||
{
|
||||
public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler)
|
||||
: base(tcpClient, messageHandler)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool WriteHandshake()
|
||||
{
|
||||
return WriteLine(ServerHandshake);
|
||||
}
|
||||
|
||||
protected override bool IsValidResponseHandshake(string handshakeLine)
|
||||
{
|
||||
return handshakeLine == ClientHandshake;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public struct GodotIdeMetadata
|
||||
{
|
||||
public int Port { get; }
|
||||
public string EditorExecutablePath { get; }
|
||||
|
||||
public GodotIdeMetadata(int port, string editorExecutablePath)
|
||||
{
|
||||
Port = port;
|
||||
EditorExecutablePath = editorExecutablePath;
|
||||
}
|
||||
|
||||
public static bool operator ==(GodotIdeMetadata a, GodotIdeMetadata b)
|
||||
{
|
||||
return a.Port == b.Port && a.EditorExecutablePath == b.EditorExecutablePath;
|
||||
}
|
||||
|
||||
public static bool operator !=(GodotIdeMetadata a, GodotIdeMetadata b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is GodotIdeMetadata metadata)
|
||||
return metadata == this;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(GodotIdeMetadata other)
|
||||
{
|
||||
return Port == other.Port && EditorExecutablePath == other.EditorExecutablePath;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (Port * 397) ^ (EditorExecutablePath != null ? EditorExecutablePath.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GodotTools.IdeConnection</RootNamespace>
|
||||
<AssemblyName>GodotTools.IdeConnection</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ConsoleLogger.cs" />
|
||||
<Compile Include="GodotIdeMetadata.cs" />
|
||||
<Compile Include="GodotIdeBase.cs" />
|
||||
<Compile Include="GodotIdeClient.cs" />
|
||||
<Compile Include="GodotIdeConnection.cs" />
|
||||
<Compile Include="GodotIdeConnectionClient.cs" />
|
||||
<Compile Include="GodotIdeConnectionServer.cs" />
|
||||
<Compile Include="ILogger.cs" />
|
||||
<Compile Include="Message.cs" />
|
||||
<Compile Include="MessageComposer.cs" />
|
||||
<Compile Include="MessageParser.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void LogDebug(string message);
|
||||
void LogInfo(string message);
|
||||
void LogWarning(string message);
|
||||
void LogError(string message);
|
||||
void LogError(string message, Exception e);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public struct Message
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string[] Arguments { get; set; }
|
||||
|
||||
public Message(string id, params string[] arguments)
|
||||
{
|
||||
Id = id;
|
||||
Arguments = arguments;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class MessageComposer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
private static readonly char[] CharsToEscape = { '\\', '"' };
|
||||
|
||||
public void AddArgument(string argument)
|
||||
{
|
||||
AddArgument(argument, quoted: argument.Contains(","));
|
||||
}
|
||||
|
||||
public void AddArgument(string argument, bool quoted)
|
||||
{
|
||||
if (stringBuilder.Length > 0)
|
||||
stringBuilder.Append(',');
|
||||
|
||||
if (quoted)
|
||||
{
|
||||
stringBuilder.Append('"');
|
||||
|
||||
foreach (char @char in argument)
|
||||
{
|
||||
if (CharsToEscape.Contains(@char))
|
||||
stringBuilder.Append('\\');
|
||||
stringBuilder.Append(@char);
|
||||
}
|
||||
|
||||
stringBuilder.Append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(argument);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public static class MessageParser
|
||||
{
|
||||
public static bool TryParse(string messageLine, out Message message)
|
||||
{
|
||||
var arguments = new List<string>();
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
bool expectingArgument = true;
|
||||
|
||||
for (int i = 0; i < messageLine.Length; i++)
|
||||
{
|
||||
char @char = messageLine[i];
|
||||
|
||||
if (@char == ',')
|
||||
{
|
||||
if (expectingArgument)
|
||||
arguments.Add(string.Empty);
|
||||
|
||||
expectingArgument = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool quoted = false;
|
||||
|
||||
if (messageLine[i] == '"')
|
||||
{
|
||||
quoted = true;
|
||||
i++;
|
||||
}
|
||||
|
||||
while (i < messageLine.Length)
|
||||
{
|
||||
@char = messageLine[i];
|
||||
|
||||
if (quoted && @char == '"')
|
||||
{
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (@char == '\\')
|
||||
{
|
||||
i++;
|
||||
if (i < messageLine.Length)
|
||||
break;
|
||||
|
||||
stringBuilder.Append(messageLine[i]);
|
||||
}
|
||||
else if (!quoted && @char == ',')
|
||||
{
|
||||
break; // We don't increment the counter to allow the colon to be parsed after this
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(@char);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
arguments.Add(stringBuilder.ToString());
|
||||
stringBuilder.Clear();
|
||||
|
||||
expectingArgument = false;
|
||||
}
|
||||
|
||||
if (arguments.Count == 0)
|
||||
{
|
||||
message = new Message();
|
||||
return false;
|
||||
}
|
||||
|
||||
message = new Message
|
||||
{
|
||||
Id = arguments[0],
|
||||
Arguments = arguments.Skip(1).ToArray()
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("GodotTools.IdeConnection")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("Godot Engine contributors")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -31,5 +33,9 @@ Global
|
||||
{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -9,7 +9,7 @@ using Path = System.IO.Path;
|
||||
|
||||
namespace GodotTools
|
||||
{
|
||||
public class MonoBottomPanel : VBoxContainer
|
||||
public class BottomPanel : VBoxContainer
|
||||
{
|
||||
private EditorInterface editorInterface;
|
||||
|
||||
@ -34,7 +34,7 @@ namespace GodotTools
|
||||
|
||||
for (int i = 0; i < buildTabs.GetChildCount(); i++)
|
||||
{
|
||||
var tab = (MonoBuildTab) buildTabs.GetChild(i);
|
||||
var tab = (BuildTab) buildTabs.GetChild(i);
|
||||
|
||||
if (tab == null)
|
||||
continue;
|
||||
@ -49,11 +49,11 @@ namespace GodotTools
|
||||
itemTooltip += "\nStatus: ";
|
||||
|
||||
if (tab.BuildExited)
|
||||
itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored";
|
||||
itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
|
||||
else
|
||||
itemTooltip += "Running";
|
||||
|
||||
if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error)
|
||||
if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
|
||||
itemTooltip += $"\nErrors: {tab.ErrorCount}";
|
||||
|
||||
itemTooltip += $"\nWarnings: {tab.WarningCount}";
|
||||
@ -68,15 +68,15 @@ namespace GodotTools
|
||||
}
|
||||
}
|
||||
|
||||
public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo)
|
||||
public BuildTab GetBuildTabFor(BuildInfo buildInfo)
|
||||
{
|
||||
foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren()))
|
||||
foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren()))
|
||||
{
|
||||
if (buildTab.BuildInfo.Equals(buildInfo))
|
||||
return buildTab;
|
||||
}
|
||||
|
||||
var newBuildTab = new MonoBuildTab(buildInfo);
|
||||
var newBuildTab = new BuildTab(buildInfo);
|
||||
AddBuildTab(newBuildTab);
|
||||
|
||||
return newBuildTab;
|
||||
@ -120,7 +120,7 @@ namespace GodotTools
|
||||
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
|
||||
throw new InvalidOperationException("No tab selected");
|
||||
|
||||
var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
|
||||
var buildTab = (BuildTab) buildTabs.GetChild(currentTab);
|
||||
buildTab.WarningsVisible = pressed;
|
||||
buildTab.UpdateIssuesList();
|
||||
}
|
||||
@ -132,7 +132,7 @@ namespace GodotTools
|
||||
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
|
||||
throw new InvalidOperationException("No tab selected");
|
||||
|
||||
var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
|
||||
var buildTab = (BuildTab) buildTabs.GetChild(currentTab);
|
||||
buildTab.ErrorsVisible = pressed;
|
||||
buildTab.UpdateIssuesList();
|
||||
}
|
||||
@ -145,7 +145,7 @@ namespace GodotTools
|
||||
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
|
||||
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
|
||||
|
||||
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
|
||||
CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
|
||||
|
||||
if (File.Exists(editorScriptsMetadataPath))
|
||||
{
|
||||
@ -166,7 +166,7 @@ namespace GodotTools
|
||||
Internal.GodotIs32Bits() ? "32" : "64"
|
||||
};
|
||||
|
||||
bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines);
|
||||
bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines);
|
||||
|
||||
if (!buildSuccess)
|
||||
return;
|
||||
@ -193,9 +193,9 @@ namespace GodotTools
|
||||
|
||||
int selectedItem = selectedItems[0];
|
||||
|
||||
var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem);
|
||||
var buildTab = (BuildTab) buildTabs.GetTabControl(selectedItem);
|
||||
|
||||
OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName));
|
||||
OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName));
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
@ -211,13 +211,13 @@ namespace GodotTools
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBuildTab(MonoBuildTab buildTab)
|
||||
public void AddBuildTab(BuildTab buildTab)
|
||||
{
|
||||
buildTabs.AddChild(buildTab);
|
||||
RaiseBuildTab(buildTab);
|
||||
}
|
||||
|
||||
public void RaiseBuildTab(MonoBuildTab buildTab)
|
||||
public void RaiseBuildTab(BuildTab buildTab)
|
||||
{
|
||||
if (buildTab.GetParent() != buildTabs)
|
||||
throw new InvalidOperationException("Build tab is not in the tabs list");
|
@ -46,8 +46,8 @@ namespace GodotTools.Build
|
||||
{
|
||||
if (OS.IsWindows())
|
||||
{
|
||||
return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
|
||||
== GodotSharpBuilds.BuildTool.MsBuildMono;
|
||||
return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
|
||||
== BuildManager.BuildTool.MsBuildMono;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -103,16 +103,16 @@ namespace GodotTools.Build
|
||||
return process;
|
||||
}
|
||||
|
||||
public static int Build(MonoBuildInfo monoBuildInfo)
|
||||
public static int Build(BuildInfo buildInfo)
|
||||
{
|
||||
return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration,
|
||||
monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
|
||||
return Build(buildInfo.Solution, buildInfo.Configuration,
|
||||
buildInfo.LogsDirPath, buildInfo.CustomProperties);
|
||||
}
|
||||
|
||||
public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo)
|
||||
public static async Task<int> BuildAsync(BuildInfo buildInfo)
|
||||
{
|
||||
return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration,
|
||||
monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
|
||||
return await BuildAsync(buildInfo.Solution, buildInfo.Configuration,
|
||||
buildInfo.LogsDirPath, buildInfo.CustomProperties);
|
||||
}
|
||||
|
||||
public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
|
||||
|
@ -19,13 +19,13 @@ namespace GodotTools.Build
|
||||
public static string FindMsBuild()
|
||||
{
|
||||
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
|
||||
var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
|
||||
var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
|
||||
|
||||
if (OS.IsWindows())
|
||||
{
|
||||
switch (buildTool)
|
||||
{
|
||||
case GodotSharpBuilds.BuildTool.MsBuildVs:
|
||||
case BuildManager.BuildTool.MsBuildVs:
|
||||
{
|
||||
if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
|
||||
{
|
||||
@ -34,7 +34,7 @@ namespace GodotTools.Build
|
||||
|
||||
if (_msbuildToolsPath.Empty())
|
||||
{
|
||||
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
|
||||
throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,13 +43,13 @@ namespace GodotTools.Build
|
||||
|
||||
return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
|
||||
}
|
||||
case GodotSharpBuilds.BuildTool.MsBuildMono:
|
||||
case BuildManager.BuildTool.MsBuildMono:
|
||||
{
|
||||
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
|
||||
|
||||
if (!File.Exists(msbuildPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
|
||||
throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
|
||||
}
|
||||
|
||||
return msbuildPath;
|
||||
@ -61,7 +61,7 @@ namespace GodotTools.Build
|
||||
|
||||
if (OS.IsUnix())
|
||||
{
|
||||
if (buildTool == GodotSharpBuilds.BuildTool.MsBuildMono)
|
||||
if (buildTool == BuildManager.BuildTool.MsBuildMono)
|
||||
{
|
||||
if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath))
|
||||
{
|
||||
@ -71,7 +71,7 @@ namespace GodotTools.Build
|
||||
|
||||
if (_msbuildUnixPath.Empty())
|
||||
{
|
||||
throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'");
|
||||
throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'");
|
||||
}
|
||||
|
||||
return _msbuildUnixPath;
|
||||
|
@ -7,7 +7,7 @@ using Path = System.IO.Path;
|
||||
namespace GodotTools
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization
|
||||
public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization
|
||||
{
|
||||
public string Solution { get; }
|
||||
public string Configuration { get; }
|
||||
@ -17,7 +17,7 @@ namespace GodotTools
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is MonoBuildInfo other)
|
||||
if (obj is BuildInfo other)
|
||||
return other.Solution == Solution && other.Configuration == Configuration;
|
||||
|
||||
return false;
|
||||
@ -34,11 +34,11 @@ namespace GodotTools
|
||||
}
|
||||
}
|
||||
|
||||
private MonoBuildInfo()
|
||||
private BuildInfo()
|
||||
{
|
||||
}
|
||||
|
||||
public MonoBuildInfo(string solution, string configuration)
|
||||
public BuildInfo(string solution, string configuration)
|
||||
{
|
||||
Solution = solution;
|
||||
Configuration = configuration;
|
@ -6,15 +6,13 @@ using GodotTools.Build;
|
||||
using GodotTools.Internals;
|
||||
using GodotTools.Utils;
|
||||
using static GodotTools.Internals.Globals;
|
||||
using Error = Godot.Error;
|
||||
using File = GodotTools.Utils.File;
|
||||
using Directory = GodotTools.Utils.Directory;
|
||||
|
||||
namespace GodotTools
|
||||
{
|
||||
public static class GodotSharpBuilds
|
||||
public static class BuildManager
|
||||
{
|
||||
private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>();
|
||||
private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>();
|
||||
|
||||
public const string PropNameMsbuildMono = "MSBuild (Mono)";
|
||||
public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
|
||||
@ -28,7 +26,7 @@ namespace GodotTools
|
||||
MsBuildVs
|
||||
}
|
||||
|
||||
private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo)
|
||||
private static void RemoveOldIssuesFile(BuildInfo buildInfo)
|
||||
{
|
||||
var issuesFile = GetIssuesFilePath(buildInfo);
|
||||
|
||||
@ -38,29 +36,21 @@ namespace GodotTools
|
||||
File.Delete(issuesFile);
|
||||
}
|
||||
|
||||
private static string _ApiFolderName(ApiAssemblyType apiType)
|
||||
{
|
||||
ulong apiHash = apiType == ApiAssemblyType.Core ?
|
||||
Internal.GetCoreApiHash() :
|
||||
Internal.GetEditorApiHash();
|
||||
return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}";
|
||||
}
|
||||
|
||||
private static void ShowBuildErrorDialog(string message)
|
||||
{
|
||||
GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
|
||||
GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab();
|
||||
GodotSharpEditor.Instance.BottomPanel.ShowBuildTab();
|
||||
}
|
||||
|
||||
public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
|
||||
public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
|
||||
public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException();
|
||||
public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException();
|
||||
|
||||
private static string GetLogFilePath(MonoBuildInfo buildInfo)
|
||||
private static string GetLogFilePath(BuildInfo buildInfo)
|
||||
{
|
||||
return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
|
||||
}
|
||||
|
||||
private static string GetIssuesFilePath(MonoBuildInfo buildInfo)
|
||||
private static string GetIssuesFilePath(BuildInfo buildInfo)
|
||||
{
|
||||
return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName);
|
||||
}
|
||||
@ -71,7 +61,7 @@ namespace GodotTools
|
||||
Godot.GD.Print(text);
|
||||
}
|
||||
|
||||
public static bool Build(MonoBuildInfo buildInfo)
|
||||
public static bool Build(BuildInfo buildInfo)
|
||||
{
|
||||
if (BuildsInProgress.Contains(buildInfo))
|
||||
throw new InvalidOperationException("A build is already in progress");
|
||||
@ -80,7 +70,7 @@ namespace GodotTools
|
||||
|
||||
try
|
||||
{
|
||||
MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
|
||||
BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
|
||||
buildTab.OnBuildStart();
|
||||
|
||||
// Required in order to update the build tasks list
|
||||
@ -103,7 +93,7 @@ namespace GodotTools
|
||||
if (exitCode != 0)
|
||||
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
|
||||
|
||||
buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
|
||||
buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
|
||||
|
||||
return exitCode == 0;
|
||||
}
|
||||
@ -120,7 +110,7 @@ namespace GodotTools
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo)
|
||||
public static async Task<bool> BuildAsync(BuildInfo buildInfo)
|
||||
{
|
||||
if (BuildsInProgress.Contains(buildInfo))
|
||||
throw new InvalidOperationException("A build is already in progress");
|
||||
@ -129,7 +119,7 @@ namespace GodotTools
|
||||
|
||||
try
|
||||
{
|
||||
MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
|
||||
BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
|
||||
|
||||
try
|
||||
{
|
||||
@ -148,7 +138,7 @@ namespace GodotTools
|
||||
if (exitCode != 0)
|
||||
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
|
||||
|
||||
buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
|
||||
buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
|
||||
|
||||
return exitCode == 0;
|
||||
}
|
||||
@ -165,32 +155,6 @@ namespace GodotTools
|
||||
}
|
||||
}
|
||||
|
||||
public static bool BuildApiSolution(string apiSlnDir, string config)
|
||||
{
|
||||
string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln");
|
||||
|
||||
string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config);
|
||||
string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll");
|
||||
|
||||
string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config);
|
||||
string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll");
|
||||
|
||||
if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile))
|
||||
return true; // The assemblies are in the output folder; assume the solution is already built
|
||||
|
||||
var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config);
|
||||
|
||||
// TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
|
||||
// once we start to actively document manually maintained C# classes
|
||||
apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings
|
||||
|
||||
if (Build(apiBuildInfo))
|
||||
return true;
|
||||
|
||||
ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
|
||||
{
|
||||
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
||||
@ -201,13 +165,13 @@ namespace GodotTools
|
||||
Internal.UpdateApiAssembliesFromPrebuilt();
|
||||
|
||||
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
|
||||
var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
|
||||
var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
|
||||
|
||||
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
|
||||
{
|
||||
pr.Step("Building project solution", 0);
|
||||
|
||||
var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config);
|
||||
var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config);
|
||||
|
||||
// Add Godot defines
|
||||
string constants = buildTool == BuildTool.MsBuildVs ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
|
||||
@ -240,11 +204,28 @@ namespace GodotTools
|
||||
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
|
||||
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
|
||||
|
||||
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
|
||||
CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
|
||||
|
||||
if (File.Exists(editorScriptsMetadataPath))
|
||||
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
|
||||
|
||||
var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest;
|
||||
|
||||
if (currentPlayRequest != null)
|
||||
{
|
||||
if (currentPlayRequest.Value.HasDebugger)
|
||||
{
|
||||
// Set the environment variable that will tell the player to connect to the IDE debugger
|
||||
// TODO: We should probably add a better way to do this
|
||||
Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT",
|
||||
"--debugger-agent=transport=dt_socket" +
|
||||
$",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" +
|
||||
",server=n");
|
||||
}
|
||||
|
||||
return true; // Requested play from an external editor/IDE which already built the project
|
||||
}
|
||||
|
||||
var godotDefines = new[]
|
||||
{
|
||||
Godot.OS.GetName(),
|
@ -7,7 +7,7 @@ using Path = System.IO.Path;
|
||||
|
||||
namespace GodotTools
|
||||
{
|
||||
public class MonoBuildTab : VBoxContainer
|
||||
public class BuildTab : VBoxContainer
|
||||
{
|
||||
public enum BuildResults
|
||||
{
|
||||
@ -55,7 +55,7 @@ namespace GodotTools
|
||||
}
|
||||
}
|
||||
|
||||
public MonoBuildInfo BuildInfo { get; private set; }
|
||||
public BuildInfo BuildInfo { get; private set; }
|
||||
|
||||
private void _LoadIssuesFromFile(string csvFile)
|
||||
{
|
||||
@ -199,7 +199,7 @@ namespace GodotTools
|
||||
ErrorCount = 0;
|
||||
UpdateIssuesList();
|
||||
|
||||
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
|
||||
GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
|
||||
}
|
||||
|
||||
public void OnBuildExit(BuildResults result)
|
||||
@ -207,10 +207,10 @@ namespace GodotTools
|
||||
BuildExited = true;
|
||||
BuildResult = result;
|
||||
|
||||
_LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName));
|
||||
_LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
|
||||
UpdateIssuesList();
|
||||
|
||||
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
|
||||
GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
|
||||
}
|
||||
|
||||
public void OnBuildExecFailed(string cause)
|
||||
@ -227,7 +227,7 @@ namespace GodotTools
|
||||
|
||||
UpdateIssuesList();
|
||||
|
||||
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
|
||||
GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
|
||||
}
|
||||
|
||||
public void RestartBuild()
|
||||
@ -235,7 +235,7 @@ namespace GodotTools
|
||||
if (!BuildExited)
|
||||
throw new InvalidOperationException("Build already started");
|
||||
|
||||
GodotSharpBuilds.RestartBuild(this);
|
||||
BuildManager.RestartBuild(this);
|
||||
}
|
||||
|
||||
public void StopBuild()
|
||||
@ -243,7 +243,7 @@ namespace GodotTools
|
||||
if (!BuildExited)
|
||||
throw new InvalidOperationException("Build is not in progress");
|
||||
|
||||
GodotSharpBuilds.StopBuild(this);
|
||||
BuildManager.StopBuild(this);
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
@ -255,11 +255,11 @@ namespace GodotTools
|
||||
AddChild(issuesList);
|
||||
}
|
||||
|
||||
private MonoBuildTab()
|
||||
private BuildTab()
|
||||
{
|
||||
}
|
||||
|
||||
public MonoBuildTab(MonoBuildInfo buildInfo)
|
||||
public BuildTab(BuildInfo buildInfo)
|
||||
{
|
||||
BuildInfo = buildInfo;
|
||||
}
|
@ -9,7 +9,7 @@ using Directory = GodotTools.Utils.Directory;
|
||||
|
||||
namespace GodotTools
|
||||
{
|
||||
public static class CSharpProject
|
||||
public static class CsProjOperations
|
||||
{
|
||||
public static string GenerateGameProject(string dir, string name)
|
||||
{
|
@ -0,0 +1,11 @@
|
||||
namespace GodotTools
|
||||
{
|
||||
public enum ExternalEditorId
|
||||
{
|
||||
None,
|
||||
VisualStudio, // TODO (Windows-only)
|
||||
VisualStudioForMac, // Mac-only
|
||||
MonoDevelop,
|
||||
VsCode
|
||||
}
|
||||
}
|
@ -2,16 +2,18 @@ using Godot;
|
||||
using GodotTools.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using GodotTools.Ides;
|
||||
using GodotTools.Internals;
|
||||
using GodotTools.ProjectEditor;
|
||||
using static GodotTools.Internals.Globals;
|
||||
using File = GodotTools.Utils.File;
|
||||
using Path = System.IO.Path;
|
||||
using OS = GodotTools.Utils.OS;
|
||||
|
||||
namespace GodotTools
|
||||
{
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
public class GodotSharpEditor : EditorPlugin, ISerializationListener
|
||||
{
|
||||
private EditorSettings editorSettings;
|
||||
@ -24,12 +26,11 @@ namespace GodotTools
|
||||
|
||||
private ToolButton bottomPanelBtn;
|
||||
|
||||
private MonoDevelopInstance monoDevelopInstance;
|
||||
private MonoDevelopInstance visualStudioForMacInstance;
|
||||
public GodotIdeManager GodotIdeManager { get; private set; }
|
||||
|
||||
private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization
|
||||
|
||||
public MonoBottomPanel MonoBottomPanel { get; private set; }
|
||||
public BottomPanel BottomPanel { get; private set; }
|
||||
|
||||
private bool CreateProjectSolution()
|
||||
{
|
||||
@ -44,7 +45,7 @@ namespace GodotTools
|
||||
if (name.Empty())
|
||||
name = "UnnamedProject";
|
||||
|
||||
string guid = CSharpProject.GenerateGameProject(path, name);
|
||||
string guid = CsProjOperations.GenerateGameProject(path, name);
|
||||
|
||||
if (guid.Length > 0)
|
||||
{
|
||||
@ -133,7 +134,7 @@ namespace GodotTools
|
||||
return; // Failed to create solution
|
||||
}
|
||||
|
||||
Instance.MonoBottomPanel.BuildProjectPressed();
|
||||
Instance.BottomPanel.BuildProjectPressed();
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
@ -153,21 +154,12 @@ namespace GodotTools
|
||||
}
|
||||
}
|
||||
|
||||
public enum MenuOptions
|
||||
private enum MenuOptions
|
||||
{
|
||||
CreateSln,
|
||||
AboutCSharp,
|
||||
}
|
||||
|
||||
public enum ExternalEditor
|
||||
{
|
||||
None,
|
||||
VisualStudio, // TODO (Windows-only)
|
||||
VisualStudioForMac, // Mac-only
|
||||
MonoDevelop,
|
||||
VsCode
|
||||
}
|
||||
|
||||
public void ShowErrorDialog(string message, string title = "Error")
|
||||
{
|
||||
errorDialog.WindowTitle = title;
|
||||
@ -184,11 +176,30 @@ namespace GodotTools
|
||||
|
||||
public Error OpenInExternalEditor(Script script, int line, int col)
|
||||
{
|
||||
var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor");
|
||||
var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor");
|
||||
|
||||
switch (editor)
|
||||
{
|
||||
case ExternalEditor.VsCode:
|
||||
case ExternalEditorId.None:
|
||||
// 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();
|
||||
case ExternalEditorId.VisualStudioForMac:
|
||||
goto case ExternalEditorId.MonoDevelop;
|
||||
case ExternalEditorId.MonoDevelop:
|
||||
{
|
||||
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
|
||||
|
||||
if (line >= 0)
|
||||
GodotIdeManager.SendOpenFile(scriptPath, line + 1, col);
|
||||
else
|
||||
GodotIdeManager.SendOpenFile(scriptPath);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ExternalEditorId.VsCode:
|
||||
{
|
||||
if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
|
||||
{
|
||||
@ -273,47 +284,6 @@ namespace GodotTools
|
||||
break;
|
||||
}
|
||||
|
||||
case ExternalEditor.VisualStudioForMac:
|
||||
goto case ExternalEditor.MonoDevelop;
|
||||
case ExternalEditor.MonoDevelop:
|
||||
{
|
||||
MonoDevelopInstance GetMonoDevelopInstance(string solutionPath)
|
||||
{
|
||||
if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac)
|
||||
{
|
||||
if (visualStudioForMacInstance == null)
|
||||
visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac);
|
||||
|
||||
return visualStudioForMacInstance;
|
||||
}
|
||||
|
||||
if (monoDevelopInstance == null)
|
||||
monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop);
|
||||
|
||||
return monoDevelopInstance;
|
||||
}
|
||||
|
||||
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
|
||||
|
||||
if (line >= 0)
|
||||
scriptPath += $";{line + 1};{col}";
|
||||
|
||||
try
|
||||
{
|
||||
GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
string editorName = editor == ExternalEditor.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
|
||||
GD.PushError($"Cannot find code editor: {editorName}");
|
||||
return Error.FileNotFound;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ExternalEditor.None:
|
||||
return Error.Unavailable;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
@ -323,12 +293,12 @@ namespace GodotTools
|
||||
|
||||
public bool OverridesExternalEditor()
|
||||
{
|
||||
return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None;
|
||||
return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None;
|
||||
}
|
||||
|
||||
public override bool Build()
|
||||
{
|
||||
return GodotSharpBuilds.EditorBuildCallback();
|
||||
return BuildManager.EditorBuildCallback();
|
||||
}
|
||||
|
||||
public override void EnablePlugin()
|
||||
@ -347,9 +317,9 @@ namespace GodotTools
|
||||
errorDialog = new AcceptDialog();
|
||||
editorBaseControl.AddChild(errorDialog);
|
||||
|
||||
MonoBottomPanel = new MonoBottomPanel();
|
||||
BottomPanel = new BottomPanel();
|
||||
|
||||
bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono".TTR());
|
||||
bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR());
|
||||
|
||||
AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
|
||||
|
||||
@ -407,7 +377,7 @@ namespace GodotTools
|
||||
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
|
||||
{
|
||||
// Make sure the existing project has Api assembly references configured correctly
|
||||
CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
|
||||
CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -427,25 +397,25 @@ namespace GodotTools
|
||||
AddControlToContainer(CustomControlContainer.Toolbar, buildButton);
|
||||
|
||||
// External editor settings
|
||||
EditorDef("mono/editor/external_editor", ExternalEditor.None);
|
||||
EditorDef("mono/editor/external_editor", ExternalEditorId.None);
|
||||
|
||||
string settingsHintStr = "Disabled";
|
||||
|
||||
if (OS.IsWindows())
|
||||
{
|
||||
settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
|
||||
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
|
||||
settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
||||
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
||||
}
|
||||
else if (OS.IsOSX())
|
||||
{
|
||||
settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" +
|
||||
$",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
|
||||
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
|
||||
settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" +
|
||||
$",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
||||
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
||||
}
|
||||
else if (OS.IsUnix())
|
||||
{
|
||||
settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
|
||||
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
|
||||
settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
|
||||
$",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
|
||||
}
|
||||
|
||||
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
|
||||
@ -461,7 +431,10 @@ namespace GodotTools
|
||||
AddExportPlugin(exportPlugin);
|
||||
exportPluginWeak = WeakRef(exportPlugin);
|
||||
|
||||
GodotSharpBuilds.Initialize();
|
||||
BuildManager.Initialize();
|
||||
|
||||
GodotIdeManager = new GodotIdeManager();
|
||||
AddChild(GodotIdeManager);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@ -478,6 +451,8 @@ namespace GodotTools
|
||||
|
||||
exportPluginWeak.Dispose();
|
||||
}
|
||||
|
||||
GodotIdeManager?.Dispose();
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
|
@ -65,14 +65,14 @@ namespace GodotTools
|
||||
string buildConfig = isDebug ? "Debug" : "Release";
|
||||
|
||||
string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
|
||||
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
|
||||
CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
|
||||
|
||||
AddFile(scriptsMetadataPath, scriptsMetadataPath);
|
||||
|
||||
// Turn export features into defines
|
||||
var godotDefines = features;
|
||||
|
||||
if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines))
|
||||
if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
|
||||
{
|
||||
GD.PushError("Failed to build project");
|
||||
return;
|
||||
|
@ -39,26 +39,31 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Build\MsBuildFinder.cs" />
|
||||
<Compile Include="ExternalEditorId.cs" />
|
||||
<Compile Include="Ides\GodotIdeManager.cs" />
|
||||
<Compile Include="Ides\GodotIdeServer.cs" />
|
||||
<Compile Include="Ides\MonoDevelop\EditorId.cs" />
|
||||
<Compile Include="Ides\MonoDevelop\Instance.cs" />
|
||||
<Compile Include="Internals\BindingsGenerator.cs" />
|
||||
<Compile Include="Internals\EditorProgress.cs" />
|
||||
<Compile Include="Internals\GodotSharpDirs.cs" />
|
||||
<Compile Include="Internals\Internal.cs" />
|
||||
<Compile Include="Internals\ScriptClassParser.cs" />
|
||||
<Compile Include="Internals\Globals.cs" />
|
||||
<Compile Include="MonoDevelopInstance.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Build\BuildSystem.cs" />
|
||||
<Compile Include="Utils\Directory.cs" />
|
||||
<Compile Include="Utils\File.cs" />
|
||||
<Compile Include="Utils\NotifyAwaiter.cs" />
|
||||
<Compile Include="Utils\OS.cs" />
|
||||
<Compile Include="GodotSharpEditor.cs" />
|
||||
<Compile Include="GodotSharpBuilds.cs" />
|
||||
<Compile Include="BuildManager.cs" />
|
||||
<Compile Include="HotReloadAssemblyWatcher.cs" />
|
||||
<Compile Include="MonoBuildInfo.cs" />
|
||||
<Compile Include="MonoBuildTab.cs" />
|
||||
<Compile Include="MonoBottomPanel.cs" />
|
||||
<Compile Include="BuildInfo.cs" />
|
||||
<Compile Include="BuildTab.cs" />
|
||||
<Compile Include="BottomPanel.cs" />
|
||||
<Compile Include="GodotSharpExport.cs" />
|
||||
<Compile Include="CSharpProject.cs" />
|
||||
<Compile Include="CsProjOperations.cs" />
|
||||
<Compile Include="Utils\CollectionExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -66,6 +71,10 @@
|
||||
<Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project>
|
||||
<Name>GodotTools.BuildLogger</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj">
|
||||
<Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project>
|
||||
<Name>GodotTools.IdeConnection</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj">
|
||||
<Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project>
|
||||
<Name>GodotTools.ProjectEditor</Name>
|
||||
@ -75,8 +84,5 @@
|
||||
<Name>GodotTools.Core</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Editor" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Godot;
|
||||
using GodotTools.IdeConnection;
|
||||
using GodotTools.Internals;
|
||||
|
||||
namespace GodotTools.Ides
|
||||
{
|
||||
public class GodotIdeManager : Node, ISerializationListener
|
||||
{
|
||||
public GodotIdeServer GodotIdeServer { get; private set; }
|
||||
|
||||
private MonoDevelop.Instance monoDevelInstance;
|
||||
private MonoDevelop.Instance vsForMacInstance;
|
||||
|
||||
private GodotIdeServer GetRunningServer()
|
||||
{
|
||||
if (GodotIdeServer != null && !GodotIdeServer.IsDisposed)
|
||||
return GodotIdeServer;
|
||||
StartServer();
|
||||
return GodotIdeServer;
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
StartServer();
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
GodotIdeServer?.Dispose();
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
StartServer();
|
||||
}
|
||||
|
||||
private ILogger logger;
|
||||
|
||||
protected ILogger Logger
|
||||
{
|
||||
get => logger ?? (logger = new ConsoleLogger());
|
||||
set => logger = value;
|
||||
}
|
||||
|
||||
private void StartServer()
|
||||
{
|
||||
GodotIdeServer?.Dispose();
|
||||
GodotIdeServer = new GodotIdeServer(LaunchIde,
|
||||
OS.GetExecutablePath(),
|
||||
ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir));
|
||||
|
||||
GodotIdeServer.Logger = Logger;
|
||||
|
||||
GodotIdeServer.StartServer();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
GodotIdeServer?.Dispose();
|
||||
}
|
||||
|
||||
private void LaunchIde()
|
||||
{
|
||||
var editor = (ExternalEditorId) GodotSharpEditor.Instance.GetEditorInterface()
|
||||
.GetEditorSettings().GetSetting("mono/editor/external_editor");
|
||||
|
||||
switch (editor)
|
||||
{
|
||||
case ExternalEditorId.None:
|
||||
case ExternalEditorId.VisualStudio:
|
||||
case ExternalEditorId.VsCode:
|
||||
throw new NotSupportedException();
|
||||
case ExternalEditorId.VisualStudioForMac:
|
||||
goto case ExternalEditorId.MonoDevelop;
|
||||
case ExternalEditorId.MonoDevelop:
|
||||
{
|
||||
MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath)
|
||||
{
|
||||
if (Utils.OS.IsOSX() && editor == ExternalEditorId.VisualStudioForMac)
|
||||
{
|
||||
vsForMacInstance = vsForMacInstance ??
|
||||
new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
|
||||
return vsForMacInstance;
|
||||
}
|
||||
|
||||
monoDevelInstance = monoDevelInstance ??
|
||||
new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop);
|
||||
return monoDevelInstance;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath);
|
||||
|
||||
if (!instance.IsRunning)
|
||||
instance.Execute();
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
|
||||
GD.PushError($"Cannot find code editor: {editorName}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteMessage(string id, params string[] arguments)
|
||||
{
|
||||
GetRunningServer().WriteMessage(new Message(id, arguments));
|
||||
}
|
||||
|
||||
public void SendOpenFile(string file)
|
||||
{
|
||||
WriteMessage("OpenFile", file);
|
||||
}
|
||||
|
||||
public void SendOpenFile(string file, int line)
|
||||
{
|
||||
WriteMessage("OpenFile", file, line.ToString());
|
||||
}
|
||||
|
||||
public void SendOpenFile(string file, int line, int column)
|
||||
{
|
||||
WriteMessage("OpenFile", file, line.ToString(), column.ToString());
|
||||
}
|
||||
|
||||
private class GodotLogger : ILogger
|
||||
{
|
||||
public void LogDebug(string message)
|
||||
{
|
||||
if (OS.IsStdoutVerbose())
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
|
||||
public void LogInfo(string message)
|
||||
{
|
||||
if (OS.IsStdoutVerbose())
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
GD.PushWarning(message);
|
||||
}
|
||||
|
||||
public void LogError(string message)
|
||||
{
|
||||
GD.PushError(message);
|
||||
}
|
||||
|
||||
public void LogError(string message, Exception e)
|
||||
{
|
||||
GD.PushError(message + "\n" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
208
modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
Normal file
208
modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
Normal file
@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GodotTools.IdeConnection;
|
||||
using GodotTools.Internals;
|
||||
using GodotTools.Utils;
|
||||
using File = System.IO.File;
|
||||
using Thread = System.Threading.Thread;
|
||||
|
||||
namespace GodotTools.Ides
|
||||
{
|
||||
public class GodotIdeServer : GodotIdeBase
|
||||
{
|
||||
private readonly TcpListener listener;
|
||||
private readonly FileStream metaFile;
|
||||
private readonly Action launchIdeAction;
|
||||
private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>();
|
||||
|
||||
private async Task<bool> AwaitClientConnected()
|
||||
{
|
||||
return await clientConnectedAwaiter.Reset();
|
||||
}
|
||||
|
||||
public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir)
|
||||
: base(projectMetadataDir)
|
||||
{
|
||||
messageHandlers = InitializeMessageHandlers();
|
||||
|
||||
this.launchIdeAction = launchIdeAction;
|
||||
|
||||
// The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing...
|
||||
const FileShare metaFileShare = FileShare.ReadWrite;
|
||||
|
||||
metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare);
|
||||
|
||||
listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
|
||||
listener.Start();
|
||||
|
||||
int port = ((IPEndPoint) listener.Server.LocalEndPoint).Port;
|
||||
using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8))
|
||||
{
|
||||
metaFileWriter.WriteLine(port);
|
||||
metaFileWriter.WriteLine(editorExecutablePath);
|
||||
}
|
||||
|
||||
StartServer();
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
var serverThread = new Thread(RunServerThread) {Name = "Godot Ide Connection Server"};
|
||||
serverThread.Start();
|
||||
}
|
||||
|
||||
private void RunServerThread()
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext);
|
||||
|
||||
try
|
||||
{
|
||||
while (!IsDisposed)
|
||||
{
|
||||
TcpClient tcpClient = listener.AcceptTcpClient();
|
||||
|
||||
Logger.LogInfo("Connection open with Ide Client");
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage);
|
||||
Connection.Logger = Logger;
|
||||
}
|
||||
|
||||
Connected += () => clientConnectedAwaiter.SetResult(true);
|
||||
|
||||
Connection.Start();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted))
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async void WriteMessage(Message message)
|
||||
{
|
||||
async Task LaunchIde()
|
||||
{
|
||||
if (IsConnected)
|
||||
return;
|
||||
|
||||
launchIdeAction();
|
||||
await Task.WhenAny(Task.Delay(10000), AwaitClientConnected());
|
||||
}
|
||||
|
||||
await LaunchIde();
|
||||
|
||||
if (!IsConnected)
|
||||
{
|
||||
Logger.LogError("Cannot write message: Godot Ide Server not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
Connection.WriteMessage(message);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
listener?.Stop();
|
||||
|
||||
metaFile?.Dispose();
|
||||
|
||||
File.Delete(MetaFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool HandleMessage(Message message)
|
||||
{
|
||||
if (messageHandlers.TryGetValue(message.Id, out var action))
|
||||
{
|
||||
action(message.Arguments);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, Action<string[]>> messageHandlers;
|
||||
|
||||
private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
|
||||
{
|
||||
return new Dictionary<string, Action<string[]>>
|
||||
{
|
||||
["Play"] = args =>
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
Play();
|
||||
return;
|
||||
case 2:
|
||||
Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1]));
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
},
|
||||
["ReloadScripts"] = args => ReloadScripts()
|
||||
};
|
||||
}
|
||||
|
||||
private void DispatchToMainThread(Action action)
|
||||
{
|
||||
var d = new SendOrPostCallback(state => action());
|
||||
Godot.Dispatcher.SynchronizationContext.Post(d, null);
|
||||
}
|
||||
|
||||
private void Play()
|
||||
{
|
||||
DispatchToMainThread(() =>
|
||||
{
|
||||
CurrentPlayRequest = new PlayRequest();
|
||||
Internal.EditorRunPlay();
|
||||
CurrentPlayRequest = null;
|
||||
});
|
||||
}
|
||||
|
||||
private void Play(string debuggerHost, int debuggerPort)
|
||||
{
|
||||
DispatchToMainThread(() =>
|
||||
{
|
||||
CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort);
|
||||
Internal.EditorRunPlay();
|
||||
CurrentPlayRequest = null;
|
||||
});
|
||||
}
|
||||
|
||||
private void ReloadScripts()
|
||||
{
|
||||
DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts);
|
||||
}
|
||||
|
||||
public PlayRequest? CurrentPlayRequest { get; private set; }
|
||||
|
||||
public struct PlayRequest
|
||||
{
|
||||
public bool HasDebugger { get; }
|
||||
public string DebuggerHost { get; }
|
||||
public int DebuggerPort { get; }
|
||||
|
||||
public PlayRequest(string debuggerHost, int debuggerPort)
|
||||
{
|
||||
HasDebugger = true;
|
||||
DebuggerHost = debuggerHost;
|
||||
DebuggerPort = debuggerPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace GodotTools.Ides.MonoDevelop
|
||||
{
|
||||
public enum EditorId
|
||||
{
|
||||
MonoDevelop = 0,
|
||||
VisualStudioForMac = 1
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
using GodotTools.Core;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
@ -6,22 +5,18 @@ using System.Diagnostics;
|
||||
using GodotTools.Internals;
|
||||
using GodotTools.Utils;
|
||||
|
||||
namespace GodotTools
|
||||
namespace GodotTools.Ides.MonoDevelop
|
||||
{
|
||||
public class MonoDevelopInstance
|
||||
public class Instance
|
||||
{
|
||||
public enum EditorId
|
||||
{
|
||||
MonoDevelop = 0,
|
||||
VisualStudioForMac = 1
|
||||
}
|
||||
|
||||
private readonly string solutionFile;
|
||||
private readonly EditorId editorId;
|
||||
|
||||
private Process process;
|
||||
|
||||
public void Execute(params string[] files)
|
||||
public bool IsRunning => process != null && !process.HasExited;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
bool newWindow = process == null || process.HasExited;
|
||||
|
||||
@ -29,7 +24,7 @@ namespace GodotTools
|
||||
|
||||
string command;
|
||||
|
||||
if (Utils.OS.IsOSX())
|
||||
if (OS.IsOSX())
|
||||
{
|
||||
string bundleId = BundleIds[editorId];
|
||||
|
||||
@ -61,16 +56,6 @@ namespace GodotTools
|
||||
if (newWindow)
|
||||
args.Add("\"" + Path.GetFullPath(solutionFile) + "\"");
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
int semicolonIndex = file.IndexOf(';');
|
||||
|
||||
string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex);
|
||||
string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex);
|
||||
|
||||
args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\"");
|
||||
}
|
||||
|
||||
if (command == null)
|
||||
throw new FileNotFoundException();
|
||||
|
||||
@ -80,7 +65,7 @@ namespace GodotTools
|
||||
{
|
||||
FileName = command,
|
||||
Arguments = string.Join(" ", args),
|
||||
UseShellExecute = false
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
else
|
||||
@ -89,14 +74,14 @@ namespace GodotTools
|
||||
{
|
||||
FileName = command,
|
||||
Arguments = string.Join(" ", args),
|
||||
UseShellExecute = false
|
||||
UseShellExecute = true
|
||||
})?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public MonoDevelopInstance(string solutionFile, EditorId editorId)
|
||||
public Instance(string solutionFile, EditorId editorId)
|
||||
{
|
||||
if (editorId == EditorId.VisualStudioForMac && !Utils.OS.IsOSX())
|
||||
if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX())
|
||||
throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform");
|
||||
|
||||
this.solutionFile = solutionFile;
|
||||
@ -106,9 +91,9 @@ namespace GodotTools
|
||||
private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames;
|
||||
private static readonly IReadOnlyDictionary<EditorId, string> BundleIds;
|
||||
|
||||
static MonoDevelopInstance()
|
||||
static Instance()
|
||||
{
|
||||
if (Utils.OS.IsOSX())
|
||||
if (OS.IsOSX())
|
||||
{
|
||||
ExecutableNames = new Dictionary<EditorId, string>
|
||||
{
|
||||
@ -122,7 +107,7 @@ namespace GodotTools
|
||||
{EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
|
||||
};
|
||||
}
|
||||
else if (Utils.OS.IsWindows())
|
||||
else if (OS.IsWindows())
|
||||
{
|
||||
ExecutableNames = new Dictionary<EditorId, string>
|
||||
{
|
||||
@ -133,7 +118,7 @@ namespace GodotTools
|
||||
{EditorId.MonoDevelop, "MonoDevelop.exe"}
|
||||
};
|
||||
}
|
||||
else if (Utils.OS.IsUnix())
|
||||
else if (OS.IsUnix())
|
||||
{
|
||||
ExecutableNames = new Dictionary<EditorId, string>
|
||||
{
|
@ -46,6 +46,12 @@ namespace GodotTools.Internals
|
||||
|
||||
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
|
||||
|
||||
public static void EditorRunPlay() => internal_EditorRunPlay();
|
||||
|
||||
public static void EditorRunStop() => internal_EditorRunStop();
|
||||
|
||||
public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
|
||||
|
||||
// Internal Calls
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
@ -95,5 +101,14 @@ namespace GodotTools.Internals
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern string internal_MonoWindowsInstallRoot();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern void internal_EditorRunPlay();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern void internal_EditorRunStop();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern void internal_ScriptEditorDebugger_ReloadScripts();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace GodotTools.Utils
|
||||
{
|
||||
@ -17,5 +18,12 @@ namespace GodotTools.Utils
|
||||
|
||||
return orElse;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> EnumerateLines(this TextReader textReader)
|
||||
{
|
||||
string line;
|
||||
while ((line = textReader.ReadLine()) != null)
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace GodotTools.Utils
|
||||
{
|
||||
public sealed class NotifyAwaiter<T> : INotifyCompletion
|
||||
{
|
||||
private Action continuation;
|
||||
private Exception exception;
|
||||
private T result;
|
||||
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
public T GetResult()
|
||||
{
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
if (this.continuation != null)
|
||||
throw new InvalidOperationException("This awaiter has already been listened");
|
||||
this.continuation = continuation;
|
||||
}
|
||||
|
||||
public void SetResult(T result)
|
||||
{
|
||||
if (IsCompleted)
|
||||
throw new InvalidOperationException("This awaiter is already completed");
|
||||
|
||||
IsCompleted = true;
|
||||
this.result = result;
|
||||
|
||||
continuation?.Invoke();
|
||||
}
|
||||
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (IsCompleted)
|
||||
throw new InvalidOperationException("This awaiter is already completed");
|
||||
|
||||
IsCompleted = true;
|
||||
this.exception = exception;
|
||||
|
||||
continuation?.Invoke();
|
||||
}
|
||||
|
||||
public NotifyAwaiter<T> Reset()
|
||||
{
|
||||
continuation = null;
|
||||
exception = null;
|
||||
result = default;
|
||||
IsCompleted = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NotifyAwaiter<T> GetAwaiter()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@ -350,6 +350,21 @@ MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void godot_icall_Internal_EditorRunPlay() {
|
||||
EditorNode::get_singleton()->run_play();
|
||||
}
|
||||
|
||||
void godot_icall_Internal_EditorRunStop() {
|
||||
EditorNode::get_singleton()->run_stop();
|
||||
}
|
||||
|
||||
void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
|
||||
ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger();
|
||||
if (sed) {
|
||||
sed->reload_scripts();
|
||||
}
|
||||
}
|
||||
|
||||
MonoString *godot_icall_Utils_OS_GetPlatformName() {
|
||||
String os_name = OS::get_singleton()->get_name();
|
||||
return GDMonoMarshal::mono_string_from_godot(os_name);
|
||||
@ -414,7 +429,9 @@ void register_editor_internal_calls() {
|
||||
mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit);
|
||||
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen);
|
||||
mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing);
|
||||
mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot);
|
||||
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay);
|
||||
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop);
|
||||
mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts);
|
||||
|
||||
// Globals
|
||||
mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale);
|
||||
|
13
modules/mono/glue/Managed/Files/Dispatcher.cs
Normal file
13
modules/mono/glue/Managed/Files/Dispatcher.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
public static class Dispatcher
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
private static extern GodotTaskScheduler godot_icall_DefaultGodotTaskScheduler();
|
||||
|
||||
public static GodotSynchronizationContext SynchronizationContext =>
|
||||
godot_icall_DefaultGodotTaskScheduler().Context;
|
||||
}
|
||||
}
|
@ -6,17 +6,16 @@ namespace Godot
|
||||
{
|
||||
public class GodotSynchronizationContext : SynchronizationContext
|
||||
{
|
||||
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
|
||||
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
|
||||
|
||||
public override void Post(SendOrPostCallback d, object state)
|
||||
{
|
||||
queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
|
||||
_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
|
||||
}
|
||||
|
||||
public void ExecutePendingContinuations()
|
||||
{
|
||||
KeyValuePair<SendOrPostCallback, object> workItem;
|
||||
while (queue.TryTake(out workItem))
|
||||
while (_queue.TryTake(out var workItem))
|
||||
{
|
||||
workItem.Key(workItem.Value);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace Godot
|
||||
{
|
||||
public class GodotTaskScheduler : TaskScheduler
|
||||
{
|
||||
private GodotSynchronizationContext Context { get; set; }
|
||||
internal GodotSynchronizationContext Context { get; }
|
||||
private readonly LinkedList<Task> _tasks = new LinkedList<Task>();
|
||||
|
||||
public GodotTaskScheduler()
|
||||
@ -28,14 +28,10 @@ namespace Godot
|
||||
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
if (SynchronizationContext.Current != Context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (taskWasPreviouslyQueued)
|
||||
{
|
||||
TryDequeue(task);
|
||||
}
|
||||
|
||||
return TryExecuteTask(task);
|
||||
}
|
||||
@ -52,7 +48,8 @@ namespace Godot
|
||||
{
|
||||
lock (_tasks)
|
||||
{
|
||||
return _tasks.ToArray();
|
||||
foreach (Task task in _tasks)
|
||||
yield return task;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@ -37,4 +37,4 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
@ -211,6 +211,10 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) {
|
||||
return GDMonoMarshal::mono_string_from_godot(vars);
|
||||
}
|
||||
|
||||
MonoObject *godot_icall_DefaultGodotTaskScheduler() {
|
||||
return GDMonoUtils::mono_cache.task_scheduler_handle->get_target();
|
||||
}
|
||||
|
||||
void godot_register_gd_icalls() {
|
||||
mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var);
|
||||
mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert);
|
||||
@ -234,6 +238,9 @@ void godot_register_gd_icalls() {
|
||||
mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists);
|
||||
mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes);
|
||||
mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str);
|
||||
|
||||
// Dispatcher
|
||||
mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler);
|
||||
}
|
||||
|
||||
#endif // MONO_GLUE_ENABLED
|
||||
|
@ -75,6 +75,8 @@ MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_object
|
||||
|
||||
MonoString *godot_icall_GD_var2str(MonoObject *p_var);
|
||||
|
||||
MonoObject *godot_icall_DefaultGodotTaskScheduler();
|
||||
|
||||
// Register internal calls
|
||||
|
||||
void godot_register_gd_icalls();
|
||||
|
@ -561,14 +561,14 @@ bool GDMono::_load_corlib_assembly() {
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) {
|
||||
bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) {
|
||||
|
||||
bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ?
|
||||
GDMono::get_singleton()->core_api_assembly_out_of_sync :
|
||||
GDMono::get_singleton()->editor_api_assembly_out_of_sync;
|
||||
|
||||
String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
|
||||
String dst_dir = GodotSharpDirs::get_res_assemblies_dir();
|
||||
String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
|
||||
String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
|
||||
|
||||
String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
|
||||
|
||||
@ -631,18 +631,28 @@ String GDMono::update_api_assemblies_from_prebuilt() {
|
||||
if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
|
||||
return String(); // No update needed
|
||||
|
||||
print_verbose("Updating API assemblies");
|
||||
const int CONFIGS_LEN = 2;
|
||||
String configs[CONFIGS_LEN] = { String("Debug"), String("Release") };
|
||||
|
||||
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
|
||||
String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
|
||||
String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
|
||||
for (int i = 0; i < CONFIGS_LEN; i++) {
|
||||
String config = configs[i];
|
||||
|
||||
if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path))
|
||||
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false);
|
||||
print_verbose("Updating '" + config + "' API assemblies");
|
||||
|
||||
// Copy the prebuilt Api
|
||||
if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE) || !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR))
|
||||
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true);
|
||||
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config);
|
||||
String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
|
||||
String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
|
||||
|
||||
if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
|
||||
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false);
|
||||
}
|
||||
|
||||
// Copy the prebuilt Api
|
||||
if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) ||
|
||||
!copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) {
|
||||
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true);
|
||||
}
|
||||
}
|
||||
|
||||
return String(); // Updated successfully
|
||||
|
||||
|
@ -165,7 +165,7 @@ public:
|
||||
#endif
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type);
|
||||
bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config);
|
||||
String update_api_assemblies_from_prebuilt();
|
||||
#endif
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user