mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-09 07:46:32 +02:00
Add thread-safety with locks to window manager and rendering code
This commit is contained in:
@@ -16,6 +16,7 @@ public class TerminalProcess : Process
|
|||||||
private readonly List<string> history = new();
|
private readonly List<string> history = new();
|
||||||
private string currentInput = "";
|
private string currentInput = "";
|
||||||
private readonly List<Text> textLines = new();
|
private readonly List<Text> textLines = new();
|
||||||
|
private readonly object textLinesLock = new object();
|
||||||
private const int LineHeight = 30;
|
private const int LineHeight = 30;
|
||||||
private Size lastSize = new Size(-1, -1);
|
private Size lastSize = new Size(-1, -1);
|
||||||
|
|
||||||
@@ -26,11 +27,11 @@ public class TerminalProcess : Process
|
|||||||
internal override void Run()
|
internal override void Run()
|
||||||
{
|
{
|
||||||
window = WindowManager.CreateWindow(this, "Terminal", new Point(50, 50), new Size(400, 300));
|
window = WindowManager.CreateWindow(this, "Terminal", new Point(50, 50), new Size(400, 300));
|
||||||
window.AutoFlush = true;
|
|
||||||
|
|
||||||
PrintLine("RemSox GUI Terminal v1.0");
|
PrintLine("RemSox GUI Terminal v1.0");
|
||||||
PrintLine("Type 'help' for commands.");
|
PrintLine("Type 'help' for commands.");
|
||||||
|
|
||||||
|
window.Flush();
|
||||||
window.OnKeyEvent += HandleKey;
|
window.OnKeyEvent += HandleKey;
|
||||||
|
|
||||||
while (!StopRequested)
|
while (!StopRequested)
|
||||||
@@ -40,7 +41,7 @@ public class TerminalProcess : Process
|
|||||||
lastSize = window.Size;
|
lastSize = window.Size;
|
||||||
UpdateDisplay();
|
UpdateDisplay();
|
||||||
}
|
}
|
||||||
System.Threading.Thread.Sleep(50);
|
Thread.Sleep(50);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.OnKeyEvent -= HandleKey;
|
window.OnKeyEvent -= HandleKey;
|
||||||
@@ -106,42 +107,47 @@ public class TerminalProcess : Process
|
|||||||
|
|
||||||
if (maxLines < 1) maxLines = 1;
|
if (maxLines < 1) maxLines = 1;
|
||||||
|
|
||||||
// Ensure we have enough Text elements for maxLines + 1 (input line)
|
lock (textLinesLock)
|
||||||
while (textLines.Count <= maxLines)
|
|
||||||
{
|
{
|
||||||
var textElement = window.CreateUIElement<Text>(t =>
|
// Ensure we have enough Text elements for maxLines + 1 (input line)
|
||||||
|
while (textLines.Count <= maxLines)
|
||||||
{
|
{
|
||||||
t.Color = Color.LightGreen;
|
var textElement = window.CreateUIElement<Text>(t =>
|
||||||
t.Content = "";
|
{
|
||||||
});
|
t.Color = Color.LightGreen;
|
||||||
textLines.Add(textElement);
|
t.Content = "";
|
||||||
}
|
});
|
||||||
|
textLines.Add(textElement);
|
||||||
// Calculate starting Y to align everything flush to the bottom margin
|
|
||||||
int startY = window.Size.Height - ((maxLines + 1) * LineHeight) - 5;
|
|
||||||
if (startY < 20) startY = 20;
|
|
||||||
|
|
||||||
for (int i = 0; i < maxLines; i++)
|
|
||||||
{
|
|
||||||
int historyIndex = history.Count - maxLines + i;
|
|
||||||
string content = "";
|
|
||||||
if (historyIndex >= 0 && historyIndex < history.Count)
|
|
||||||
{
|
|
||||||
content = history[historyIndex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textLines[i].Content = content;
|
// Calculate starting Y to align everything flush to the bottom margin
|
||||||
textLines[i].Position = new Point(5, startY + (i * LineHeight));
|
int startY = window.Size.Height - ((maxLines + 1) * LineHeight) - 5;
|
||||||
}
|
if (startY < 20) startY = 20;
|
||||||
|
|
||||||
// The last line is the input line
|
for (int i = 0; i < maxLines; i++)
|
||||||
textLines[maxLines].Content = "> " + currentInput + "_";
|
{
|
||||||
textLines[maxLines].Position = new Point(5, startY + (maxLines * LineHeight));
|
int historyIndex = history.Count - maxLines + i;
|
||||||
|
string content = "";
|
||||||
|
if (historyIndex >= 0 && historyIndex < history.Count)
|
||||||
|
{
|
||||||
|
content = history[historyIndex];
|
||||||
|
}
|
||||||
|
|
||||||
// Hide any extra text lines we don't need
|
textLines[i].Content = content;
|
||||||
for (int i = maxLines + 1; i < textLines.Count; i++)
|
textLines[i].Position = new Point(5, startY + (i * LineHeight));
|
||||||
{
|
}
|
||||||
textLines[i].Content = "";
|
|
||||||
|
// The last line is the input line
|
||||||
|
textLines[maxLines].Content = "> " + currentInput + "_";
|
||||||
|
textLines[maxLines].Position = new Point(5, startY + (maxLines * LineHeight));
|
||||||
|
|
||||||
|
// Hide any extra text lines we don't need
|
||||||
|
for (int i = maxLines + 1; i < textLines.Count; i++)
|
||||||
|
{
|
||||||
|
textLines[i].Content = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,130 +17,137 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
private static Point lastPointerPosition = new Point(-1, -1);
|
private static Point lastPointerPosition = new Point(-1, -1);
|
||||||
private static List<int> orderedWindowsCache = new();
|
private static List<int> orderedWindowsCache = new();
|
||||||
private static bool isZOrderDirty = true;
|
private static bool isZOrderDirty = true;
|
||||||
|
private static readonly object renderLock = new object();
|
||||||
|
|
||||||
public void Render(IEnumerable<RenderCommand> commands)
|
public void Render(IEnumerable<RenderCommand> commands)
|
||||||
{
|
{
|
||||||
bool changed = false;
|
lock (renderLock)
|
||||||
foreach (RenderCommand command in commands)
|
|
||||||
{
|
{
|
||||||
changed = true;
|
bool changed = false;
|
||||||
if (command.ElementType == "WindowClose")
|
foreach (RenderCommand command in commands)
|
||||||
{
|
{
|
||||||
windowCanvases.Remove(command.WindowId);
|
changed = true;
|
||||||
windowPositions.Remove(command.WindowId);
|
if (command.ElementType == "WindowClose")
|
||||||
windowZIndices.Remove(command.WindowId);
|
|
||||||
isZOrderDirty = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "Window" || command.ElementType == "WindowMove")
|
|
||||||
{
|
|
||||||
if (command.Properties.TryGetValue("ZIndex", out object? rawZIndex) && rawZIndex is int z)
|
|
||||||
{
|
{
|
||||||
windowZIndices[command.WindowId] = z;
|
windowCanvases.Remove(command.WindowId);
|
||||||
|
windowPositions.Remove(command.WindowId);
|
||||||
|
windowZIndices.Remove(command.WindowId);
|
||||||
isZOrderDirty = true;
|
isZOrderDirty = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "Window")
|
if (command.ElementType == "Window" || command.ElementType == "WindowMove")
|
||||||
{
|
|
||||||
Size size = command.Properties.TryGetValue("Size", out object? rawSize) && rawSize is Size windowSize
|
|
||||||
? windowSize
|
|
||||||
: new Size(160, 120);
|
|
||||||
|
|
||||||
if (!windowCanvases.TryGetValue(command.WindowId, out Canvas? currentCanvas) ||
|
|
||||||
currentCanvas.Mode.Width != size.Width ||
|
|
||||||
currentCanvas.Mode.Height != size.Height)
|
|
||||||
{
|
{
|
||||||
windowCanvases[command.WindowId] = new Canvas(size.Width, size.Height);
|
if (command.Properties.TryGetValue("ZIndex", out object? rawZIndex) && rawZIndex is int z)
|
||||||
|
{
|
||||||
|
windowZIndices[command.WindowId] = z;
|
||||||
|
isZOrderDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Window")
|
||||||
|
{
|
||||||
|
Size size = command.Properties.TryGetValue("Size", out object? rawSize) && rawSize is Size windowSize
|
||||||
|
? windowSize
|
||||||
|
: new Size(160, 120);
|
||||||
|
|
||||||
|
if (!windowCanvases.TryGetValue(command.WindowId, out Canvas? currentCanvas) ||
|
||||||
|
currentCanvas.Mode.Width != size.Width ||
|
||||||
|
currentCanvas.Mode.Height != size.Height)
|
||||||
|
{
|
||||||
|
windowCanvases[command.WindowId] = new Canvas(size.Width, size.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!windowCanvases.ContainsKey(command.WindowId))
|
||||||
|
{
|
||||||
|
windowCanvases[command.WindowId] = new Canvas(160, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas windowCanvas = windowCanvases[command.WindowId];
|
||||||
|
|
||||||
|
if (command.ElementType == "Window")
|
||||||
|
{
|
||||||
|
RenderWindow(windowCanvas, command);
|
||||||
|
windowPositions[command.WindowId] = command.Position;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "WindowMove")
|
||||||
|
{
|
||||||
|
windowPositions[command.WindowId] = command.Position;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Circle")
|
||||||
|
{
|
||||||
|
RenderCircle(windowCanvas, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Rectangle")
|
||||||
|
{
|
||||||
|
RenderRectangle(windowCanvas, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Text")
|
||||||
|
{
|
||||||
|
RenderText(windowCanvas, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Line")
|
||||||
|
{
|
||||||
|
RenderLine(windowCanvas, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Button")
|
||||||
|
{
|
||||||
|
RenderButton(windowCanvas, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "CheckBox")
|
||||||
|
{
|
||||||
|
RenderCheckBox(windowCanvas, command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!windowCanvases.ContainsKey(command.WindowId))
|
if (changed)
|
||||||
{
|
{
|
||||||
windowCanvases[command.WindowId] = new Canvas(160, 120);
|
isDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas windowCanvas = windowCanvases[command.WindowId];
|
|
||||||
|
|
||||||
if (command.ElementType == "Window")
|
|
||||||
{
|
|
||||||
RenderWindow(windowCanvas, command);
|
|
||||||
windowPositions[command.WindowId] = command.Position;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "WindowMove")
|
|
||||||
{
|
|
||||||
windowPositions[command.WindowId] = command.Position;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "Circle")
|
|
||||||
{
|
|
||||||
RenderCircle(windowCanvas, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "Rectangle")
|
|
||||||
{
|
|
||||||
RenderRectangle(windowCanvas, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "Text")
|
|
||||||
{
|
|
||||||
RenderText(windowCanvas, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "Line")
|
|
||||||
{
|
|
||||||
RenderLine(windowCanvas, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "Button")
|
|
||||||
{
|
|
||||||
RenderButton(windowCanvas, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.ElementType == "CheckBox")
|
|
||||||
{
|
|
||||||
RenderCheckBox(windowCanvas, command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed)
|
|
||||||
{
|
|
||||||
isDirty = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CompositeAndDisplay(Canvas screenCanvas, Point pointerPosition)
|
public static void CompositeAndDisplay(Canvas screenCanvas, Point pointerPosition)
|
||||||
{
|
{
|
||||||
if (!isDirty && pointerPosition == lastPointerPosition)
|
lock (renderLock)
|
||||||
{
|
{
|
||||||
return;
|
if (!isDirty && pointerPosition == lastPointerPosition)
|
||||||
}
|
|
||||||
|
|
||||||
if (isZOrderDirty)
|
|
||||||
{
|
|
||||||
orderedWindowsCache = windowPositions.Keys.OrderBy(id => windowZIndices.TryGetValue(id, out int z) ? z : 0).ToList();
|
|
||||||
isZOrderDirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
screenCanvas.Clear(Color.Black);
|
|
||||||
|
|
||||||
foreach (var windowId in orderedWindowsCache)
|
|
||||||
{
|
|
||||||
if (windowPositions.TryGetValue(windowId, out Point position) && windowCanvases.TryGetValue(windowId, out Canvas? windowCanvas))
|
|
||||||
{
|
{
|
||||||
screenCanvas.DrawCanvas(windowCanvas, position.X, position.Y);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isZOrderDirty)
|
||||||
|
{
|
||||||
|
orderedWindowsCache = windowPositions.Keys.OrderBy(id => windowZIndices.TryGetValue(id, out int z) ? z : 0).ToList();
|
||||||
|
isZOrderDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
screenCanvas.Clear(Color.Black);
|
||||||
|
|
||||||
|
foreach (var windowId in orderedWindowsCache)
|
||||||
|
{
|
||||||
|
if (windowPositions.TryGetValue(windowId, out Point position) && windowCanvases.TryGetValue(windowId, out Canvas? windowCanvas))
|
||||||
|
{
|
||||||
|
screenCanvas.DrawCanvas(windowCanvas, position.X, position.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenCanvas.DrawFilledCircle(Color.White, pointerPosition.X, pointerPosition.Y, 5);
|
||||||
|
screenCanvas.Display();
|
||||||
|
|
||||||
|
lastPointerPosition = pointerPosition;
|
||||||
|
isDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
screenCanvas.DrawFilledCircle(Color.White, pointerPosition.X, pointerPosition.Y, 5);
|
|
||||||
screenCanvas.Display();
|
|
||||||
|
|
||||||
lastPointerPosition = pointerPosition;
|
|
||||||
isDirty = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
|
|
||||||
public bool IsDragging => currentInteraction == InteractionMode.Drag;
|
public bool IsDragging => currentInteraction == InteractionMode.Drag;
|
||||||
|
|
||||||
|
private readonly object uiElementsLock = new object();
|
||||||
private readonly Dictionary<int, UIElement> uiElements = [];
|
private readonly Dictionary<int, UIElement> uiElements = [];
|
||||||
|
|
||||||
private int nextUIElementId = 1;
|
private int nextUIElementId = 1;
|
||||||
@@ -87,7 +88,10 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
Flush();
|
Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
uiElements.Add(uiElementId, uiElement);
|
lock (uiElementsLock)
|
||||||
|
{
|
||||||
|
uiElements.Add(uiElementId, uiElement);
|
||||||
|
}
|
||||||
|
|
||||||
return uiElement;
|
return uiElement;
|
||||||
}
|
}
|
||||||
@@ -105,8 +109,16 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool anyChildChanged;
|
||||||
|
List<UIElement> elementsCopy;
|
||||||
|
|
||||||
|
lock (uiElementsLock)
|
||||||
|
{
|
||||||
|
anyChildChanged = uiElements.Values.Any(e => e.AnyPropertyChanged);
|
||||||
|
elementsCopy = uiElements.Values.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
bool windowStateChanged = isFirstRender || Size != lastRenderedSize || IsFocused != lastRenderedIsFocused || Title != lastRenderedTitle;
|
bool windowStateChanged = isFirstRender || Size != lastRenderedSize || IsFocused != lastRenderedIsFocused || Title != lastRenderedTitle;
|
||||||
bool anyChildChanged = uiElements.Values.Any(e => e.AnyPropertyChanged);
|
|
||||||
bool positionChanged = Position != lastRenderedPosition;
|
bool positionChanged = Position != lastRenderedPosition;
|
||||||
bool zIndexChanged = ZIndex != lastRenderedZIndex;
|
bool zIndexChanged = ZIndex != lastRenderedZIndex;
|
||||||
|
|
||||||
@@ -133,7 +145,7 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (UIElement element in uiElements.Values)
|
foreach (UIElement element in elementsCopy)
|
||||||
{
|
{
|
||||||
commands.Add(new RenderCommand
|
commands.Add(new RenderCommand
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ namespace RemSox.UI.GUI.Windows;
|
|||||||
|
|
||||||
public static class WindowManager
|
public static class WindowManager
|
||||||
{
|
{
|
||||||
|
private static readonly object windowsLock = new object();
|
||||||
// Process ID to list of windows
|
// Process ID to list of windows
|
||||||
private static readonly ConcurrentDictionary<int, List<Window>> windows = new();
|
private static readonly Dictionary<int, List<Window>> windows = new();
|
||||||
|
|
||||||
private static int nextWindowId = 1;
|
private static int nextWindowId = 1;
|
||||||
private static int nextZIndex = 1;
|
private static int nextZIndex = 1;
|
||||||
@@ -76,21 +77,27 @@ public static class WindowManager
|
|||||||
ZIndex = nextZIndex++
|
ZIndex = nextZIndex++
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!windows.ContainsKey(process.Id))
|
lock (windowsLock)
|
||||||
{
|
{
|
||||||
windows[process.Id] = [];
|
if (!windows.ContainsKey(process.Id))
|
||||||
}
|
{
|
||||||
|
windows[process.Id] = [];
|
||||||
|
}
|
||||||
|
|
||||||
windows[process.Id].Add(window);
|
windows[process.Id].Add(window);
|
||||||
|
}
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CloseWindow(Window window)
|
public static void CloseWindow(Window window)
|
||||||
{
|
{
|
||||||
if (windows.TryGetValue(window.ProcessId, out var processWindows))
|
lock (windowsLock)
|
||||||
{
|
{
|
||||||
processWindows.Remove(window);
|
if (windows.TryGetValue(window.ProcessId, out var processWindows))
|
||||||
|
{
|
||||||
|
processWindows.Remove(window);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSource.Render(new[] { new RenderCommand { WindowId = window.Id, ElementId = window.Id, ElementType = "WindowClose", Position = window.Position, Properties = new Dictionary<string, object?>() } });
|
renderSource.Render(new[] { new RenderCommand { WindowId = window.Id, ElementId = window.Id, ElementType = "WindowClose", Position = window.Position, Properties = new Dictionary<string, object?>() } });
|
||||||
@@ -98,17 +105,23 @@ public static class WindowManager
|
|||||||
|
|
||||||
public static List<Window> GetWindowsForProcess(Process process)
|
public static List<Window> GetWindowsForProcess(Process process)
|
||||||
{
|
{
|
||||||
if (windows.TryGetValue(process.Id, out var processWindows))
|
lock (windowsLock)
|
||||||
{
|
{
|
||||||
return processWindows;
|
if (windows.TryGetValue(process.Id, out var processWindows))
|
||||||
}
|
{
|
||||||
|
return processWindows.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CloseWindowsForProcess(Process process)
|
public static void CloseWindowsForProcess(Process process)
|
||||||
{
|
{
|
||||||
windows.TryRemove(process.Id, out _);
|
lock (windowsLock)
|
||||||
|
{
|
||||||
|
windows.Remove(process.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FocusWindow(Window? window)
|
public static void FocusWindow(Window? window)
|
||||||
@@ -137,7 +150,12 @@ public static class WindowManager
|
|||||||
|
|
||||||
public static Window? TryBeginInteract(Point pointerPosition)
|
public static Window? TryBeginInteract(Point pointerPosition)
|
||||||
{
|
{
|
||||||
var allWindows = windows.Values.SelectMany(w => w).OrderByDescending(w => w.ZIndex);
|
List<Window> allWindows;
|
||||||
|
lock (windowsLock)
|
||||||
|
{
|
||||||
|
allWindows = windows.Values.SelectMany(w => w).OrderByDescending(w => w.ZIndex).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var window in allWindows)
|
foreach (var window in allWindows)
|
||||||
{
|
{
|
||||||
if (window.TryBeginInteract(pointerPosition))
|
if (window.TryBeginInteract(pointerPosition))
|
||||||
@@ -150,12 +168,15 @@ public static class WindowManager
|
|||||||
|
|
||||||
public static void InvalidateAll()
|
public static void InvalidateAll()
|
||||||
{
|
{
|
||||||
foreach (var processWindows in windows.Values)
|
List<Window> allWindows;
|
||||||
|
lock (windowsLock)
|
||||||
{
|
{
|
||||||
foreach (var window in processWindows)
|
allWindows = windows.Values.SelectMany(w => w).ToList();
|
||||||
{
|
}
|
||||||
window.Invalidate();
|
|
||||||
}
|
foreach (var window in allWindows)
|
||||||
|
{
|
||||||
|
window.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,13 +187,33 @@ public static class WindowManager
|
|||||||
|
|
||||||
sealed private class MuliRenderSource(List<IRenderSource> sources) : IRenderSource
|
sealed private class MuliRenderSource(List<IRenderSource> sources) : IRenderSource
|
||||||
{
|
{
|
||||||
public void AddSource(IRenderSource source) => sources.Add(source);
|
private readonly object sourcesLock = new object();
|
||||||
|
|
||||||
public void RemoveSource(IRenderSource source) => sources.Remove(source);
|
public void AddSource(IRenderSource source)
|
||||||
|
{
|
||||||
|
lock (sourcesLock)
|
||||||
|
{
|
||||||
|
sources.Add(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSource(IRenderSource source)
|
||||||
|
{
|
||||||
|
lock (sourcesLock)
|
||||||
|
{
|
||||||
|
sources.Remove(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Render(IEnumerable<RenderCommand> commands)
|
public void Render(IEnumerable<RenderCommand> commands)
|
||||||
{
|
{
|
||||||
foreach (IRenderSource source in sources)
|
List<IRenderSource> sourcesCopy;
|
||||||
|
lock (sourcesLock)
|
||||||
|
{
|
||||||
|
sourcesCopy = sources.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (IRenderSource source in sourcesCopy)
|
||||||
{
|
{
|
||||||
source.Render(commands);
|
source.Render(commands);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user