Rust中替代HashMap实现高效易用的GamepadState结构体方案问询
解决方案
1. 用固定大小数组替代HashMap重构GamepadState
HashMap的哈希计算、动态内存分配是主要性能瓶颈,换成固定大小数组可以实现O(1)的访问和更新,完全消除HashMap的运行时开销,同时保持原有API的简洁性。
实现步骤:
首先为gilrs::Axis和gilrs::Button枚举实现索引转换,将每个枚举变体映射到唯一的数组索引:
use gilrs::{Axis, Button}; // 为Axis实现索引转换 trait AxisIndex { fn index(&self) -> usize; } impl AxisIndex for Axis { fn index(&self) -> usize { match self { Axis::LeftStickX => 0, Axis::LeftStickY => 1, Axis::RightStickX => 2, Axis::RightStickY => 3, Axis::LeftZ => 4, Axis::RightZ => 5, Axis::DpadX => 6, Axis::DpadY => 7, // 若gilrs新增Axis变体,仅需补充对应索引 } } } // 为Button实现索引转换 trait ButtonIndex { fn index(&self) -> usize; } impl ButtonIndex for Button { fn index(&self) -> usize { match self { Button::South => 0, Button::East => 1, Button::North => 2, Button::West => 3, Button::LeftTrigger => 4, Button::RightTrigger => 5, Button::LeftTrigger2 => 6, Button::RightTrigger2 => 7, Button::Select => 8, Button::Start => 9, Button::Mode => 10, Button::LeftThumb => 11, Button::RightThumb => 12, Button::DpadUp => 13, Button::DpadDown => 14, Button::DpadLeft => 15, Button::DpadRight => 16, // 若gilrs新增Button变体,仅需补充对应索引 } } }
然后重构GamepadState,用数组存储状态:
#[derive(Default, Clone)] pub struct GamepadState { // 数组长度对应Axis变体总数,默认值0.0 axes: [f32; 8], // 数组长度对应Button变体总数,默认值false buttons: [bool; 17], } impl GamepadState { // 保持原有API不变,上层代码无需修改 pub fn axis(&self, axis: Axis) -> f32 { self.axes[axis.index()] } pub fn is_pressed(&self, button: Button) -> bool { self.buttons[button.index()] } }
最后更新事件处理逻辑,直接通过索引操作数组:
match event { gilrs::EventType::AxisChanged(c_axis, value, _) => { gamepad_state.axes[c_axis.index()] = value; } gilrs::EventType::ButtonPressed(c_button, _) => { gamepad_state.buttons[c_button.index()] = true; } gilrs::EventType::ButtonReleased(c_button, _) => { gamepad_state.buttons[c_button.index()] = false; } _ => {} }
2. 进阶优化:用bitvec压缩按钮状态
如果对内存占用有要求,可使用bitvec库将按钮状态从每个bool占1字节压缩到每个按钮占1位,进一步降低内存开销,同时保持性能:
- 添加依赖:
bitvec = { version = "1.0", features = ["prelude"] } - 修改
GamepadState:
use bitvec::prelude::*; #[derive(Clone)] pub struct GamepadState { axes: [f32; 8], buttons: BitVec, } impl Default for GamepadState { fn default() -> Self { Self { axes: [0.0; 8], // 初始化对应Button数量的位向量,默认false buttons: BitVec::repeat(false, 17), } } } impl GamepadState { pub fn is_pressed(&self, button: Button) -> bool { self.buttons[button.index()] } }
事件处理时修改为:
gilrs::EventType::ButtonPressed(c_button, _) => { gamepad_state.buttons.set(c_button.index(), true); } gilrs::EventType::ButtonReleased(c_button, _) => { gamepad_state.buttons.set(c_button.index(), false); }
额外建议:用num_enum简化索引转换
手动编写枚举到索引的匹配逻辑容易出错,推荐用num_enum库自动生成转换代码:
- 添加依赖:
num_enum = { version = "0.7", features = ["derive"] } - 封装自定义枚举并派生转换 trait:
use num_enum::{IntoPrimitive, TryFromPrimitive}; #[derive(Debug, Clone, Copy, IntoPrimitive, TryFromPrimitive)] #[repr(usize)] enum MyAxis { LeftStickX, LeftStickY, RightStickX, RightStickY, LeftZ, RightZ, DpadX, DpadY, } // 实现从gilrs::Axis到自定义MyAxis的转换 impl From<gilrs::Axis> for MyAxis { fn from(axis: gilrs::Axis) -> Self { match axis { gilrs::Axis::LeftStickX => Self::LeftStickX, gilrs::Axis::LeftStickY => Self::LeftStickY, // ... 其他变体映射 } } }
这样可直接用my_axis.into()获取索引,避免手动编写match的繁琐。
内容的提问来源于stack exchange,提问作者Sreyas Sabbani
相关产品推荐
相关产品推荐

