初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PCL.Core.UI.Animation.Clock;
|
||||
|
||||
public interface IClock
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前是否在运行。
|
||||
/// </summary>
|
||||
bool IsRunning { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 时钟频率 (FPS),如果为 <see cref="int.MaxValue"/> 则表示每次循环都触发 Tick 事件。
|
||||
/// </summary>
|
||||
int Fps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动时钟。
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// 停止时钟。
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// 每一帧触发的事件。
|
||||
/// </summary>
|
||||
event EventHandler<long>? Tick;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PCL.Core.UI.Animation.Clock;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public interface IUIClock : IClock
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PCL.Core.Utils;
|
||||
|
||||
namespace PCL.Core.UI.Animation.Clock;
|
||||
|
||||
/// <summary>
|
||||
/// 一个基于 Stopwatch 的时钟实现。
|
||||
/// </summary>
|
||||
/// <param name="fps">帧率。</param>
|
||||
public class StopwatchClock(int fps = 60) : IClock, IDisposable
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
private long _lastStamp;
|
||||
private long _lastFrame;
|
||||
|
||||
public event EventHandler<long>? Tick;
|
||||
|
||||
public int Fps { get; set; } = fps;
|
||||
|
||||
public bool IsRunning => _cts is not null && !_cts.IsCancellationRequested;
|
||||
|
||||
~StopwatchClock()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (IsRunning) return;
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
_lastStamp = FrameUtils.NowStamp();
|
||||
|
||||
while (!_cts.IsCancellationRequested)
|
||||
{
|
||||
if (Fps == int.MaxValue)
|
||||
{
|
||||
_lastFrame++;
|
||||
Tick?.Invoke(this, _lastFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
var frame = FrameUtils.StampToFrameIndex(_lastStamp, Fps);
|
||||
if (frame == _lastFrame) continue;
|
||||
_lastFrame = frame;
|
||||
|
||||
Tick?.Invoke(this, frame);
|
||||
}
|
||||
}
|
||||
}, _cts.Token);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_cts is null) return;
|
||||
_cts.Cancel();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PCL.Core.UI.Animation.Clock;
|
||||
|
||||
public sealed partial class WinMMClock(int fps = 60) : IClock, IDisposable
|
||||
{
|
||||
private uint _timerId;
|
||||
private long _frameIndex;
|
||||
private TimeProc? _callback;
|
||||
|
||||
public event EventHandler<long>? Tick;
|
||||
|
||||
public int Fps { get; set; } = fps;
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
~WinMMClock()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (IsRunning) return;
|
||||
IsRunning = true;
|
||||
|
||||
_frameIndex = 0;
|
||||
|
||||
// 计算帧间隔(毫秒)
|
||||
var delay = (uint)Math.Max(1, 1000.0 / Fps);
|
||||
|
||||
// 定义回调函数
|
||||
_callback = (_, _, _, _, _) =>
|
||||
{
|
||||
_frameIndex++;
|
||||
Tick?.Invoke(this, _frameIndex);
|
||||
};
|
||||
|
||||
// 设置定时器
|
||||
_timerId = _TimeSetEvent(
|
||||
delay,
|
||||
0,
|
||||
_callback,
|
||||
IntPtr.Zero,
|
||||
TimePeriodic | TimeCallbackFunction
|
||||
);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
IsRunning = false;
|
||||
|
||||
// 停止定时器
|
||||
if (_timerId != 0)
|
||||
{
|
||||
_TimeKillEvent(_timerId);
|
||||
_timerId = 0;
|
||||
}
|
||||
_callback = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[LibraryImport("winmm.dll", EntryPoint = "timeSetEvent", SetLastError = true)]
|
||||
private static partial uint _TimeSetEvent(uint uDelay, uint uResolution, TimeProc lpTimeProc, IntPtr dwUser, uint fuEvent);
|
||||
|
||||
[LibraryImport("winmm.dll", EntryPoint = "timeKillEvent", SetLastError = true)]
|
||||
private static partial void _TimeKillEvent(uint uTimerId);
|
||||
|
||||
private delegate void TimeProc(uint id, uint msg, IntPtr user, IntPtr dw1, IntPtr dw2);
|
||||
|
||||
private const uint TimePeriodic = 0x0001;
|
||||
private const uint TimeCallbackFunction = 0x0000;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using PCL.Core.Utils;
|
||||
|
||||
namespace PCL.Core.UI.Animation.Clock;
|
||||
|
||||
/// <summary>
|
||||
/// 一个基于 WPF CompositionTarget.Rendering 事件的时钟实现。
|
||||
/// 该时钟引发的所有事件均在 UI 线程上执行。
|
||||
/// </summary>
|
||||
public class WpfCompositionTargetRenderingClock(int fps = 60) : IUIClock, IDisposable
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
private TimeSpan _lastTime = TimeSpan.Zero;
|
||||
private long _lastFrame;
|
||||
|
||||
public event EventHandler<long>? Tick;
|
||||
|
||||
public int Fps { get; set; } = fps;
|
||||
|
||||
public bool IsRunning => _cts is not null && !_cts.IsCancellationRequested;
|
||||
|
||||
~WpfCompositionTargetRenderingClock()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (IsRunning) return;
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
CompositionTarget.Rendering += _OnCompositionTargetOnRendering;
|
||||
}
|
||||
|
||||
private void _OnCompositionTargetOnRendering(object? _, EventArgs args)
|
||||
{
|
||||
if (args is not RenderingEventArgs renderingEventArgs) return;
|
||||
|
||||
if (_cts!.IsCancellationRequested)
|
||||
{
|
||||
CompositionTarget.Rendering -= _OnCompositionTargetOnRendering;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Fps == int.MaxValue)
|
||||
{
|
||||
_lastFrame++;
|
||||
Tick?.Invoke(this, _lastFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_lastTime == TimeSpan.Zero)
|
||||
{
|
||||
_lastTime = renderingEventArgs.RenderingTime;
|
||||
}
|
||||
|
||||
var frame = FrameUtils.TimeSpanToFrameIndex(_lastTime, renderingEventArgs.RenderingTime, Fps);
|
||||
if (frame == _lastFrame) return;
|
||||
_lastFrame = frame;
|
||||
|
||||
_lastTime = renderingEventArgs.RenderingTime;
|
||||
|
||||
Tick?.Invoke(this, frame);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
CompositionTarget.Rendering -= _OnCompositionTargetOnRendering;
|
||||
|
||||
if (_cts is null) return;
|
||||
_cts.Cancel();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CompositionTarget.Rendering -= _OnCompositionTargetOnRendering;
|
||||
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user