Skip to content

Commit b8b21fb

Browse files
authored
Merge pull request #18 from ProjectLighthouseCAU/new-input-api
Implement new input API and refactor geometry types
2 parents 9de6ac1 + bdd8adb commit b8b21fb

26 files changed

+581
-179
lines changed

lighthouse-client/examples/snake.rs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clap::Parser;
22
use futures::{Stream, lock::Mutex, StreamExt};
3-
use lighthouse_client::{Lighthouse, Result, TokioWebSocket, LIGHTHOUSE_URL, protocol::{Authentication, Color, Delta, Frame, Pos, ServerMessage, LIGHTHOUSE_RECT, LIGHTHOUSE_SIZE}};
4-
use lighthouse_protocol::Model;
3+
use lighthouse_client::{Lighthouse, Result, TokioWebSocket, LIGHTHOUSE_URL, protocol::{Authentication, Color, Frame, ServerMessage, LIGHTHOUSE_RECT, LIGHTHOUSE_SIZE}};
4+
use lighthouse_protocol::{Delta, InputEvent, KeyEvent, Pos};
55
use tracing::{info, debug};
66
use tokio::{task, time};
77
use std::{collections::{VecDeque, HashSet}, sync::Arc, time::Duration};
@@ -13,14 +13,14 @@ const SNAKE_INITIAL_LENGTH: usize = 3;
1313

1414
#[derive(Debug, PartialEq, Eq, Clone)]
1515
struct Snake {
16-
fields: VecDeque<Pos>,
17-
dir: Delta,
16+
fields: VecDeque<Pos<i32>>,
17+
dir: Delta<i32>,
1818
}
1919

2020
impl Snake {
2121
fn from_initial_length(length: usize) -> Self {
22-
let mut pos: Pos = LIGHTHOUSE_RECT.sample_random().unwrap();
23-
let dir = Delta::random_cardinal();
22+
let mut pos: Pos<i32> = LIGHTHOUSE_RECT.sample_random().unwrap();
23+
let dir = Delta::<i32>::random_cardinal();
2424

2525
let mut fields = VecDeque::new();
2626
for _ in 0..length {
@@ -31,9 +31,9 @@ impl Snake {
3131
Self { fields, dir }
3232
}
3333

34-
fn head(&self) -> Pos { *self.fields.front().unwrap() }
34+
fn head(&self) -> Pos<i32> { *self.fields.front().unwrap() }
3535

36-
fn back(&self) -> Pos { *self.fields.back().unwrap() }
36+
fn back(&self) -> Pos<i32> { *self.fields.back().unwrap() }
3737

3838
fn grow(&mut self) {
3939
self.fields.push_back(LIGHTHOUSE_RECT.wrap(self.back() - self.dir));
@@ -49,7 +49,7 @@ impl Snake {
4949
self.fields.iter().collect::<HashSet<_>>().len() < self.fields.len()
5050
}
5151

52-
fn rotate_head(&mut self, dir: Delta) {
52+
fn rotate_head(&mut self, dir: Delta<i32>) {
5353
self.dir = dir;
5454
}
5555

@@ -63,7 +63,7 @@ impl Snake {
6363
self.fields.len()
6464
}
6565

66-
fn random_fruit_pos(&self) -> Option<Pos> {
66+
fn random_fruit_pos(&self) -> Option<Pos<i32>> {
6767
let fields = self.fields.iter().collect::<HashSet<_>>();
6868
if fields.len() >= LIGHTHOUSE_SIZE {
6969
None
@@ -81,7 +81,7 @@ impl Snake {
8181
#[derive(Debug, PartialEq, Eq, Clone)]
8282
struct State {
8383
snake: Snake,
84-
fruit: Pos,
84+
fruit: Pos<i32>,
8585
}
8686

8787
impl State {
@@ -142,16 +142,16 @@ async fn run_updater(lh: Lighthouse<TokioWebSocket>, shared_state: Arc<Mutex<Sta
142142
}
143143
}
144144

145-
async fn run_controller(mut stream: impl Stream<Item = Result<ServerMessage<Model>>> + Unpin, shared_state: Arc<Mutex<State>>) -> Result<()> {
145+
async fn run_controller(mut stream: impl Stream<Item = Result<ServerMessage<InputEvent>>> + Unpin, shared_state: Arc<Mutex<State>>) -> Result<()> {
146146
while let Some(msg) = stream.next().await {
147-
if let Model::InputEvent(event) = msg?.payload {
148-
if event.is_down {
147+
match msg?.payload {
148+
InputEvent::Key(KeyEvent { key, down, .. }) if down => {
149149
// Map the key code to a direction vector
150-
let opt_dir = match event.key {
151-
Some(37) => Some(Delta::LEFT),
152-
Some(38) => Some(Delta::UP),
153-
Some(39) => Some(Delta::RIGHT),
154-
Some(40) => Some(Delta::DOWN),
150+
let opt_dir = match key.as_str() {
151+
"ArrowLeft" => Some(Delta::<i32>::LEFT),
152+
"ArrowUp" => Some(Delta::<i32>::UP),
153+
"ArrowRight" => Some(Delta::<i32>::RIGHT),
154+
"ArrowDown" => Some(Delta::<i32>::DOWN),
155155
_ => None,
156156
};
157157

@@ -162,6 +162,7 @@ async fn run_controller(mut stream: impl Stream<Item = Result<ServerMessage<Mode
162162
state.snake.rotate_head(dir);
163163
}
164164
}
165+
_ => {},
165166
}
166167
}
167168

@@ -193,7 +194,7 @@ async fn main() -> Result<()> {
193194
let lh = Lighthouse::connect_with_tokio_to(&args.url, auth).await?;
194195
info!("Connected to the Lighthouse server");
195196

196-
let stream = lh.stream_model().await?;
197+
let stream = lh.stream_input().await?;
197198

198199
let updater_handle = task::spawn(run_updater(lh, state.clone()));
199200
let controller_handle = task::spawn(run_controller(stream, state));

lighthouse-client/src/lighthouse.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt::Debug, sync::{atomic::{AtomicI32, Ordering}
22

33
use async_tungstenite::tungstenite::{Message, self};
44
use futures::{prelude::*, channel::mpsc::{Sender, self}, stream::{SplitSink, SplitStream}, lock::Mutex};
5-
use lighthouse_protocol::{Authentication, ClientMessage, DirectoryTree, Frame, LaserMetrics, Model, ServerMessage, Value, Verb};
5+
use lighthouse_protocol::{Authentication, ClientMessage, DirectoryTree, Frame, InputEvent, LaserMetrics, Model, ServerMessage, Value, Verb};
66
use serde::{Deserialize, Serialize};
77
use stream_guard::GuardStreamExt;
88
use tracing::{warn, error, debug, info};
@@ -130,6 +130,25 @@ impl<S> Lighthouse<S>
130130
self.stream(&["user".into(), username, "model".into()], ()).await
131131
}
132132

133+
/// Sends an input event to the user's input endpoint.
134+
///
135+
/// Note that this is the new API which not all clients may support.
136+
pub async fn put_input(&self, payload: InputEvent) -> Result<ServerMessage<()>> {
137+
let username = self.authentication.username.clone();
138+
self.put(&["user".into(), username, "input".into()], payload).await
139+
}
140+
141+
/// Streams input events from the user's input endpoint.
142+
///
143+
/// Note that this is the new API which not all clients may support (in LUNA
144+
/// disabling the legacy mode will send events to this endpoint). If your
145+
/// client or library does not support this, you may need to `stream_model`
146+
/// and parse `LegacyInputEvent`s from there.
147+
pub async fn stream_input(&self) -> Result<impl Stream<Item = Result<ServerMessage<InputEvent>>>> {
148+
let username = self.authentication.username.clone();
149+
self.stream(&["user".into(), username, "input".into()], ()).await
150+
}
151+
133152
/// Fetches lamp server metrics.
134153
pub async fn get_laser_metrics(&self) -> Result<ServerMessage<LaserMetrics>> {
135154
self.get(&["metrics", "laser"]).await

lighthouse-protocol/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ rand = "0.8"
1414
rmpv = { version = "1.0.1", features = ["with-serde"] }
1515
serde = { version = "1.0", features = ["derive"] }
1616
serde_with = "3.4"
17+
18+
[dev-dependencies]
19+
serde_json = "1.0"

lighthouse-protocol/src/constants.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{Rect, Pos, Delta};
1+
use crate::{Rect, Vec2, Zero};
22

33
/// The number of rows of the lighthouse.
44
pub const LIGHTHOUSE_ROWS: usize = 14;
@@ -9,4 +9,4 @@ pub const LIGHTHOUSE_SIZE: usize = LIGHTHOUSE_ROWS * LIGHTHOUSE_COLS;
99
/// The total number of bytes in a lighthouse frame.
1010
pub const LIGHTHOUSE_BYTES: usize = LIGHTHOUSE_SIZE * 3;
1111
/// The rectangle of valid coordinates on the lighthouse.
12-
pub const LIGHTHOUSE_RECT: Rect = Rect::new(Pos::ZERO, Delta::new(LIGHTHOUSE_COLS as i32, LIGHTHOUSE_ROWS as i32));
12+
pub const LIGHTHOUSE_RECT: Rect<i32> = Rect::new(Vec2::ZERO, Vec2::new(LIGHTHOUSE_COLS as i32, LIGHTHOUSE_ROWS as i32));

lighthouse-protocol/src/frame.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@ impl Frame {
5151
}
5252
}
5353

54-
impl Index<Pos> for Frame {
54+
impl Index<Pos<i32>> for Frame {
5555
type Output = Color;
5656

57-
fn index(&self, pos: Pos) -> &Color {
57+
fn index(&self, pos: Pos<i32>) -> &Color {
5858
&self.pixels[LIGHTHOUSE_RECT.index_of(pos)]
5959
}
6060
}
6161

62-
impl IndexMut<Pos> for Frame {
63-
fn index_mut(&mut self, pos: Pos) -> &mut Color {
62+
impl IndexMut<Pos<i32>> for Frame {
63+
fn index_mut(&mut self, pos: Pos<i32>) -> &mut Color {
6464
&mut self.pixels[LIGHTHOUSE_RECT.index_of(pos)]
6565
}
6666
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// An identifier that is unique per client + device combo.
4+
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
5+
#[serde(untagged)]
6+
pub enum EventSource {
7+
String(String),
8+
Int(i32),
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// An axis event on a gamepad.
4+
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
5+
#[serde(tag = "control", rename_all = "camelCase")]
6+
pub struct GamepadAxisEvent {
7+
/// The axis index.
8+
pub index: usize,
9+
/// The value of the axis (between -1.0 and 1.0, modeled after the Web Gamepad API).
10+
pub value: f64,
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// A button event on a gamepad.
4+
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
5+
#[serde(tag = "control", rename_all = "camelCase")]
6+
pub struct GamepadButtonEvent {
7+
/// The button index.
8+
pub index: usize,
9+
/// Whether the button is pressed.
10+
pub down: bool,
11+
/// The value of the button (between 0.0 and 1.0, modeled after the Web Gamepad API).
12+
pub value: f64,
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
use super::{GamepadAxisEvent, GamepadButtonEvent};
4+
5+
/// A control-specific event on a gamepad.
6+
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
7+
#[serde(tag = "control", rename_all = "camelCase")]
8+
pub enum GamepadControlEvent {
9+
Button(GamepadButtonEvent),
10+
Axis(GamepadAxisEvent),
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
use super::{EventSource, GamepadControlEvent};
4+
5+
/// A gamepad/controller event.
6+
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
7+
#[serde(rename_all = "camelCase")]
8+
pub struct GamepadEvent {
9+
/// The client identifier. Also unique per gamepad.
10+
pub source: EventSource,
11+
/// The control-specific info.
12+
#[serde(flatten)]
13+
pub control: GamepadControlEvent,
14+
}

0 commit comments

Comments
 (0)