feat(input-server): add InputServer<D: InputDevice> with protocol, mock + UART adapters, and tests
This commit is contained in:
38
crates/servers/input-server/src/adapters/mock.rs
Normal file
38
crates/servers/input-server/src/adapters/mock.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use crate::input_device::InputDevice;
|
||||
use crate::protocol::InputEvent;
|
||||
|
||||
pub struct MockInputDevice {
|
||||
events: [Option<InputEvent>; 16],
|
||||
head: usize,
|
||||
tail: usize,
|
||||
}
|
||||
|
||||
impl MockInputDevice {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
events: [None; 16],
|
||||
head: 0,
|
||||
tail: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_event(&mut self, event: InputEvent) {
|
||||
self.events[self.tail] = Some(event);
|
||||
self.tail = (self.tail + 1) % self.events.len();
|
||||
}
|
||||
}
|
||||
|
||||
impl InputDevice for MockInputDevice {
|
||||
fn poll(&mut self) -> Option<InputEvent> {
|
||||
if self.head == self.tail {
|
||||
return None;
|
||||
}
|
||||
let event = self.events[self.head].take();
|
||||
self.head = (self.head + 1) % self.events.len();
|
||||
event
|
||||
}
|
||||
|
||||
fn has_event(&self) -> bool {
|
||||
self.head != self.tail
|
||||
}
|
||||
}
|
||||
2
crates/servers/input-server/src/adapters/mod.rs
Normal file
2
crates/servers/input-server/src/adapters/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod mock;
|
||||
pub mod qemu_uart;
|
||||
48
crates/servers/input-server/src/adapters/qemu_uart.rs
Normal file
48
crates/servers/input-server/src/adapters/qemu_uart.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use crate::input_device::InputDevice;
|
||||
use crate::protocol::{InputEvent, Key};
|
||||
|
||||
const UART_BASE: usize = 0x10000000;
|
||||
const UART_LSR: usize = 5;
|
||||
|
||||
pub struct UartInputDevice {
|
||||
base: usize,
|
||||
}
|
||||
|
||||
impl UartInputDevice {
|
||||
pub fn new() -> Self {
|
||||
Self { base: UART_BASE }
|
||||
}
|
||||
|
||||
fn read_byte(&self) -> Option<u8> {
|
||||
let lsr = unsafe { core::ptr::read_volatile((self.base + UART_LSR) as *const u8) };
|
||||
if lsr & 1 != 0 {
|
||||
Some(unsafe { core::ptr::read_volatile(self.base as *const u8) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_to_key(byte: u8) -> Key {
|
||||
match byte {
|
||||
b'w' | b'W' => Key::Up,
|
||||
b's' | b'S' => Key::Down,
|
||||
b'a' | b'A' => Key::Left,
|
||||
b'd' | b'D' => Key::Right,
|
||||
b'\r' | b'\n' => Key::Enter,
|
||||
0x1B => Key::Escape,
|
||||
c => Key::Char(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputDevice for UartInputDevice {
|
||||
fn poll(&mut self) -> Option<InputEvent> {
|
||||
let byte = self.read_byte()?;
|
||||
Some(InputEvent::KeyDown(Self::byte_to_key(byte)))
|
||||
}
|
||||
|
||||
fn has_event(&self) -> bool {
|
||||
let lsr = unsafe { core::ptr::read_volatile((self.base + UART_LSR) as *const u8) };
|
||||
lsr & 1 != 0
|
||||
}
|
||||
}
|
||||
6
crates/servers/input-server/src/input_device.rs
Normal file
6
crates/servers/input-server/src/input_device.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use crate::protocol::InputEvent;
|
||||
|
||||
pub trait InputDevice {
|
||||
fn poll(&mut self) -> Option<InputEvent>;
|
||||
fn has_event(&self) -> bool;
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
#![no_std]
|
||||
|
||||
pub mod adapters;
|
||||
pub mod input_device;
|
||||
pub mod protocol;
|
||||
pub mod server;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
#[path = "tests/mod.rs"]
|
||||
mod tests;
|
||||
|
||||
107
crates/servers/input-server/src/protocol.rs
Normal file
107
crates/servers/input-server/src/protocol.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use server_framework::{Decode, Encode};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Key {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Enter,
|
||||
Escape,
|
||||
Char(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InputEvent {
|
||||
KeyDown(Key),
|
||||
KeyUp(Key),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InputRequest {
|
||||
PollEvent,
|
||||
WaitEvent,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InputResponse {
|
||||
Event(InputEvent),
|
||||
NoEvent,
|
||||
}
|
||||
|
||||
impl Encode for InputRequest {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
InputRequest::PollEvent => [0, 0, 0, 0],
|
||||
InputRequest::WaitEvent => [1, 0, 0, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for InputRequest {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => Some(InputRequest::PollEvent),
|
||||
1 => Some(InputRequest::WaitEvent),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_key(key: Key) -> (usize, usize) {
|
||||
match key {
|
||||
Key::Up => (0, 0),
|
||||
Key::Down => (1, 0),
|
||||
Key::Left => (2, 0),
|
||||
Key::Right => (3, 0),
|
||||
Key::Enter => (4, 0),
|
||||
Key::Escape => (5, 0),
|
||||
Key::Char(c) => (6, c as usize),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_key(kind: usize, data: usize) -> Option<Key> {
|
||||
match kind {
|
||||
0 => Some(Key::Up),
|
||||
1 => Some(Key::Down),
|
||||
2 => Some(Key::Left),
|
||||
3 => Some(Key::Right),
|
||||
4 => Some(Key::Enter),
|
||||
5 => Some(Key::Escape),
|
||||
6 => Some(Key::Char(data as u8)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for InputResponse {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
InputResponse::NoEvent => [0, 0, 0, 0],
|
||||
InputResponse::Event(InputEvent::KeyDown(key)) => {
|
||||
let (kind, data) = encode_key(*key);
|
||||
[1, kind, data, 0]
|
||||
}
|
||||
InputResponse::Event(InputEvent::KeyUp(key)) => {
|
||||
let (kind, data) = encode_key(*key);
|
||||
[2, kind, data, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for InputResponse {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => Some(InputResponse::NoEvent),
|
||||
1 => {
|
||||
let key = decode_key(words[1], words[2])?;
|
||||
Some(InputResponse::Event(InputEvent::KeyDown(key)))
|
||||
}
|
||||
2 => {
|
||||
let key = decode_key(words[1], words[2])?;
|
||||
Some(InputResponse::Event(InputEvent::KeyUp(key)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
36
crates/servers/input-server/src/server.rs
Normal file
36
crates/servers/input-server/src/server.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use server_framework::Server;
|
||||
|
||||
use crate::input_device::InputDevice;
|
||||
use crate::protocol::{InputRequest, InputResponse};
|
||||
|
||||
pub struct InputServer<D: InputDevice> {
|
||||
device: D,
|
||||
}
|
||||
|
||||
impl<D: InputDevice> InputServer<D> {
|
||||
pub fn new(device: D) -> Self {
|
||||
Self { device }
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &D {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: InputDevice> Server for InputServer<D> {
|
||||
type Request = InputRequest;
|
||||
type Response = InputResponse;
|
||||
|
||||
fn handle(&mut self, request: InputRequest) -> InputResponse {
|
||||
match request {
|
||||
InputRequest::PollEvent => match self.device.poll() {
|
||||
Some(event) => InputResponse::Event(event),
|
||||
None => InputResponse::NoEvent,
|
||||
},
|
||||
InputRequest::WaitEvent => match self.device.poll() {
|
||||
Some(event) => InputResponse::Event(event),
|
||||
None => InputResponse::NoEvent,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
2
crates/servers/input-server/src/tests/mod.rs
Normal file
2
crates/servers/input-server/src/tests/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod protocol;
|
||||
mod server;
|
||||
46
crates/servers/input-server/src/tests/protocol.rs
Normal file
46
crates/servers/input-server/src/tests/protocol.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::protocol::*;
|
||||
use server_framework::{Decode, Encode};
|
||||
|
||||
#[test]
|
||||
fn poll_request_roundtrip() {
|
||||
let words = InputRequest::PollEvent.encode();
|
||||
assert_eq!(InputRequest::decode(words), Some(InputRequest::PollEvent));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_request_roundtrip() {
|
||||
let words = InputRequest::WaitEvent.encode();
|
||||
assert_eq!(InputRequest::decode(words), Some(InputRequest::WaitEvent));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_down_response_roundtrip() {
|
||||
let resp = InputResponse::Event(InputEvent::KeyDown(Key::Up));
|
||||
let words = resp.encode();
|
||||
assert_eq!(InputResponse::decode(words), Some(resp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_up_response_roundtrip() {
|
||||
let resp = InputResponse::Event(InputEvent::KeyUp(Key::Left));
|
||||
let words = resp.encode();
|
||||
assert_eq!(InputResponse::decode(words), Some(resp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_key_roundtrip() {
|
||||
let resp = InputResponse::Event(InputEvent::KeyDown(Key::Char(b'x')));
|
||||
let words = resp.encode();
|
||||
assert_eq!(InputResponse::decode(words), Some(resp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_event_roundtrip() {
|
||||
let words = InputResponse::NoEvent.encode();
|
||||
assert_eq!(InputResponse::decode(words), Some(InputResponse::NoEvent));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_request_returns_none() {
|
||||
assert!(InputRequest::decode([99, 0, 0, 0]).is_none());
|
||||
}
|
||||
51
crates/servers/input-server/src/tests/server.rs
Normal file
51
crates/servers/input-server/src/tests/server.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use crate::adapters::mock::MockInputDevice;
|
||||
use crate::protocol::*;
|
||||
use crate::server::InputServer;
|
||||
use server_framework::Server;
|
||||
|
||||
fn test_server() -> InputServer<MockInputDevice> {
|
||||
InputServer::new(MockInputDevice::new())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poll_with_no_events_returns_no_event() {
|
||||
let mut server = test_server();
|
||||
let resp = server.handle(InputRequest::PollEvent);
|
||||
assert_eq!(resp, InputResponse::NoEvent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poll_returns_queued_event() {
|
||||
let mut device = MockInputDevice::new();
|
||||
device.push_event(InputEvent::KeyDown(Key::Up));
|
||||
let mut server = InputServer::new(device);
|
||||
|
||||
let resp = server.handle(InputRequest::PollEvent);
|
||||
assert_eq!(resp, InputResponse::Event(InputEvent::KeyDown(Key::Up)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poll_drains_events_in_order() {
|
||||
let mut device = MockInputDevice::new();
|
||||
device.push_event(InputEvent::KeyDown(Key::Left));
|
||||
device.push_event(InputEvent::KeyDown(Key::Right));
|
||||
let mut server = InputServer::new(device);
|
||||
|
||||
let r1 = server.handle(InputRequest::PollEvent);
|
||||
let r2 = server.handle(InputRequest::PollEvent);
|
||||
let r3 = server.handle(InputRequest::PollEvent);
|
||||
|
||||
assert_eq!(r1, InputResponse::Event(InputEvent::KeyDown(Key::Left)));
|
||||
assert_eq!(r2, InputResponse::Event(InputEvent::KeyDown(Key::Right)));
|
||||
assert_eq!(r3, InputResponse::NoEvent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_event_preserved() {
|
||||
let mut device = MockInputDevice::new();
|
||||
device.push_event(InputEvent::KeyDown(Key::Char(b'z')));
|
||||
let mut server = InputServer::new(device);
|
||||
|
||||
let resp = server.handle(InputRequest::PollEvent);
|
||||
assert_eq!(resp, InputResponse::Event(InputEvent::KeyDown(Key::Char(b'z'))));
|
||||
}
|
||||
Reference in New Issue
Block a user