Skip to content

Commit

Permalink
webapi: Replace JSON inputs with strongly typed API model
Browse files Browse the repository at this point in the history
  • Loading branch information
mrozycki committed Jan 3, 2024
1 parent ea18cc3 commit be9b5d9
Show file tree
Hide file tree
Showing 27 changed files with 501 additions and 308 deletions.
14 changes: 12 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"lightfx",
"webapi",
"webapi-client",
"webapi-model",
"webui",
"visualizer",
"events",
Expand Down
5 changes: 5 additions & 0 deletions animation-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ lightfx = { path = "../lightfx" }
midi-msg = "0.4.0"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
yew = { version = "0.19.0", optional = true }

[features]
default = []
yew = ["dep:yew"]
20 changes: 14 additions & 6 deletions animation-api/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::fmt;
use std::{collections::HashMap, fmt};

use serde::{de::DeserializeOwned, Deserialize, Serialize};

use crate::schema::{ConfigurationSchema, GetSchema};
use crate::schema::{ConfigurationSchema, GetSchema, ParameterValue};

#[derive(Serialize, Deserialize, Debug)]
pub struct AnimationError {
Expand Down Expand Up @@ -52,13 +52,21 @@ pub trait Animation {
#[derive(Serialize, Deserialize)]
#[serde(tag = "method", content = "params")]
pub enum JsonRpcMethod {
Initialize { points: Vec<(f64, f64, f64)> },
Initialize {
points: Vec<(f64, f64, f64)>,
},
AnimationName,
ParameterSchema,
SetParameters { params: serde_json::Value },
SetParameters {
params: HashMap<String, ParameterValue>,
},
GetParameters,
GetFps,
Update { time_delta: f64 },
OnEvent { event: crate::event::Event },
Update {
time_delta: f64,
},
OnEvent {
event: crate::event::Event,
},
Render,
}
48 changes: 48 additions & 0 deletions animation-api/src/schema.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::collections::HashMap;

use lightfx::Color;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
Expand Down Expand Up @@ -46,3 +49,48 @@ impl GetSchema for () {
pub trait GetEnumOptions {
fn enum_options() -> Vec<EnumOption>;
}

#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
#[serde(untagged)]
pub enum ParameterValue {
Number(f64),
Color(Color),
EnumOption(String),
}

#[cfg(feature = "yew")]
impl yew::html::ImplicitClone for ParameterValue {}

impl ParameterValue {
pub fn number(&self) -> Option<f64> {
if let Self::Number(n) = self {
Some(*n)
} else {
None
}
}

pub fn color(&self) -> Option<&Color> {
if let Self::Color(c) = self {
Some(c)
} else {
None
}
}

pub fn enum_option(&self) -> Option<&str> {
if let Self::EnumOption(s) = self {
Some(s)
} else {
None
}
}
}

#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct Configuration {
pub id: String,
pub name: String,
pub schema: ConfigurationSchema,
pub values: HashMap<String, ParameterValue>,
}
2 changes: 1 addition & 1 deletion animation-plugin-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub fn plugin(_attr: TokenStream, item: TokenStream) -> TokenStream {
},
JsonRpcMethod::SetParameters { params } => {
if let Some(mut animation) = animation.as_mut() {
match serde_json::from_value(params) {
match serde_json::from_value(serde_json::json!(params)) {
Ok(params) => {
animation.set_parameters(params);
respond(message.id, ());
Expand Down
2 changes: 1 addition & 1 deletion animations/src/bin/audio_visualizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use animation_api::event::Event;
use animation_api::Animation;
use animation_utils::{
decorators::{BrightnessControlled, SpeedControlled},
to_polar, Schema, EnumSchema,
to_polar, EnumSchema, Schema,
};
use lightfx::Color;
use serde::{Deserialize, Serialize};
Expand Down
134 changes: 61 additions & 73 deletions animator/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use animation_api::event::Event;
use animation_api::schema::{Configuration, ParameterValue};
use chrono::{DateTime, Duration, Utc};
use client::combined::{CombinedLightClient, CombinedLightClientBuilder};
use events::beat_generator::BeatEventGenerator;
Expand All @@ -13,7 +14,6 @@ use events::midi_generator::MidiEventGenerator;
use log::{info, warn};
use rustmas_light_client as client;
use rustmas_light_client::LightClientError;
use serde_json::json;
use thiserror::Error;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
Expand All @@ -32,6 +32,9 @@ pub enum ControllerError {

#[error("animation factory error: {0}")]
AnimationFactoryError(#[from] AnimationFactoryError),

#[error("no animation selected")]
NoAnimationSelected,
}

struct ControllerState {
Expand All @@ -42,6 +45,21 @@ struct ControllerState {
event_generators: HashMap<String, Box<dyn EventGenerator>>,
}

impl ControllerState {
fn set_animation(&mut self, animation: Option<JsonRpcPlugin>) -> Result<(), ControllerError> {
let now = Utc::now();
self.fps = animation
.as_ref()
.map(|a| a.get_fps())
.transpose()?
.unwrap_or_default();
self.last_frame = now;
self.next_frame = now;
self.animation = animation;
Ok(())
}
}

pub struct Controller {
animation_join_handle: JoinHandle<()>,
event_generator_join_handle: JoinHandle<()>,
Expand Down Expand Up @@ -85,7 +103,7 @@ impl Controller {
.unwrap()
.animation
.as_ref()
.map(|animation| animation.config().clone())
.map(|animation| animation.plugin_config().clone())
}

fn start_generators(
Expand Down Expand Up @@ -188,54 +206,33 @@ impl Controller {
.for_each(|(_, evg)| evg.restart());
}

pub fn get_event_generator_parameters(&self) -> serde_json::Value {
pub fn get_event_generator_parameters(&self) -> Vec<Configuration> {
self.state
.lock()
.unwrap()
.event_generators
.iter()
.map(|(id, evg)| {
json!({
"id": id.clone(),
"name": evg.get_name(),
"schema": evg.get_schema(),
"values": evg.get_parameters(),
})
.map(|(id, evg)| Configuration {
id: id.clone(),
name: evg.get_name().to_owned(),
schema: evg.get_schema(),
values: evg.get_parameters(),
})
.collect()
}

pub fn set_event_generator_parameters(
&self,
values: serde_json::Value,
values: &HashMap<String, HashMap<String, ParameterValue>>,
) -> Result<(), ControllerError> {
let mut state = self.state.lock().unwrap();

for parameters in values.as_array().unwrap_or(&Vec::new()) {
let Some(id) = parameters
.as_object()
.and_then(|p| p.get("id"))
.and_then(|p| p.as_str())
else {
warn!("No event generator id provided");
continue;
};
for (id, parameters) in values {
let Some(evg) = state.event_generators.get_mut(id) else {
warn!("No such event generator: {}", id);
continue;
};

let Some(parameters) = parameters.as_object().and_then(|o| o.get("values")) else {
warn!("No parameters provided for event generator: {}", id);
continue;
};

let parameters = serde_json::from_value(parameters.clone()).map_err(|e| {
ControllerError::InternalError {
reason: e.to_string(),
}
})?;

evg.set_parameters(parameters)
.map_err(|e| ControllerError::InternalError {
reason: e.to_string(),
Expand All @@ -245,68 +242,59 @@ impl Controller {
Ok(())
}

pub fn reload_animation(&self) -> Result<(), ControllerError> {
pub fn reload_animation(&self) -> Result<Configuration, ControllerError> {
let mut state = self.state.lock().unwrap();
let Some(id) = state.animation.as_ref().map(|a| a.config().animation_id()) else {
return Ok(());
let Some(id) = state
.animation
.as_ref()
.map(|a| a.plugin_config().animation_id())
else {
return Err(ControllerError::NoAnimationSelected);
};
info!("Reloading animation \"{}\"", id);
let new_animation = self.animation_factory.make(id)?;

let now = Utc::now();
state.fps = new_animation.get_fps()?;
state.last_frame = now;
state.next_frame = now;
state.animation = Some(new_animation);
Ok(())
let animation = self.animation_factory.make(id)?;
let configuration = animation.configuration()?;
state.set_animation(Some(animation))?;
Ok(configuration)
}

pub fn switch_animation(&self, name: &str) -> Result<(), ControllerError> {
info!("Trying to switch animation to \"{}\"", name);
pub fn switch_animation(&self, animation_id: &str) -> Result<Configuration, ControllerError> {
info!("Trying to switch animation to \"{}\"", animation_id);
let mut state = self.state.lock().unwrap();
let new_animation = self.animation_factory.make(name)?;

let now = Utc::now();
state.fps = new_animation.get_fps()?;
state.last_frame = now;
state.next_frame = now;
state.animation = Some(new_animation);
Ok(())
let animation = self.animation_factory.make(animation_id)?;
let configuration = animation.configuration()?;
state.set_animation(Some(animation))?;
Ok(configuration)
}

pub fn turn_off(&self) {
info!("Turning off the animation");
let mut state = self.state.lock().unwrap();

let now = Utc::now();
state.fps = 0.0;
state.last_frame = now;
state.next_frame = now;
state.animation = None;
let _ = self.state.lock().unwrap().set_animation(None);
}

pub fn get_parameters(&self) -> Result<serde_json::Value, ControllerError> {
if let Some(animation) = &self.state.lock().unwrap().animation {
Ok(json!({
"id": animation.config().animation_id(),
"name": animation.config().animation_name(),
"schema": animation.get_schema()?,
"values": animation.get_parameters()?,
}))
} else {
Ok(json!(()))
}
pub fn get_parameters(&self) -> Result<Option<Configuration>, ControllerError> {
Ok(self
.state
.lock()
.unwrap()
.animation
.as_ref()
.map(Plugin::configuration)
.transpose()?)
}

pub fn get_parameter_values(&self) -> Result<serde_json::Value, ControllerError> {
pub fn get_parameter_values(&self) -> Result<HashMap<String, ParameterValue>, ControllerError> {
if let Some(animation) = &self.state.lock().unwrap().animation {
Ok(animation.get_parameters()?)
} else {
Ok(json!(()))
Ok(HashMap::new())
}
}

pub fn set_parameters(&mut self, parameters: serde_json::Value) -> Result<(), ControllerError> {
pub fn set_parameters(
&mut self,
parameters: &HashMap<String, ParameterValue>,
) -> Result<(), ControllerError> {
let mut state = self.state.lock().unwrap();
if let Some(ref mut animation) = state.animation {
animation.set_parameters(parameters)?;
Expand Down
Loading

0 comments on commit be9b5d9

Please sign in to comment.