Chapter 03 · 引擎基础

帧循环:xgeRun 与回调模式

理解 XGE 的两种帧驱动模式——自动游戏循环与手动驱动,掌握帧回调的编写规范与退出机制。

03

概念:帧循环是什么

图形应用的核心是一个无限循环:每帧执行 输入处理 → 逻辑更新 → 渲染绘制 → 呈现到屏幕。XGE 将这个循环封装在 xgeRun() 内部,你只需提供一个帧回调函数。

关键约定:帧回调签名为 int (*)(void* pUser)。返回 XGE_OK (0) 继续循环;返回任何其他值(通常为 XGE_ERROR 或自定义正整数)将终止循环。

API 签名

// 帧回调类型
typedef int (*xge_scene_proc)(void* pUser);

// 进入主循环(阻塞直到回调返回非 XGE_OK 或调用 xgeQuit)
int xgeRun(xge_scene_proc procFrame, void* pUser);

// 请求退出主循环(可在任意线程调用)
void xgeQuit(void);

运行模式

模式常量行为
游戏循环XGE_RUN_GAME_LOOP (0)引擎自动驱动:每帧调用回调 → 渲染 → 呈现 → 等待 VSync/帧率限制
手动XGE_RUN_MANUAL (1)xgeRun 不阻塞;你自己决定何时调用 xgeFrame()

编译本章示例

gcc -O2 -I. -o ch03.exe ch03_main.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch03.exe

示例 1:基本游戏循环

#include "xge.h"

typedef struct {
    float fAngle;
    int   iFrameCount;
} AppState;

static int onFrame(void* pUser) {
    AppState* app = (AppState*)pUser;
    float dt = xgeGetDelta();  // 帧间隔(秒)

    // 更新逻辑
    app->fAngle += dt * 90.0f;  // 每秒旋转 90°
    app->iFrameCount++;

    // 渲染
    xgeClear(0x0A0F14FF);
    // ... 绘制旋转图形(后续章节详解)

    // 300 帧后自动退出(演示用)
    if (app->iFrameCount >= 300)
        return XGE_ERROR;  // 非 XGE_OK → 退出循环

    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth = 800; d.iHeight = 600;
    d.sTitle = "Game Loop";
    d.iRunMode = XGE_RUN_GAME_LOOP;

    xgeInit(&d);

    AppState app = {0};
    xgeRun(onFrame, &app);  // 阻塞直到退出

    xgeUnit();
    return 0;
}

示例 2:用 xgeQuit 响应按键退出

static int onFrame(void* pUser) {
    xgeClear(0x0C181CFF);

    // 按 ESC 退出
    if (xgeKeyPressed(XGE_KEY_ESCAPE)) {
        xgeQuit();  // 设置退出标志,本帧结束后循环终止
    }

    return XGE_OK;  // 仍返回 OK,xgeQuit 会覆盖
}
陷阱:xgeQuit() 不会立即中断当前帧——它设置一个标志,循环在本帧回调返回后检查该标志并退出。因此 xgeQuit() 后面的代码仍会执行。

帧循环内部流程

// xgeRun 内部伪代码(GAME_LOOP 模式)
while (!quit_requested) {
    pollEvents();           // 1. 收集输入事件
    dispatchToScene();     // 2. 分发到场景栈
    result = procFrame(pUser); // 3. 调用你的回调
    if (result != XGE_OK) break;
    render();              // 4. 提交渲染命令
    present();             // 5. 交换帧缓冲
    frameTiming();         // 6. 帧率限制 / Delta 计算
}

要点与陷阱

  • 不要在帧回调中阻塞——任何耗时操作(文件 I/O、网络)都会直接降低帧率。使用异步加载 API(第 114 章)。
  • pUser 的用途:避免全局变量。将应用状态打包为结构体,通过 pUser 传入。
  • 返回值语义:XGE_OK = 继续;其他值 = 退出且 xgeRun 返回该值。你可以用不同返回值区分退出原因。
  • 线程安全:xgeQuit() 可从任意线程调用(如网络线程收到断开通知),但渲染 API 只能在帧回调线程调用。

进阶练习

  1. 在帧回调中累计时间,每 5 秒调用 xgeQuit() 实现定时退出。
  2. 用 pUser 传递一个计数器结构体,在标题栏显示当前帧号。
  3. 将 iRunMode 改为 XGE_RUN_MANUAL,观察 xgeRun 是否立即返回(预习第 4 章)。

完整代码与可观察结果

权威源码为 examples/tutorial_capture/ch03_main1.c。它将帧回调的一个确定时刻渲染到离屏目标;使用 --frames N 可验证循环会自动退出,使用 --capture 可生成下图。

XGE frame-loop rotation snapshot
图:第 3 章真实运行输出。旋转方形、中心点和轨迹环来自帧回调,说明绘制已进入 XGE 的帧循环。