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,110 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace PCL.Core.Utils.Threading;
// 使用 AI 生成的代码
// 时间: 2025/9/2
// 模型: GPT-5
/// <summary>
/// 一个带配额(Permit)的 <see cref="System.Threading.AutoResetEvent"/> 变体。
/// 支持一次性释放多个等待任务。
/// 类似于 <see cref="System.Threading.SemaphoreSlim"/>,但语义更接近 AutoResetEvent。
/// </summary>
public sealed class AsyncCountResetEvent : IDisposable
{
private readonly Queue<TaskCompletionSource<bool>> _waiters = new();
private readonly object _lock = new();
/// <summary>
/// 当前剩余的配额数。如果 &gt; 0,新的等待者会立即通过。
/// </summary>
private int _permits;
/// <summary>
/// 标记当前对象是否已释放。
/// </summary>
private bool _disposed;
/// <summary>
/// 析构函数。
/// </summary>
~AsyncCountResetEvent()
{
Dispose();
}
/// <summary>
/// 等待一个信号。当信号可用时返回完成的 <see cref="Task"/>。
/// 如果没有信号,则进入队列等待。
/// </summary>
/// <returns>
/// 一个 <see cref="Task"/>,表示等待操作。
/// 如果对象被释放,则返回的 Task 会异常结束。
/// </returns>
public Task WaitAsync()
{
lock (_lock)
{
ObjectDisposedException.ThrowIf(_disposed, nameof(AsyncCountResetEvent));
if (_permits > 0)
{
_permits--;
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<bool>(
TaskCreationOptions.RunContinuationsAsynchronously);
_waiters.Enqueue(tcs);
return tcs.Task;
}
}
/// <summary>
/// 释放一个或多个信号,让等待者继续执行。
/// </summary>
/// <param name="count">要释放的配额数量,默认为 1。</param>
public void Set(int count = 1)
{
if (count <= 0) return;
lock (_lock)
{
if (_disposed) return;
while (count > 0 && _waiters.Count > 0)
{
var tcs = _waiters.Dequeue();
tcs.TrySetResult(true);
count--;
}
// 如果没有等待者,就累积到配额里
_permits += count;
}
}
/// <summary>
/// 释放当前对象。
/// 会让所有等待中的任务异常完成。
/// </summary>
public void Dispose()
{
lock (_lock)
{
if (_disposed) return;
_disposed = true;
while (_waiters.Count > 0)
{
var tcs = _waiters.Dequeue();
tcs.TrySetException(new ObjectDisposedException(nameof(AsyncCountResetEvent)));
}
}
GC.SuppressFinalize(this);
}
}
@@ -0,0 +1,114 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.Utils.Threading;
/// <summary>
/// 可重置的异步延时器。在指定延时后执行异步任务并等待下一次重置后重复该逻辑,指定延时未到达时重置将会重新开始计时。
/// <p>实例创建后并不会立即开始计时,而是等待第一次 <see cref="ResetAsync"/>
/// 调用。因此,若有特殊需求,请不要忘了创建实例后调用一次 <see cref="ResetAsync"/>。</p>
/// </summary>
public class AsyncDebounce(CancellationToken cancelToken = default) : IDisposable
{
/// <summary>
/// 执行延迟。
/// </summary>
public required TimeSpan Delay { get; init; }
/// <summary>
/// 异步任务实例。
/// </summary>
public required Func<Task> ScheduledTask { get; init; }
/// <summary>
/// 指示本次延迟任务是否已经完成。
/// </summary>
public bool IsCurrentTaskCompleted { get; private set; }
/// <summary>
/// 指示本次延迟任务是否正在运行。
/// </summary>
public bool IsCurrentTaskRunning => _currentTask is not null;
private Task? _currentTask;
private Task? _worker; // 跟踪最近一次 worker
private CancellationTokenSource? _currentDelayCts;
private readonly CancellationTokenSource _cts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken);
private readonly object _resetLock = new();
/// <summary>
/// 重置延时。
/// </summary>
public async Task ResetAsync()
{
IsCurrentTaskCompleted = false;
CancellationTokenSource? capturedCts;
Task? runningToAwait;
#pragma warning disable VSTHRD103 // 禁用检查 避免智障警告
lock (_resetLock)
{
// 只取消,不在这里 Dispose
try { _currentDelayCts?.Cancel(); }
catch(ObjectDisposedException) { /* ignored */ }
_currentDelayCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
capturedCts = _currentDelayCts;
// 记录当前运行中的 ScheduledTask,稍后在锁外等待,避免重叠
runningToAwait = _currentTask;
_worker = Task.Run(async () =>
{
try { await Task.Delay(Delay, capturedCts.Token).ConfigureAwait(false); }
catch (OperationCanceledException) { return; }
finally
{
// 仅由使用者自己释放,避免跨线程 Dispose 竞态
// 注意:不要在这里释放 _cancelToken
capturedCts.Dispose();
}
// 身份校验,确保自己仍是“当前”那一次
if (
!ReferenceEquals(_currentDelayCts, capturedCts) ||
capturedCts.IsCancellationRequested ||
_cts.IsCancellationRequested
) return;
if (_currentTask is not null) await _currentTask.ConfigureAwait(false);
var task = ScheduledTask();
lock (_resetLock) _currentTask = task;
try
{
await task.ConfigureAwait(false);
}
finally
{
lock (_resetLock)
{
_currentTask = null;
IsCurrentTaskCompleted = true;
}
}
}, _cts.Token);
}
#pragma warning restore VSTHRD103
// 避免 ScheduledTask 并发
if (runningToAwait is not null) await runningToAwait.ConfigureAwait(false);
}
public void Dispose()
{
_cts.Cancel();
try { _worker?.Wait(); } catch { /* ignored */ }
_currentDelayCts?.Dispose();
_cts.Dispose();
GC.SuppressFinalize(this);
}
}
@@ -0,0 +1,109 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.Utils.Threading;
// Partly generated by gpt-5-mini (20250808)
public sealed class AsyncManualResetEvent : IDisposable
{
private readonly object _syncLock = new();
private TaskCompletionSource<bool> _tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly ManualResetEventSlim _mre = new(false);
private bool _disposed;
public AsyncManualResetEvent(bool initialState = false)
{
if (!initialState) return;
_tcs.SetResult(true);
_mre.Set();
}
/// <summary>
/// 事件是否已触发。
/// </summary>
public bool IsSet
{
get { lock (_syncLock) { return _tcs.Task.IsCompleted; } }
}
/// <summary>
/// 异步等待。
/// </summary>
/// <param name="cancellationToken">用于结束等待的取消信号</param>
public Task WaitAsync(CancellationToken cancellationToken = default)
{
TaskCompletionSource<bool> t;
lock (_syncLock) { t = _tcs; }
if (!cancellationToken.CanBeCanceled || t.Task.IsCompleted) return t.Task;
return _WaitWithCancellationAsync(t.Task, cancellationToken);
}
private static async Task _WaitWithCancellationAsync(Task waitTask, CancellationToken ct)
{
var cancelTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
using (ct.Register(s => ((TaskCompletionSource<bool>)s!).TrySetResult(true), cancelTcs))
{
var completed = await Task.WhenAny(waitTask, cancelTcs.Task).ConfigureAwait(false);
if (completed == cancelTcs.Task) ct.ThrowIfCancellationRequested();
await waitTask.ConfigureAwait(false); // propagate exceptions if any
}
}
/// <summary>
/// 同步等待。
/// </summary>
public void Wait() => _mre.Wait();
/// <summary>
/// 同步等待,并在超时后结束。
/// </summary>
/// <param name="millisecondsTimeout">等待超时的毫秒数</param>
/// <returns>若已触发事件则为 <c>true</c>,否则为 <c>false</c></returns>
public bool Wait(int millisecondsTimeout) => _mre.Wait(millisecondsTimeout);
/// <summary>
/// 同步等待,并在超时后结束。
/// </summary>
/// <param name="timeout">等待超时</param>
/// <returns>若已触发事件则为 <c>true</c>,否则为 <c>false</c></returns>
public bool Wait(TimeSpan timeout) => _mre.Wait(timeout);
/// <summary>
/// 同步等待,并传递用于结束等待的取消信号。
/// </summary>
/// <param name="cancellationToken">用于结束等待的取消信号</param>
public void Wait(CancellationToken cancellationToken) => _mre.Wait(cancellationToken);
/// <summary>
/// 触发事件。
/// </summary>
public void Set()
{
lock (_syncLock)
{
_tcs.TrySetResult(true); // Use TrySetResult to avoid exceptions on repeated Set
_mre.Set();
}
}
/// <summary>
/// 重置事件。
/// </summary>
public void Reset()
{
lock (_syncLock)
{
if (!_tcs.Task.IsCompleted) return; // already reset
_tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_mre.Reset();
}
}
public void Dispose()
{
if (_disposed) return;
_mre.Dispose();
_disposed = true;
}
}
@@ -0,0 +1,76 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.Utils.Threading;
// Partly generated by o4-mini-high (20250709)
/// <summary>
/// 使用两个线程池的调度器,分为 CPU 线程池和 IO 线程池,分别负责 CPU 密集型任务和 IO 密集型任务
/// </summary>
public class DualThreadPool
{
/// <summary>
/// 两线程池分别计算的最大线程数
/// </summary>
public int MaxThread { get; }
private readonly TaskFactory _ioFactory;
private readonly TaskFactory _cpuFactory;
private readonly CancellationTokenSource _cts = new();
/// <summary>
/// 初始化 <see cref="DualThreadPool"/> 实例
/// </summary>
/// <param name="maxThread">参考 <see cref="MaxThread"/>,最小为 1</param>
/// <exception cref="ArgumentOutOfRangeException">最大线程数小于 1</exception>
public DualThreadPool(int maxThread)
{
if (maxThread < 1) throw new ArgumentOutOfRangeException(nameof(maxThread));
MaxThread = maxThread;
var ioScheduler = new LimitedConcurrencyLevelTaskScheduler(maxThread);
var cpuScheduler = new LimitedConcurrencyLevelTaskScheduler(maxThread);
var cancellationToken = _cts.Token;
// DenyChildAttach 防止子任务跑到外层 scheduler
_ioFactory = new TaskFactory(
cancellationToken,
TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.None,
ioScheduler);
_cpuFactory = new TaskFactory(
cancellationToken,
TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.None,
cpuScheduler);
}
/// <summary>
/// 提交一段 IO 密集工作
/// </summary>
public Task QueueIo(Action work) => _ioFactory.StartNew(work);
/// <summary>
/// 提交一段异步 IO 密集工作
/// </summary>
public Task QueueIo(Func<Task> work) => _ioFactory.StartNew(work).Unwrap();
/// <summary>
/// 提交一段 CPU 密集工作
/// </summary>
public Task QueueCpu(Action work) => _cpuFactory.StartNew(work);
/// <summary>
/// 提交一段异步 CPU 密集工作
/// </summary>
public Task QueueCpu(Func<Task> work) => _cpuFactory.StartNew(work).Unwrap();
/// <summary>
/// 取消所有正在执行的工作
/// </summary>
public void CancelAll() => _cts.Cancel();
}
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.Utils.Threading;
// Partly generated by o4-mini-high (20250709)
/// <summary>
/// 允许限制最大并发度的 TaskScheduler
/// </summary>
public sealed class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
private readonly LinkedList<Task> _tasks = [];
private int _delegatesQueuedOrRunning = 0;
private readonly int _maxDegreeOfParallelism;
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1)
throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
public override int MaximumConcurrencyLevel => _maxDegreeOfParallelism;
protected override IEnumerable<Task> GetScheduledTasks()
{
var lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);
if (lockTaken) return _tasks.ToArray();
else throw new NotSupportedException();
}
finally
{
if (lockTaken) Monitor.Exit(_tasks);
}
}
protected override void QueueTask(Task task)
{
lock (_tasks)
{
_tasks.AddLast(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
_delegatesQueuedOrRunning++;
ThreadPool.UnsafeQueueUserWorkItem(_ => _ProcessTasks(), null);
}
}
}
private void _ProcessTasks()
{
_currentThreadIsProcessingItems = true;
try
{
while (true)
{
Task item;
lock (_tasks)
{
if (_tasks.Count == 0)
{
_delegatesQueuedOrRunning--;
break;
}
item = _tasks.First!.Value;
_tasks.RemoveFirst();
}
TryExecuteTask(item);
}
}
finally
{
_currentThreadIsProcessingItems = false;
}
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
if (!_currentThreadIsProcessingItems) return false;
if (taskWasPreviouslyQueued)
{
return TryDequeue(task) && TryExecuteTask(task);
}
else
{
return TryExecuteTask(task);
}
}
protected override bool TryDequeue(Task task)
{
lock (_tasks)
{
return _tasks.Remove(task);
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.Utils.Threading;
// Partly generated by o4-mini-high (20250709)
/// <summary>
/// 可限制并发线程数量的任务池。
/// </summary>
public class LimitedTaskPool
{
/// <summary>
/// 最大并发线程数。
/// </summary>
public int MaxThread { get; }
private readonly TaskFactory _factory;
private readonly CancellationTokenSource _cts = new();
/// <summary>
/// 初始化 <see cref="LimitedTaskPool"/> 实例
/// </summary>
/// <param name="maxThread">参考 <see cref="MaxThread"/>,最小为 1</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="maxThread"/> 小于 1</exception>
public LimitedTaskPool(int maxThread)
{
if (maxThread < 1) throw new ArgumentOutOfRangeException(nameof(maxThread));
MaxThread = maxThread;
var scheduler = new LimitedConcurrencyLevelTaskScheduler(maxThread);
var cancellationToken = _cts.Token;
_factory = new TaskFactory(
cancellationToken,
TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.None,
scheduler);
}
/// <summary>
/// 提交一个任务。
/// </summary>
public Task Submit(Action work) => _factory.StartNew(work);
/// <summary>
/// 提交一个异步任务。
/// </summary>
public Task Submit(Func<Task> work) => _factory.StartNew(work).Unwrap();
/// <summary>
/// 取消所有任务。
/// </summary>
public void CancelAll() => _cts.Cancel();
}