diff --git a/crates/servers/input-server/Cargo.toml b/crates/servers/input-server/Cargo.toml index 12177ff..c118c5e 100644 --- a/crates/servers/input-server/Cargo.toml +++ b/crates/servers/input-server/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +types = { workspace = true } +server-framework = { workspace = true } diff --git a/crates/servers/input-server/src/adapters/mock.rs b/crates/servers/input-server/src/adapters/mock.rs new file mode 100644 index 0000000..3259fe0 --- /dev/null +++ b/crates/servers/input-server/src/adapters/mock.rs @@ -0,0 +1,38 @@ +use crate::input_device::InputDevice; +use crate::protocol::InputEvent; + +pub struct MockInputDevice { + events: [Option; 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 { + 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 + } +} diff --git a/crates/servers/input-server/src/adapters/mod.rs b/crates/servers/input-server/src/adapters/mod.rs new file mode 100644 index 0000000..f58329f --- /dev/null +++ b/crates/servers/input-server/src/adapters/mod.rs @@ -0,0 +1,2 @@ +pub mod mock; +pub mod qemu_uart; diff --git a/crates/servers/input-server/src/adapters/qemu_uart.rs b/crates/servers/input-server/src/adapters/qemu_uart.rs new file mode 100644 index 0000000..8816266 --- /dev/null +++ b/crates/servers/input-server/src/adapters/qemu_uart.rs @@ -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 { + 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 { + 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 + } +} diff --git a/crates/servers/input-server/src/input_device.rs b/crates/servers/input-server/src/input_device.rs new file mode 100644 index 0000000..2f6db05 --- /dev/null +++ b/crates/servers/input-server/src/input_device.rs @@ -0,0 +1,6 @@ +use crate::protocol::InputEvent; + +pub trait InputDevice { + fn poll(&mut self) -> Option; + fn has_event(&self) -> bool; +} diff --git a/crates/servers/input-server/src/lib.rs b/crates/servers/input-server/src/lib.rs index b93cf3f..295ca34 100644 --- a/crates/servers/input-server/src/lib.rs +++ b/crates/servers/input-server/src/lib.rs @@ -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; diff --git a/crates/servers/input-server/src/protocol.rs b/crates/servers/input-server/src/protocol.rs new file mode 100644 index 0000000..dcdcfd2 --- /dev/null +++ b/crates/servers/input-server/src/protocol.rs @@ -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 { + 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 { + 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 { + 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, + } + } +} diff --git a/crates/servers/input-server/src/server.rs b/crates/servers/input-server/src/server.rs new file mode 100644 index 0000000..0826620 --- /dev/null +++ b/crates/servers/input-server/src/server.rs @@ -0,0 +1,36 @@ +use server_framework::Server; + +use crate::input_device::InputDevice; +use crate::protocol::{InputRequest, InputResponse}; + +pub struct InputServer { + device: D, +} + +impl InputServer { + pub fn new(device: D) -> Self { + Self { device } + } + + pub fn device(&self) -> &D { + &self.device + } +} + +impl Server for InputServer { + 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, + }, + } + } +} diff --git a/crates/servers/input-server/src/tests/mod.rs b/crates/servers/input-server/src/tests/mod.rs new file mode 100644 index 0000000..aa77244 --- /dev/null +++ b/crates/servers/input-server/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod protocol; +mod server; diff --git a/crates/servers/input-server/src/tests/protocol.rs b/crates/servers/input-server/src/tests/protocol.rs new file mode 100644 index 0000000..8f7823a --- /dev/null +++ b/crates/servers/input-server/src/tests/protocol.rs @@ -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()); +} diff --git a/crates/servers/input-server/src/tests/server.rs b/crates/servers/input-server/src/tests/server.rs new file mode 100644 index 0000000..732b549 --- /dev/null +++ b/crates/servers/input-server/src/tests/server.rs @@ -0,0 +1,51 @@ +use crate::adapters::mock::MockInputDevice; +use crate::protocol::*; +use crate::server::InputServer; +use server_framework::Server; + +fn test_server() -> InputServer { + 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')))); +}