-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
166 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
///! a 'sticky' key buffer to store key presses for a guaranteed amount of time | ||
const assert = @import("std").debug.assert; | ||
|
||
// comptime config options | ||
const Config = struct { | ||
num_slots: comptime_int = 8, // number of max simultanously pressed keys | ||
}; | ||
|
||
pub fn Type(comptime cfg: Config) type { | ||
assert(cfg.num_slots > 0); | ||
return struct { | ||
const Self = @This(); | ||
|
||
// runtime options | ||
pub const Options = struct { | ||
sticky_time: u64 = 32, // number of time units a pressed key should 'stick' | ||
}; | ||
|
||
// state of a single currently pressed key | ||
pub const Slot = struct { | ||
// key code of the pressed key (zero if slot is not populated) | ||
key: u32 = 0, | ||
// optional keyboard matrix bit mask | ||
mask: u32 = 0, | ||
// timestamp of when the key was pressed down | ||
pressed_time: u64 = 0, | ||
// set to true when the key has been released | ||
released: bool = false, | ||
}; | ||
|
||
slots: [cfg.num_slots]Slot = [_]Slot{.{}} ** cfg.num_slots, | ||
cur_time: u64 = 0, | ||
sticky_time: u64 = 0, | ||
|
||
pub fn init(options: Options) Self { | ||
return .{ | ||
.sticky_time = options.sticky_time, | ||
}; | ||
} | ||
|
||
// call once per frame with frame duration in your chosen time unit | ||
pub fn update(self: *Self, frame_time: u64) void { | ||
self.cur_time +%= frame_time; | ||
for (&self.slots) |*slot| { | ||
if (slot.released) { | ||
// properly handle time wraparound | ||
if ((self.cur_time < slot.pressed_time) or | ||
(self.cur_time >= (slot.pressed_time +% self.sticky_time))) | ||
{ | ||
slot.* = .{}; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// call when a key is pressed down | ||
pub fn keyDown(self: *Self, key: u32, mask: u32) void { | ||
// first check if the key is already buffered, if yes only update timestamp | ||
for (&self.slots) |*slot| { | ||
if (key == slot.key) { | ||
assert(slot.mask == mask); | ||
slot.pressed_time = self.cur_time; | ||
return; | ||
} | ||
} | ||
// otherwise find a populate a free slot | ||
for (&self.slots) |*slot| { | ||
if (0 == slot.key) { | ||
slot.key = key; | ||
slot.mask = mask; | ||
slot.pressed_time = self.cur_time; | ||
slot.released = false; | ||
return; | ||
} | ||
} | ||
} | ||
|
||
// call when a pressed key is released | ||
pub fn keyUp(self: *Self, key: u32) void { | ||
for (&self.slots) |*slot| { | ||
if (key == slot.key) { | ||
slot.released = true; | ||
return; | ||
} | ||
} | ||
} | ||
|
||
// 'unpress' all keys, call this when the emulator window looses focus | ||
pub fn flush(self: *Self) void { | ||
for (&self.slots) |*slot| { | ||
slot.* = .{}; | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
const expect = @import("std").testing.expect; | ||
const keybuf = @import("chipz").common.keybuf; | ||
|
||
const KeyBuf = keybuf.Type(.{ .num_slots = 4 }); | ||
|
||
test "single key" { | ||
var kb = KeyBuf.init(.{ .sticky_time = 1000 }); | ||
try expect(kb.sticky_time == 1000); | ||
kb.update(250); | ||
try expect(kb.cur_time == 250); | ||
kb.keyDown('A', 0x1234); | ||
try expect(kb.slots[0].key == 'A'); | ||
try expect(kb.slots[0].mask == 0x1234); | ||
try expect(kb.slots[0].pressed_time == 250); | ||
try expect(kb.slots[0].released == false); | ||
try expect(kb.slots[1].key == 0); | ||
kb.keyUp('A'); | ||
try expect(kb.slots[0].key == 'A'); | ||
try expect(kb.slots[0].released); | ||
kb.update(250); | ||
try expect(kb.cur_time == 500); | ||
try expect(kb.slots[0].key == 'A'); | ||
try expect(kb.slots[0].released); | ||
kb.update(250); | ||
try expect(kb.cur_time == 750); | ||
try expect(kb.slots[0].key == 'A'); | ||
try expect(kb.slots[0].released); | ||
kb.update(250); | ||
try expect(kb.cur_time == 1000); | ||
try expect(kb.slots[0].key == 'A'); | ||
try expect(kb.slots[0].released); | ||
kb.update(250); | ||
try expect(kb.cur_time == 1250); | ||
try expect(kb.slots[0].key == 0); | ||
try expect(kb.slots[0].mask == 0); | ||
try expect(kb.slots[0].pressed_time == 0); | ||
try expect(kb.slots[0].released == false); | ||
} | ||
|
||
test "update existing key" { | ||
var kb = KeyBuf.init(.{ .sticky_time = 1000 }); | ||
try expect(kb.sticky_time == 1000); | ||
kb.update(250); | ||
try expect(kb.cur_time == 250); | ||
kb.keyDown('A', 0x1234); | ||
try expect(kb.slots[0].key == 'A'); | ||
try expect(kb.slots[0].mask == 0x1234); | ||
try expect(kb.slots[0].pressed_time == 250); | ||
try expect(kb.slots[0].released == false); | ||
kb.update(250); | ||
try expect(kb.cur_time == 500); | ||
kb.keyDown('A', 0x1234); | ||
try expect(kb.slots[0].key == 'A'); | ||
try expect(kb.slots[0].mask == 0x1234); | ||
try expect(kb.slots[0].pressed_time == 500); | ||
try expect(kb.slots[0].released == false); | ||
try expect(kb.slots[1].key == 0); | ||
kb.update(500); | ||
try expect(kb.slots[0].key == 'A'); | ||
kb.keyUp('A'); | ||
kb.update(500); | ||
try expect(kb.cur_time == 1500); | ||
try expect(kb.slots[0].key == 0); | ||
try expect(kb.slots[0].mask == 0); | ||
try expect(kb.slots[0].pressed_time == 0); | ||
try expect(kb.slots[0].released == false); | ||
} | ||
|
||
// FIXME: multiple pressed keys |