Files
glitchy-engine-beef/GlitchyEditor/src/CodeEditors/EnvDte/DteOpenRequest.bf
T

129 lines
3.3 KiB
Beef

#if BF_PLATFORM_WINDOWS
using System;
using System.Diagnostics;
using System.Threading;
using DirectX.Common;
using GlitchyEngine;
using GlitchyEditor.Platform.Windows.Com;
namespace GlitchyEditor.CodeEditors;
/// A request to open the script solution (or a file in it) in Visual Studio.
/// Runs on its own worker thread so the editor never blocks on a busy or starting Visual Studio.
class DteOpenRequest
{
private const int ColdStartMaxPolls = 120;
private const int ColdStartPollIntervalMs = 500;
private append String _solutionPath = .();
private append String _devenvPath = .();
private append String _fileName = .();
public StringView SolutionPath
{
get => _solutionPath;
set => _solutionPath.Set(value);
}
public StringView DevenvPath
{
get => _devenvPath;
set => _devenvPath.Set(value);
}
/// Leave empty to just open the project.
public StringView FileName
{
get => _fileName;
set => _fileName.Set(value);
}
public int LineNumber;
public int ColumnNumber;
private static int32 sWorkerActive = 0;
public static void Start(DteOpenRequest ownRequest)
{
if (Interlocked.CompareExchange(ref sWorkerActive, 0, 1) != 0)
{
Log.EngineLogger.Info("Visual Studio is already being opened, ignoring the request.");
delete ownRequest;
return;
}
Thread workerThread = new Thread(new () =>
{
ownRequest.Run();
delete ownRequest;
Interlocked.Exchange(ref sWorkerActive, 0);
});
workerThread.SetName("Visual Studio DTE worker");
// Don't block editor shutdown on a worker that is still polling; skipping the COM cleanup
// at process exit is harmless.
workerThread.IsBackground = true;
workerThread.Start(true);
}
private void Run()
{
// The worker does its own COM initialization, the editor main thread (STA via OleInitialize)
// stays untouched.
if (CoInitializeEx(null, COINIT_APARTMENTTHREADED).Failed(let result))
{
Log.EngineLogger.Error(scope $"CoInitializeEx failed: {result} ({(uint32)result:X8})");
return;
}
defer CoUninitialize();
if (VisualStudioDte.FindRunningInstance(SolutionPath) case .Ok(let dte))
{
if (FileName.IsEmpty)
VisualStudioDte.ActivateMainWindow(dte);
else
VisualStudioDte.OpenFileAtLine(dte, FileName, LineNumber, ColumnNumber).IgnoreError();
delete dte;
return;
}
Log.EngineLogger.Info(scope $"No running Visual Studio instance has \"{SolutionPath}\" open, starting a new one.");
ProcessStartInfo startInfo = scope .();
startInfo.SetFileName(DevenvPath);
startInfo.SetArguments(scope $"\"{SolutionPath}\"");
if (scope SpawnedProcess().Start(startInfo) case .Err)
{
Log.EngineLogger.Error(scope $"Failed to start Visual Studio (\"{DevenvPath}\").");
return;
}
if (FileName.IsEmpty)
return;
// Wait for the new instance to finish loading the solution (Solution.FullName stays empty
// until then), then open the file in it.
for (int i < ColdStartMaxPolls)
{
Thread.Sleep(ColdStartPollIntervalMs);
if (VisualStudioDte.FindRunningInstance(SolutionPath) case .Ok(let startedDte))
{
VisualStudioDte.OpenFileAtLine(startedDte, FileName, LineNumber, ColumnNumber).IgnoreError();
delete startedDte;
return;
}
}
Log.EngineLogger.Warning(scope $"Visual Studio was started but its automation interface never became available, \"{FileName}\" was not opened automatically.");
}
}
#endif