mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
advanced async copy of files
This commit is contained in:
@@ -6,6 +6,7 @@ using System.Collections;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using GlitchyEngine.Content;
|
using GlitchyEngine.Content;
|
||||||
|
using GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
namespace GlitchyEditor.Assets;
|
namespace GlitchyEditor.Assets;
|
||||||
|
|
||||||
@@ -662,6 +663,41 @@ class AssetHierarchy
|
|||||||
Combine
|
Combine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*public Result<BackgroundTask> CopyExternFileToNodeBackground(TreeNode<AssetNode> targetDirectory, String sourcePath, OverwriteMode overwrite)
|
||||||
|
{
|
||||||
|
if (!targetDirectory->IsDirectory)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
FileInfo sourceInfo = scope FileInfo(sourcePath);
|
||||||
|
|
||||||
|
if (!sourceInfo.Exists)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
CopyBackgroundTask copyTask = new CopyBackgroundTask(sourcePath, targetDirectory->Path);
|
||||||
|
|
||||||
|
EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(copyTask);
|
||||||
|
|
||||||
|
return copyTask;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public Result<BackgroundTask> CopyExternFilesToNodeBackground(TreeNode<AssetNode> targetDirectory, List<String> sourcePaths, OverwriteMode overwrite)
|
||||||
|
{
|
||||||
|
if (!targetDirectory->IsDirectory)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
/*FileInfo sourceInfo = scope FileInfo(sourcePath);
|
||||||
|
|
||||||
|
if (!sourceInfo.Exists)
|
||||||
|
return .Err;*/
|
||||||
|
|
||||||
|
CopyBackgroundTask copyTask = new CopyBackgroundTask(sourcePaths, targetDirectory->Path);
|
||||||
|
copyTask.DeleteWhenStopped = true;
|
||||||
|
|
||||||
|
EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(copyTask);
|
||||||
|
|
||||||
|
return copyTask;
|
||||||
|
}
|
||||||
|
|
||||||
public Result<void, CopyError> CopyExternFileToNode(TreeNode<AssetNode> targetDirectory, String sourcePath, OverwriteMode overwrite)
|
public Result<void, CopyError> CopyExternFileToNode(TreeNode<AssetNode> targetDirectory, String sourcePath, OverwriteMode overwrite)
|
||||||
{
|
{
|
||||||
if (!targetDirectory->IsDirectory)
|
if (!targetDirectory->IsDirectory)
|
||||||
@@ -691,7 +727,7 @@ class AssetHierarchy
|
|||||||
case .KeepBoth:
|
case .KeepBoth:
|
||||||
String fileExtension = scope .();
|
String fileExtension = scope .();
|
||||||
Path.GetExtension(targetPath, fileExtension);
|
Path.GetExtension(targetPath, fileExtension);
|
||||||
FindFreePath(targetDirectory->Path, fileName.Substring(0..<^fileExtension.Length), fileExtension, targetPath..Clear());
|
Path.FindFreePath(targetDirectory->Path, fileName.Substring(0..<^fileExtension.Length), fileExtension, targetPath..Clear());
|
||||||
case .Overwrite:
|
case .Overwrite:
|
||||||
forceOverwrite = true;
|
forceOverwrite = true;
|
||||||
default:
|
default:
|
||||||
@@ -709,7 +745,7 @@ class AssetHierarchy
|
|||||||
case .KeepBoth:
|
case .KeepBoth:
|
||||||
String fileExtension = scope .();
|
String fileExtension = scope .();
|
||||||
Path.GetExtension(targetPath, fileExtension);
|
Path.GetExtension(targetPath, fileExtension);
|
||||||
FindFreePath(targetDirectory->Path, fileName.Substring(0..<^fileExtension.Length), fileExtension, targetPath..Clear());
|
Path.FindFreePath(targetDirectory->Path, fileName.Substring(0..<^fileExtension.Length), fileExtension, targetPath..Clear());
|
||||||
case .Overwrite:
|
case .Overwrite:
|
||||||
forceOverwrite = true;
|
forceOverwrite = true;
|
||||||
default:
|
default:
|
||||||
@@ -722,25 +758,4 @@ class AssetHierarchy
|
|||||||
|
|
||||||
return .Ok;
|
return .Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FindFreePath(StringView directory, StringView wantedName, StringView fileExtension, String outFreePath)
|
|
||||||
{
|
|
||||||
int fileNumber = 0;
|
|
||||||
|
|
||||||
String currentFileName = scope $"{wantedName}{fileExtension}";
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
Path.Combine(outFreePath..Clear(), directory, currentFileName);
|
|
||||||
|
|
||||||
FileInfo targetInfo = scope FileInfo(outFreePath);
|
|
||||||
|
|
||||||
if (!targetInfo.Exists)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileNumber++;
|
|
||||||
currentFileName..Clear().AppendF($"{wantedName} ({fileNumber}){fileExtension}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class MaterialEditor
|
|||||||
if (DrawLockButton(bufferVariable.Flags.HasFlag(.Locked)) && !readOnly)
|
if (DrawLockButton(bufferVariable.Flags.HasFlag(.Locked)) && !readOnly)
|
||||||
{
|
{
|
||||||
bufferVariable.[Friend]_flags ^= .Locked;
|
bufferVariable.[Friend]_flags ^= .Locked;
|
||||||
|
// Also mark as dirty so we update our children!
|
||||||
|
bufferVariable.[Friend]_flags |= .Dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -165,6 +167,8 @@ class MaterialEditor
|
|||||||
if (DrawLockButton(texture.Flags.HasFlag(.Locked)) && !readOnly)
|
if (DrawLockButton(texture.Flags.HasFlag(.Locked)) && !readOnly)
|
||||||
{
|
{
|
||||||
texture.Flags ^= .Locked;
|
texture.Flags ^= .Locked;
|
||||||
|
// Also mark as dirty so we update our children!
|
||||||
|
texture.Flags |= .Dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|||||||
@@ -608,12 +608,14 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
List<String> droppedFiles = (.)Internal.UnsafeCastToObject(*(void**)peekPayload.Data);
|
List<String> droppedFiles = (.)Internal.UnsafeCastToObject(*(void**)peekPayload.Data);
|
||||||
|
|
||||||
for (String path in droppedFiles)
|
_manager.AssetHierarchy.CopyExternFilesToNodeBackground(dropTarget, droppedFiles, .None);
|
||||||
|
|
||||||
|
/*for (String path in droppedFiles)
|
||||||
{
|
{
|
||||||
Log.EngineLogger.Info($"Dropping file {path}");
|
Log.EngineLogger.Info($"Dropping file {path}");
|
||||||
|
|
||||||
_manager.AssetHierarchy.CopyExternFileToNode(dropTarget, path, .None);
|
//_manager.AssetHierarchy.CopyExternFileToNodeBackground(dropTarget, path, .None);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using GlitchyEditor.Assets.Processors;
|
|||||||
using GlitchyEditor.Assets.Exporters;
|
using GlitchyEditor.Assets.Exporters;
|
||||||
using DirectX.Common;
|
using DirectX.Common;
|
||||||
using GlitchyEditor.Platform.Windows;
|
using GlitchyEditor.Platform.Windows;
|
||||||
|
using GlitchyEditor.Multithreading;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace GlitchyEditor
|
namespace GlitchyEditor
|
||||||
{
|
{
|
||||||
@@ -14,22 +16,31 @@ namespace GlitchyEditor
|
|||||||
{
|
{
|
||||||
EditorContentManager _contentManager;
|
EditorContentManager _contentManager;
|
||||||
|
|
||||||
|
public new static EditorApp Instance => Application.Instance as EditorApp;
|
||||||
|
|
||||||
|
private append BackgroundTaskManager _backgroundTaskManager = .();
|
||||||
|
|
||||||
|
public BackgroundTaskManager BackgroundTaskManager => _backgroundTaskManager;
|
||||||
|
|
||||||
public this(String[] args)
|
public this(String[] args)
|
||||||
{
|
{
|
||||||
Log.ClientLogger = new EditorLogger();
|
Log.ClientLogger = new EditorLogger();
|
||||||
Log.EngineLogger = new EditorLogger() { IsEngineLogger = true };
|
Log.EngineLogger = new EditorLogger() { IsEngineLogger = true };
|
||||||
|
|
||||||
|
_backgroundTaskManager.Init();
|
||||||
|
|
||||||
// TODO: Windows only
|
// TODO: Windows only
|
||||||
HResult result = OleInitialize(null);
|
HResult result = OleInitialize(null);
|
||||||
Log.EngineLogger.Assert(result case .S_OK);
|
Log.EngineLogger.Assert(result case .S_OK);
|
||||||
|
|
||||||
PushLayer(new EditorLayer(args, _contentManager));
|
PushLayer(new EditorLayer(args, _contentManager));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ~this()
|
public ~this()
|
||||||
{
|
{
|
||||||
OleUninitialize();
|
OleUninitialize();
|
||||||
|
|
||||||
|
_backgroundTaskManager.Deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IContentManager InitContentManager()
|
protected override IContentManager InitContentManager()
|
||||||
|
|||||||
@@ -1243,6 +1243,8 @@ namespace GlitchyEditor
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
EditorApp.Instance.BackgroundTaskManager.ImGuiRender();
|
||||||
|
|
||||||
_editor.SceneViewportWindow.RenderTarget = _editorViewportTarget;
|
_editor.SceneViewportWindow.RenderTarget = _editorViewportTarget;
|
||||||
_editor.GameViewportWindow.RenderTarget = _gameViewportTarget;
|
_editor.GameViewportWindow.RenderTarget = _gameViewportTarget;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ImGui;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
|
using internal GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
|
abstract class BackgroundTask
|
||||||
|
{
|
||||||
|
internal BackgroundTaskList _list;
|
||||||
|
internal BackgroundTask _previous;
|
||||||
|
internal BackgroundTask _next;
|
||||||
|
|
||||||
|
internal BackgroundTaskManager _taskManager;
|
||||||
|
|
||||||
|
public enum RunState
|
||||||
|
{
|
||||||
|
Ready,
|
||||||
|
Blocked,
|
||||||
|
Paused,
|
||||||
|
Running,
|
||||||
|
Aborted,
|
||||||
|
Finished
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RunResult
|
||||||
|
{
|
||||||
|
/// The task gives up it's runtime, but has to run again.
|
||||||
|
Continue,
|
||||||
|
/// The task gives up it's runtime, because it cannot continue without user intervention through the UI.
|
||||||
|
Pause,
|
||||||
|
/// The task finished and wont run again.
|
||||||
|
Finished,
|
||||||
|
/// The operation was aborted, the task will not run again.
|
||||||
|
Abort
|
||||||
|
}
|
||||||
|
|
||||||
|
public RunState State { get; internal set; } = .Ready;
|
||||||
|
|
||||||
|
public bool Ready => State == .Ready;
|
||||||
|
public bool Paused => State == .Paused;
|
||||||
|
public bool Blocked => State == .Blocked;
|
||||||
|
public bool Running => State == .Running;
|
||||||
|
public bool Aborted => State == .Aborted;
|
||||||
|
public bool Finished => State == .Finished;
|
||||||
|
|
||||||
|
public bool Ended => Finished || Aborted;
|
||||||
|
|
||||||
|
public bool DeleteWhenStopped { get; set; }
|
||||||
|
|
||||||
|
public abstract RunResult Run();
|
||||||
|
|
||||||
|
public abstract void OnRenderPopup();
|
||||||
|
|
||||||
|
public void Continue()
|
||||||
|
{
|
||||||
|
_taskManager.ContinueTask(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pause()
|
||||||
|
{
|
||||||
|
if (State == .Ready || State == .Running)
|
||||||
|
{
|
||||||
|
State = .Paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Abort()
|
||||||
|
{
|
||||||
|
_taskManager.AbortTask(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
|
using internal GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
|
class BackgroundTaskList
|
||||||
|
{
|
||||||
|
BackgroundTask _sentinel = new BackgroundTask()
|
||||||
|
{
|
||||||
|
public override BackgroundTask.RunResult Run() => .Finished;
|
||||||
|
public override void OnRenderPopup() { }
|
||||||
|
_next = _,
|
||||||
|
_previous = _,
|
||||||
|
_list = this
|
||||||
|
} ~ delete _;
|
||||||
|
|
||||||
|
private int _count;
|
||||||
|
|
||||||
|
public BackgroundTask First => _sentinel._next == _sentinel ? null : _sentinel._next;
|
||||||
|
public BackgroundTask Last => _sentinel._previous == _sentinel ? null : _sentinel._previous;
|
||||||
|
public BackgroundTask End => _sentinel;
|
||||||
|
|
||||||
|
public int Count => _count;
|
||||||
|
|
||||||
|
private void InsertBefore(BackgroundTask item, BackgroundTask beforeNode)
|
||||||
|
{
|
||||||
|
item._next = beforeNode;
|
||||||
|
item._previous = beforeNode._previous;
|
||||||
|
item._next._previous = item;
|
||||||
|
item._previous._next = item;
|
||||||
|
|
||||||
|
item._list = this;
|
||||||
|
|
||||||
|
_count++;
|
||||||
|
|
||||||
|
Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFirst(BackgroundTask item)
|
||||||
|
{
|
||||||
|
if (item._list != null)
|
||||||
|
item._list.Remove(item, false);
|
||||||
|
|
||||||
|
InsertBefore(item, _sentinel._next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddLast(BackgroundTask item)
|
||||||
|
{
|
||||||
|
if (item._list != null)
|
||||||
|
item._list.Remove(item, false);
|
||||||
|
|
||||||
|
InsertBefore(item, _sentinel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Clear(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(bool deleteElements)
|
||||||
|
{
|
||||||
|
BackgroundTask task = _sentinel._next;
|
||||||
|
|
||||||
|
while (task != _sentinel)
|
||||||
|
{
|
||||||
|
BackgroundTask nextTask = task._next;
|
||||||
|
|
||||||
|
UnlinkElement(task);
|
||||||
|
if (deleteElements)
|
||||||
|
delete task;
|
||||||
|
|
||||||
|
task = nextTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sentinel._next = _sentinel._previous = _sentinel;
|
||||||
|
|
||||||
|
_count = 0;
|
||||||
|
|
||||||
|
Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnlinkElement(BackgroundTask element)
|
||||||
|
{
|
||||||
|
element._next = null;
|
||||||
|
element._previous = null;
|
||||||
|
element._list = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(BackgroundTask item)
|
||||||
|
{
|
||||||
|
return item._list == this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(BackgroundTask item)
|
||||||
|
{
|
||||||
|
return Remove(item, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(BackgroundTask item, bool deleteElement)
|
||||||
|
{
|
||||||
|
if (item._list != this)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
item._previous._next = item._next;
|
||||||
|
item._next._previous = item._previous;
|
||||||
|
|
||||||
|
UnlinkElement(item);
|
||||||
|
|
||||||
|
if (deleteElement)
|
||||||
|
delete item;
|
||||||
|
|
||||||
|
_count--;
|
||||||
|
|
||||||
|
Validate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<BackgroundTask> TryPopFront()
|
||||||
|
{
|
||||||
|
if (_sentinel._next == _sentinel)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
BackgroundTask first = _sentinel._next;
|
||||||
|
|
||||||
|
Remove(first, false);
|
||||||
|
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<BackgroundTask> TryPopBack()
|
||||||
|
{
|
||||||
|
if (_sentinel._previous == _sentinel)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
BackgroundTask first = _sentinel._previous;
|
||||||
|
|
||||||
|
Remove(first, false);
|
||||||
|
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Validate()
|
||||||
|
{
|
||||||
|
BackgroundTask task = _sentinel._next;
|
||||||
|
|
||||||
|
int visitedNodes = 0;
|
||||||
|
|
||||||
|
while (task != _sentinel)
|
||||||
|
{
|
||||||
|
visitedNodes++;
|
||||||
|
|
||||||
|
Debug.Assert(visitedNodes <= Count);
|
||||||
|
Debug.Assert(task._list == this);
|
||||||
|
|
||||||
|
BackgroundTask nextTask = task._next;
|
||||||
|
|
||||||
|
Debug.Assert(nextTask._previous == task);
|
||||||
|
task = nextTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(visitedNodes == Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackgroundTaskListTests
|
||||||
|
{
|
||||||
|
// Stub implementation
|
||||||
|
class BackgroundTask : GlitchyEditor.Multithreading.BackgroundTask
|
||||||
|
{
|
||||||
|
public override BackgroundTask.RunResult Run()
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRenderPopup()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TestBasicOperations()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("🧪 Testing Basic Operations...");
|
||||||
|
|
||||||
|
var list1 = scope BackgroundTaskList();
|
||||||
|
|
||||||
|
var task1 = scope BackgroundTask();
|
||||||
|
var task2 = scope BackgroundTask();
|
||||||
|
|
||||||
|
// Test AddFirst
|
||||||
|
list1.AddFirst(task1);
|
||||||
|
Debug.Assert(list1.Count == 1);
|
||||||
|
Debug.Assert(task1._list == list1);
|
||||||
|
Debug.Assert(list1.Contains(task1));
|
||||||
|
list1.Validate();
|
||||||
|
|
||||||
|
// Test AddLast
|
||||||
|
list1.AddLast(task2);
|
||||||
|
Debug.Assert(list1.Count == 2);
|
||||||
|
Debug.Assert(task2._list == list1);
|
||||||
|
Debug.Assert(list1.Contains(task2));
|
||||||
|
list1.Validate();
|
||||||
|
|
||||||
|
// Test Remove
|
||||||
|
list1.Remove(task1);
|
||||||
|
Debug.Assert(list1.Count == 1);
|
||||||
|
Debug.Assert(task1._list == null);
|
||||||
|
Debug.Assert(!list1.Contains(task1));
|
||||||
|
Debug.Assert(list1.Contains(task2));
|
||||||
|
list1.Validate();
|
||||||
|
|
||||||
|
Debug.WriteLine("✅ Basic Operations passed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TestAutoRemoveFromOldList()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("🧪 Testing Auto-Remove from Old List...");
|
||||||
|
|
||||||
|
var list1 = scope BackgroundTaskList();
|
||||||
|
var list2 = scope BackgroundTaskList();
|
||||||
|
|
||||||
|
var task = scope BackgroundTask();
|
||||||
|
|
||||||
|
// Add to list1
|
||||||
|
list1.AddFirst(task);
|
||||||
|
Debug.Assert(list1.Count == 1);
|
||||||
|
Debug.Assert(list2.Count == 0);
|
||||||
|
Debug.Assert(task._list == list1);
|
||||||
|
list1.Validate();
|
||||||
|
list2.Validate();
|
||||||
|
|
||||||
|
// Add to list2 - should remove from list1
|
||||||
|
list2.AddLast(task);
|
||||||
|
Debug.Assert(list1.Count == 0);
|
||||||
|
Debug.Assert(list2.Count == 1);
|
||||||
|
Debug.Assert(task._list == list2);
|
||||||
|
Debug.Assert(!list1.Contains(task));
|
||||||
|
Debug.Assert(list2.Contains(task));
|
||||||
|
list1.Validate();
|
||||||
|
list2.Validate();
|
||||||
|
|
||||||
|
// add to list1 again, should be removed from list2
|
||||||
|
list1.AddFirst(task);
|
||||||
|
Debug.Assert(list1.Count == 1);
|
||||||
|
Debug.Assert(list2.Count == 0);
|
||||||
|
Debug.Assert(task._list == list1);
|
||||||
|
list1.Validate();
|
||||||
|
list2.Validate();
|
||||||
|
|
||||||
|
Debug.WriteLine("✅ Auto-Remove passed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TestTryPopFront()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("🧪 Testing TryPopFront...");
|
||||||
|
|
||||||
|
var list = scope BackgroundTaskList();
|
||||||
|
|
||||||
|
// Test empty list
|
||||||
|
let emptyResult = list.TryPopFront();
|
||||||
|
Debug.Assert(emptyResult == .Err);
|
||||||
|
list.Validate();
|
||||||
|
|
||||||
|
var task1 = scope BackgroundTask();
|
||||||
|
var task2 = scope BackgroundTask();
|
||||||
|
var task3 = scope BackgroundTask();
|
||||||
|
|
||||||
|
// Add three elements
|
||||||
|
list.AddLast(task1);
|
||||||
|
list.Validate();
|
||||||
|
list.AddLast(task2);
|
||||||
|
list.Validate();
|
||||||
|
list.AddLast(task3);
|
||||||
|
list.Validate();
|
||||||
|
Debug.Assert(list.Count == 3);
|
||||||
|
|
||||||
|
// Pop first element
|
||||||
|
let pop1 = list.TryPopFront();
|
||||||
|
Debug.Assert(pop1 == .Ok(task1));
|
||||||
|
Debug.Assert(list.Count == 2);
|
||||||
|
Debug.Assert(task1._list == null);
|
||||||
|
list.Validate();
|
||||||
|
|
||||||
|
// Pop second element
|
||||||
|
let pop2 = list.TryPopFront();
|
||||||
|
Debug.Assert(pop2 == .Ok(task2));
|
||||||
|
Debug.Assert(list.Count == 1);
|
||||||
|
Debug.Assert(task2._list == null);
|
||||||
|
list.Validate();
|
||||||
|
|
||||||
|
// Pop third element
|
||||||
|
let pop3 = list.TryPopFront();
|
||||||
|
Debug.Assert(pop3 == .Ok(task3));
|
||||||
|
Debug.Assert(list.Count == 0);
|
||||||
|
Debug.Assert(task3._list == null);
|
||||||
|
list.Validate();
|
||||||
|
|
||||||
|
// Pop on now empty list
|
||||||
|
let pop4 = list.TryPopFront();
|
||||||
|
Debug.Assert(pop4 == .Err);
|
||||||
|
list.Validate();
|
||||||
|
|
||||||
|
Debug.WriteLine("✅ TryPopFront passed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static void TestIterator()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("🧪 Testing Iterator...");
|
||||||
|
|
||||||
|
var list = scope BackgroundTaskList();
|
||||||
|
var tasks = scope BackgroundTask*[5];
|
||||||
|
|
||||||
|
for (int i = 0; i < tasks.Count; i++)
|
||||||
|
{
|
||||||
|
tasks[i] = new BackgroundTask();
|
||||||
|
list.AddLast(tasks[i]);
|
||||||
|
}
|
||||||
|
defer { for (var t in tasks) delete t; }
|
||||||
|
|
||||||
|
// Teste Iterator
|
||||||
|
int count = 0;
|
||||||
|
for (var task in list)
|
||||||
|
{
|
||||||
|
Debug.Assert(task == tasks[count]);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
Debug.Assert(count == 5);
|
||||||
|
|
||||||
|
Debug.WriteLine("✅ Iterator passed!");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
static void TestClear()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("🧪 Testing Clear...");
|
||||||
|
|
||||||
|
var list = scope BackgroundTaskList();
|
||||||
|
|
||||||
|
var task1 = scope BackgroundTask();
|
||||||
|
var task2 = scope BackgroundTask();
|
||||||
|
var task3 = scope BackgroundTask();
|
||||||
|
|
||||||
|
list.AddLast(task1);
|
||||||
|
list.Validate();
|
||||||
|
list.AddLast(task2);
|
||||||
|
list.Validate();
|
||||||
|
list.AddLast(task3);
|
||||||
|
list.Validate();
|
||||||
|
Debug.Assert(list.Count == 3);
|
||||||
|
|
||||||
|
// Clear sollte task1 und task3 löschen, aber nicht task2
|
||||||
|
list.Clear();
|
||||||
|
Debug.Assert(list.Count == 0);
|
||||||
|
list.Validate();
|
||||||
|
|
||||||
|
Debug.WriteLine("✅ Clear passed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TestComplexScenario()
|
||||||
|
{
|
||||||
|
// Simulate processing queues.
|
||||||
|
Debug.WriteLine("🧪 Testing Complex Scenario...");
|
||||||
|
|
||||||
|
var pending = scope BackgroundTaskList();
|
||||||
|
var processing = scope BackgroundTaskList();
|
||||||
|
var completed = scope BackgroundTaskList();
|
||||||
|
|
||||||
|
// Add to pending
|
||||||
|
var tasks = scope BackgroundTask[10];
|
||||||
|
for (int i = 0; i < tasks.Count; i++)
|
||||||
|
{
|
||||||
|
tasks[i] = scope:: BackgroundTask();
|
||||||
|
pending.AddLast(tasks[i]);
|
||||||
|
pending.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move from pending to processing
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
let result = pending.TryPopFront();
|
||||||
|
if (result == .Err) break;
|
||||||
|
|
||||||
|
let task = result.Value;
|
||||||
|
processing.AddLast(task);
|
||||||
|
processing.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(pending.Count == 0);
|
||||||
|
Debug.Assert(processing.Count == 10);
|
||||||
|
|
||||||
|
// Move to completed
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
let result = processing.TryPopFront();
|
||||||
|
if (result == .Err) break;
|
||||||
|
|
||||||
|
completed.AddLast(result.Value);
|
||||||
|
completed.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(processing.Count == 0);
|
||||||
|
Debug.Assert(completed.Count == 10);
|
||||||
|
|
||||||
|
Debug.WriteLine("✅ Complex Scenario passed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RunTests()
|
||||||
|
{
|
||||||
|
Debug.WriteLine("🚀 Starting BackgroundTaskList Tests...\n");
|
||||||
|
|
||||||
|
TestBasicOperations();
|
||||||
|
TestAutoRemoveFromOldList();
|
||||||
|
TestTryPopFront();
|
||||||
|
//TestIterator();
|
||||||
|
TestClear();
|
||||||
|
TestComplexScenario();
|
||||||
|
|
||||||
|
Debug.WriteLine("\n🎉 All tests passed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.Collections;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
|
using internal GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
|
// This would go nicely with some light weight threading!
|
||||||
|
class BackgroundTaskManager
|
||||||
|
{
|
||||||
|
private Thread _workerThread;
|
||||||
|
|
||||||
|
private bool _run;
|
||||||
|
|
||||||
|
private BackgroundTask _runningTask;
|
||||||
|
private append BackgroundTaskList _readyQueue = .();
|
||||||
|
private append BackgroundTaskList _waitingTasks = .();
|
||||||
|
private append Monitor _queueLock = .();
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
_run = true;
|
||||||
|
_workerThread = new Thread(new => WorkerLoop);
|
||||||
|
_workerThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deinit()
|
||||||
|
{
|
||||||
|
_run = false;
|
||||||
|
|
||||||
|
// TODO: Delete jobs if necessary
|
||||||
|
// TODO: Danger if thread is deadlocked.
|
||||||
|
_workerThread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WorkerLoop()
|
||||||
|
{
|
||||||
|
while (_run)
|
||||||
|
{
|
||||||
|
Result<BackgroundTask> nextRunningTask = .Err;
|
||||||
|
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
nextRunningTask = _readyQueue.TryPopFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextRunningTask case .Ok(let task))
|
||||||
|
{
|
||||||
|
_runningTask = task;
|
||||||
|
_runningTask.State = .Running;
|
||||||
|
BackgroundTask.RunResult runResult = task.Run();
|
||||||
|
_runningTask = null;
|
||||||
|
|
||||||
|
switch (runResult)
|
||||||
|
{
|
||||||
|
case .Continue:
|
||||||
|
task.State = .Ready;
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
_readyQueue.AddLast(task);
|
||||||
|
}
|
||||||
|
case .Pause:
|
||||||
|
task.State = .Paused;
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
_waitingTasks.AddLast(task);
|
||||||
|
}
|
||||||
|
case .Finished:
|
||||||
|
task.State = .Finished;
|
||||||
|
|
||||||
|
if (task.DeleteWhenStopped)
|
||||||
|
{
|
||||||
|
delete task;
|
||||||
|
}
|
||||||
|
case .Abort:
|
||||||
|
task.State = .Aborted;
|
||||||
|
|
||||||
|
if (task.DeleteWhenStopped)
|
||||||
|
{
|
||||||
|
delete task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<void> StartBackgroundTask(BackgroundTask task)
|
||||||
|
{
|
||||||
|
if (task._taskManager != null)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Task is already managed by a taskmanager.");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.State == .Running || task.State == .Aborted)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Task State illegal. (Did it already run?)");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
task._taskManager = this;
|
||||||
|
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
if (task.State == .Ready)
|
||||||
|
_readyQueue.AddLast(task);
|
||||||
|
else
|
||||||
|
_waitingTasks.AddLast(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ContinueTask(BackgroundTask task)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(task._taskManager == this);
|
||||||
|
Log.EngineLogger.AssertDebug(!task.Ended);
|
||||||
|
|
||||||
|
Application.Instance.InvokeOnMainThread(new () =>
|
||||||
|
{
|
||||||
|
if (task.State == .Paused)
|
||||||
|
{
|
||||||
|
task.State = .Ready;
|
||||||
|
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(!_readyQueue.Contains(task));
|
||||||
|
// We assume this was a user interaction so this task will get some priority.
|
||||||
|
_readyQueue.AddFirst(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Pause(BackgroundTask task)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(task._taskManager == this);
|
||||||
|
Log.EngineLogger.AssertDebug(!task.Ended);
|
||||||
|
|
||||||
|
Application.Instance.InvokeOnMainThread(new () =>
|
||||||
|
{
|
||||||
|
if (task.State == .Ready)
|
||||||
|
{
|
||||||
|
task.State = .Ready;
|
||||||
|
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(!_waitingTasks.Contains(task));
|
||||||
|
_waitingTasks.AddLast(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AbortTask(BackgroundTask task)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(task._taskManager == this);
|
||||||
|
Log.EngineLogger.AssertDebug(!task.Ended);
|
||||||
|
|
||||||
|
Application.Instance.InvokeOnMainThread(new () =>
|
||||||
|
{
|
||||||
|
if (task.State != .Aborted)
|
||||||
|
{
|
||||||
|
task.State = .Aborted;
|
||||||
|
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
_waitingTasks.Remove(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.DeleteWhenStopped)
|
||||||
|
{
|
||||||
|
delete task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ImGuiRender()
|
||||||
|
{
|
||||||
|
using (_queueLock.Enter())
|
||||||
|
{
|
||||||
|
_runningTask?.OnRenderPopup();
|
||||||
|
|
||||||
|
BackgroundTask task = _readyQueue.First;
|
||||||
|
|
||||||
|
while (task != null && task != _readyQueue.End)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(task._list == _readyQueue);
|
||||||
|
|
||||||
|
task.OnRenderPopup();
|
||||||
|
task = task._next;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackgroundTask wtask = _waitingTasks.First;
|
||||||
|
|
||||||
|
while (wtask != null && wtask != _waitingTasks.End)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(wtask._list == _waitingTasks);
|
||||||
|
|
||||||
|
wtask.OnRenderPopup();
|
||||||
|
wtask = wtask._next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ImGui;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Multithreading;
|
||||||
|
|
||||||
|
class CopyBackgroundTask : BackgroundTask
|
||||||
|
{
|
||||||
|
private List<String> _sourcePath ~ {ClearAndDeleteItems!(_); delete:append _;};
|
||||||
|
private String _targetDirectoryPath ~ delete:append _;
|
||||||
|
|
||||||
|
private int _totalEntriesToCopy;
|
||||||
|
private append Queue<CopyInfo> _pathsToCopy = .() ~ ClearAndDeleteItems!(_);
|
||||||
|
private append Queue<String> _scanQueue = .() ~ ClearAndDeleteItems!(_);
|
||||||
|
|
||||||
|
private enum OverwriteMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
OverwriteFile = 1,
|
||||||
|
KeepBoth = 2,
|
||||||
|
CombineDirectories = 4,
|
||||||
|
Skip = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
private OverwriteMode _nextFileMode;
|
||||||
|
private OverwriteMode _allFilesMode;
|
||||||
|
|
||||||
|
private OverwriteMode CurrentFileMode => _nextFileMode | _allFilesMode;
|
||||||
|
|
||||||
|
private class CopyInfo
|
||||||
|
{
|
||||||
|
public String SourcePath ~ delete:append _;
|
||||||
|
public String TargetPath ~ delete:append _;
|
||||||
|
public OverwriteMode OverwriteMode;
|
||||||
|
|
||||||
|
[AllowAppend]
|
||||||
|
public this(StringView sourcePath, StringView targetPath, OverwriteMode overwriteMode)
|
||||||
|
{
|
||||||
|
String src = append String(sourcePath);
|
||||||
|
String tgt = append String(targetPath);
|
||||||
|
|
||||||
|
SourcePath = src;
|
||||||
|
TargetPath = tgt;
|
||||||
|
|
||||||
|
OverwriteMode = overwriteMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ScanningFiles => !_scanQueue.IsEmpty;
|
||||||
|
|
||||||
|
[AllowAppend]
|
||||||
|
public this(List<String> sourcePaths, StringView targetDirectoryPath)
|
||||||
|
{
|
||||||
|
List<String> sourcePathList = append List<String>(sourcePaths.Count);
|
||||||
|
String targetDirectoryPathStr = append String(targetDirectoryPath);
|
||||||
|
|
||||||
|
_sourcePath = sourcePathList;
|
||||||
|
_targetDirectoryPath = targetDirectoryPathStr;
|
||||||
|
|
||||||
|
for (String path in sourcePaths)
|
||||||
|
{
|
||||||
|
_sourcePath.Add(new String(path));
|
||||||
|
_scanQueue.Add(new String(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
_totalEntriesToCopy = sourcePaths.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CopyError : IDisposable
|
||||||
|
{
|
||||||
|
case None;
|
||||||
|
case TargetDirectoryIsNotDirectory;
|
||||||
|
case SourceDoesntExist(String SourcePath);
|
||||||
|
case TargetFileExists(String FileName);
|
||||||
|
case TargetDirectoryExists(String DirectoryName);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
switch (this)
|
||||||
|
{
|
||||||
|
case .None:
|
||||||
|
case .TargetDirectoryIsNotDirectory:
|
||||||
|
case .SourceDoesntExist(let SourcePath):
|
||||||
|
delete SourcePath;
|
||||||
|
case .TargetFileExists(let FileName):
|
||||||
|
delete FileName;
|
||||||
|
case .TargetDirectoryExists(let DirectoryName):
|
||||||
|
delete DirectoryName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result<void, CopyError> CollectFilesToCopy()
|
||||||
|
{
|
||||||
|
void RemoveFront()
|
||||||
|
{
|
||||||
|
String path = _scanQueue.PopFront();
|
||||||
|
delete path;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!_scanQueue.IsEmpty && Running)
|
||||||
|
{
|
||||||
|
//Thread.Sleep(1000);
|
||||||
|
|
||||||
|
String currentPath = _scanQueue.Peek();
|
||||||
|
|
||||||
|
//FileInfo sourceInfo = scope FileInfo(currentPath);
|
||||||
|
|
||||||
|
String fileName = scope .();
|
||||||
|
Path.GetFileName(currentPath, fileName);
|
||||||
|
|
||||||
|
String targetPath = scope .();
|
||||||
|
Path.Combine(targetPath, _targetDirectoryPath, fileName);
|
||||||
|
|
||||||
|
//FileInfo targetInfo = scope FileInfo(targetPath);
|
||||||
|
|
||||||
|
if (Directory.Exists(currentPath))
|
||||||
|
{
|
||||||
|
/*for (FileFindEntry e in Directory.Enumerate(currentPath))
|
||||||
|
{
|
||||||
|
String entryPath = new String();
|
||||||
|
e.GetFilePath(entryPath);
|
||||||
|
_scanQueue.Add(entryPath);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
else if (File.Exists(currentPath))
|
||||||
|
{
|
||||||
|
if (File.Exists(targetPath))
|
||||||
|
{
|
||||||
|
switch (CurrentFileMode)
|
||||||
|
{
|
||||||
|
case .KeepBoth, .OverwriteFile, .Skip:
|
||||||
|
default:
|
||||||
|
return .Err(.TargetFileExists(new String(fileName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_pathsToCopy.Add(new CopyInfo(currentPath, targetPath, CurrentFileMode));
|
||||||
|
RemoveFront();
|
||||||
|
_nextFileMode = .None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_totalEntriesToCopy = _pathsToCopy.Count;
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CopyError _currentError = .None ~ _.Dispose();
|
||||||
|
|
||||||
|
public override RunResult Run()
|
||||||
|
{
|
||||||
|
_currentError.Dispose();
|
||||||
|
_currentError = .None;
|
||||||
|
if (CollectFilesToCopy() case .Err(out _currentError))
|
||||||
|
{
|
||||||
|
return .Pause;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!_pathsToCopy.IsEmpty && Running)
|
||||||
|
{
|
||||||
|
//Thread.Sleep(1000);
|
||||||
|
CopyInfo currentPath = _pathsToCopy.PopFront();
|
||||||
|
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
delete currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyPath(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result<void> CopyPath(CopyInfo sourcePath)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(sourcePath.SourcePath))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else if (File.Exists(sourcePath.SourcePath))
|
||||||
|
{
|
||||||
|
bool forceOverwrite = false;
|
||||||
|
if (File.Exists(sourcePath.TargetPath))
|
||||||
|
{
|
||||||
|
switch (sourcePath.OverwriteMode)
|
||||||
|
{
|
||||||
|
case .KeepBoth:
|
||||||
|
String fileExtension = scope .();
|
||||||
|
Path.GetExtension(sourcePath.TargetPath, fileExtension);
|
||||||
|
|
||||||
|
String fileName = scope .();
|
||||||
|
Path.GetFileName(sourcePath.SourcePath, fileName);
|
||||||
|
|
||||||
|
Path.FindFreePath(_targetDirectoryPath, fileName.Substring(0..<^fileExtension.Length), fileExtension, sourcePath.TargetPath..Clear());
|
||||||
|
case .OverwriteFile:
|
||||||
|
forceOverwrite = true;
|
||||||
|
case .Skip:
|
||||||
|
default:
|
||||||
|
return .Err;//(.TargetFileExists);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Copy(sourcePath.SourcePath, sourcePath.TargetPath, forceOverwrite);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO error
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRenderPopup()
|
||||||
|
{
|
||||||
|
if (ImGui.Begin("Copying files..."))
|
||||||
|
{
|
||||||
|
if (ScanningFiles)
|
||||||
|
{
|
||||||
|
ImGui.ProgressBar(-1.0f * (float)ImGui.GetTime(), .(-1, 0), scope $"Found {_pathsToCopy.Count} files...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int copiedFiles = _totalEntriesToCopy - _pathsToCopy.Count;
|
||||||
|
|
||||||
|
ImGui.ProgressBar(copiedFiles / _totalEntriesToCopy, .(-1, 0), scope $"Copied {copiedFiles} / {_totalEntriesToCopy} files.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (_currentError)
|
||||||
|
{
|
||||||
|
case .None:
|
||||||
|
// Do nothing
|
||||||
|
case .TargetDirectoryIsNotDirectory:
|
||||||
|
// This shouldn't be possible
|
||||||
|
Debug.Break();
|
||||||
|
case .SourceDoesntExist(let SourcePath):
|
||||||
|
// This isn't good!
|
||||||
|
case .TargetFileExists(let fileName):
|
||||||
|
ImGui.PushStyleColor(.Text, 0xFF0000FF);
|
||||||
|
ImGui.TextUnformatted(scope $"A file with the name \"{fileName}\" already exists.");
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
if (ImGui.Button("Overwrite"))
|
||||||
|
{
|
||||||
|
_nextFileMode = .OverwriteFile;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Overwrites the existing file. Will ask again if another conflict occurs.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Overwrite All"))
|
||||||
|
{
|
||||||
|
_allFilesMode = .OverwriteFile;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Overwrites all existing files.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Keep both"))
|
||||||
|
{
|
||||||
|
_nextFileMode = .KeepBoth;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Keeps both the existing file as well as the new one, differentiates by adding a number to the name. Will ask again if another conflict occurs.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Keep all"))
|
||||||
|
{
|
||||||
|
_allFilesMode = .KeepBoth;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Keeps both the existing files as well as the new ones, differentiates by adding a number to the name.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Cancel"))
|
||||||
|
{
|
||||||
|
Abort();
|
||||||
|
}
|
||||||
|
case .TargetDirectoryExists(let directoryName):
|
||||||
|
ImGui.PushStyleColor(.Text, 0xFF0000FF);
|
||||||
|
ImGui.TextUnformatted(scope $"A file with the name \"{directoryName}\" already exists.");
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
if (ImGui.Button("Combine"))
|
||||||
|
{
|
||||||
|
_nextFileMode = .CombineDirectories;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Merges the copied directory into the target directory. Will ask again if another conflict occurs.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Combine All"))
|
||||||
|
{
|
||||||
|
_allFilesMode = .CombineDirectories;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Merges all copied directories into the target directories.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Keep both"))
|
||||||
|
{
|
||||||
|
_nextFileMode = .KeepBoth;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Renames the copied directory, so that they no longer conflict. Will ask again if another conflict occurs.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Keep all"))
|
||||||
|
{
|
||||||
|
_allFilesMode = .KeepBoth;
|
||||||
|
Continue();
|
||||||
|
}
|
||||||
|
ImGui.AttachTooltip("Renames the copied directory, so that they no longer conflict.");
|
||||||
|
|
||||||
|
if (ImGui.Button("Cancel"))
|
||||||
|
{
|
||||||
|
Abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Collections;
|
||||||
|
|
||||||
|
/*class IntrusiveLinkedList<T> : ICollection<T> where T : ILinkedListElement, var
|
||||||
|
{
|
||||||
|
private T _head;
|
||||||
|
private T _tail;
|
||||||
|
|
||||||
|
private bool _ownsElements;
|
||||||
|
|
||||||
|
public void Add(T element)
|
||||||
|
{
|
||||||
|
element.List = this;
|
||||||
|
if (_head == null)
|
||||||
|
{
|
||||||
|
_head = _tail = element;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//_tail.ListLink.Previous = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(Span<T> span)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListLink<T> where T : ILinkedListElement
|
||||||
|
{
|
||||||
|
public IntrusiveLinkedList<T> List { get; internal set; }
|
||||||
|
public ListLink<T> Previous { get; internal set; }
|
||||||
|
public ListLink<T> Next { get; internal set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ILinkedListElement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(.Class | .Struct)]
|
||||||
|
struct IntrusiveLinkedListAttribute : Attribute, IComptimeTypeApply
|
||||||
|
{
|
||||||
|
[Comptime]
|
||||||
|
public void ApplyToType(Type type)
|
||||||
|
{
|
||||||
|
Compiler.EmitAddInterface(type, typeof(ILinkedListElement));
|
||||||
|
|
||||||
|
Compiler.EmitTypeBody(type, new $"""
|
||||||
|
//ListLink<{type}> _listLink;
|
||||||
|
public using ListLink<{type}> _listLink;
|
||||||
|
public ListLink<{type}> ListLink => _listLink;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[IntrusiveLinkedList]
|
||||||
|
class LLTest
|
||||||
|
{
|
||||||
|
}*/
|
||||||
@@ -34,6 +34,7 @@ extension Path
|
|||||||
path.Remove(0, 1);
|
path.Remove(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void Combine(String target, params StringView[] components)
|
public static void Combine(String target, params StringView[] components)
|
||||||
{
|
{
|
||||||
for (var component in components)
|
for (var component in components)
|
||||||
@@ -46,4 +47,32 @@ extension Path
|
|||||||
|
|
||||||
target.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
target.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a file with the given path ([targetDirectory]/[wantedName][fileExtension]) already exists it will add a number at the end of the file name.
|
||||||
|
* @param targetDirectory The directory in which the file will be put.
|
||||||
|
* @param wantedFileName The wanted name of the file. If it already exists a number will be put behind it.
|
||||||
|
* @param fileExtension The file extension. This should contain the dot.
|
||||||
|
* @param outFreePath The string that will contain the resulting filepath. Note: This will be cleared before writing the file name.
|
||||||
|
*/
|
||||||
|
public static void FindFreePath(StringView targetDirectory, StringView wantedName, StringView fileExtension, String outFreePath)
|
||||||
|
{
|
||||||
|
int fileNumber = 0;
|
||||||
|
|
||||||
|
String currentFileName = scope $"{wantedName}{fileExtension}";
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Path.Combine(outFreePath..Clear(), targetDirectory, currentFileName);
|
||||||
|
|
||||||
|
FileInfo targetInfo = scope FileInfo(outFreePath);
|
||||||
|
|
||||||
|
if (!targetInfo.Exists)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileNumber++;
|
||||||
|
currentFileName..Clear().AppendF($"{wantedName} ({fileNumber}){fileExtension}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user