Files
xyou f70b061d1a
CI / Go Backend (push) Canceled after 0s
feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
2026-08-08 14:07:56 +08:00

1031 lines
37 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.VisualBasic.CompilerServices;
using PCL.Core.App;
using PCL.Core.Utils;
using System.Collections;
using System.IO;
using System.Windows.Shell;
using PCL.Network;
using PCL.Network.Loaders;
using PCL.Core.App.Localization;
namespace PCL;
public static class ModLoader
{
public enum LoaderFolderRunType
{
RunOnUpdated,
ForceRun,
UpdateOnly
}
// 任务栏进度条
public static ModBase.SafeList<LoaderBase> loaderTaskbar = new();
public static double loaderTaskbarProgress; // 平滑后的进度
private static TaskbarItemProgressState loaderTaskbarProgressLast = TaskbarItemProgressState.None;
// 文件夹刷新类委托
private static readonly Dictionary<LoaderBase, LoaderFolderDictionaryEntry> loaderFolderDictionary = new();
public static void LoaderTaskbarAdd<T>(LoaderCombo<T> loader)
{
if (ModMain.frmSpeedLeft is not null)
ModMain.frmSpeedLeft.TaskRemove(loader);
loaderTaskbar.Add(loader);
ModBase.Log($"[Taskbar] {loader.name} 已加入任务列表");
}
public static void LoaderTaskbarProgressRefresh()
{
try
{
TaskbarItemProgressState newState;
var newProgress = LoaderTaskbarProgressGet();
// 若单个任务已中止,或全部任务已完成,则刷新并移除
foreach (var Task in loaderTaskbar)
if (loaderTaskbar.All(l => l.State != ModBase.LoadState.Loading) ||
Task.State == ModBase.LoadState.Waiting || Task.State == ModBase.LoadState.Aborted)
{
ModMain.frmSpeedLeft?.TaskRefresh(Task);
loaderTaskbar.Remove(Task);
ModBase.Log($"[Taskbar] {Task.name} 已移出任务列表");
}
// 更新平滑后的进度
if (newProgress <= 0d || newProgress >= 1d || loaderTaskbarProgress > newProgress)
loaderTaskbarProgress = newProgress;
else
loaderTaskbarProgress = loaderTaskbarProgress * 0.9d + newProgress * 0.1d;
ModBase.RunInUi(() => ModMain.frmMain.BtnExtraDownload.Progress = loaderTaskbarProgress);
// 更新任务栏信息
if (!loaderTaskbar.Any() || loaderTaskbarProgress == 1d)
{
newState = TaskbarItemProgressState.None;
}
else if (loaderTaskbarProgress < 0.015d)
{
newState = TaskbarItemProgressState.Indeterminate;
}
else
{
newState = TaskbarItemProgressState.Normal;
ModMain.frmMain.TaskbarItemInfo.ProgressValue = loaderTaskbarProgress;
}
if (loaderTaskbarProgressLast != newState)
{
loaderTaskbarProgressLast = newState;
ModMain.frmMain.TaskbarItemInfo.ProgressState = newState;
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
}
}
catch (Exception ex)
{
ModBase.Log(
ex,
"刷新任务栏进度显示失败",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Application.Loader.Error.OperationFailed"));
}
}
public static double LoaderTaskbarProgressGet()
{
try
{
if (!loaderTaskbar.Any())
return 1d;
return ModBase.MathClamp(
loaderTaskbar.Select(l => l.Progress).Average(),
0,
1
);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"获取任务栏进度出错",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Application.Loader.Error.OperationFailed"));
return 0.5d;
}
}
/// <summary>
/// 执行以文件夹检测作为输入的加载器。加载器需以文件夹路径为输入值。
/// 返回是否执行了加载器。
/// </summary>
/// <param name="extraPath">用于检查文件夹修改的额外路径。该路径不会传入加载器。</param>
/// <param name="loaderInput">如果不想要文件夹路径为输入值,则传入期望数据</param>
public static bool LoaderFolderRun(LoaderBase loader, string folderPath, LoaderFolderRunType type, int maxDepth = 0,
string extraPath = "", bool waitForExit = false, object loaderInput = null)
{
DirectoryInfo folderInfo;
var value = new LoaderFolderDictionaryEntry { folderPath = folderPath + extraPath, lastCheckTime = default };
try
{
// 获取数据
folderInfo = new DirectoryInfo(folderPath + extraPath);
value.lastCheckTime = folderInfo.Exists ? GetActualLastWriteTimeUtc(folderInfo, maxDepth) : null;
// 如果已经检查过,则跳过
if (type == LoaderFolderRunType.RunOnUpdated && loaderFolderDictionary.ContainsKey(loader))
{
if (folderInfo.Exists)
{
if (loaderFolderDictionary[loader].lastCheckTime is not null &&
value.Equals(loaderFolderDictionary[loader]))
return false;
}
else if (loaderFolderDictionary[loader].lastCheckTime is null)
{
return false;
}
}
}
catch (Exception ex)
{
ModBase.Log(ex, "文件夹加载器启动检测出错");
}
// 写入检查数据
loaderFolderDictionary[loader] = value;
// 开始检查
if (type == LoaderFolderRunType.UpdateOnly)
return false;
if (waitForExit)
loader.WaitForExit(loaderInput ?? folderPath, isForceRestart: true);
else
loader.Start(loaderInput ?? folderPath, true);
return true;
}
private static DateTime GetActualLastWriteTimeUtc(DirectoryInfo folderInfo, int maxDepth)
{
var time = folderInfo.LastWriteTimeUtc;
if (maxDepth > 0)
foreach (var Folder in folderInfo.EnumerateDirectories())
{
var folderTime = GetActualLastWriteTimeUtc(Folder, maxDepth - 1);
if (folderTime > time)
time = folderTime;
}
return time;
}
// 各类加载器
/// <summary>
/// 加载器的统一基类。
/// </summary>
public abstract class LoaderBase : ILoadingTrigger
{
public delegate void OnStateChangedThreadEventHandler(LoaderBase loader, ModBase.LoadState newState,
ModBase.LoadState oldState);
public delegate void OnStateChangedUiEventHandler(LoaderBase loader, ModBase.LoadState newState,
ModBase.LoadState oldState);
public delegate void PreviewFinishEventHandler(LoaderBase loader);
// 等待结束
public const string waitForExitTimeoutMessage = "等待加载器执行超时。";
/// <summary>
/// 用于状态改变检测的同步锁。
/// </summary>
public readonly object lockState = new();
/// <summary>
/// 使用 LoaderCombo 加载时,该任务是否会阻碍后续任务的进行。
/// </summary>
public bool block = true;
public bool hasOnStateChangedThread;
/// <summary>
/// 当前加载器是否由 IsForceRestart 强制调起。
/// 这个属性自身不会干任何事,而是提供给加载器执行的函数,使得加载器调用另一个加载器时,可以继承强制重启属性。
/// </summary>
public bool isForceRestarting;
/// <summary>
/// 加载器的名称。
/// </summary>
public string name;
/// <summary>
/// 父加载器。
/// </summary>
public LoaderBase parent;
/// <summary>
/// 该加载器是否显示在列表中。
/// </summary>
public bool show = true;
// 基础属性
/// <summary>
/// 加载器的标识编号。
/// </summary>
public int Uuid = ModBase.GetUuid();
public LoaderBase()
{
name = "未命名任务 " + Uuid + "#";
}
/// <summary>
/// 最上级的加载器。
/// </summary>
public LoaderBase RealParent
{
get
{
LoaderBase realParentRet = default;
try
{
realParentRet = parent;
while (realParentRet is not null && realParentRet.parent is not null)
realParentRet = realParentRet.parent;
}
catch (Exception ex)
{
ModBase.Log(
ex,
"获取父加载器失败(" + name + "",
ModBase.LogLevel.Feedback,
userSummary: Lang.Text("Application.Loader.Error.OperationFailed"));
return null;
}
return realParentRet;
}
}
/// <summary>
/// 简易的在 UI 线程添加触发事件的方式。主要用于在新建 Loader 时直接使用 With 绑定事件,以及进行老代码兼容。
/// </summary>
public Action<LoaderBase> OnStateChanged
{
set { OnStateChangedUi += (loader, newState, oldState) => value(loader); }
}
// 状态监控
/// <summary>
/// 加载器的状态。
/// </summary>
public ModBase.LoadState State
{
get => field;
set
{
if (field == value)
return;
var oldState = field;
if (value == ModBase.LoadState.Finished && Config.Debug.AddRandomDelay)
Thread.Sleep(RandomUtils.NextInt(100, 2000));
field = value;
ModBase.Log("[Loader] 加载器 " + name + " 状态改变:" + ModBase.GetStringFromEnum(value));
// 实现 ILoadingTrigger 接口与 OnStateChanged 回调
ModBase.RunInUi(() =>
{
switch (value)
{
case ModBase.LoadState.Loading:
{
LoadingState = MyLoading.MyLoadingState.Run;
break;
}
case ModBase.LoadState.Failed:
{
LoadingState = MyLoading.MyLoadingState.Error;
break;
}
default:
{
LoadingState = MyLoading.MyLoadingState.Stop;
break;
}
}
OnStateChangedUi?.Invoke(this, value, oldState);
});
if (hasOnStateChangedThread)
ModBase.RunInThread(() => OnStateChangedThread?.Invoke(this, value, oldState));
}
} = ModBase.LoadState.Waiting;
/// <summary>
/// 若加载器出错,可提供给外部参考的异常。
/// </summary>
public Exception Error { get; set; }
// 进度监控
/// <summary>
/// 加载器的执行进度,为 0 至 1 的小数。
/// </summary>
public virtual double Progress
{
get
{
switch (State)
{
case ModBase.LoadState.Waiting:
{
return 0d;
}
case ModBase.LoadState.Loading:
{
return field == -1 ? 0.02d : field;
}
default:
{
return 1d;
}
}
}
set
{
if (field == value)
return;
var oldValue = field;
field = value;
ProgressChanged?.Invoke(value, oldValue);
}
} = -1;
/// <summary>
/// 计算总进度时的权重。它应该为预计时间(秒)。
/// </summary>
public double ProgressWeight { get; set; } = 1d;
public bool IsLoader { get; } = true;
public MyLoading.MyLoadingState LoadingState
{
get => field;
set
{
if (field == value)
return;
var oldState = field;
field = value;
LoadingStateChanged?.Invoke(value, oldState);
}
} = MyLoading.MyLoadingState.Stop;
public event ILoadingTrigger.LoadingStateChangedEventHandler? LoadingStateChanged;
public event ILoadingTrigger.ProgressChangedEventHandler? ProgressChanged;
public virtual void InitParent(LoaderBase parent)
{
this.parent = parent;
}
// 事件
/// <summary>
/// 当状态改变时,在工作线程触发代码。在添加事件后,必须将 HasOnStateChangedThread 设为 True。
/// </summary>
public event OnStateChangedThreadEventHandler? OnStateChangedThread;
/// <summary>
/// 当状态改变时,在 UI 线程触发代码。
/// </summary>
public event OnStateChangedUiEventHandler? OnStateChangedUi;
/// <summary>
/// 在加载器目标事件执行完成,加载器状态即将变为 Finish 时调用。可以视为扩展加载器目标事件。
/// </summary>
public event PreviewFinishEventHandler? PreviewFinish;
protected void RaisePreviewFinish()
{
PreviewFinish?.Invoke(this);
}
// 状态变化
public abstract void Start(object? input = null, bool isForceRestart = false);
public abstract void Abort();
/// <summary>
/// 无限期地等待加载器完成,直到结束或抛出异常。若加载器尚未开始,则会开始执行。
/// </summary>
public void WaitForExit(object input = null, LoaderBase loaderToSyncProgress = null,
bool isForceRestart = false)
{
Start(input, isForceRestart);
while (State == ModBase.LoadState.Loading)
{
if (loaderToSyncProgress is not null)
loaderToSyncProgress.Progress = Progress;
Thread.Sleep(10);
}
if (State == ModBase.LoadState.Finished)
{
}
else if (State == ModBase.LoadState.Aborted)
{
throw new ThreadInterruptedException("加载器执行已中断。");
}
else if (Error is null)
{
throw new Exception("未知错误!");
}
else
{
throw new Exception(Error.Message, Error);
} // 保留调用堆栈,同时不影响信息输出与单元测试
}
/// <summary>
/// 等待加载器完成,直到结束、抛出异常或超时。若加载器尚未开始,则会开始执行。
/// </summary>
/// <param name="timeout">等待的超时时间,以毫秒为单位。</param>
/// <param name="timeoutMessage">若执行超时,将会抛出的异常信息。</param>
public void WaitForExitTime(int timeout, object input = null, string timeoutMessage = waitForExitTimeoutMessage,
object loaderToSyncProgress = null, bool isForceRestart = false)
{
Start(input, isForceRestart);
while (State == ModBase.LoadState.Loading)
{
if (loaderToSyncProgress is not null)
((dynamic)loaderToSyncProgress).Progress = Progress;
Thread.Sleep(10);
timeout -= 10;
if (timeout < 0)
throw new TimeoutException(timeoutMessage);
}
if (State == ModBase.LoadState.Finished)
{
}
else if (State == ModBase.LoadState.Aborted)
{
throw new ThreadInterruptedException("加载器执行已中断。");
}
else if (Error is null)
{
throw new Exception("未知错误!");
}
else
{
throw Error;
}
}
// 相同重载
public override bool Equals(object obj)
{
var @base = obj as LoaderBase;
return @base is not null && Uuid == @base.Uuid;
}
}
// 说实话,我真的觉得 C# 应该学学 VB 的那种近乎 Java 泛型擦除的兼容性,省掉一堆麻烦
public abstract class LoaderTask : LoaderBase
{
/// <summary>
/// 上次完成加载时的时间。
/// </summary>
public long lastFinishedTime;
/// <summary>
/// 最后一次运行加载器的线程。可能为 Nothing,或线程已结束。
/// </summary>
public Task? lastRunningTask;
/// <summary>
/// 在输入相同时使用原有结果的超时,单位为毫秒。
/// </summary>
public int reloadTimeout = -1;
// 状态指示
/// <summary>
/// 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
/// </summary>
public bool IsAborted => IsAbortedWithThread(Task.CurrentId ?? -1);
/// <summary>
/// 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
/// </summary>
public bool IsAbortedWithThread(int compareTaskId)
{
return lastRunningTask is null || compareTaskId != lastRunningTask.Id ||
State == ModBase.LoadState.Aborted;
}
public abstract bool ShouldStart(ref object? input, bool isForceRestart = false, bool ignoreReloadTimeout = false);
// 装箱!装箱!装箱圣地!
public abstract object? StartGetInputNoType(object? input = null, Func<object>? inputDelegate = null);
}
/// <summary>
/// 用于异步执行并监控单一函数的加载器。
/// </summary>
public class LoaderTask<InputType, OutputType> : LoaderTask
{
// 输入输出
public InputType input;
protected internal Func<InputType?>? inputDelegate;
// 执行事件
protected internal Action<LoaderTask<InputType, OutputType>> loadDelegate;
public OutputType output = default;
private CancellationTokenSource? cancelToken;
// 线程设定
protected internal ThreadPriority threadPriority;
public LoaderTask(string name, Action<LoaderTask<InputType, OutputType>> loadDelegate,
Func<InputType?>? inputDelegate = null, ThreadPriority priority = ThreadPriority.Normal)
{
this.name = name;
this.loadDelegate = loadDelegate;
this.inputDelegate = inputDelegate;
}
// 获取输入
public InputType? StartGetInput(InputType? input = default, Func<InputType?>? inputDelegate = null) // InputDelegate 参数存在匿名调用
{
inputDelegate ??= this.inputDelegate;
// 按照龙猫的逻辑,此处将 input 与默认值直接进行等价比较,若相等则认为 input 未传入具体值,而调用 inputDelegate 获取
// 这种逻辑未考虑值类型恰好传入 default 值 (如 double 传了 0.0) 的情况,这是一个陷阱,可能会产生 undefined behavior
if (EqualityComparer<InputType>.Default.Equals(input, default) && inputDelegate is not null)
ModBase.RunInUiWait(() => input = inputDelegate());
return input;
}
public override object? StartGetInputNoType(object? input = null, Func<object?>? inputDelegate = null)
{
return StartGetInput(input is null ? default : (InputType?)input, inputDelegate is null ? null : () => (InputType?)inputDelegate());
}
// 代码执行
public override bool ShouldStart(ref object? input, bool isForceRestart = false, bool ignoreReloadTimeout = false)
{
// 获取输入
try
{
input = StartGetInput(Conversions.ToGenericParameter<InputType>(input));
}
catch (Exception ex)
{
ModBase.Log(
ex,
"加载输入获取失败(" + name + "",
ModBase.LogLevel.Hint,
userSummary: Lang.Text("Application.Loader.Error.OperationFailed"));
Error = ex;
lock (lockState)
{
State = ModBase.LoadState.Failed;
}
}
// 检验输入以确定情况
if (isForceRestart)
return true; // 强制要求重启
if (input is null != this.input is null || (input is not null && !input.Equals(this.input)))
return true; // 输入不同
if ((State == ModBase.LoadState.Loading || State == ModBase.LoadState.Finished) && (ignoreReloadTimeout ||
reloadTimeout == -1 || lastFinishedTime == 0L ||
TimeUtils.GetTimeTick() - lastFinishedTime < reloadTimeout)) // 正在加载或已结束
// 没有超时
return false; // 则不重试
return true;
// 需要开始
}
public override void Start(object input = null, bool isForceRestart = false)
{
// 确认是否开始加载
if (ShouldStart(ref input, isForceRestart))
{
// 输入不同或失败,开始加载
if (State == ModBase.LoadState.Loading)
TriggerThreadAbort();
this.input = Conversions.ToGenericParameter<InputType>(input);
lock (lockState)
{
State = ModBase.LoadState.Loading;
Progress = -1;
}
}
else return;
// 如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted
// 如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted
lastRunningTask = Task.Run(() =>
{
try
{
isForceRestarting = isForceRestart;
if (ModBase.modeDebug)
ModBase.Log(
$"[Loader] 加载线程 {name} ({Task.CurrentId}) 已{(isForceRestarting ? "强制" : "")}启动");
loadDelegate(this);
if (IsAborted)
{
ModBase.Log(
$"[Loader] 加载线程 {name} ({Task.CurrentId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{(lastRunningTask is null ? -1 : lastRunningTask.Id)}",
ModBase.LogLevel.Developer);
return;
}
if (ModBase.modeDebug)
ModBase.Log($"[Loader] 加载线程 {name} ({Task.CurrentId}) 已完成");
RaisePreviewFinish();
State = ModBase.LoadState.Finished;
lastFinishedTime = TimeUtils.GetTimeTick();
}
catch (ModBase.CancelledException ex)
{
if (ModBase.modeDebug)
ModBase.Log(ex,
$"加载线程 {name} ({Task.CurrentId}) 已触发取消中断,已完成 {Math.Round(Progress * 100d)}%");
if (!IsAborted) State = ModBase.LoadState.Aborted;
}
catch (ThreadInterruptedException ex)
{
if (ModBase.modeDebug)
ModBase.Log(ex,
$"加载线程 {name} ({Task.CurrentId}) 已触发线程中断,已完成 {Math.Round(Progress * 100d)}%");
if (!IsAborted) State = ModBase.LoadState.Aborted;
}
catch (Exception ex)
{
if (IsAborted) return;
ModBase.Log(ex,
$"加载线程 {name} ({Task.CurrentId}) 出错,已完成 {Math.Round(Progress * 100d)}%",
ModBase.LogLevel.Developer);
Error = ex;
State = ModBase.LoadState.Failed;
}
}, (cancelToken ??= new CancellationTokenSource()).Token); // 未中断,本次输出有效
// LastRunningTask.Start(); // 不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
}
public override void Abort()
{
if (State != ModBase.LoadState.Loading)
return;
lock (lockState)
{
State = ModBase.LoadState.Aborted;
}
TriggerThreadAbort();
}
private void TriggerThreadAbort()
{
if (lastRunningTask is null) return;
if (ModBase.modeDebug) ModBase.Log($"[Loader] 加载线程 {name} ({lastRunningTask.Id}) 已中断");
if (!lastRunningTask.IsCompleted) cancelToken?.Cancel();
lastRunningTask = null;
cancelToken = null;
}
}
/// <summary>
/// 支持多个加载器连续运作的复合加载器。
/// </summary>
public class LoaderCombo : LoaderBase
{
public object? input;
public List<LoaderBase> loaders = new();
public LoaderCombo(string name, IEnumerable<LoaderBase> loaders)
{
this.loaders.Clear();
foreach (var Loader in loaders)
if (Loader is not null)
{
this.loaders.Add(Loader);
Loader.OnStateChangedThread += SubTaskStateChanged;
Loader.hasOnStateChangedThread = true;
}
InitParent(null);
this.name = name;
}
public override double Progress
{
get
{
switch (State)
{
case ModBase.LoadState.Waiting:
{
return 0d;
}
case ModBase.LoadState.Loading:
{
var total = 0d;
var finished = 0d;
foreach (var Loader in loaders)
{
total += Loader.ProgressWeight;
finished += Loader.ProgressWeight * Loader.Progress;
}
if (total == 0d)
return 0d;
return finished / total;
}
default:
{
return 1d;
}
}
}
set => throw new Exception("多重加载器不支持设置进度");
}
public override void InitParent(LoaderBase parent)
{
this.parent = parent;
foreach (var Loader in loaders)
Loader.InitParent(this);
}
public override void Start(object input = null, bool isForceRestart = false)
{
isForceRestarting = isForceRestart;
lock (lockState)
{
if (State == ModBase.LoadState.Loading) return;
State = ModBase.LoadState.Loading;
}
// 启动加载
this.input = input;
if (isForceRestart)
foreach (var Loader in loaders)
Loader.State = ModBase.LoadState.Waiting;
ModBase.RunInThread(Update);
}
public override void Abort()
{
lock (lockState)
{
if (State != ModBase.LoadState.Loading && State != ModBase.LoadState.Waiting)
return;
}
foreach (var Loader in loaders) Loader.Abort();
lock (lockState)
{
if (State == ModBase.LoadState.Loading || State == ModBase.LoadState.Waiting)
State = ModBase.LoadState.Aborted;
}
}
/// <summary>
/// 子任务状态变更。
/// </summary>
private void SubTaskStateChanged(LoaderBase loader, ModBase.LoadState newState, ModBase.LoadState oldState)
{
switch (newState)
{
case ModBase.LoadState.Loading:
{
break;
}
// 开始,啥都不干
case ModBase.LoadState.Waiting:
{
break;
}
// 子加载器可能由于外部输入改变而暂时变为 Waiting,之后会立即重新启动
// 所以啥都不干就行
case ModBase.LoadState.Finished:
{
// 正常结束,触发刷新
Update();
break;
}
case ModBase.LoadState.Aborted:
{
// 被中断,这个任务也中断
Abort();
break;
}
default:
{
// 完蛋,出错了
lock (lockState)
{
if (State >= ModBase.LoadState.Finished)
return;
Error = new Exception(loader.name + "失败", loader.Error);
State = loader.State;
}
foreach (var currentLoader in loaders)
{
loader = currentLoader;
loader.Abort();
}
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
return;
}
}
}
/// <summary>
/// 触发一次更新,以启动新加载器或完成。
/// </summary>
private void Update()
{
if (State == ModBase.LoadState.Finished
|| State == ModBase.LoadState.Failed
|| State == ModBase.LoadState.Aborted)
return;
var isFinished = true;
var blocked = false;
object input = this.input;
foreach (var loader in loaders)
switch (loader.State)
{
case ModBase.LoadState.Finished:
{
if (loader is LoaderTask task)
{
var genericArg = task.GetType().GenericTypeArguments.FirstOrDefault();
var shouldInput = input is not null && genericArg == input.GetType()
? input
: null;
if (task.ShouldStart(ref shouldInput, false, true))
{
ModBase.Log("[Loader] 由于输入条件变更,重启已完成的加载器 " + loader.name);
goto Restart;
}
input = ((dynamic)loader).output; // 何意味啊,没法匹配 LoaderTask<,>
}
if (loader.block && !isFinished)
blocked = true;
break;
}
case ModBase.LoadState.Loading:
{
if (loader is LoaderTask task)
{
var genericArg = loader.GetType().GenericTypeArguments.FirstOrDefault();
var shouldInput = input is not null && genericArg == input.GetType()
? input
: null;
if (task.ShouldStart(ref shouldInput, false, true))
{
ModBase.Log($"[Loader] 由于输入条件变更,重启进行中的加载器 {loader.name}",
ModBase.LogLevel.Developer);
goto Restart;
}
}
isFinished = false;
blocked = true;
break;
}
default:
Restart:
isFinished = false;
if (blocked)
continue;
if (input is not null)
{
switch (loader)
{
case LoaderTask:
case LoaderCombo:
var genericArg = loader.GetType().GenericTypeArguments.FirstOrDefault();
loader.Start(
genericArg == input.GetType() ? input : null,
isForceRestarting);
break;
case not null:
loader.Start(
input is List<DownloadFile> ? input : null,
isForceRestarting);
break;
default:
throw new Exception($"未知的加载器类型({loader?.GetType()}");
}
}
else
{
loader.Start(isForceRestart: isForceRestarting);
}
if (loader.block)
blocked = true;
break;
}
if (isFinished)
{
RaisePreviewFinish();
State = ModBase.LoadState.Finished;
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
}
}
/// <summary>
/// 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
/// </summary>
public static void GetLoaderList(LoaderCombo loader, ref List<LoaderBase> list, bool requireShow = true)
{
foreach (var SubLoader in loader.loaders)
{
if (SubLoader.show || !requireShow)
list.Add(SubLoader);
if (SubLoader is LoaderCombo combo)
GetLoaderList(combo, ref list);
}
}
/// <summary>
/// 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
/// </summary>
public void GetLoaderList(ref List<LoaderBase> list, bool requireShow = true)
{
GetLoaderList(this, ref list, requireShow);
}
/// <summary>
/// 获得最底层的,应被显示给用户的加载器列表。
/// </summary>
public List<LoaderBase> GetLoaderList(bool requireShow = true)
{
var list = new List<LoaderBase>();
GetLoaderList(ref list, requireShow);
return list;
}
}
/// <summary>
/// 支持多个加载器连续运作的复合加载器(泛型版本)。
/// </summary>
public class LoaderCombo<InputType> : LoaderCombo
{
public new InputType typedInput;
public LoaderCombo(string name, IEnumerable<LoaderBase> loaders) : base(name, loaders) { }
public override void Start(object input = null, bool isForceRestart = false)
{
this.typedInput = Conversions.ToGenericParameter<InputType>(input);
base.Start(this.typedInput, isForceRestart);
}
}
private struct LoaderFolderDictionaryEntry
{
public DateTime? lastCheckTime;
public string folderPath;
public override bool Equals(object obj)
{
if (obj is not LoaderFolderDictionaryEntry)
return false;
var entry = (LoaderFolderDictionaryEntry)obj;
return EqualityComparer<DateTime?>.Default.Equals(lastCheckTime, entry.lastCheckTime) &&
(folderPath ?? "") == (entry.folderPath ?? "");
}
}
}