feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

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

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

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,106 @@
using System;
namespace PCL.Core.App.IoC;
/// <summary>
/// 注册生命周期服务项,将由生命周期管理统一创建实例,然后在指定生命周期自动启动或加入等待手动启动列表。<br/>
/// 使用此注解的类型必须直接或间接实现 <see cref="ILifecycleService"/> 接口,否则将被忽略。
/// </summary>
/// <param name="startState">详见 <see cref="StartState"/></param>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class LifecycleServiceAttribute(LifecycleState startState) : Attribute
{
/// <summary>
/// 指定该服务项应于何种生命周期状态启动。生命周期管理将在指定的状态按照 <see cref="Priority"/> 自动启动服务项。
/// </summary>
public LifecycleState StartState { get; } = startState;
/// <summary>
/// 启动优先级。同一个生命周期状态有多个服务项需要启动时,将会按优先级数值<b>降序</b>启动,即数值越大越优先。<br/>
/// 虽然这个值可以为任意 32 位整数,但是<b>非核心服务请勿使用较为极端的值,尤其是
/// <c>int.MaxValue</c> <c>int.MinValue</c></b>,这可能导致一些核心服务的启动时机出现问题。
/// </summary>
public int Priority { get; init; } = 0;
}
/// <summary>
/// 标记一个注解,使其成为依赖收集器。<br/>
/// 使用被标记的注解标记的 <see langword="public"/> 类 与 <see langword="public"/> <see langword="static"/>
/// 方法/属性 会被作为依赖收集到一个统一的存储位置,并用于运行时的依赖注入。
/// </summary>
/// <param name="identifier">依赖标记</param>
/// <param name="targets">目标,仅支持类、方法、属性,可以使用 <c>|</c> 分隔以添加多个目标</param>
/// <typeparam name="TDependency">依赖类型</typeparam>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class DependencyCollectorAttribute<TDependency>(string identifier, AttributeTargets targets) : Attribute;
/// <summary>
/// 标记一个方法以自动生成注入操作,该方法需在 <see langword="partial"/> 类中。<br/>
/// 自动生成的注入操作将收集依赖并调用该方法以注入,该方法第一个参数必须为依赖类型,其余参数需与对应依赖收集器匹配。
/// </summary>
/// <param name="identifier">依赖标记</param>
/// <param name="lifecycleAutoInvoke">是否在生命周期启动时自动执行注入</param>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class DependencyInjectionPointAttribute(string identifier, bool lifecycleAutoInvoke = true) : Attribute;
/// <summary>
/// 标记一个 partial 类,自动实现 <see cref="ILifecycleService"/> 接口,并基于其他有标记的方法生成
/// <see cref="ILifecycleService.StartAsync"/> 和 <see cref="ILifecycleService.StopAsync"/> 方法
/// </summary>
/// <param name="identifier">See <see cref="ILifecycleService.Identifier"/></param>
/// <param name="name">See <see cref="ILifecycleService.Name"/></param>
/// <param name="asyncStart">See <see cref="ILifecycleService.SupportAsync"/></param>
[AttributeUsage(AttributeTargets.Class)]
public sealed class LifecycleScopeAttribute(string identifier, string name, bool asyncStart = true) : Attribute;
/// <summary>
/// 标记一个 Start 方法,可以标记多个
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleStartAttribute : Attribute;
/// <summary>
/// 标记一个 Stop 方法,可以标记多个
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleStopAttribute : Attribute;
/// <summary>
/// 标记一个命令处理器,可以标记多个,将自动识别和处理方法参数。<br/>
/// 若方法的第一个参数类型为 <see cref="PCL.Core.App.Cli.CommandLine"/> 则会传入指定指令的命令行模型。<br/>
/// 除命令行模型以外,任何参数在未显式声明默认值时均默认传入 <see langword="default"/> 值,请尽可能为所有参数提供显式默认值。<br/>
/// <b>NOTE</b>: 请勿使用异步实现,返回 Task 的方法将被直接忽略。<br/>
/// <b>NOTE</b>: 该方法可能在任意线程被调用,请注意线程上下文和同步问题。
/// <p/>示例:
/// <code>
/// [LifecycleCommandHandler("foo")]
/// private static void _FooHandler(CommandLine model, string bar, bool flag) {
/// // process logic...
/// // argument example: foo --flag --bar blablabla
/// }
/// </code>
/// </summary>
/// <param name="command">命令名</param>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleCommandHandlerAttribute(string command) : Attribute;
/// <summary>
/// 标记一个依赖注入入口,可以标记多个。
/// <p/>示例:
/// <code>
/// [LifecycleDependencyInjection("some-property", AttributeTargets.Property)]
/// private static void _LoadProperties(ImmutableList&lt;(PropertyAccessor&lt;string&gt; prop, string name)&gt; items)
/// {
/// // process logic...
/// }
/// [LifecycleDependencyInjection("some-method", AttributeTargets.Method)]
/// private static void _LoadMethods(ImmutableList&lt;(Action method, string name)&gt; items)
/// {
/// // process logic...
/// }
/// </code>
/// </summary>
/// <param name="identifier">依赖标识符</param>
/// <param name="targets">依赖类型,可以使用 <c>|</c> 连接多个</param>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleDependencyInjectionAttribute(string identifier, AttributeTargets targets) : Attribute;
@@ -0,0 +1,9 @@
namespace PCL.Core.App.IoC;
public abstract class DependencyArguments
{
}
public class DependencyArguments<TArguments> : DependencyArguments
{
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
public abstract class DependencyGroup
{
public abstract void InvokeInjection(Delegate injection);
}
public class DependencyGroup<TValue> : DependencyGroup
{
public required ImmutableList<TValue> Items { get; init; }
public override void InvokeInjection(Delegate injection)
{
if (injection is Action<ImmutableList<TValue>> action) action(Items);
else if (injection is Func<ImmutableList<TValue>, Task> awaitableAction) awaitableAction(Items).Wait();
else throw new InvalidCastException($"Injection point signature mismatch, must be: void/Task (ImmutableList<{typeof(TValue).Name}>)");
}
}
public class DependencyGroup<TValue, TArguments> : DependencyGroup
{
public required ImmutableList<(TValue value, TArguments args)> Items { get; init; }
public override void InvokeInjection(Delegate injection)
{
if (injection is Action<ImmutableList<(TValue, TArguments)>> action) action(Items);
else if (injection is Func<ImmutableList<(TValue, TArguments)>, Task> awaitableAction) awaitableAction(Items).Wait();
else throw new InvalidCastException($"Injection point signature mismatch, must be: void/Task (ImmutableList<({typeof(TValue).Name}, {typeof(TArguments).Name})>)");
}
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PCL.Core.App.IoC;
public static partial class DependencyGroups
{
private static readonly AttributeTargets[] _TargetsToTry = [
AttributeTargets.Method,
AttributeTargets.Property,
AttributeTargets.Class
];
public static bool InvokeInjection(Delegate injection, string identifier, AttributeTargets targets)
{
if (!_GroupMap.TryGetValue(identifier, out var groups)) return false;
var targetGroups = new List<DependencyGroup>();
foreach (var targetToTry in _TargetsToTry.Where(t => targets.HasFlag(t)))
{
if (groups.GetValueOrDefault(targetToTry) is not { } group) return false;
targetGroups.Add(group);
}
foreach (var group in targetGroups) group.InvokeInjection(injection);
return true;
}
}
@@ -0,0 +1,63 @@
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
/// <summary>
/// General service class for constructing <see cref="ILifecycleService"/> in a more convenient way.
/// </summary>
public abstract class GeneralService : ILifecycleService
{
/// <inheritdoc/>
public string Identifier { get; }
/// <inheritdoc/>
public string Name { get; }
/// <inheritdoc/>
public bool SupportAsync { get; }
/// <summary>
/// The context of the service instance,
/// used for declaration, logging, lifecycle operation, etc.
/// </summary>
protected LifecycleContext ServiceContext { get; }
/// <summary>
/// Initialize a general service instance.
/// This constructor should only be extended rather than invoked directly.
/// </summary>
/// <param name="identifier">see <see cref="Identifier"/></param>
/// <param name="name">see <see cref="Name"/></param>
/// <param name="asyncStart">see <see cref="SupportAsync"/></param>
protected GeneralService(string identifier, string name, bool asyncStart = true)
{
Identifier = identifier;
Name = name;
SupportAsync = asyncStart;
ServiceContext = Lifecycle.GetContext(this);
}
/// <summary>
/// Start the service, will be invoked on the specified state
/// from <see cref="LifecycleServiceAttribute.StartState"/> of <see cref="LifecycleServiceAttribute"/>.
/// </summary>
public virtual void Start() { }
/// <summary>
/// Stop the service, will be invoked while program exiting,
/// or if <see cref="Start"/> throws an exception.
/// </summary>
public virtual void Stop() { }
public Task StartAsync()
{
Start();
return Task.CompletedTask;
}
public Task StopAsync()
{
Stop();
return Task.CompletedTask;
}
}
@@ -0,0 +1,26 @@
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
/// <summary>
/// 启动器生命周期管理
/// </summary>
[LifecycleService(LifecycleState.BeforeLoading, Priority = int.MaxValue)]
public sealed partial class Lifecycle : ILifecycleService
{
public string Identifier => "lifecycle";
public string Name => "生命周期";
public bool SupportAsync => false;
private static LifecycleContext? _context;
private Lifecycle() { _context = GetContext(this); }
private static LifecycleContext Context => _context ?? SystemContext;
public Task StartAsync() => Task.CompletedTask;
public Task StopAsync()
{
_context = null;
return Task.CompletedTask;
}
}
@@ -0,0 +1,132 @@
using System;
using System.Threading.Tasks;
using PCL.Core.Logging;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private class SystemLifecycleService : ILifecycleService
{
public string Name => "系统";
public string Identifier => "system";
public bool SupportAsync => false;
public Task StartAsync() => Task.CompletedTask;
public Task StopAsync() => Task.CompletedTask;
}
private static readonly ILifecycleService _SystemService = new SystemLifecycleService();
private static readonly LifecycleServiceInfo _SystemServiceInfo = new(_SystemService, LifecycleState.BeforeLoading);
/// <summary>
/// 系统默认上下文,无特殊需求请勿使用。
/// </summary>
internal static readonly LifecycleContext SystemContext = GetContext(_SystemService);
/// <summary>
/// 获取指定服务项对应的上下文实例用于日志输出、多任务通信等。一般情况下只推荐获取自身上下文。
/// </summary>
/// <param name="self">服务项实例</param>
/// <returns>上下文实例</returns>
public static LifecycleContext GetContext(ILifecycleService self) => new(
service: self,
onLog: item =>
{
lock (_PendingLogs)
{
if (_logService is null) _PendingLogs.Add(item);
else _PushLog(item, _logService);
}
if (item.ActionLevel == ActionLevel.MsgBoxFatal) _FatalExit();
},
onRequestExit: statusCode =>
{
if (CurrentState != LifecycleState.BeforeLoading)
throw new InvalidOperationException("只能在 BeforeLoading 时请求退出");
Context.Info($"{_ServiceName(self)} 已请求退出程序");
_Exit(statusCode);
},
onRequestRestart: args =>
{
_hasRequestedRestart = true;
_requestRestartService = self;
_requestRestartArguments = args;
},
onDeclareStopped: () =>
{
var identifier = self.Identifier;
if (GetServiceInfo(identifier)?.Identifier == identifier)
throw new InvalidOperationException("只能在服务启动阶段调用");
_DeclaredStoppedServices.Add(self);
},
onRequestStopLoading: () =>
{
if (CurrentState != LifecycleState.BeforeLoading)
throw new InvalidOperationException("只能在 BeforeLoading 时请求停止加载");
Context.Info($"{_ServiceName(self)} 已请求停止继续加载");
_hasRequestedStopLoading = true;
}
);
}
/// <summary>
/// 若要获取服务项自身的上下文实例,请使用 <see cref="Lifecycle.GetContext"/> 。
/// </summary>
public class LifecycleContext(
ILifecycleService service,
Action<LifecycleLogItem> onLog,
Action<int> onRequestExit,
Action<string?> onRequestRestart,
Action onDeclareStopped,
Action onRequestStopLoading)
{
#region
public void CustomLog(
string message,
Exception? ex = null,
LogLevel level = LogLevel.Trace,
ActionLevel? actionLevel = null
) => onLog(new LifecycleLogItem(service, message, ex, level, actionLevel ?? level.DefaultActionLevel()));
public void Trace(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Trace, actionLevel);
public void Debug(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Debug, actionLevel);
public void Info(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Info, actionLevel);
public void Warn(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Warning, actionLevel);
public void Error(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Error, actionLevel);
public void Fatal(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Fatal, actionLevel);
#endregion
/// <summary>
/// 服务项自身实例。
/// </summary>
public ILifecycleService ServiceInstance => service;
/// <summary>
/// 请求退出程序。仅可在 <see cref="LifecycleState.BeforeLoading"/> 状态调用。
/// </summary>
/// <param name="statusCode">程序返回的状态码</param>
/// <exception cref="InvalidOperationException">尝试在非 <see cref="LifecycleState.BeforeLoading"/> 状态调用</exception>
public void RequestExit(int statusCode = 0) => onRequestExit(statusCode);
/// <summary>
/// 请求在程序退出时重启。调用该方法后,程序将在正常退出流程中自动执行重启,通常与退出程序结合使用。
/// </summary>
/// <param name="arguments">重启进程时使用的命令行参数</param>
public void RequestRestartOnExit(string? arguments = null) => onRequestRestart(arguments);
/// <summary>
/// 标记自身已经结束运行。调用该方法将会直接从正在运行列表中移除该服务项,后续的
/// <c>Stop</c> 等均不会触发。仅可在服务启动阶段即 <c>Start</c> 方法结束前调用。
/// </summary>
/// <exception cref="InvalidOperationException">尝试在非启动阶段调用</exception>
public void DeclareStopped() => onDeclareStopped();
/// <summary>
/// 请求停止继续加载。多用于初始阶段在非 STA 线程中运行的服务接管进程整个生命周期,仅可在
/// <see cref="LifecycleState.BeforeLoading"/> 状态调用。
/// </summary>
/// <exception cref="InvalidOperationException">尝试在非 <see cref="LifecycleState.BeforeLoading"/> 状态调用</exception>
public void RequestStopLoading() => onRequestStopLoading();
}
@@ -0,0 +1,197 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static DateTime? _countRunningStart;
private static bool _isApplicationStarted = false;
private static bool _isLoadingStarted = false;
private static bool _isWindowCreated = false;
private static bool _hasRequestedStopLoading = false;
/// <summary>
/// [请勿调用] 处理未捕获异常流程
/// </summary>
/// <param name="ex">异常对象</param>
public static void OnException(object ex)
{
Context.Fatal("未捕获的异常", ex as Exception);
}
/// <summary>
/// [请勿调用] 程序初始化流程
/// </summary>
public static void OnInitialize()
{
// 检测重复调用
if (_isApplicationStarted) return;
_isApplicationStarted = true;
// 修改 STA 线程名
Thread.CurrentThread.Name = "STA";
// 注册全局事件
AppDomain.CurrentDomain.UnhandledException += (_, e) => OnException(e.ExceptionObject);
AppDomain.CurrentDomain.ProcessExit += (_, _) => _Exit();
TaskScheduler.UnobservedTaskException += (_, e) =>
{
Context.Error("未观测到的异步任务异常", e.Exception);
e.SetObserved();
};
// 添加系统服务
_RunningServiceInfoMap["system"] = _SystemServiceInfo;
// 实例化并存储手动服务
foreach (var service in _GetServiceTypes(LifecycleState.Manual))
{
var instance = _CreateService(service);
var identifier = instance.Identifier;
if (_ManualServiceMap.TryAdd(identifier, instance)) continue;
Context.Warn($"{_ServiceName(instance, LifecycleState.Manual)} 标识符重复,已跳过");
}
// 运行预加载服务
_StartStateFlow(LifecycleState.BeforeLoading);
if (_hasRequestedStopLoading) return;
// 运行应用程序容器
var statusCode = CurrentApplication.Run();
if (!HasShutdownStarted) _Exit(statusCode);
}
/// <summary>
/// [请勿调用] 组件加载流程
/// </summary>
public static void OnLoading()
{
// 检测重复调用
if (_isLoadingStarted) return;
_isLoadingStarted = true;
// 运行加载阶段服务
_StartStateFlow(LifecycleState.Loading, LifecycleState.WindowCreating);
// 运行窗体
CurrentApplication.MainWindow!.Show();
}
/// <summary>
/// [请勿调用] 窗口创建结束流程
/// </summary>
public static void OnWindowCreated()
{
// 检测重复调用
if (_isWindowCreated) return;
_isWindowCreated = true;
// 启动窗口流程后的服务项
_StartStateFlow(LifecycleState.WindowCreated);
_countRunningStart = DateTime.Now;
_StartWorker(LifecycleState.Running, LifecycleState.Exiting, false);
}
private static bool _hasRequestedRestart = false;
private static string? _requestRestartArguments;
private static ILifecycleService? _requestRestartService;
private static readonly object _ExitLock = new();
/// <summary>
/// WPF 应用程序容器,在 <see cref="LifecycleState.BeforeLoading"/> 阶段为空值
/// </summary>
public static Application CurrentApplication { get; set; } = null!;
/// <summary>
/// 是否正在关闭程序
/// </summary>
public static bool HasShutdownStarted { get; private set; } = false;
/// <summary>
/// 正在进行的关闭程序流程是否是强制关闭
/// </summary>
public static bool IsForceShutdown { get; private set; } = false;
private static void _FatalExit()
{
ForceShutdown(-1);
}
private static void _Exit(int statusCode = 0)
{
lock (_ExitLock)
{
if (HasShutdownStarted) return;
HasShutdownStarted = true;
}
// 结束 Running 计时
if (_countRunningStart is { } start)
{
var countSpan = DateTime.Now - start;
_LogStateCount(countSpan, LifecycleState.Running);
}
// 开始 Exiting 状态
_StartStateFlow(LifecycleState.Exiting, count: false);
// 停止服务
Context.Debug("正在停止运行中的服务");
ILifecycleLogService? logService = null;
while (_StartedServiceStack.TryPop(out var service))
{
// 跳过已标记为停止的服务
if (_RunningServiceInfoMap.TryGetValue(service.Identifier, out var info) && info.IsStopped) continue;
// 跳过日志服务
if (service is ILifecycleLogService ls)
{
Context.Trace($"已跳过日志服务: {_ServiceName(ls)}");
logService = ls;
continue;
}
// 执行停止流程
_StopService(service, service.SupportAsync);
}
_WaitStoppingServiceTasks();
if (logService is not null)
{
Context.Trace("退出过程已结束,正在停止日志服务");
// 直接调用 StopAsync() 不使用常规停止实现 以保证正常情况下不会向等待区输出日志
logService.StopAsync().ConfigureAwait(false).GetAwaiter().GetResult();
Console.WriteLine("[Lifecycle] Log service stopped");
}
_SavePendingLogs();
if (_hasRequestedRestart && _requestRestartService is { } s)
{
Console.WriteLine($"[Lifecycle] Requested by '{s.Identifier}', restarting the program...");
_RunCurrentExecutable(_requestRestartArguments);
}
// 退出程序
Console.WriteLine($"[Lifecycle] Exiting program with status: {statusCode}");
// 执行正常退出
if (statusCode == -1) Basics.CurrentProcess.Kill();
else Environment.Exit(statusCode);
// 保险起见,只要运行环境正常根本不可能执行到这里,但是永远都不能假设用户的环境是正常的
Console.WriteLine("[Lifecycle] Warning! Abnormal behaviour, try to kill process 1s later.");
Thread.Sleep(1000);
_KillCurrentProcess();
}
/// <summary>
/// 发起关闭程序流程。<br/>
/// <see cref="LifecycleState.BeforeLoading"/> 状态请使用 <see cref="LifecycleContext.RequestExit"/>。
/// </summary>
/// <param name="statusCode">退出状态码 (返回值)</param>
/// <param name="force">指定是否强制关闭,即不执行 WPF 标准关闭流程</param>
/// <exception cref="InvalidOperationException">尝试在 <see cref="LifecycleState.BeforeLoading"/> 状态调用</exception>
public static void Shutdown(int statusCode = 0, bool force = false)
{
if (CurrentState == LifecycleState.BeforeLoading) throw new InvalidOperationException();
if (HasShutdownStarted) return;
Context.Info(force ? "开始强制关闭程序" : "正在关闭程序");
IsForceShutdown = force;
if (force) _Exit(statusCode);
else new Thread(() => _Exit(statusCode)) { Name = "Lifecycle/Shutdown" }.Start();
}
/// <summary>
/// 强制关闭程序,不执行 WPF 标准关闭流程。
/// </summary>
/// <param name="statusCode">退出状态码 (返回值)</param>
/// <exception cref="InvalidOperationException">尝试在 <see cref="LifecycleState.BeforeLoading"/> 时调用</exception>
public static void ForceShutdown(int statusCode = 0) => Shutdown(statusCode, true);
}
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PCL.Core.Logging;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static ILifecycleLogService? _logService;
private static readonly List<LifecycleLogItem> _PendingLogs = [];
private static void _PushLog(LifecycleLogItem item, ILifecycleLogService service)
{
service.OnLog(item);
}
public static string PendingLogDirectory { get; set; } = @"PCL\Log";
public static string PendingLogFileName { get; set; } = "LastPending.log";
/// <summary>
/// 日志服务启动状态
/// </summary>
public static bool IsLogServiceStarted => _logService is not null;
private static void _SavePendingLogs()
{
if (_PendingLogs.Count == 0)
{
Console.WriteLine("[Lifecycle] No pending logs");
return;
}
try
{
// 直接写入剩余未输出日志到程序目录
var path = Path.Combine(PendingLogDirectory, PendingLogFileName);
if (!Path.IsPathRooted(path)) path = Path.Combine(Basics.ExecutableDirectory, path);
Directory.CreateDirectory(Basics.GetParentPathOrDefault(path));
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
using var writer = new StreamWriter(stream, Encoding.UTF8);
foreach (var item in _PendingLogs) writer.WriteLine(item.ComposeMessage());
Console.WriteLine($"[Lifecycle] Pending logs saved to {path}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine("[Lifecycle] Error saving pending logs, writing to stdout...");
foreach (var item in _PendingLogs) Console.WriteLine(item.ComposeMessage());
}
}
}
/// <summary>
/// 生命周期日志项
/// </summary>
/// <param name="Source">日志来源</param>
/// <param name="Message">日志内容</param>
/// <param name="Exception">相关异常</param>
/// <param name="Level">日志等级</param>
/// <param name="ActionLevel">行为等级</param>
public readonly record struct LifecycleLogItem(
ILifecycleService? Source,
string Message,
Exception? Exception,
LogLevel Level,
ActionLevel ActionLevel)
{
/// <summary>
/// 创建该日志项的时间
/// </summary>
public DateTime Time { get; } = DateTime.Now;
/// <summary>
/// 创建该日志项的 Task ID 或线程名
/// </summary>
public string ContextName { get; } =
(Task.CurrentId is { } id ? $"TSK#{id}" : null)
?? Thread.CurrentThread.Name
?? $"#{Environment.CurrentManagedThreadId}";
public override string ToString()
{
var source = (Source is null) ? "" : $" [{Source.Name}|{Source.Identifier}]";
var basic = $"[{Time:HH:mm:ss.fff}]{source}";
return Exception is null ? $"{basic} {Message}" : $"{basic} ({Message}) {Exception.GetType().FullName}: {Exception.Message}";
}
public string ComposeMessage()
{
var source = (Source is null) ? "" : $" [{Source.Name}|{Source.Identifier}]";
var result = $"[{Time:HH:mm:ss.fff}] [{Level.RealLevel().PrintName()}] [{ContextName}]{source} {Message}";
if (Exception is not null) result += $"\n{Exception}";
return result;
}
}
/// <summary>
/// 日志服务专用接口。整个生命周期只能有一个日志服务,若出现第二个将会报错。
/// </summary>
public interface ILifecycleLogService : ILifecycleService
{
/// <summary>
/// 记录日志的事件
/// </summary>
public void OnLog(LifecycleLogItem item);
}
@@ -0,0 +1,26 @@
using System;
using System.Diagnostics;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static void _RunCurrentExecutable(string? arguments)
{
var fileName = Environment.ProcessPath!;
if (arguments is null) Process.Start(fileName);
else Process.Start(fileName, arguments);
}
private static void _KillCurrentProcess()
{
var psi = new ProcessStartInfo
{
FileName = "taskkill.exe",
Arguments = $"/f /t /pid {Environment.ProcessId}",
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(psi);
}
}
@@ -0,0 +1,402 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static readonly ConcurrentDictionary<string, LifecycleServiceInfo> _RunningServiceInfoMap = [];
private static readonly ConcurrentStack<ILifecycleService> _StartedServiceStack = [];
private static readonly Dictionary<string, ILifecycleService> _ManualServiceMap = [];
private static readonly HashSet<ILifecycleService> _DeclaredStoppedServices = [];
private static readonly ConcurrentDictionary<string, Exception> _ServiceLastExceptionMap = [];
/// <summary>
/// 所有正在运行的服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)
/// </summary>
public static ICollection<string> RunningServices => _RunningServiceInfoMap.Keys;
/// <summary>
/// 服务项启动前触发的事件。<br/>
/// 该事件会在与服务项初始化相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStarting;
/// <summary>
/// 服务项启动后触发的事件。<br/>
/// 该事件会在与服务项初始化相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStarted;
/// <summary>
/// 服务项停止前触发的事件。特别地,主动声明停止 (<see cref="LifecycleContext.DeclareStopped"/>) 的服务不会触发该事件。<br/>
/// 该事件会在与服务项停止相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStopping;
/// <summary>
/// 服务项停止后触发的事件。特别地,主动声明停止 (<see cref="LifecycleContext.DeclareStopped"/>) 的服务不会触发该事件。<br/>
/// 该事件会在与服务项停止相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStopped;
/// <summary>
/// 服务项主动声明停止 (<see cref="LifecycleContext.DeclareStopped"/>) 触发的事件。<br/>
/// 该事件会在与服务项初始化相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceDeclaredStopped;
/// <summary>
/// 服务项抛出异常时触发的事件。<br/>
/// 该事件会在服务抛出异常的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string, Exception>? ServiceUnhandledException;
private static string _ServiceName(ILifecycleService service, LifecycleState? state = null)
{
#if DEBUG
var info = GetServiceInfo(service.Identifier);
if (info is not null) state = info.StartState;
var stateText = (state is null) ? "" : $"{state}/";
return $"{service.Name} ({stateText}{service.Identifier})";
#else
return service.Name;
#endif
}
private static Task _StartServiceTask(ILifecycleService service, bool manual = false)
{
ILifecycleLogService? logService = null;
// 检测日志服务
if (service is ILifecycleLogService ls)
{
if (_logService is not null) throw new InvalidOperationException("日志服务只能有一个");
logService = ls;
}
var state = manual ? LifecycleState.Manual : CurrentState;
var name = _ServiceName(service, state);
// 确保不存在重复的标识符
lock (_ManualServiceMap) {
if (_ManualServiceMap.ContainsKey(service.Identifier) && IsServiceRunning(service.Identifier))
{
Context.Warn($"{name} 标识符重复,已跳过");
return Task.CompletedTask;
}
// 先找个东西占着防止异步加载中检测逻辑失效
_RunningServiceInfoMap[service.Identifier] = _SystemServiceInfo;
}
// 运行服务项并添加到正在运行列表
return service.SupportAsync ? Task.Run(AsyncCall) : AsyncCall();
async Task AsyncCall()
{
try
{
Context.Trace($"正在启动 {name}");
ServiceStarting?.Invoke(service.Identifier);
await service.StartAsync().ConfigureAwait(!service.SupportAsync);
var serviceInfo = new LifecycleServiceInfo(service, state);
Context.Debug($"{name} 启动成功");
if (_DeclaredStoppedServices.Contains(service))
{
_DeclaredStoppedServices.Remove(service);
ServiceDeclaredStopped?.Invoke(service.Identifier);
Context.Trace($"{name} 已中止");
}
else
{
// 若该服务未声明自己已结束运行,将其添加到正在运行列表
_StartedServiceStack.Push(service);
_RunningServiceInfoMap[service.Identifier] = serviceInfo;
ServiceStarted?.Invoke(service.Identifier);
}
}
catch (Exception ex)
{
Context.Warn($"{name} 启动失败,尝试停止", ex);
// 存储异常并停止服务
_UpdateLastException(service, ex);
_StopService(service, false);
}
// 若日志服务已启动则清空日志缓冲
if (logService is null) return;
lock (_PendingLogs)
{
foreach (var item in _PendingLogs) _PushLog(item, logService);
_PendingLogs.Clear();
_logService = logService;
}
}
}
private static Type[] _GetServiceTypes(LifecycleState state) => LifecycleServiceTypes.GetServiceTypes(state);
private static ILifecycleService _CreateService(Type type)
{
var fullname = type.FullName;
try
{
SystemContext.Trace($"正在实例化 {fullname}");
var instance = (ILifecycleService)Activator.CreateInstance(type, true)!;
var supportAsyncText = instance.SupportAsync ? "异步" : "同步";
SystemContext.Trace($"实例化完成: {instance.Name} ({instance.Identifier}), 启动方式: {supportAsyncText}");
return instance;
}
catch (Exception ex)
{
SystemContext.Fatal($"注册服务项实例化失败: {fullname}", ex);
throw;
}
}
private static void _LogStateCount(TimeSpan count, LifecycleState state)
{
Context.Debug($"状态 {state} 共用时 {Math.Round(count.TotalMilliseconds)} ms");
}
private static void _InitializeAndStartStateServices(LifecycleState state)
{
var types = _GetServiceTypes(state);
if (types.Length == 0) return; // 跳过空列表
var asyncInstances = new List<ILifecycleService>();
// 运行非异步启动服务并存储异步启动服务
foreach (var service in types)
{
var instance = _CreateService(service);
if (instance.SupportAsync) asyncInstances.Add(instance);
else _StartServiceTask(instance).ConfigureAwait(false).GetAwaiter().GetResult();
if (_hasRequestedStopLoading) return; // 若请求停止加载则提前结束
}
// 运行异步启动服务并等待所有服务启动完成
Task.WaitAll(asyncInstances.Select(instance => _StartServiceTask(instance)).ToArray());
}
private static void _StartStateFlow(LifecycleState start, LifecycleState? end = null, bool count = true)
{
var index = (int)start;
var endIndex = end is null ? index : (int)end;
while (index <= endIndex)
{
DateTime? countStart = count ? DateTime.Now : null; //开始计时
var state = (LifecycleState)index;
_NextState(state);
_InitializeAndStartStateServices(state);
if (countStart is { } s)
{
var countSpan = DateTime.Now - s; // 结束计时
_LogStateCount(countSpan, state);
}
index++;
}
}
private static void _StartWorker(LifecycleState state, LifecycleState? wait = null, bool count = true)
{
new Thread(() =>
{
_StartStateFlow(state, count: count);
if (wait is { } w) WaitForState(w);
})
{ IsBackground = true, Name = $"Lifecycle/{state}" }.Start();
}
private static void _RemoveRunningInstance(ILifecycleService service)
{
_RunningServiceInfoMap.TryRemove(service.Identifier, out var removed);
removed?.MarkAsStopped();
}
private static readonly ConcurrentBag<Task> _StoppingServiceTasks = [];
private static void _WaitStoppingServiceTasks()
{
Task.WaitAll(_StoppingServiceTasks.ToArray());
}
private static void _StopService(ILifecycleService service, bool async, bool manual = false)
{
var name = _ServiceName(service, manual ? LifecycleState.Manual : CurrentState);
if (async) _StoppingServiceTasks.Add(Task.Run(Stop));
else Stop().ConfigureAwait(false).GetAwaiter().GetResult();
return;
async Task Stop()
{
try
{
Context.Trace($"正在停止 {name}");
ServiceStopping?.Invoke(service.Identifier);
await service.StopAsync().ConfigureAwait(!async);
ServiceStopped?.Invoke(service.Identifier);
Context.Debug($"{name} 已停止");
}
catch (Exception ex)
{
// 若出错则存储异常并忽略
Context.Warn($"停止 {name} 时出错,已跳过", ex);
_UpdateLastException(service, ex);
}
// 从正在运行列表移除
_RemoveRunningInstance(service);
}
}
private static void _UpdateLastException(ILifecycleService service, Exception ex)
{
_ServiceLastExceptionMap[service.Identifier] = ex;
ServiceUnhandledException?.Invoke(service.Identifier, ex);
}
/// <summary>
/// 检查指定标识符的服务项是否正在运行
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <returns>服务项是否正在运行</returns>
public static bool IsServiceRunning(string identifier) => _RunningServiceInfoMap.ContainsKey(identifier);
/// <summary>
/// 根据标识符获取正在运行的服务项的相关信息
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <returns>服务项信息</returns>
public static LifecycleServiceInfo? GetServiceInfo(string? identifier)
{
if (identifier is null) return null;
_RunningServiceInfoMap.TryGetValue(identifier, out var info);
return info;
}
/// <summary>
/// 获取服务项初始化或结束逻辑的最后一次异常。
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <returns>异常实例,若未出现异常则为 <c>null</c></returns>
public static Exception? GetServiceLastException(string identifier)
{
var result = _ServiceLastExceptionMap.TryGetValue(identifier, out var ex);
return result ? ex : null;
}
/// <summary>
/// 手动请求启动一个周期为 <see cref="LifecycleState.Manual"/> 的服务项。
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <param name="async">是否异步启动,默认遵循服务项自身的声明</param>
/// <returns>是否成功请求启动,若该服务项正在运行或周期不是 <see cref="LifecycleState.Manual"/> 则无法启动</returns>
public static bool StartService(string identifier, bool? async = null)
{
_ManualServiceMap.TryGetValue(identifier, out var service);
if (service is null || IsServiceRunning(identifier)) return false;
async ??= service.SupportAsync;
if (async == true) Task.Run(() => _StartServiceTask(service, true));
else _StartServiceTask(service, true);
return true;
}
/// <summary>
/// 手动请求停止一个周期为 <see cref="LifecycleState.Manual"/> 的服务项。
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <param name="async">是否异步停止,默认为 <c>true</c></param>
/// <returns>是否成功请求停止,若该服务项未运行或周期不是 <see cref="LifecycleState.Manual"/> 则无法停止</returns>
public static bool StopService(string identifier, bool async = true)
{
_ManualServiceMap.TryGetValue(identifier, out var service);
if (service is null || !IsServiceRunning(identifier)) return false;
_StopService(service, async, true);
return true;
}
/// <summary>
/// 运行自定义服务项。该服务项将使用当前生命周期状态作为启动状态,若无特殊需求请尽可能不要使用,而是直接注册服务项。
/// </summary>
/// <param name="service">服务项实例</param>
/// <returns>是否成功请求运行,若标识符与正在运行的服务或已注册的手动服务冲突则无法运行。</returns>
public static bool StartCustomService(ILifecycleService service)
{
if (IsServiceRunning(service.Identifier) || _ManualServiceMap.ContainsKey(service.Identifier)) return false;
_StartServiceTask(service);
return true;
}
}
/// <summary>
/// 用于特定生命周期的服务模型。<br/>
/// 实现特殊的子接口 <see cref="ILifecycleLogService"/> 以声明自己是日志服务。
/// </summary>
public interface ILifecycleService
{
/// <summary>
/// 全局唯一标识符,统一使用纯小写字母与 “-” 的命名格式,如 <c>logger</c> <c>yggdrasil-server</c> 等。
/// </summary>
public string Identifier { get; }
/// <summary>
/// 友好名称,如 “日志” “验证服务端” 等,将会用于记录日志等场合。
/// </summary>
public string Name { get; }
/// <summary>
/// 声明该服务是否支持异步启动。
/// 每个生命周期均会依次同步启动不支持异步启动的服务,然后依次异步启动支持异步启动的服务,启动的执行顺序遵循声明的优先级。<br/>
/// 支持异步启动对启动器整体启动速度有一定帮助,在允许的情况下应尽最大可能支持。
/// </summary>
public bool SupportAsync { get; }
/// <summary>
/// 启动该服务。应由生命周期管理自动调用,若无特殊情况,请勿手动调用。
/// </summary>
public Task StartAsync();
/// <summary>
/// 停止该服务。应由生命周期管理自动调用,若无特殊情况,请勿手动调用。
/// </summary>
public Task StopAsync();
}
/// <summary>
/// 生命周期服务项的信息记录
/// </summary>
public record LifecycleServiceInfo
{
private readonly ILifecycleService _service;
public string Identifier => _service.Identifier;
public string Name => _service.Name;
public bool CanStartAsync => _service.SupportAsync;
public LifecycleState StartState { get; }
/// <summary>
/// 服务开始运行的时间。初始值为调用 <c>Start()</c> 方法的时刻,在 <c>Start()</c> 方法结束之后会更新一次。
/// </summary>
public DateTime StartTime { get; init; } = DateTime.Now;
/// <summary>
/// 附带启动状态的完整标识符。
/// </summary>
public string FullIdentifier => $"{StartState}/{Identifier}";
/// <summary>
/// 服务是否正常运行,若已停止则该值为 <c>false</c>,否则为 <c>true</c>。
/// </summary>
public bool IsStopped { get; private set; } = false;
/// <summary>
/// 将该服务标记为已停止,将不会在程序退出流程中调用该服务的 <c>Stop()</c> 方法。
/// </summary>
public void MarkAsStopped() => IsStopped = true;
/// <summary>
/// 本 record 应由生命周期管理自动构造,若无特殊情况,请勿手动调用。
/// </summary>
/// <param name="service">生命周期服务项实例</param>
/// <param name="startState">启动的生命周期状态</param>
public LifecycleServiceInfo(ILifecycleService service, LifecycleState startState)
{
_service = service;
StartState = startState;
StartTime = DateTime.Now;
}
}
@@ -0,0 +1,177 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
/// <summary>
/// 生命周期状态改变时触发的事件。<br/>
/// <b>非异步执行,请注意自行实现必要的异步,否则会卡住生命周期管理线程。</b>
/// </summary>
public static event Action<LifecycleState>? StateChanged;
/// <summary>
/// 当前的生命周期状态,会随生命周期变化随时更新。
/// </summary>
public static LifecycleState CurrentState
{
get;
private set
{
Context.Debug($"状态改变: {value}");
field = value;
try
{
StateChanged?.Invoke(value);
}
catch (Exception ex)
{
Context.Warn("状态更改事件出错", ex);
}
}
} = LifecycleState.BeforeLoading;
private static void _NextState(LifecycleState? enforce = null)
{
if (enforce is { } state) CurrentState = state;
else CurrentState++;
}
/// <summary>
/// 阻塞当前线程并等待到达指定生命周期状态。
/// </summary>
/// <param name="state">指定生命周期状态</param>
/// <returns>
/// 是否真正“等待”过(若调用该方法时已经到达或晚于指定状态,则为 <c>false</c>
/// </returns>
public static bool WaitForState(LifecycleState state)
{
if (CurrentState >= state) return false; // 如果已经是目标状态,直接返回
using var mre = new ManualResetEventSlim(false);
StateChanged += TempHandler;
try { mre.Wait(); } // 等待 Set() 方法
finally { StateChanged -= TempHandler; } // 取消订阅,避免内存泄漏或重复唤醒
return true;
void TempHandler(LifecycleState s)
{
// ReSharper disable once AccessToDisposedClosure
if (s == state) mre.Set();
}
}
/// <summary>
/// 异步等待到达指定生命周期状态。
/// </summary>
/// <param name="state">指定生命周期状态</param>
/// <returns>
/// 结果表示是否真正“等待”过的 <see cref="Task"/> 实例(若调用该方法时已经到达或晚于指定状态,则结果为 <c>false</c>
/// </returns>
public static Task<bool> WaitForStateAsync(LifecycleState state)
{
if (CurrentState >= state) return Task.FromResult(false); // 如果已经是目标状态,则直接返回 false
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
StateChanged += TempHandler;
return tcs.Task;
void TempHandler(LifecycleState s)
{
if (s != state) return;
StateChanged -= TempHandler;
tcs.TrySetResult(true);
}
}
/// <summary>
/// 快速注册改变到目标生命周期状态的事件,与直接注册 <see cref="StateChanged"/> 的区别是会自动判断目标状态并自动移除事件注册。
/// </summary>
/// <param name="when">目标生命周期状态</param>
/// <param name="action">事件触发委托</param>
public static void When(LifecycleState when, Action action)
{
if (CurrentState >= when) return;
StateChanged += TempHandler;
return;
void TempHandler(LifecycleState state)
{
if (state != when) return;
action();
StateChanged -= TempHandler;
}
}
}
/// <summary>
/// 生命周期状态
/// </summary>
public enum LifecycleState
{
/// <summary>
/// <b>手动运行</b><br/>
/// 表示不应由生命周期管理自动运行。拥有该状态的服务项可以使用 <see cref="Lifecycle.StartService"/>
/// 和 <see cref="Lifecycle.StopService"/> 手动控制启动和停止。<br/>
/// 非异步启动可能在任意线程执行。
/// </summary>
Manual,
/// <summary>
/// <b>预加载</b><br/>
/// 一些提前运行的无需使用基本组件的事件,如检测单例、提权进程、更新等。
/// 在该状态运行的服务可以使用 <see cref="LifecycleContext.RequestExit"/> 请求直接退出程序。<br/>
/// 非异步启动将在 STA 线程执行。
/// </summary>
BeforeLoading,
/// <summary>
/// <b>加载</b><br/>
/// 基本组件初始化,如日志、系统基本信息、设置项等。<br/>
/// 非异步启动将在 STA 线程执行。
/// </summary>
Loading,
/// <summary>
/// <b>加载结束</b><br/>
/// 非基本组件初始化,大多数功能性组件如 RPC 服务端、Yggdrasil 服务端的初始化等,均应在此时运行。<br/>
/// 非异步启动将在 STA 线程执行,不建议在此状态非异步启动。
/// </summary>
Loaded,
/// <summary>
/// <b>窗口创建</b><br/>
/// 主窗体内容初始化,正常情况下不应有任何与主窗体初始化无关的事件在此时运行。<br/>
/// 非异步启动将在 STA 线程执行。
/// </summary>
WindowCreating,
/// <summary>
/// <b>窗口创建结束</b><br/>
/// 一些事件需要依赖已经加载完成的窗体,如初始弹窗提示、主题刷新等,应在此时运行。<br/>
/// 非异步启动将在 STA 线程执行,耗时操作可能导致主窗体卡顿。
/// </summary>
WindowCreated,
/// <summary>
/// <b>正在运行</b><br/>
/// 程序开始正常运行后的工作,如检查更新。<br/>
/// 非异步启动将在新的工作线程执行。
/// </summary>
Running,
/// <summary>
/// <b>尝试关闭程序</b><br/>
/// 可能有服务需要阻止启动器退出?类似 WPF 窗体的 Closing 事件,但启动器应该没这需求吧...<br/>
/// 非异步启动将在 STA 线程执行,耗时操作可能导致主窗体卡顿。
/// </summary>
Closing,
/// <summary>
/// <b>关闭程序</b><br/>
/// 确认关闭程序后开始执行关闭流程,一些需要保存状态的服务项应在此时运行。
/// 生命周期管理会在此时自动执行所有托管的未停止服务项的 <c>Stop</c> 方法,因此托管的服务项无需额外关注该状态。<br/>
/// 非异步启动可能在任意线程执行,耗时操作可能导致主窗体卡顿。
/// </summary>
Exiting,
}
@@ -0,0 +1,17 @@
using System;
namespace PCL.Core.App.IoC;
public class PropertyAccessor<TProperty>(Func<TProperty>? getter = null, Action<TProperty>? setter = null)
{
public TProperty Value
{
get => (getter ?? throw new NotSupportedException("Write-only property"))();
set => (setter ?? throw new NotSupportedException("Read-only property"))(value);
}
public bool CanGet => getter is not null;
public bool CanSet => setter is not null;
public bool IsReadOnly => setter is null;
public bool IsWriteOnly => getter is null;
}