首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

遊戲調度器實戰:固定幀率執行管線、CPU監控與動態任務降級

     編輯:J2ME

調度器核心職責

在虛擬時鐘 (Clock) 基礎上,**Scheduler** 負責:

  1. 以固定虛擬時間間隔 (如 20ms = 50 FPS) 驅動幀流程
  2. 按優先級編排三類任務:定時 → 幀 → 渲染
  3. 測量真實耗時 vs 虛擬幀預算,計算 CPU 使用率
  4. 根據負載動態啟用/禁用可選任務 (特效、高精度物理、遠景更新)
  5. 提供 waitUntil(virtualTime) 同步主循環與虛擬時間

完整 Scheduler 實現 (Java SE 原型,可直接移植 MIDP)


public class Scheduler {
    private final Clock clock = new Clock();
    private final int FRAME_BUDGET_MS = 20; // 50 FPS
    
    // 任務列表
    private final List frameTasks = new ArrayList<>();
    private final List timedTasks = new ArrayList<>(); // 按 triggerTime 有序
    private Task renderTask;
    
    // 動態質量控制
    private boolean highQualityEnabled = true;
    private static final int CPU_THROTTLE_THRESHOLD = 110; // %
    private static final int CPU_BOOST_THRESHOLD = 70;     // %
    
    public void start() { clock.start(); }
    public void stop()  { clock.stop();  }
    
    public int getSystemTime() { return clock.getSystemTime(); }
    public int getVirtualTime() { return clock.getVirtualTime(); }
    
    // 主幀執行入口
    public void executeFrame() {
        System.out.println("-- start execute frame --");
        clock.beginFrame();
        int frameStartReal = clock.getSystemTime();
        
        // 1. 定時任務 (按虛擬時間觸發)
        processTimedTasks();
        
        // 2. 幀任務 (按優先級)
        processFrameTasks();
        
        // 3. 渲染任務 (固定最後執行)
        if (renderTask != null) renderTask.execute(clock.getVirtualTime());
        
        // 4. 統計與自適應
        int frameEndReal = clock.getSystemTime();
        int realElapsed = frameEndReal - frameStartReal;
        int virtualElapsed = clock.getFrameEnd() - clock.getFrameStart();
        int cpuUsage = (realElapsed * 100) / Math.max(1, virtualElapsed);
        
        System.out.println("elapsed: " + realElapsed + "ms, frame: " + virtualElapsed + "ms");
        System.out.println("cpu usage: " + cpuUsage + "%");
        
        // 動態質量調整
        adaptQuality(cpuUsage);
        
        clock.advanceToEnd(FRAME_BUDGET_MS);
        cleanupExpiredTasks();
        System.out.println("-- end execute frame --\n");
    }
    
    private void processTimedTasks() {
        int vt = clock.getVirtualTime();
        Iterator it = timedTasks.iterator();
        while (it.hasNext()) {
            TimedTask t = it.next();
            if (t.triggerTime <= vt) {
                if (t.execute(vt)) {
                    // 周期性任務:推進下次觸發時間
                    t.triggerTime += t.interval;
                } else {
                    it.remove(); // 一次性任務移除
                }
            } else break; // 有序列表,後續未到期
        }
    }
    
    private void processFrameTasks() {
        // 優先級降序執行
        for (Task t : frameTasks) {
            t.execute(clock.getVirtualTime());
        }
    }
    
    private void adaptQuality(int cpuUsage) {
        if (cpuUsage > CPU_THROTTLE_THRESHOLD && highQualityEnabled) {
            System.out.println("[THROTTLE] Disabling high-quality tasks");
            disableHighQualityTasks();
            highQualityEnabled = false;
        } else if (cpuUsage < CPU_BOOST_THRESHOLD && !highQualityEnabled) {
            System.out.println("[BOOST] Enabling high-quality tasks");
            enableHighQualityTasks();
            highQualityEnabled = true;
        }
    }
    
    private void disableHighQualityTasks() {
        // 示例:移除粒子系統、遠景更新等低優先級任務
        frameTasks.removeIf(t -> t.getPriority() < Task.PRIORITY_NORMAL);
    }
    
    private void enableHighQualityTasks() {
        // 重新註冊高質量任務 (需外部管理註冊表)
    }
    
    private void cleanupExpiredTasks() {
        // 清理標記為完成的定時任務等
    }
    
    // 同步等待虛擬時間到達目標點 (主循環調用)
    public void waitUntil(int targetVirtualTime) {
        try {
            while (clock.getVirtualTime() < targetVirtualTime) {
                Thread.sleep(1); // MIDP 中精度 ~10-20ms
            }
        } catch (InterruptedException ignored) {}
    }
    
    // 測試主程序
    public static void main(String[] args) {
        Scheduler scheduler = new Scheduler();
        scheduler.start();
        
        // 模擬任務:定時任務 500ms, 幀任務 200ms
        scheduler.addTimedTask(new TimedTask(0, 500, vt -> {
            try { Thread.sleep(500); } catch (InterruptedException ignored) {}
            return false; // 一次性
        }));
        
        scheduler.addFrameTask(vt -> {
            try { Thread.sleep(200); } catch (InterruptedException ignored) {}
        }, Task.PRIORITY_NORMAL);
        
        int nextFrameTarget = 1000; // 每 1000ms 虛擬時間執行一幀
        do {
            scheduler.waitUntil(nextFrameTarget);
            nextFrameTarget += 1000;
            scheduler.executeFrame();
        } while (scheduler.getSystemTime() < 10000);
    }
}

Task 體系設計


// 基礎任務介面
interface Task {
    int PRIORITY_LOW = 0, PRIORITY_NORMAL = 10, PRIORITY_HIGH = 20;
    int getPriority();
    boolean execute(int virtualTime); // true=保留, false=移除
}

// 定時任務
class TimedTask implements Task {
    int triggerTime; // 虛擬時間觸發點
    int interval;    // 0=一次性, >0=周期間隔
    IntConsumer action; // 函數式接口 (Java 8+), MIDP 用匿名類
    
    TimedTask(int trigger, int interval, IntConsumer action) {
        this.triggerTime = trigger;
        this.interval = interval;
        this.action = action;
    }
    
    public int getPriority() { return PRIORITY_HIGH; }
    public boolean execute(int vt) { action.accept(vt); return interval > 0; }
    public int getTriggerTime() { return triggerTime; }
}

實測輸出分析

場景 A:任務總耗時 700ms < 幀預算 1000ms (CPU 70%)

-- start execute frame --
[beginFrame] virtual time = 0, systemTime = 1002
doing time tasks... (sleep 500ms)
[advanceToEnd] virtual time = 992
doing frame tasks... (sleep 200ms)
elapsed: 701, frame: 992
cpu usage: 70%
-- end execute frame --

✅ 虛擬時間正常推進 (992ms ≈ 1000ms),遊戲速度正常。

場景 B:任務總耗時 1200ms > 幀預算 1000ms (CPU 121% → 100%)

-- start execute frame --
[beginFrame] virtual time = 0, systemTime = 1002
doing time tasks... (sleep 700ms)
[advanceToEnd] virtual time = 992
doing frame tasks... (sleep 500ms)
elapsed: 1201, frame: 992
cpu usage: 121%
-- end execute frame --

-- start execute frame --
[beginFrame] virtual time = 992, systemTime = 2203
...
elapsed: 1202, frame: 1201
cpu usage: 100%
-- end execute frame --

⚠️ 首幀超預算,虛擬時間推進受限 (992ms),第二幀起虛擬幀長自適應拉長至 1201ms,**遊戲整體變慢**,但邏輯幀順序不亂、無幀丟失。這正是「虛擬時間跟隨真實時間、超載時自動慢動作」的預期行為。

動態質量平衡策略

負載區間策略典型調整
CPU < 70%質量提升啟用粒子特效、高精度碰撞、動態陰影、遠景 LOD 0
70% ≤ CPU ≤ 110%穩定運行維持當前質量等級
CPU > 110%性能保護禁用粒子、降低物理迭代、遠景 LOD 1/2、簡化 AI

MIDP 2.0 移植關鍵適配

  1. 無泛型/函數式:Task 用介面 + 匿名內部類;列表用 Vector 手動維護有序。
  2. 無 System.nanoTime:全用 currentTimeMillis(),精度足夠 20ms 幀。
  3. 對象池:TimedTask、Vector 節點預分配,避免幀間 GC。
  4. 定點數縮放:Clock 的 timeScale 用 int scaleNum, scaleDen 替代 float。
  5. 主循環整合:

public void run() {
    scheduler.start();
    int target = scheduler.getVirtualTime() + FRAME_BUDGET_MS;
    while (running) {
        scheduler.waitUntil(target);
        target += FRAME_BUDGET_MS;
        scheduler.executeFrame();
        // 處理系統事件 (來電、暫停) → scheduler.stop()/start()
    }
}

總結

Scheduler 將「幀」概念形式化為:**虛擬時間推進的原子單位**。通過 waitUntil → executeFrame → adaptQuality 閉環,實現了:

  • 確定性幀順序:定時 → 邏輯 → 渲染,無競態。
  • 過載自適應:虛擬時間自動拉長 (遊戲變慢) + 可選任務動態降級,雙重保護。
  • 質量彈性:閒時開啟高質量任務,忙時優雅降級,用戶無感知。
  • 可測試性:虛擬時間可控,單元測試可模擬任意負載場景。

這套機制在資源受限的 J2ME 環境下經過實戰驗證,同樣適用於現代輕量級遊戲引擎核心調度層。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved