mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-04 00:56:19 +02:00
Reorganize project structure
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
namespace RemSox.Kernel.Processing.IPC;
|
||||
|
||||
internal static class InterProcessCommunicator
|
||||
{
|
||||
public static void Send(int targetProcessId, Message message, Process sender)
|
||||
{
|
||||
message.SenderProcessId = sender.Id; // automatically set
|
||||
ProcessManager.GetProcess(targetProcessId)?.HandleInterProcessMessage(message);
|
||||
}
|
||||
|
||||
public static void SendToAll(Message message, Process sender)
|
||||
{
|
||||
message.SenderProcessId = sender.Id; // automatically set
|
||||
|
||||
foreach (Process process in ProcessManager.GetAllProcesses())
|
||||
{
|
||||
process.HandleInterProcessMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace RemSox.Kernel.Processing.IPC;
|
||||
|
||||
public abstract class Message
|
||||
{
|
||||
public int SenderProcessId { get; internal set; }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using RemSox.Kernel.Logging;
|
||||
using RemSox.Kernel.Processing.IPC;
|
||||
using RemSox.Kernel.UI.GUI.Windows;
|
||||
|
||||
using System.Drawing;
|
||||
|
||||
namespace RemSox.Kernel.Processing;
|
||||
|
||||
public abstract class Process(string name)
|
||||
{
|
||||
public int Id { get; init; } // Will be set by ProcessManager when the process is spawned
|
||||
|
||||
public ILogger Logger { protected get; init; } = null!; // Will be set by ProcessManager when the process is spawned
|
||||
|
||||
public string Name { get; set; } = name;
|
||||
|
||||
public bool StopRequested { get; private set; } = false;
|
||||
|
||||
public bool IsRunning => !StopRequested;
|
||||
|
||||
internal virtual void Start(string[] args) { }
|
||||
|
||||
internal abstract void Tick();
|
||||
|
||||
internal virtual void Stop() { }
|
||||
|
||||
internal virtual void HandleInterProcessMessage(Message message)
|
||||
{
|
||||
}
|
||||
|
||||
protected Window CreateWindow(string title, Size size)
|
||||
{
|
||||
return WindowManager.CreateWindow(this, title, size);
|
||||
}
|
||||
|
||||
protected Window CreateWindow(string title, Size size, Point position)
|
||||
{
|
||||
return WindowManager.CreateWindow(this, title, size, position);
|
||||
}
|
||||
|
||||
protected void SendMessageToProcess(int targetProcessId, Message message)
|
||||
{
|
||||
InterProcessCommunicator.Send(targetProcessId, message, this);
|
||||
}
|
||||
|
||||
protected void SendMessageToAllProcesses(Message message)
|
||||
{
|
||||
InterProcessCommunicator.SendToAll(message, this);
|
||||
}
|
||||
|
||||
internal void RequestStop()
|
||||
{
|
||||
StopRequested = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
using RemSox.Kernel.Logging;
|
||||
using RemSox.Kernel.UI.GUI.Windows;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace RemSox.Kernel.Processing;
|
||||
|
||||
public static class ProcessManager
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource)> processes = new();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, ConcurrentHashSet<int>> processesByType = new();
|
||||
|
||||
private static readonly InMemoryLogger logger = new();
|
||||
private static readonly ConcurrentDictionary<int, InMemoryLogger> processLoggers = new();
|
||||
|
||||
private static int nextProcessId = 0;
|
||||
|
||||
private static int nextSystemProcessId = -1;
|
||||
|
||||
public static int SpawnProcess<T>(string[]? args = null) where T : Process, new()
|
||||
{
|
||||
logger.Log($"Attempting to spawn process of type {typeof(T).Name}...", LogSeverity.Info);
|
||||
|
||||
if (ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.Singleton) && IsProcessRunning<T>())
|
||||
{
|
||||
logger.Log($"Cannot spawn process of type {typeof(T).Name} because it is marked as a singleton and an instance is already running.", LogSeverity.Warning);
|
||||
throw new InvalidOperationException($"An instance of process type {typeof(T).Name} is already running.");
|
||||
}
|
||||
|
||||
int id = ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.System) ? GetNextSystemProcessId() : GetNextProcessId();
|
||||
|
||||
InMemoryLogger processLogger = new();
|
||||
ProxyLogger proxyLogger = new([logger, processLogger]);
|
||||
|
||||
_ = processLoggers.TryAdd(id, processLogger);
|
||||
|
||||
T process = new()
|
||||
{
|
||||
Id = id,
|
||||
Logger = proxyLogger
|
||||
};
|
||||
|
||||
_ = processesByType.AddOrUpdate(typeof(T), _ => [id], (_, set) =>
|
||||
{
|
||||
set.Add(id);
|
||||
return set;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
process.Start(args ?? []);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Log($"Process {process.Name} (ID: {process.Id}) terminated with an exception: {ex}", LogSeverity.Error);
|
||||
}
|
||||
|
||||
_ = processes.TryAdd(id, (process, new ProcessMetrics(), new TaskCompletionSource()));
|
||||
logger.Log($"Spawned process {process.Name} of type {typeof(T).Name} with ID {id}.", LogSeverity.Info);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void StopProcess(int processId)
|
||||
{
|
||||
if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.Log($"Requesting stop of process {entry.Process.Name} (ID: {entry.Process.Id}).", LogSeverity.Info);
|
||||
entry.Process.RequestStop();
|
||||
}
|
||||
|
||||
public static async Task StopProcessAndWaitAsync(int processId)
|
||||
{
|
||||
if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logger.Log($"Requesting stop of process {entry.Process.Name} (ID: {entry.Process.Id}).", LogSeverity.Info);
|
||||
|
||||
entry.Process.RequestStop();
|
||||
|
||||
await entry.ExitSource.Task;
|
||||
}
|
||||
|
||||
public static void StopAllProcesses()
|
||||
{
|
||||
foreach ((Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry in processes.Values)
|
||||
{
|
||||
StopProcess(entry.Process.Id);
|
||||
}
|
||||
|
||||
processes.Clear();
|
||||
}
|
||||
|
||||
public static void WaitForProcessExit(int processId)
|
||||
{
|
||||
if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
entry.ExitSource.Task.Wait();
|
||||
}
|
||||
|
||||
public static async Task WaitForProcessExitAsync(int processId)
|
||||
{
|
||||
if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await entry.ExitSource.Task;
|
||||
}
|
||||
|
||||
public static Process? GetProcess(int processId)
|
||||
{
|
||||
if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry))
|
||||
{
|
||||
return entry.Process;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsProcessRunning<T>() where T : Process
|
||||
{
|
||||
return processesByType.TryGetValue(typeof(T), out ConcurrentHashSet<int>? set) && set.Count > 0;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> GetProcessesOfType<T>() where T : Process
|
||||
{
|
||||
if (processesByType.TryGetValue(typeof(T), out ConcurrentHashSet<int>? set))
|
||||
{
|
||||
foreach (int processId in set)
|
||||
{
|
||||
if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry) && entry.Process is T typedProcess)
|
||||
{
|
||||
yield return typedProcess;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Process> GetAllProcesses()
|
||||
{
|
||||
foreach ((Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry in processes.Values)
|
||||
{
|
||||
yield return entry.Process;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetProcess(int processId, out Process? process)
|
||||
{
|
||||
if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry))
|
||||
{
|
||||
process = entry.Process;
|
||||
return true;
|
||||
}
|
||||
|
||||
process = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<LogEntry> GetLogs(int? count = null)
|
||||
{
|
||||
return logger.GetLogs(count);
|
||||
}
|
||||
|
||||
public static IEnumerable<LogEntry> GetProcessLogs(int processId, int? count = null)
|
||||
{
|
||||
if (processLoggers.TryGetValue(processId, out InMemoryLogger? processLogger))
|
||||
{
|
||||
return processLogger.GetLogs(count);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
internal static void TickAllProcesses()
|
||||
{
|
||||
foreach ((Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry in processes.Values)
|
||||
{
|
||||
if (entry.Process.IsRunning)
|
||||
{
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
entry.Process.Tick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Log($"Process {entry.Process.Name} (ID: {entry.Process.Id}) threw an exception during Tick: {ex}", LogSeverity.Error);
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
int tickTimeMs = (int)stopwatch.ElapsedMilliseconds;
|
||||
entry.Metrics.LastTickTimeMs = tickTimeMs;
|
||||
entry.Metrics.AverageTickTimeMs = ((entry.Metrics.AverageTickTimeMs * 7) + tickTimeMs) / 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanupProcess(entry.Process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CleanupProcess(Process process)
|
||||
{
|
||||
process.Stop();
|
||||
|
||||
if (processes.TryRemove(process.Id, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) processEntry))
|
||||
{
|
||||
_ = processEntry.ExitSource.TrySetResult();
|
||||
}
|
||||
|
||||
if (processesByType.TryGetValue(process.GetType(), out ConcurrentHashSet<int>? set))
|
||||
{
|
||||
_ = set.TryRemove(process.Id);
|
||||
if (set.Count == 0)
|
||||
{
|
||||
_ = processesByType.TryRemove(process.GetType(), out _);
|
||||
}
|
||||
}
|
||||
|
||||
WindowManager.CloseWindowsForProcess(process.Id);
|
||||
|
||||
logger.Log($"Process {process.Name} (ID: {process.Id}) has stopped.", LogSeverity.Info);
|
||||
|
||||
_ = processLoggers.TryRemove(process.Id, out _);
|
||||
}
|
||||
|
||||
private static int GetNextProcessId()
|
||||
{
|
||||
return nextProcessId++;
|
||||
}
|
||||
|
||||
private static int GetNextSystemProcessId()
|
||||
{
|
||||
return nextSystemProcessId--;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using RemSox.Kernel.Processes;
|
||||
|
||||
namespace RemSox.Kernel.Processing;
|
||||
|
||||
public static class ProcessManifest
|
||||
{
|
||||
[Flags]
|
||||
public enum ProcessManifestFlags
|
||||
{
|
||||
None = 0,
|
||||
Singleton = 1 << 0,
|
||||
System = 1 << 1
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, ProcessManifestFlags> map = new()
|
||||
{
|
||||
{ typeof(DesktopProcess), ProcessManifestFlags.Singleton | ProcessManifestFlags.System },
|
||||
{ typeof(CliProcess), ProcessManifestFlags.Singleton | ProcessManifestFlags.System },
|
||||
{ typeof(RemoteDesktopProcess), ProcessManifestFlags.Singleton }
|
||||
};
|
||||
|
||||
public static bool HasFlag<T>(ProcessManifestFlags flag) where T : Process
|
||||
{
|
||||
return map.TryGetValue(typeof(T), out ProcessManifestFlags flags) && flags.HasFlag(flag);
|
||||
}
|
||||
|
||||
public static bool HasFlag(Type t, ProcessManifestFlags flag)
|
||||
{
|
||||
return map.TryGetValue(t, out ProcessManifestFlags flags) && flags.HasFlag(flag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace RemSox.Kernel.Processing;
|
||||
|
||||
public class ProcessMetrics
|
||||
{
|
||||
public int AverageTickTimeMs { get; set; }
|
||||
|
||||
public int LastTickTimeMs { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user