為遊戲調度器提供**可控、可暫停、可縮放**的時間基線。區分三個時間域:
System.currentTimeMillis() 原始值,單調遞增,不可控。
class Clock {
// 運行狀態
private boolean running = false;
// 硬件時間追蹤
private int thisTime; // 當前硬件時間
private int lastTime; // 上次更新硬件時間
// 系統時間 (從 0 起算,暫停凍結)
private int systemTime; // 當前系統時間
private int systemOffset; // 硬件時間 → 系統時間 偏移量
private int pauseAt; // 暫停時刻的系統時間
// 虛擬時間 (可縮放)
private int virtualTime; // 當前虛擬時間
private int virtualOffset; // 暫停累積偏移
private float timeScale = 1.0f; // 時間縮放因子
// 幀邊界 (供調度器統計)
private int frameStart;
private int frameEnd;
private int frameCount;
}
public void reset() {
running = false;
thisTime = lastTime = (int) System.currentTimeMillis();
systemTime = 0;
systemOffset = thisTime;
pauseAt = 0;
virtualTime = 0;
virtualOffset = 0;
timeScale = 1.0f;
frameStart = frameEnd = frameCount = 0;
}
private void updateHardwareTime() {
lastTime = thisTime;
thisTime = (int) System.currentTimeMillis();
if (running) {
// 系統時間隨硬件時間推進
systemTime += (thisTime - lastTime);
}
}
public void start() {
if (!running) {
running = true;
updateHardwareTime();
// 虛擬時間補償暫停期間流逝的系統時間
virtualOffset += (systemTime - pauseAt);
// 可選:重置幀計數器
frameCount = 0;
}
}
public void stop() {
if (running) {
running = false;
updateHardwareTime();
pauseAt = systemTime; // 記錄暫停瞬間系統時間
}
}
// 幀開始
public void beginFrame() {
updateHardwareTime();
frameStart = systemTime;
frameCount++;
}
// 幀結束:推進虛擬時間至幀預算末尾
public void advanceToEnd(int frameBudgetMs) {
updateHardwareTime();
frameEnd = systemTime;
// 虛擬時間 = (系統時間 - 虛擬偏移) * 縮放因子
virtualTime = (int) ((systemTime - virtualOffset) * timeScale);
}
public void setTimeScale(float scale) {
if (scale <= 0) scale = 0.01f; // 防止除零
// 縮放前先同步當前虛擬時間
updateHardwareTime();
virtualTime = (int) ((systemTime - virtualOffset) * timeScale);
// 應用新縮放,調整偏移保證虛擬時間連續
timeScale = scale;
virtualOffset = systemTime - (int) (virtualTime / timeScale);
}
public int getSystemTime() {
if (running) updateHardwareTime();
return systemTime;
}
public int getVirtualTime() { return virtualTime; }
public int getFrameStart() { return frameStart; }
public int getFrameEnd() { return frameEnd; }
public boolean isRunning() { return running; }
[RESET] → start() → [RUNNING] → stop() → [PAUSED] → start() → [RUNNING]
↑___________________________________________________|
public class Scheduler {
private Clock clock = new Clock();
private final int FRAME_BUDGET = 20; // 50 FPS
public void executeFrame() {
clock.beginFrame();
int startReal = clock.getSystemTime();
// 執行任務...
doTimeTasks(clock.getVirtualTime());
doFrameTasks();
doRenderTask();
int endReal = clock.getSystemTime();
clock.advanceToEnd(FRAME_BUDGET);
// CPU 使用率 = 真實耗時 / 幀預算
int cpu = (endReal - startReal) * 100 / FRAME_BUDGET;
if (cpu > 100) enableFrameSkipping(); // 動態降級
}
public void waitUntil(int targetVirtualTime) {
while (clock.getVirtualTime() < targetVirtualTime) {
try { Thread.sleep(1); } catch (InterruptedException ignored) {}
}
}
}
long 性能優勢,32 位 int 可表示 ~24 天毫秒數,遊戲會話遠小於此。timeScale 用定點數 (Q16.16) 或分數 scaleNum/scaleDen 實現。Thread.sleep(1) 實際精度 10-20ms,waitUntil 循環可接受。systemOffset, virtualOffset, timeScale, pauseAt 即可完整恢復時間狀態。Clock 類將「時間」抽象為**可編程資源**:硬件時間提供物理基線,系統時間提供邏輯基線,虛擬時間提供遊戲基線。通過 virtualOffset 吸收暫停間隙,timeScale 實現時間縮放,beginFrame/advanceToEnd 界定幀邊界,構成調度器、網絡同步、回放系統、動畫引擎的統一時間基石。極簡 100 行代碼,承載遊戲時間的所有高級特性。