- tweak: Change content running logic

This commit is contained in:
2025-06-17 21:07:32 +03:00
parent eb188321af
commit a7943adb76
16 changed files with 430 additions and 194 deletions

View File

@@ -0,0 +1,25 @@
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ProcessHelper;
public abstract class DotnetProcessStartInfoProviderBase(DotnetResolverService resolverService) : IProcessStartInfoProvider
{
protected abstract string GetDllPath();
public virtual async Task<ProcessStartInfo> GetProcessStartInfo()
{
return new ProcessStartInfo
{
FileName = await resolverService.EnsureDotnet(),
Arguments = GetDllPath(),
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8
};
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Nebula.Shared;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ProcessHelper;
[ServiceRegister(isSingleton:false)]
public sealed class GameProcessStartInfoProvider(DotnetResolverService resolverService, AuthService authService) :
DotnetProcessStartInfoProviderBase(resolverService)
{
private string? _publicKey;
private RobustUrl _address = default!;
protected override string GetDllPath()
{
var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
return Path.Join(path, "Nebula.Runner.dll");
}
public GameProcessStartInfoProvider WithBuildInfo(string publicKey, RobustUrl address)
{
_publicKey = publicKey;
_address = address;
return this;
}
public override async Task<ProcessStartInfo> GetProcessStartInfo()
{
var baseStart = await base.GetProcessStartInfo();
var authProv = authService.SelectedAuth;
if(authProv is null)
throw new Exception("Client is without selected auth");
baseStart.EnvironmentVariables["ROBUST_AUTH_USERID"] = authProv.UserId.ToString();
baseStart.EnvironmentVariables["ROBUST_AUTH_TOKEN"] = authProv.Token.Token;
baseStart.EnvironmentVariables["ROBUST_AUTH_SERVER"] = authProv.AuthServer;
baseStart.EnvironmentVariables["AUTH_LOGIN"] = authProv.Login;
baseStart.EnvironmentVariables["ROBUST_AUTH_PUBKEY"] = _publicKey;
baseStart.EnvironmentVariables["GAME_URL"] = _address.ToString();
return baseStart;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Shared;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
namespace Nebula.Launcher.ProcessHelper;
[ServiceRegister]
public sealed class GameRunnerPreparer(IServiceProvider provider, ContentService contentService, EngineService engineService, DebugService debugService)
{
public async Task<ProcessRunHandler<GameProcessStartInfoProvider>> GetGameProcessStartInfoProvider(RobustUrl address, ILoadingHandler loadingHandler, CancellationToken cancellationToken = default)
{
var buildInfo = await contentService.GetBuildInfo(address, cancellationToken);
var engine = await engineService.EnsureEngine(buildInfo.BuildInfo.Build.EngineVersion);
if (engine is null)
throw new Exception("Engine version not found: " + buildInfo.BuildInfo.Build.EngineVersion);
await contentService.EnsureItems(buildInfo.RobustManifestInfo, loadingHandler, cancellationToken);
await engineService.EnsureEngineModules("Robust.Client.WebView", buildInfo.BuildInfo.Build.EngineVersion);
var gameInfo =
provider.GetService<GameProcessStartInfoProvider>()!.WithBuildInfo(buildInfo.BuildInfo.Auth.PublicKey,
address);
var gameProcessRunHandler = new ProcessRunHandler<GameProcessStartInfoProvider>(gameInfo);
return gameProcessRunHandler;
}
}

View File

@@ -0,0 +1,6 @@
namespace Nebula.Launcher.ProcessHelper;
public interface IProcessConsumerCollection
{
public void RegisterLogger(IProcessLogConsumer consumer);
}

View File

@@ -0,0 +1,8 @@
namespace Nebula.Launcher.ProcessHelper;
public interface IProcessLogConsumer
{
public void Out(string text);
public void Error(string text);
public void Fatal(string text);
}

View File

@@ -0,0 +1,9 @@
using System.Diagnostics;
using System.Threading.Tasks;
namespace Nebula.Launcher.ProcessHelper;
public interface IProcessStartInfoProvider
{
public Task<ProcessStartInfo> GetProcessStartInfo();
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
namespace Nebula.Launcher.ProcessHelper;
public sealed class ProcessLogConsumerCollection: IProcessLogConsumer, IProcessConsumerCollection
{
private readonly List<IProcessLogConsumer> _consumers = [];
public void RegisterLogger(IProcessLogConsumer consumer)
{
_consumers.Add(consumer);
}
public void Out(string text)
{
foreach (var consumer in _consumers)
{
consumer.Out(text);
}
}
public void Error(string text)
{
foreach (var consumer in _consumers)
{
consumer.Error(text);
}
}
public void Fatal(string text)
{
foreach (var consumer in _consumers)
{
consumer.Fatal(text);
}
}
}

View File

@@ -0,0 +1,137 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Nebula.Shared.Services;
using Nebula.Shared.Services.Logging;
namespace Nebula.Launcher.ProcessHelper;
public class ProcessRunHandler<T> : IProcessConsumerCollection, IDisposable where T: IProcessStartInfoProvider
{
private ProcessStartInfo? _processInfo;
private Task<ProcessStartInfo>? _processInfoTask;
private Process? _process;
private ProcessLogConsumerCollection _consumerCollection = new();
private string _lastError = string.Empty;
private readonly T _currentProcessStartInfoProvider;
public T GetCurrentProcessStartInfo() => _currentProcessStartInfoProvider;
public bool IsRunning => _processInfo is not null;
public Action<ProcessRunHandler<T>>? OnProcessExited;
public void RegisterLogger(IProcessLogConsumer consumer)
{
_consumerCollection.RegisterLogger(consumer);
}
public ProcessRunHandler(T processStartInfoProvider)
{
_currentProcessStartInfoProvider = processStartInfoProvider;
_processInfoTask = _currentProcessStartInfoProvider.GetProcessStartInfo();
_processInfoTask.GetAwaiter().OnCompleted(OnInfoProvided);
}
private void OnInfoProvided()
{
if (_processInfoTask == null)
return;
_processInfo = _processInfoTask.GetAwaiter().GetResult();
_processInfoTask = null;
}
public void Start()
{
if (_processInfoTask != null)
{
_processInfoTask.Wait();
}
_process = Process.Start(_processInfo!);
if (_process is null) return;
_process.EnableRaisingEvents = true;
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
_process.OutputDataReceived += OnOutputDataReceived;
_process.ErrorDataReceived += OnErrorDataReceived;
_process.Exited += OnExited;
}
public void Stop()
{
_process?.CloseMainWindow();
}
private void OnExited(object? sender, EventArgs e)
{
if (_process is null) return;
_process.OutputDataReceived -= OnOutputDataReceived;
_process.ErrorDataReceived -= OnErrorDataReceived;
_process.Exited -= OnExited;
if (_process.ExitCode != 0)
_consumerCollection.Fatal(_lastError);
_process.Dispose();
_process = null;
OnProcessExited?.Invoke(this);
}
private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
_lastError = e.Data;
_consumerCollection.Error(e.Data);
}
}
private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
_consumerCollection.Out(e.Data);
}
}
public void Dispose()
{
_processInfoTask?.Dispose();
_process?.Dispose();
}
}
public sealed class DebugLoggerBridge : IProcessLogConsumer
{
private ILogger _logger;
public DebugLoggerBridge(ILogger logger)
{
_logger = logger;
}
public void Out(string text)
{
_logger.Log(LoggerCategory.Log, text);
}
public void Error(string text)
{
_logger.Log(LoggerCategory.Error, text);
}
public void Fatal(string text)
{
_logger.Log(LoggerCategory.Error, text);
}
}