shoo the puppets to the ground (ugly ahh graphics lmao)
This commit is contained in:
@@ -20,6 +20,7 @@ use tauri::Manager;
|
||||
async fn launch_app(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let handle = app.handle();
|
||||
db::init(handle).await?;
|
||||
scene_configuration::init(handle).await?;
|
||||
keypair::init(handle).await?;
|
||||
app.manage(interactions::InteractionState::default());
|
||||
network::init(handle).await?;
|
||||
|
||||
@@ -11,10 +11,12 @@ use tauri_specta::Event;
|
||||
use tokio::time::MissedTickBehavior;
|
||||
|
||||
use crate::cursor::{CursorPosition, CursorPositions, CursorState};
|
||||
use crate::scene_configuration::{PuppetMovementMode, SceneConfigurationState};
|
||||
|
||||
const TICK_INTERVAL: Duration = Duration::from_millis(125);
|
||||
const SPEED_LOGICAL_PIXELS_PER_SECOND: f64 = 80.0;
|
||||
const FOLLOW_RADIUS_LOGICAL_PIXELS: f64 = 48.0;
|
||||
const BOTTOM_MOVEMENT_Y: f64 = 1.0;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -39,9 +41,16 @@ impl PuppetStateStore {
|
||||
cursor_positions: &HashMap<String, CursorPositions>,
|
||||
viewport: Viewport,
|
||||
elapsed: Duration,
|
||||
movement_mode: PuppetMovementMode,
|
||||
) -> Result<Option<Vec<PuppetState>>, String> {
|
||||
let mut puppets = self.0.write().map_err(|error| error.to_string())?;
|
||||
let changed = advance_puppets(&mut puppets, cursor_positions, viewport, elapsed);
|
||||
let changed = advance_puppets(
|
||||
&mut puppets,
|
||||
cursor_positions,
|
||||
viewport,
|
||||
elapsed,
|
||||
movement_mode,
|
||||
);
|
||||
Ok(changed.then(|| sorted_snapshot(&puppets)))
|
||||
}
|
||||
|
||||
@@ -95,10 +104,21 @@ pub fn init(handle: &AppHandle) -> Result<(), String> {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let movement_mode = match handle
|
||||
.state::<SceneConfigurationState>()
|
||||
.puppet_movement_mode()
|
||||
{
|
||||
Ok(mode) => mode,
|
||||
Err(error) => {
|
||||
eprintln!("failed to read Puppet movement mode: {error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let puppets = match handle.state::<PuppetStateStore>().update(
|
||||
&cursor_positions,
|
||||
viewport,
|
||||
elapsed,
|
||||
movement_mode,
|
||||
) {
|
||||
Ok(Some(puppets)) => puppets,
|
||||
Ok(None) => continue,
|
||||
@@ -128,18 +148,20 @@ fn advance_puppets(
|
||||
cursor_positions: &HashMap<String, CursorPositions>,
|
||||
viewport: Viewport,
|
||||
elapsed: Duration,
|
||||
movement_mode: PuppetMovementMode,
|
||||
) -> bool {
|
||||
let previous_len = puppets.len();
|
||||
puppets.retain(|id, _| cursor_positions.contains_key(id));
|
||||
let mut changed = puppets.len() != previous_len;
|
||||
|
||||
for (id, cursor) in cursor_positions {
|
||||
let target = movement_target(&cursor.mapped, movement_mode);
|
||||
let Some(puppet) = puppets.get_mut(id) else {
|
||||
puppets.insert(
|
||||
id.clone(),
|
||||
PuppetState {
|
||||
id: id.clone(),
|
||||
position: cursor.mapped.clone(),
|
||||
position: target,
|
||||
is_moving: false,
|
||||
},
|
||||
);
|
||||
@@ -147,12 +169,26 @@ fn advance_puppets(
|
||||
continue;
|
||||
};
|
||||
|
||||
changed |= advance_puppet(puppet, &cursor.mapped, viewport, elapsed.as_secs_f64());
|
||||
if movement_mode == PuppetMovementMode::Bottom && puppet.position.y != BOTTOM_MOVEMENT_Y {
|
||||
puppet.position.y = BOTTOM_MOVEMENT_Y;
|
||||
changed = true;
|
||||
}
|
||||
changed |= advance_puppet(puppet, &target, viewport, elapsed.as_secs_f64());
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
fn movement_target(cursor: &CursorPosition, mode: PuppetMovementMode) -> CursorPosition {
|
||||
CursorPosition {
|
||||
x: cursor.x,
|
||||
y: match mode {
|
||||
PuppetMovementMode::Free => cursor.y,
|
||||
PuppetMovementMode::Bottom => BOTTOM_MOVEMENT_Y,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_puppet(
|
||||
puppet: &mut PuppetState,
|
||||
target: &CursorPosition,
|
||||
@@ -220,6 +256,7 @@ mod tests {
|
||||
&cursors(&[("friend", 0.75, 0.25)]),
|
||||
viewport(),
|
||||
TICK_INTERVAL,
|
||||
PuppetMovementMode::Free,
|
||||
);
|
||||
|
||||
assert!(changed);
|
||||
@@ -296,10 +333,46 @@ mod tests {
|
||||
&HashMap::new(),
|
||||
viewport(),
|
||||
TICK_INTERVAL,
|
||||
PuppetMovementMode::Free,
|
||||
));
|
||||
assert!(puppets.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bottom_mode_preserves_horizontal_target_and_insets_vertical_target() {
|
||||
let cursor = CursorPosition { x: 0.25, y: 0.1 };
|
||||
|
||||
assert_eq!(
|
||||
movement_target(&cursor, PuppetMovementMode::Bottom),
|
||||
CursorPosition {
|
||||
x: 0.25,
|
||||
y: BOTTOM_MOVEMENT_Y,
|
||||
}
|
||||
);
|
||||
assert_eq!(movement_target(&cursor, PuppetMovementMode::Free), cursor);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bottom_mode_emits_a_fixed_inset_y_for_existing_puppets() {
|
||||
let mut puppets = HashMap::from([(
|
||||
"friend".to_owned(),
|
||||
PuppetState {
|
||||
id: "friend".to_owned(),
|
||||
position: CursorPosition { x: 0.25, y: 0.1 },
|
||||
is_moving: false,
|
||||
},
|
||||
)]);
|
||||
|
||||
assert!(advance_puppets(
|
||||
&mut puppets,
|
||||
&cursors(&[("friend", 0.25, 0.1)]),
|
||||
viewport(),
|
||||
TICK_INTERVAL,
|
||||
PuppetMovementMode::Bottom,
|
||||
));
|
||||
assert_eq!(puppets["friend"].position.y, BOTTOM_MOVEMENT_Y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_step_accounts_for_viewport_aspect_ratio() {
|
||||
let mut horizontal = PuppetState {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::db::{self, AppDatabase};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use tauri::{AppHandle, State};
|
||||
use std::sync::RwLock;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tauri_specta::Event;
|
||||
|
||||
const SCENE_CONFIGURATION_ID: i64 = 1;
|
||||
@@ -10,11 +11,41 @@ const MAX_PUPPET_SCALE: f64 = 2.0;
|
||||
const MIN_PUPPET_OPACITY: f64 = 0.1;
|
||||
const MAX_PUPPET_OPACITY: f64 = 1.0;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, Type, sqlx::Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[sqlx(type_name = "TEXT", rename_all = "lowercase")]
|
||||
pub enum PuppetMovementMode {
|
||||
#[default]
|
||||
Free,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, sqlx::FromRow)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SceneConfiguration {
|
||||
pub puppet_scale: f64,
|
||||
pub puppet_opacity: f64,
|
||||
pub puppet_movement_mode: PuppetMovementMode,
|
||||
}
|
||||
|
||||
pub struct SceneConfigurationState(RwLock<SceneConfiguration>);
|
||||
|
||||
impl SceneConfigurationState {
|
||||
fn new(configuration: SceneConfiguration) -> Self {
|
||||
Self(RwLock::new(configuration))
|
||||
}
|
||||
|
||||
fn replace(&self, configuration: SceneConfiguration) -> Result<(), String> {
|
||||
*self.0.write().map_err(|error| error.to_string())? = configuration;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn puppet_movement_mode(&self) -> Result<PuppetMovementMode, String> {
|
||||
self.0
|
||||
.read()
|
||||
.map(|configuration| configuration.puppet_movement_mode)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||
@@ -25,7 +56,7 @@ pub struct SceneConfigurationChanged {
|
||||
|
||||
async fn get(database: &AppDatabase) -> Result<SceneConfiguration, sqlx::Error> {
|
||||
sqlx::query_as::<_, SceneConfiguration>(
|
||||
"SELECT puppet_scale, puppet_opacity FROM scene_configuration WHERE id = ?1",
|
||||
"SELECT puppet_scale, puppet_opacity, puppet_movement_mode FROM scene_configuration WHERE id = ?1",
|
||||
)
|
||||
.bind(SCENE_CONFIGURATION_ID)
|
||||
.fetch_one(database.pool())
|
||||
@@ -52,10 +83,11 @@ async fn update(
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE scene_configuration SET puppet_scale = ?1, puppet_opacity = ?2 WHERE id = ?3",
|
||||
"UPDATE scene_configuration SET puppet_scale = ?1, puppet_opacity = ?2, puppet_movement_mode = ?3 WHERE id = ?4",
|
||||
)
|
||||
.bind(configuration.puppet_scale)
|
||||
.bind(configuration.puppet_opacity)
|
||||
.bind(configuration.puppet_movement_mode)
|
||||
.bind(SCENE_CONFIGURATION_ID)
|
||||
.execute(database.pool())
|
||||
.await
|
||||
@@ -64,6 +96,12 @@ async fn update(
|
||||
get(database).await.map_err(db::command_error)
|
||||
}
|
||||
|
||||
pub async fn init(handle: &AppHandle) -> Result<(), sqlx::Error> {
|
||||
let configuration = get(&handle.state::<AppDatabase>()).await?;
|
||||
handle.manage(SceneConfigurationState::new(configuration));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_changed(handle: &AppHandle, configuration: SceneConfiguration) -> Result<(), String> {
|
||||
SceneConfigurationChanged { configuration }
|
||||
.emit(handle)
|
||||
@@ -75,8 +113,10 @@ fn emit_changed(handle: &AppHandle, configuration: SceneConfiguration) -> Result
|
||||
pub async fn get_scene_configuration(
|
||||
handle: AppHandle,
|
||||
database: State<'_, AppDatabase>,
|
||||
state: State<'_, SceneConfigurationState>,
|
||||
) -> Result<SceneConfiguration, String> {
|
||||
let configuration = get(&database).await.map_err(db::command_error)?;
|
||||
state.replace(configuration.clone())?;
|
||||
emit_changed(&handle, configuration.clone())?;
|
||||
Ok(configuration)
|
||||
}
|
||||
@@ -86,9 +126,11 @@ pub async fn get_scene_configuration(
|
||||
pub async fn update_scene_configuration(
|
||||
handle: AppHandle,
|
||||
database: State<'_, AppDatabase>,
|
||||
state: State<'_, SceneConfigurationState>,
|
||||
configuration: SceneConfiguration,
|
||||
) -> Result<SceneConfiguration, String> {
|
||||
let configuration = update(&database, configuration).await?;
|
||||
state.replace(configuration.clone())?;
|
||||
emit_changed(&handle, configuration.clone())?;
|
||||
Ok(configuration)
|
||||
}
|
||||
@@ -121,6 +163,7 @@ mod tests {
|
||||
SceneConfiguration {
|
||||
puppet_scale: 1.0,
|
||||
puppet_opacity: 1.0,
|
||||
puppet_movement_mode: PuppetMovementMode::Free,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -134,12 +177,17 @@ mod tests {
|
||||
SceneConfiguration {
|
||||
puppet_scale: 1.5,
|
||||
puppet_opacity: 0.4,
|
||||
puppet_movement_mode: PuppetMovementMode::Bottom,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("update scene configuration");
|
||||
assert_eq!(configuration.puppet_scale, 1.5);
|
||||
assert_eq!(configuration.puppet_opacity, 0.4);
|
||||
assert_eq!(
|
||||
configuration.puppet_movement_mode,
|
||||
PuppetMovementMode::Bottom
|
||||
);
|
||||
assert_eq!(get(&database).await.unwrap(), configuration);
|
||||
|
||||
let error = update(
|
||||
@@ -147,6 +195,7 @@ mod tests {
|
||||
SceneConfiguration {
|
||||
puppet_scale: 2.1,
|
||||
puppet_opacity: 0.4,
|
||||
puppet_movement_mode: PuppetMovementMode::Bottom,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -159,6 +208,7 @@ mod tests {
|
||||
SceneConfiguration {
|
||||
puppet_scale: 1.5,
|
||||
puppet_opacity: 0.05,
|
||||
puppet_movement_mode: PuppetMovementMode::Bottom,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user