接自定义输入时,先把改动落在输入适配层,不要动 HelixWorld 的主循环或渲染步进。主循环通常承担固定步长推进、状态更新和调度,改动它会作用到所有输入路径,延迟问题也更难隔离;适配层只负责把新设备的数据转成核心已经认识的输入事件。判断标准不是“代码少”,而是“是否改变了核心循环每帧的时间预算”。
若目标是接入键盘、手柄或传感器,建议把新输入收敛到一层适配代码:采样、归一化、映射到已有输入结构,再由核心循环按原节奏读取。验证方式用采样时间戳与状态生效时间戳对照,看端到端延迟和抖动是否变化。风险边界是:若必须修改核心循环的读取顺序或加入阻塞等待,实时性不再由适配层保证,需要单独评估后再动。
先确认现有输入是从哪里进来的
不清楚输入从哪进来,就很容易改错位置。先用检索把可疑范围缩小,再根据调用栈判断它是事件驱动还是每帧轮询。
grep -rn `--include`='*.c' `--include`='*.cpp' `--include`='*.h' \
-E 'input|key|gamepad|axis|poll' . | head -50
把结果按目录归类,常见会出现 input/、input_adapter、input_system 这类路径。接着确认关键函数是否被主循环调用:如果函数只出现在事件回调里,说明是事件驱动;如果每帧都会走到,说明是轮询。把文件路径、函数名和调用它的那几行记录下来,后面选改动点就靠这些证据,而不是靠猜测。
在不改核心循环的前提下加一路输入
适配层只做三件事:读取设备、归一化数值、写入核心已有的输入结构。不要在里面做阻塞等待、平台弹窗或长时间初始化。下面是一个通用骨架,接口名按你项目里的实际命名替换。
// 放在 input/adapter/ 下,避免直接改主循环
struct CustomInputState {
float axis_x;
float axis_y;
bool button_a;
long sample_time_ms;
};
class CustomInputAdapter {
public:
bool init();
// 只读取和归一化,不做阻塞等待
bool poll(CustomInputState& out);
void shutdown();
};
接入时优先复用已有的输入读取入口。如果核心已经有一个“读取本帧输入”的函数,就在它内部增加对适配器的调用,而不是在主循环里额外插入新分支。最小可运行验证分三步:先只编译适配器,单独调用 poll 并打印原始值;再打开开关,打印映射后的值,确认与设备动作一致;最后关闭开关,确认原有输入行为不变。接口细节不确定时,先只验证“读到值”,不要急着做映射。
测改动前后的端到端延迟
延迟要看你关心的那一段,不要只看适配层内部耗时。比较稳妥的做法是记录两个时间戳:设备采样或适配层读到的时刻 t0,核心状态生效的时刻 t1,两者的差值就是这一路的端到端延迟。拿不到设备时间戳时,就用适配层 poll 返回的时刻作为内侧延迟,并在记录里注明口径。
# 日志格式示例,字段按你的项目替换
LAT sample=... adapter_out=... core_apply=... delta_ms=...
改动前后用同一套操作重复采几组,记录 delta 的最大值、最小值和大致分布,不要只记一次平均值。对照表可以先写成这样:
测试项 改动前 delta_ms 改动后 delta_ms 备注
单次短按 记录区间 记录区间 同一次操作
持续按住 记录区间 记录区间 观察是否累计变慢
快速交替 记录区间 记录区间 观察丢帧或抖动
关闭新输入 基线 基线 确认无回归
如果改动后延迟分布明显变宽,先查适配层是否在 poll 里做了耗时操作,再看新输入是否被放到了核心步进之后。
处理新输入与原有输入的冲突
两路输入写同一个状态时,必须有一个明确的裁决规则,否则会出现互相打断。常见做法是给每个来源一个优先级和一个“持有窗口”:高优先级来源活动时,低优先级不写入;高优先级来源静默超过窗口后释放控制权。互斥可以按轴粒度做,同一根轴同一时刻只允许一个来源拥有。
- 键盘与新输入同时给相反方向:检查最终值只来自高优先级来源,而不是两者相加。
- 新输入保持时松开键盘:检查方向不会跳变或归中。
- 新输入断开后:检查在约定帧数内归零或交还给原输入。
- 高优先级持续按住:检查低优先级输入不会间歇性生效。
这些用例可以在适配层外单独跑,不需要动核心逻辑。测试时打开适配层日志,直接看每帧胜出的来源和最终输出值。
把接入方式做成可回滚的一层
回滚能力要在接入时就设计好,而不是出问题后再补。最省事的方式是给适配层一个独立开关,默认关闭,开启时才实例化。
# 配置或启动参数示例
enable_custom_input=false
custom_input_device=auto
回滚步骤:先把配置改回关闭,再重启或让系统重新加载配置,确认适配器没有被实例化,最后用原有输入做一遍短按、长按和方向测试。验证动作包括:日志中不再出现适配层输出、原有输入延迟回到改动前的记录区间、核心循环的调用路径与改动前一致。开关本身不要依赖新设备是否在线,设备异常时也要能退回原状。