From f7eea21de53e3b34b9a2e288759b5c2213512061 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 9 Jun 2026 22:53:13 +0200 Subject: [PATCH] Add singleton/system manifest, process args, and async management support --- Processes/DesktopProcess.cs | 17 +-- Processes/TerminalProcess.cs | 2 +- Processing/Process.cs | 4 +- Processing/ProcessManager.cs | 75 +++++++++--- Processing/ProcessManiest.cs | 36 ++++++ Utils/ConcurrentHashSet.cs | 214 +++++++++++++++++++++++++++++++++++ 6 files changed, 318 insertions(+), 30 deletions(-) create mode 100644 Processing/ProcessManiest.cs create mode 100644 Utils/ConcurrentHashSet.cs diff --git a/Processes/DesktopProcess.cs b/Processes/DesktopProcess.cs index ab91c87..25fe661 100644 --- a/Processes/DesktopProcess.cs +++ b/Processes/DesktopProcess.cs @@ -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. } } diff --git a/Processes/TerminalProcess.cs b/Processes/TerminalProcess.cs index 8fc8de8..381c253 100644 --- a/Processes/TerminalProcess.cs +++ b/Processes/TerminalProcess.cs @@ -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)); diff --git a/Processing/Process.cs b/Processing/Process.cs index c261182..bf84c9a 100644 --- a/Processing/Process.cs +++ b/Processing/Process.cs @@ -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) { diff --git a/Processing/ProcessManager.cs b/Processing/ProcessManager.cs index 6779d32..c9f65f7 100644 --- a/Processing/ProcessManager.cs +++ b/Processing/ProcessManager.cs @@ -8,42 +8,60 @@ public static class ProcessManager { private static readonly ConcurrentDictionary processes = new(); + private static readonly ConcurrentDictionary> processesByType = new(); + private static int nextProcessId = 0; - public static int SpawnProcess() where T : Process, new() + private static int nextSystemProcessId = -1; + + public static int SpawnProcess(string[]? args = null) where T : Process, new() { - int id = GetNextProcessId(); + if (ProcessManifest.HasFlag(ProcessManifest.ProcessManifestFlags.Singleton) && IsProcessRunning()) + { + throw new InvalidOperationException($"An instance of process type {typeof(T).Name} is already running."); + } + + int id = ProcessManifest.HasFlag(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() where T : Process + { + return processesByType.TryGetValue(typeof(T), out var set) && set.Count > 0; + } + + public static IEnumerable GetProcessesOfType() 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 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--; + } } diff --git a/Processing/ProcessManiest.cs b/Processing/ProcessManiest.cs new file mode 100644 index 0000000..6f9c588 --- /dev/null +++ b/Processing/ProcessManiest.cs @@ -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 map = new() + { + { typeof(DesktopProcess), ProcessManifestFlags.Singleton | ProcessManifestFlags.System }, + { typeof(CliProcess), ProcessManifestFlags.Singleton | ProcessManifestFlags.System } + }; + + public static bool HasFlag(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); + } + } +} \ No newline at end of file diff --git a/Utils/ConcurrentHashSet.cs b/Utils/ConcurrentHashSet.cs new file mode 100644 index 0000000..680b1fa --- /dev/null +++ b/Utils/ConcurrentHashSet.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +/// +/// Represents a thread-safe hash set, backed by a . +/// +/// The type of elements in the set. +public partial class ConcurrentHashSet : + ICollection, + IReadOnlyCollection, + 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 _dictionary; + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /// Initializes a new, empty instance using the default comparer. + public ConcurrentHashSet() + { + _dictionary = new ConcurrentDictionary(); + } + + /// Initializes a new instance that contains elements copied from the specified collection. + public ConcurrentHashSet(IEnumerable collection) + { + ArgumentNullException.ThrowIfNull(collection); + _dictionary = new ConcurrentDictionary( + collection.Select(item => new KeyValuePair(item, DummyValue))); + } + + /// Initializes a new instance that contains elements copied from the specified collection + /// and uses the specified equality comparer. + public ConcurrentHashSet(IEnumerable collection, IEqualityComparer? comparer) + { + ArgumentNullException.ThrowIfNull(collection); + _dictionary = new ConcurrentDictionary( + collection.Select(item => new KeyValuePair(item, DummyValue)), + comparer); + } + + /// Initializes a new, empty instance using the specified equality comparer. + public ConcurrentHashSet(IEqualityComparer? comparer) + { + _dictionary = new ConcurrentDictionary(comparer); + } + + /// Initializes a new instance with the specified concurrency level, initial collection, + /// and equality comparer. + public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer? comparer) + { + ArgumentNullException.ThrowIfNull(collection); + _dictionary = new ConcurrentDictionary( + concurrencyLevel, + collection.Select(item => new KeyValuePair(item, DummyValue)), + comparer); + } + + /// Initializes a new, empty instance with the specified concurrency level and initial capacity. + public ConcurrentHashSet(int concurrencyLevel, int capacity) + { + _dictionary = new ConcurrentDictionary(concurrencyLevel, capacity); + } + + /// Initializes a new, empty instance with the specified concurrency level, initial capacity, + /// and equality comparer. + public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer? comparer) + { + _dictionary = new ConcurrentDictionary(concurrencyLevel, capacity, comparer); + } + + // ------------------------------------------------------------------------- + // Public properties + // ------------------------------------------------------------------------- + + /// Gets the number of elements contained in the set. + public int Count => _dictionary.Count; + + /// Gets a value indicating whether the set is empty. + public bool IsEmpty => _dictionary.IsEmpty; + + // ------------------------------------------------------------------------- + // Public methods + // ------------------------------------------------------------------------- + + /// Removes all elements from the set. + public void Clear() => _dictionary.Clear(); + + /// Determines whether the set contains the specified element. + public bool Contains(T item) + { + if (item is null) throw new ArgumentNullException(nameof(item)); + return _dictionary.ContainsKey(item); + } + + /// Returns an enumerator that iterates through the elements of the set. + public IEnumerator GetEnumerator() => _dictionary.Keys.GetEnumerator(); + + /// + /// Returns the element from the set if it already exists, or adds and returns + /// the specified item if it does not. + /// + /// The element to get or add. + /// The existing element if found; otherwise after it was added. + 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; + } + + /// + /// Adds the specified element to the set. Duplicate elements are silently ignored. + /// This overload exists to support collection initializer syntax (new ConcurrentHashSet<T> { item }). + /// + public void Add(T item) => TryAdd(item); + + /// Attempts to add the specified element to the set. + /// if the element was added; if it was already present. + public bool TryAdd(T item) + { + if (item is null) throw new ArgumentNullException(nameof(item)); + return _dictionary.TryAdd(item, DummyValue); + } + + /// Attempts to remove the specified element from the set. + /// if the element was removed; if it was not found. + public bool TryRemove(T item) + { + if (item is null) throw new ArgumentNullException(nameof(item)); + return _dictionary.TryRemove(item, out _); + } + + /// Copies the elements of the set to a new array. + public T[] ToArray() => [.. _dictionary.Keys]; + + /// Returns a (non-thread-safe) snapshot of the current elements. + public HashSet ToHashSet() => new(_dictionary.Keys, _dictionary.Comparer); + + // ------------------------------------------------------------------------- + // ICollection explicit implementation + // ------------------------------------------------------------------------- + + bool ICollection.IsReadOnly => false; + + void ICollection.Add(T item) => Add(item); + + bool ICollection.Contains(T item) => Contains(item); + + void ICollection.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.Remove(T item) => TryRemove(item); + + // ------------------------------------------------------------------------- + // ICollection (non-generic) explicit implementation + // ------------------------------------------------------------------------- + + /// + /// does not expose a meaningful + /// sync root; following the same convention we return for + /// and for + /// , mirroring the BCL approach. + /// + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => this; + + void ICollection.CopyTo(Array array, int index) + { + ArgumentNullException.ThrowIfNull(array); + if (array is T[] typedArray) + { + ((ICollection)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(); +} \ No newline at end of file