mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-04 00:56:19 +02:00
Add singleton/system manifest, process args, and async management support
This commit is contained in:
@@ -6,20 +6,12 @@ using RemSox.UI.GUI.Windows;
|
||||
|
||||
namespace RemSox.Processes;
|
||||
|
||||
public class DesktopProcess : Process
|
||||
internal class DesktopProcess() : Process("Desktop Manager")
|
||||
{
|
||||
private static bool isGraphicsInitialized = false;
|
||||
|
||||
public static bool IsRunning { get; private set; } = false;
|
||||
|
||||
public DesktopProcess() : base("Desktop Manager")
|
||||
internal override void Run(string[] args)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Run()
|
||||
{
|
||||
IsRunning = true;
|
||||
|
||||
if (!isGraphicsInitialized)
|
||||
{
|
||||
WindowManager.AddRenderSource(new CanvasRenderSource());
|
||||
@@ -70,10 +62,5 @@ public class DesktopProcess : Process
|
||||
// Sleep slightly to yield CPU to the main CLI thread (approx 60 FPS)
|
||||
//Thread.Sleep(16);
|
||||
}
|
||||
|
||||
IsRunning = false;
|
||||
|
||||
// Cosmos doesn't robustly support switching back to text mode yet.
|
||||
// We will stop updating, but the screen will remain in graphics mode.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class TerminalProcess : Process
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Run()
|
||||
internal override void Run(string[] args)
|
||||
{
|
||||
window = WindowManager.CreateWindow(this, "Terminal", new Point(50, 50), new Size(400, 300));
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ public abstract class Process(string name)
|
||||
|
||||
public bool StopRequested { get; private set; } = false;
|
||||
|
||||
internal abstract void Run();
|
||||
public bool IsRunning => !StopRequested;
|
||||
|
||||
internal abstract void Run(string[] args);
|
||||
|
||||
internal virtual void HandleInterProcessMessage(Message message)
|
||||
{
|
||||
|
||||
@@ -8,42 +8,60 @@ public static class ProcessManager
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, (Process Process, Thread Thread)> processes = new();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, ConcurrentHashSet<int>> processesByType = new();
|
||||
|
||||
private static int nextProcessId = 0;
|
||||
|
||||
public static int SpawnProcess<T>() where T : Process, new()
|
||||
private static int nextSystemProcessId = -1;
|
||||
|
||||
public static int SpawnProcess<T>(string[]? args = null) where T : Process, new()
|
||||
{
|
||||
int id = GetNextProcessId();
|
||||
if (ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.Singleton) && IsProcessRunning<T>())
|
||||
{
|
||||
throw new InvalidOperationException($"An instance of process type {typeof(T).Name} is already running.");
|
||||
}
|
||||
|
||||
int id = ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.System) ? GetNextSystemProcessId() : GetNextProcessId();
|
||||
|
||||
T process = new()
|
||||
{
|
||||
Id = id
|
||||
};
|
||||
|
||||
processesByType.AddOrUpdate(typeof(T), _ => [id], (_, set) =>
|
||||
{
|
||||
set.Add(id);
|
||||
return set;
|
||||
});
|
||||
|
||||
Thread thread = new(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Process {process.Name} (ID: {process.Id}) terminated with an exception: {ex}");
|
||||
process.Run(args ?? []);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ = processes.TryRemove(id, out _);
|
||||
processes.TryRemove(id, out _);
|
||||
|
||||
if (processesByType.TryGetValue(typeof(T), out var set))
|
||||
{
|
||||
set.TryRemove(id);
|
||||
if (set.Count == 0)
|
||||
processesByType.TryRemove(typeof(T), out _);
|
||||
}
|
||||
|
||||
WindowManager.CloseWindowsForProcess(id);
|
||||
}
|
||||
});
|
||||
|
||||
_ = processes.TryAdd(id, (process, thread));
|
||||
|
||||
processes.TryAdd(id, (process, thread));
|
||||
thread.Start();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void StopProcess(int processId)
|
||||
public static void StopProcess(int processId, bool waitForExit = false)
|
||||
{
|
||||
if (!processes.TryGetValue(processId, out (Process Process, Thread Thread) entry))
|
||||
{
|
||||
@@ -51,10 +69,17 @@ public static class ProcessManager
|
||||
}
|
||||
|
||||
entry.Process.RequestStop();
|
||||
}
|
||||
|
||||
WindowManager.CloseWindowsForProcess(processId);
|
||||
public static async Task StopProcessAndWaitAsync(int processId)
|
||||
{
|
||||
if (!processes.TryGetValue(processId, out (Process Process, Thread Thread) entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = processes.TryRemove(processId, out _);
|
||||
entry.Process.RequestStop();
|
||||
await Task.Run(() => entry.Thread.Join());
|
||||
}
|
||||
|
||||
public static void StopAllProcesses()
|
||||
@@ -77,6 +102,25 @@ public static class ProcessManager
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsProcessRunning<T>() where T : Process
|
||||
{
|
||||
return processesByType.TryGetValue(typeof(T), out var set) && set.Count > 0;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> GetProcessesOfType<T>() where T : Process
|
||||
{
|
||||
if (processesByType.TryGetValue(typeof(T), out var set))
|
||||
{
|
||||
foreach (int processId in set)
|
||||
{
|
||||
if (processes.TryGetValue(processId, out (Process Process, Thread Thread) entry) && entry.Process is T typedProcess)
|
||||
{
|
||||
yield return typedProcess;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Process> GetAllProcesses()
|
||||
{
|
||||
foreach ((Process Process, Thread Thread) entry in processes.Values)
|
||||
@@ -101,4 +145,9 @@ public static class ProcessManager
|
||||
{
|
||||
return nextProcessId++;
|
||||
}
|
||||
|
||||
private static int GetNextSystemProcessId()
|
||||
{
|
||||
return nextSystemProcessId--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using RemSox.Processes;
|
||||
using RemSox.Processing;
|
||||
|
||||
namespace RemSox.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 }
|
||||
};
|
||||
|
||||
public static bool HasFlag<T>(ProcessManifestFlags flag) where T : Process
|
||||
{
|
||||
return map.TryGetValue(typeof(T), out var flags) && flags.HasFlag(flag);
|
||||
}
|
||||
|
||||
public static bool HasFlag(Type t, ProcessManifestFlags flag)
|
||||
{
|
||||
return map.TryGetValue(t, out var flags) && flags.HasFlag(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a thread-safe hash set, backed by a <see cref="ConcurrentDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the set.</typeparam>
|
||||
public partial class ConcurrentHashSet<T> :
|
||||
ICollection<T>,
|
||||
IReadOnlyCollection<T>,
|
||||
ICollection
|
||||
where T : notnull
|
||||
{
|
||||
// The dummy value stored for every key — we only care about keys.
|
||||
private static readonly byte DummyValue = 0;
|
||||
|
||||
private readonly ConcurrentDictionary<T, byte> _dictionary;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructors
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Initializes a new, empty instance using the default comparer.</summary>
|
||||
public ConcurrentHashSet()
|
||||
{
|
||||
_dictionary = new ConcurrentDictionary<T, byte>();
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance that contains elements copied from the specified collection.</summary>
|
||||
public ConcurrentHashSet(IEnumerable<T> collection)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
_dictionary = new ConcurrentDictionary<T, byte>(
|
||||
collection.Select(item => new KeyValuePair<T, byte>(item, DummyValue)));
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance that contains elements copied from the specified collection
|
||||
/// and uses the specified equality comparer.</summary>
|
||||
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T>? comparer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
_dictionary = new ConcurrentDictionary<T, byte>(
|
||||
collection.Select(item => new KeyValuePair<T, byte>(item, DummyValue)),
|
||||
comparer);
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new, empty instance using the specified equality comparer.</summary>
|
||||
public ConcurrentHashSet(IEqualityComparer<T>? comparer)
|
||||
{
|
||||
_dictionary = new ConcurrentDictionary<T, byte>(comparer);
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance with the specified concurrency level, initial collection,
|
||||
/// and equality comparer.</summary>
|
||||
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T>? comparer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
_dictionary = new ConcurrentDictionary<T, byte>(
|
||||
concurrencyLevel,
|
||||
collection.Select(item => new KeyValuePair<T, byte>(item, DummyValue)),
|
||||
comparer);
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new, empty instance with the specified concurrency level and initial capacity.</summary>
|
||||
public ConcurrentHashSet(int concurrencyLevel, int capacity)
|
||||
{
|
||||
_dictionary = new ConcurrentDictionary<T, byte>(concurrencyLevel, capacity);
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new, empty instance with the specified concurrency level, initial capacity,
|
||||
/// and equality comparer.</summary>
|
||||
public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer<T>? comparer)
|
||||
{
|
||||
_dictionary = new ConcurrentDictionary<T, byte>(concurrencyLevel, capacity, comparer);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Gets the number of elements contained in the set.</summary>
|
||||
public int Count => _dictionary.Count;
|
||||
|
||||
/// <summary>Gets a value indicating whether the set is empty.</summary>
|
||||
public bool IsEmpty => _dictionary.IsEmpty;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Removes all elements from the set.</summary>
|
||||
public void Clear() => _dictionary.Clear();
|
||||
|
||||
/// <summary>Determines whether the set contains the specified element.</summary>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
if (item is null) throw new ArgumentNullException(nameof(item));
|
||||
return _dictionary.ContainsKey(item);
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through the elements of the set.</summary>
|
||||
public IEnumerator<T> GetEnumerator() => _dictionary.Keys.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the element from the set if it already exists, or adds and returns
|
||||
/// the specified item if it does not.
|
||||
/// </summary>
|
||||
/// <param name="item">The element to get or add.</param>
|
||||
/// <returns>The existing element if found; otherwise <paramref name="item"/> after it was added.</returns>
|
||||
public T GetOrAdd(T item)
|
||||
{
|
||||
if (item is null) throw new ArgumentNullException(nameof(item));
|
||||
|
||||
// TryAdd is atomic; if it fails the item was already present.
|
||||
_dictionary.TryAdd(item, DummyValue);
|
||||
|
||||
// Because ConcurrentDictionary keys are de-duplicated by the comparer,
|
||||
// we need to retrieve the canonical key that is actually stored.
|
||||
// Keys returns a snapshot; iterate to find the stored instance.
|
||||
foreach (T key in _dictionary.Keys)
|
||||
{
|
||||
if (_dictionary.Comparer.Equals(key, item))
|
||||
return key;
|
||||
}
|
||||
|
||||
// Fallback — should not happen in practice.
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified element to the set. Duplicate elements are silently ignored.
|
||||
/// This overload exists to support collection initializer syntax (<c>new ConcurrentHashSet<T> { item }</c>).
|
||||
/// </summary>
|
||||
public void Add(T item) => TryAdd(item);
|
||||
|
||||
/// <summary>Attempts to add the specified element to the set.</summary>
|
||||
/// <returns><see langword="true"/> if the element was added; <see langword="false"/> if it was already present.</returns>
|
||||
public bool TryAdd(T item)
|
||||
{
|
||||
if (item is null) throw new ArgumentNullException(nameof(item));
|
||||
return _dictionary.TryAdd(item, DummyValue);
|
||||
}
|
||||
|
||||
/// <summary>Attempts to remove the specified element from the set.</summary>
|
||||
/// <returns><see langword="true"/> if the element was removed; <see langword="false"/> if it was not found.</returns>
|
||||
public bool TryRemove(T item)
|
||||
{
|
||||
if (item is null) throw new ArgumentNullException(nameof(item));
|
||||
return _dictionary.TryRemove(item, out _);
|
||||
}
|
||||
|
||||
/// <summary>Copies the elements of the set to a new array.</summary>
|
||||
public T[] ToArray() => [.. _dictionary.Keys];
|
||||
|
||||
/// <summary>Returns a (non-thread-safe) <see cref="HashSet{T}"/> snapshot of the current elements.</summary>
|
||||
public HashSet<T> ToHashSet() => new(_dictionary.Keys, _dictionary.Comparer);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ICollection<T> explicit implementation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
bool ICollection<T>.IsReadOnly => false;
|
||||
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
|
||||
bool ICollection<T>.Contains(T item) => Contains(item);
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int index)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
// Take a snapshot to avoid races during copy.
|
||||
T[] snapshot = ToArray();
|
||||
Array.Copy(snapshot, 0, array, index, snapshot.Length);
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item) => TryRemove(item);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ICollection (non-generic) explicit implementation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <remarks>
|
||||
/// <see cref="ConcurrentDictionary{TKey,TValue}"/> does not expose a meaningful
|
||||
/// sync root; following the same convention we return <see langword="false"/> for
|
||||
/// <see cref="ICollection.IsSynchronized"/> and <see langword="this"/> for
|
||||
/// <see cref="ICollection.SyncRoot"/>, mirroring the BCL approach.
|
||||
/// </remarks>
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
if (array is T[] typedArray)
|
||||
{
|
||||
((ICollection<T>)this).CopyTo(typedArray, index);
|
||||
return;
|
||||
}
|
||||
|
||||
// Slower path for object arrays (e.g. object[]).
|
||||
T[] snapshot = ToArray();
|
||||
Array.Copy(snapshot, 0, array, index, snapshot.Length);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// IEnumerable explicit implementation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
Reference in New Issue
Block a user