feat(input-server): add InputServer<D: InputDevice> with protocol, mock + UART adapters, and tests

This commit is contained in:
2026-10-18 03:08:36 +02:00
parent 5600ce0260
commit c9bd1d2c35
11 changed files with 346 additions and 12 deletions

View File

@@ -4,3 +4,5 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
types = { workspace = true }
server-framework = { workspace = true }

View 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
}
}

View File

@@ -0,0 +1,2 @@
pub mod mock;
pub mod qemu_uart;

View 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
}
}

View File

@@ -0,0 +1,6 @@
use crate::protocol::InputEvent;
pub trait InputDevice {
fn poll(&mut self) -> Option<InputEvent>;
fn has_event(&self) -> bool;
}

View File

@@ -1,14 +1,10 @@
pub fn add(left: u64, right: u64) -> u64 { #![no_std]
left + right
} pub mod adapters;
pub mod input_device;
pub mod protocol;
pub mod server;
#[cfg(test)] #[cfg(test)]
mod tests { #[path = "tests/mod.rs"]
use super::*; mod tests;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View 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,
}
}
}

View 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,
},
}
}
}

View File

@@ -0,0 +1,2 @@
mod protocol;
mod server;

View 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());
}

View 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'))));
}