Skip to content
Merged
41 changes: 21 additions & 20 deletions lighthouse-client/examples/snake.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clap::Parser;
use futures::{Stream, lock::Mutex, StreamExt};
use lighthouse_client::{Lighthouse, Result, TokioWebSocket, LIGHTHOUSE_URL, protocol::{Authentication, Color, Delta, Frame, Pos, ServerMessage, LIGHTHOUSE_RECT, LIGHTHOUSE_SIZE}};
use lighthouse_protocol::Model;
use lighthouse_client::{Lighthouse, Result, TokioWebSocket, LIGHTHOUSE_URL, protocol::{Authentication, Color, Frame, ServerMessage, LIGHTHOUSE_RECT, LIGHTHOUSE_SIZE}};
use lighthouse_protocol::{Delta, InputEvent, KeyEvent, Pos};
use tracing::{info, debug};
use tokio::{task, time};
use std::{collections::{VecDeque, HashSet}, sync::Arc, time::Duration};
Expand All @@ -13,14 +13,14 @@ const SNAKE_INITIAL_LENGTH: usize = 3;

#[derive(Debug, PartialEq, Eq, Clone)]
struct Snake {
fields: VecDeque<Pos>,
dir: Delta,
fields: VecDeque<Pos<i32>>,
dir: Delta<i32>,
}

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

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

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

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

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

fn rotate_head(&mut self, dir: Delta) {
fn rotate_head(&mut self, dir: Delta<i32>) {
self.dir = dir;
}

Expand All @@ -63,7 +63,7 @@ impl Snake {
self.fields.len()
}

fn random_fruit_pos(&self) -> Option<Pos> {
fn random_fruit_pos(&self) -> Option<Pos<i32>> {
let fields = self.fields.iter().collect::<HashSet<_>>();
if fields.len() >= LIGHTHOUSE_SIZE {
None
Expand All @@ -81,7 +81,7 @@ impl Snake {
#[derive(Debug, PartialEq, Eq, Clone)]
struct State {
snake: Snake,
fruit: Pos,
fruit: Pos<i32>,
}

impl State {
Expand Down Expand Up @@ -142,16 +142,16 @@ async fn run_updater(lh: Lighthouse<TokioWebSocket>, shared_state: Arc<Mutex<Sta
}
}

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

Expand All @@ -162,6 +162,7 @@ async fn run_controller(mut stream: impl Stream<Item = Result<ServerMessage<Mode
state.snake.rotate_head(dir);
}
}
_ => {},
}
}

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

let stream = lh.stream_model().await?;
let stream = lh.stream_input().await?;

let updater_handle = task::spawn(run_updater(lh, state.clone()));
let controller_handle = task::spawn(run_controller(stream, state));
Expand Down
21 changes: 20 additions & 1 deletion lighthouse-client/src/lighthouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt::Debug, sync::{atomic::{AtomicI32, Ordering}

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

/// Sends an input event to the user's input endpoint.
///
/// Note that this is the new API which not all clients may support.
pub async fn put_input(&self, payload: InputEvent) -> Result<ServerMessage<()>> {
let username = self.authentication.username.clone();
self.put(&["user".into(), username, "input".into()], payload).await
}

/// Streams input events from the user's input endpoint.
///
/// Note that this is the new API which not all clients may support (in LUNA
/// disabling the legacy mode will send events to this endpoint). If your
/// client or library does not support this, you may need to `stream_model`
/// and parse `LegacyInputEvent`s from there.
pub async fn stream_input(&self) -> Result<impl Stream<Item = Result<ServerMessage<InputEvent>>>> {
let username = self.authentication.username.clone();
self.stream(&["user".into(), username, "input".into()], ()).await
}

/// Fetches lamp server metrics.
pub async fn get_laser_metrics(&self) -> Result<ServerMessage<LaserMetrics>> {
self.get(&["metrics", "laser"]).await
Expand Down
3 changes: 3 additions & 0 deletions lighthouse-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ rand = "0.8"
rmpv = { version = "1.0.1", features = ["with-serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_with = "3.4"

[dev-dependencies]
serde_json = "1.0"
4 changes: 2 additions & 2 deletions lighthouse-protocol/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Rect, Pos, Delta};
use crate::{Rect, Vec2, Zero};

/// The number of rows of the lighthouse.
pub const LIGHTHOUSE_ROWS: usize = 14;
Expand All @@ -9,4 +9,4 @@ pub const LIGHTHOUSE_SIZE: usize = LIGHTHOUSE_ROWS * LIGHTHOUSE_COLS;
/// The total number of bytes in a lighthouse frame.
pub const LIGHTHOUSE_BYTES: usize = LIGHTHOUSE_SIZE * 3;
/// The rectangle of valid coordinates on the lighthouse.
pub const LIGHTHOUSE_RECT: Rect = Rect::new(Pos::ZERO, Delta::new(LIGHTHOUSE_COLS as i32, LIGHTHOUSE_ROWS as i32));
pub const LIGHTHOUSE_RECT: Rect<i32> = Rect::new(Vec2::ZERO, Vec2::new(LIGHTHOUSE_COLS as i32, LIGHTHOUSE_ROWS as i32));
8 changes: 4 additions & 4 deletions lighthouse-protocol/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ impl Frame {
}
}

impl Index<Pos> for Frame {
impl Index<Pos<i32>> for Frame {
type Output = Color;

fn index(&self, pos: Pos) -> &Color {
fn index(&self, pos: Pos<i32>) -> &Color {
&self.pixels[LIGHTHOUSE_RECT.index_of(pos)]
}
}

impl IndexMut<Pos> for Frame {
fn index_mut(&mut self, pos: Pos) -> &mut Color {
impl IndexMut<Pos<i32>> for Frame {
fn index_mut(&mut self, pos: Pos<i32>) -> &mut Color {
&mut self.pixels[LIGHTHOUSE_RECT.index_of(pos)]
}
}
Expand Down
9 changes: 9 additions & 0 deletions lighthouse-protocol/src/input/event_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};

/// An identifier that is unique per client + device combo.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(untagged)]
pub enum EventSource {
String(String),
Int(i32),
}
11 changes: 11 additions & 0 deletions lighthouse-protocol/src/input/gamepad_axis_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use serde::{Deserialize, Serialize};

/// An axis event on a gamepad.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(tag = "control", rename_all = "camelCase")]
pub struct GamepadAxisEvent {
/// The axis index.
pub index: usize,
/// The value of the axis (between -1.0 and 1.0, modeled after the Web Gamepad API).
pub value: f64,
}
13 changes: 13 additions & 0 deletions lighthouse-protocol/src/input/gamepad_button_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};

/// A button event on a gamepad.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(tag = "control", rename_all = "camelCase")]
pub struct GamepadButtonEvent {
/// The button index.
pub index: usize,
/// Whether the button is pressed.
pub down: bool,
/// The value of the button (between 0.0 and 1.0, modeled after the Web Gamepad API).
pub value: f64,
}
11 changes: 11 additions & 0 deletions lighthouse-protocol/src/input/gamepad_control_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use serde::{Deserialize, Serialize};

use super::{GamepadAxisEvent, GamepadButtonEvent};

/// A control-specific event on a gamepad.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(tag = "control", rename_all = "camelCase")]
pub enum GamepadControlEvent {
Button(GamepadButtonEvent),
Axis(GamepadAxisEvent),
}
14 changes: 14 additions & 0 deletions lighthouse-protocol/src/input/gamepad_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use serde::{Deserialize, Serialize};

use super::{EventSource, GamepadControlEvent};

/// A gamepad/controller event.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GamepadEvent {
/// The client identifier. Also unique per gamepad.
pub source: EventSource,
/// The control-specific info.
#[serde(flatten)]
pub control: GamepadControlEvent,
}
98 changes: 98 additions & 0 deletions lighthouse-protocol/src/input/input_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use serde::{Deserialize, Serialize};

use super::{GamepadEvent, KeyEvent, MouseEvent};

/// A user input event, as generated by the new frontend (LUNA).
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum InputEvent {
Key(KeyEvent),
Mouse(MouseEvent),
Gamepad(GamepadEvent),
}

#[cfg(test)]
mod tests {
use serde_json::json;

use crate::{EventSource, GamepadAxisEvent, GamepadButtonEvent, GamepadControlEvent, GamepadEvent, InputEvent, KeyEvent, MouseButton, MouseEvent, Pos};

#[test]
fn key_event() {
assert_eq!(
serde_json::from_value::<InputEvent>(json!({
"type": "key",
"source": 0,
"down": true,
"key": "ArrowUp",
})).unwrap(),
InputEvent::Key(KeyEvent {
source: EventSource::Int(0),
down: true,
key: "ArrowUp".into(),
})
);
}

#[test]
fn mouse_event() {
assert_eq!(
serde_json::from_value::<InputEvent>(json!({
"type": "mouse",
"source": 1,
"button": "left",
"pos": {
"x": 2,
"y": 4,
},
})).unwrap(),
InputEvent::Mouse(MouseEvent {
source: EventSource::Int(1),
button: MouseButton::Left,
pos: Pos::new(2.0, 4.0),
})
);
}

#[test]
fn gamepad_button_event() {
assert_eq!(
serde_json::from_value::<InputEvent>(json!({
"type": "gamepad",
"source": 1,
"control": "button",
"index": 42,
"down": true,
"value": 0.25,
})).unwrap(),
InputEvent::Gamepad(GamepadEvent {
source: EventSource::Int(1),
control: GamepadControlEvent::Button(GamepadButtonEvent {
index: 42,
down: true,
value: 0.25,
}),
})
);
}

#[test]
fn gamepad_axis_event() {
assert_eq!(
serde_json::from_value::<InputEvent>(json!({
"type": "gamepad",
"source": 1,
"control": "axis",
"index": 42,
"value": 0.25,
})).unwrap(),
InputEvent::Gamepad(GamepadEvent {
source: EventSource::Int(1),
control: GamepadControlEvent::Axis(GamepadAxisEvent {
index: 42,
value: 0.25,
}),
})
);
}
}
15 changes: 15 additions & 0 deletions lighthouse-protocol/src/input/key_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};

use super::EventSource;

/// A keyboard event.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct KeyEvent {
/// The client identifier.
pub source: EventSource,
/// Whether the key was pressed.
pub down: bool,
/// The key pressed, see the docs on JS's `KeyboardEvent.key` for details.
pub key: String, // TODO: Extract stronger `Key` type
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use serde::{Serialize, Deserialize};

/// A key/controller input event.
/// A keyboard/controller input event, as generated by the new frontend (LUNA)
/// in "Legacy Mode" (or the old website).
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
pub struct InputEvent {
pub struct LegacyInputEvent {
#[serde(rename = "src")]
pub source: i32,
pub key: Option<i32>,
Expand Down
21 changes: 21 additions & 0 deletions lighthouse-protocol/src/input/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod event_source;
mod gamepad_axis_event;
mod gamepad_button_event;
mod gamepad_control_event;
mod gamepad_event;
mod input_event;
mod key_event;
mod legacy_input_event;
mod mouse_button;
mod mouse_event;

pub use event_source::*;
pub use gamepad_axis_event::*;
pub use gamepad_button_event::*;
pub use gamepad_control_event::*;
pub use gamepad_event::*;
pub use input_event::*;
pub use key_event::*;
pub use legacy_input_event::*;
pub use mouse_button::*;
pub use mouse_event::*;
Loading