consolidation & handling of data from backend pt 2
This commit is contained in:
@@ -1,34 +1,34 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DollColorSchemeDto {
|
pub struct DollColorSchemeDto {
|
||||||
pub outline: String,
|
pub outline: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DollConfigurationDto {
|
pub struct DollConfigurationDto {
|
||||||
pub color_scheme: DollColorSchemeDto,
|
pub color_scheme: DollColorSchemeDto,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreateDollDto {
|
pub struct CreateDollDto {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub configuration: Option<DollConfigurationDto>,
|
pub configuration: Option<DollConfigurationDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UpdateDollDto {
|
pub struct UpdateDollDto {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub configuration: Option<DollConfigurationDto>,
|
pub configuration: Option<DollConfigurationDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type)]
|
#[derive(Default, Serialize, Deserialize, Clone, Debug, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DollDto {
|
pub struct DollDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ use super::dolls::DollDto;
|
|||||||
use super::friends::UserBasicDto;
|
use super::friends::UserBasicDto;
|
||||||
use crate::services::presence_modules::models::PresenceStatus;
|
use crate::services::presence_modules::models::PresenceStatus;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum UserStatusState {
|
pub enum UserStatusState {
|
||||||
Idle,
|
Idle,
|
||||||
Resting,
|
Resting,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, Type)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UserStatusPayload {
|
pub struct UserStatusPayload {
|
||||||
pub presence_status: PresenceStatus,
|
pub presence_status: PresenceStatus,
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use tauri_specta::Event as _;
|
use tauri_specta::Event as _;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CursorPosition {
|
pub struct CursorPosition {
|
||||||
pub x: f64,
|
pub x: f64,
|
||||||
pub y: f64,
|
pub y: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CursorPositions {
|
pub struct CursorPositions {
|
||||||
pub raw: CursorPosition,
|
pub raw: CursorPosition,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PresenceStatus {
|
pub struct PresenceStatus {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
get_app_handle, lock_r, lock_w, models::event_payloads::UserStatusPayload,
|
get_app_handle, lock_r, lock_w,
|
||||||
services::app_events::PresenceStateUpdated, state::FDOLL,
|
models::event_payloads::UserStatusPayload,
|
||||||
|
services::app_events::PresenceStateUpdated,
|
||||||
|
state::{get_known_friend_ids, FDOLL},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
@@ -22,44 +24,44 @@ pub fn get_presence_state_snapshot() -> PresenceStateSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_presence(status: UserStatusPayload) {
|
pub fn set_current_presence(status: UserStatusPayload) -> bool {
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
|
|
||||||
|
if guard.presence.current.as_ref() == Some(&status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
guard.presence.current = Some(status);
|
guard.presence.current = Some(status);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_friend_presence(friend_id: String, status: UserStatusPayload) {
|
pub fn set_friend_presence(friend_id: String, status: UserStatusPayload) -> bool {
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
|
|
||||||
|
if guard.presence.friends.get(&friend_id) == Some(&status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
guard.presence.friends.insert(friend_id, status);
|
guard.presence.friends.insert(friend_id, status);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_friend_presence(friend_id: &str) {
|
pub fn remove_friend_presence(friend_id: &str) -> bool {
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
guard.presence.friends.remove(friend_id);
|
guard.presence.friends.remove(friend_id).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_missing_friends_from_presence_state() {
|
pub fn clear_missing_friends_from_presence_state() -> bool {
|
||||||
let friend_ids = {
|
let friend_ids = get_known_friend_ids();
|
||||||
let guard = lock_r!(FDOLL);
|
|
||||||
guard
|
|
||||||
.user_data
|
|
||||||
.friends
|
|
||||||
.as_ref()
|
|
||||||
.map(|friends| {
|
|
||||||
friends
|
|
||||||
.iter()
|
|
||||||
.filter_map(|friendship| {
|
|
||||||
friendship.friend.as_ref().map(|friend| friend.id.clone())
|
|
||||||
})
|
|
||||||
.collect::<std::collections::HashSet<_>>()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
|
let initial_count = guard.presence.friends.len();
|
||||||
guard
|
guard
|
||||||
.presence
|
.presence
|
||||||
.friends
|
.friends
|
||||||
.retain(|friend_id, _| friend_ids.contains(friend_id));
|
.retain(|friend_id, _| friend_ids.contains(friend_id));
|
||||||
|
|
||||||
|
guard.presence.friends.len() != initial_count
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_presence_state_updated() {
|
pub fn emit_presence_state_updated() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
get_app_handle, lock_r, lock_w,
|
get_app_handle, lock_r, lock_w,
|
||||||
models::{dolls::DollDto, scene::SceneFriendNeko},
|
models::{dolls::DollDto, scene::SceneFriendNeko},
|
||||||
services::{app_events::SceneFriendsUpdated, cursor::CursorPositions},
|
services::{app_events::SceneFriendsUpdated, cursor::CursorPositions},
|
||||||
state::FDOLL,
|
state::{get_known_friend_ids, FDOLL},
|
||||||
};
|
};
|
||||||
use tauri_specta::Event as _;
|
use tauri_specta::Event as _;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
@@ -34,44 +34,49 @@ pub fn get_scene_friends_snapshot() -> Vec<SceneFriendNeko> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_friend_cursor_position(friend_id: String, position: CursorPositions) {
|
pub fn set_friend_cursor_position(friend_id: String, position: CursorPositions) -> bool {
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
|
|
||||||
|
if guard.friend_scene.cursor_positions.get(&friend_id) == Some(&position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
guard
|
guard
|
||||||
.friend_scene
|
.friend_scene
|
||||||
.cursor_positions
|
.cursor_positions
|
||||||
.insert(friend_id, position);
|
.insert(friend_id, position);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_friend_active_doll(friend_id: String, doll: Option<DollDto>) {
|
pub fn set_friend_active_doll(friend_id: String, doll: Option<DollDto>) -> bool {
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
|
|
||||||
|
if guard.friend_scene.active_dolls.get(&friend_id) == Some(&doll) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
guard.friend_scene.active_dolls.insert(friend_id, doll);
|
guard.friend_scene.active_dolls.insert(friend_id, doll);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_friend(friend_id: &str) {
|
pub fn remove_friend(friend_id: &str) -> bool {
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
guard.friend_scene.cursor_positions.remove(friend_id);
|
let removed_cursor = guard
|
||||||
guard.friend_scene.active_dolls.remove(friend_id);
|
.friend_scene
|
||||||
|
.cursor_positions
|
||||||
|
.remove(friend_id)
|
||||||
|
.is_some();
|
||||||
|
let removed_doll = guard.friend_scene.active_dolls.remove(friend_id).is_some();
|
||||||
|
|
||||||
|
removed_cursor || removed_doll
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_missing_friends_from_runtime_state() {
|
pub fn clear_missing_friends_from_runtime_state() -> bool {
|
||||||
let friend_ids = {
|
let friend_ids = get_known_friend_ids();
|
||||||
let guard = lock_r!(FDOLL);
|
|
||||||
guard
|
|
||||||
.user_data
|
|
||||||
.friends
|
|
||||||
.as_ref()
|
|
||||||
.map(|friends| {
|
|
||||||
friends
|
|
||||||
.iter()
|
|
||||||
.filter_map(|friendship| {
|
|
||||||
friendship.friend.as_ref().map(|friend| friend.id.clone())
|
|
||||||
})
|
|
||||||
.collect::<std::collections::HashSet<_>>()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut guard = lock_w!(FDOLL);
|
let mut guard = lock_w!(FDOLL);
|
||||||
|
let initial_cursor_count = guard.friend_scene.cursor_positions.len();
|
||||||
|
let initial_active_doll_count = guard.friend_scene.active_dolls.len();
|
||||||
guard
|
guard
|
||||||
.friend_scene
|
.friend_scene
|
||||||
.cursor_positions
|
.cursor_positions
|
||||||
@@ -80,6 +85,9 @@ pub fn clear_missing_friends_from_runtime_state() {
|
|||||||
.friend_scene
|
.friend_scene
|
||||||
.active_dolls
|
.active_dolls
|
||||||
.retain(|friend_id, _| friend_ids.contains(friend_id));
|
.retain(|friend_id, _| friend_ids.contains(friend_id));
|
||||||
|
|
||||||
|
guard.friend_scene.cursor_positions.len() != initial_cursor_count
|
||||||
|
|| guard.friend_scene.active_dolls.len() != initial_active_doll_count
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_scene_friends_updated() {
|
pub fn emit_scene_friends_updated() {
|
||||||
|
|||||||
@@ -75,28 +75,34 @@ pub fn on_friend_cursor_position(payload: Payload, _socket: RawClient) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
scene_friends::set_friend_cursor_position(
|
let scene_friends_changed = scene_friends::set_friend_cursor_position(
|
||||||
outgoing_payload.user_id.clone(),
|
outgoing_payload.user_id.clone(),
|
||||||
outgoing_payload.position.clone(),
|
outgoing_payload.position.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
emitter::emit_to_frontend_typed(&FriendCursorPositionUpdated(outgoing_payload));
|
emitter::emit_to_frontend_typed(&FriendCursorPositionUpdated(outgoing_payload));
|
||||||
|
if scene_friends_changed {
|
||||||
scene_friends::emit_scene_friends_updated();
|
scene_friends::emit_scene_friends_updated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handler for friend-disconnected event
|
/// Handler for friend-disconnected event
|
||||||
pub fn on_friend_disconnected(payload: Payload, _socket: RawClient) {
|
pub fn on_friend_disconnected(payload: Payload, _socket: RawClient) {
|
||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendDisconnectedPayload>(payload, "friend-disconnected")
|
utils::extract_and_parse::<FriendDisconnectedPayload>(payload, "friend-disconnected")
|
||||||
{
|
{
|
||||||
scene_friends::remove_friend(&data.user_id);
|
let scene_friends_changed = scene_friends::remove_friend(&data.user_id);
|
||||||
presence_state::remove_friend_presence(&data.user_id);
|
let presence_changed = presence_state::remove_friend_presence(&data.user_id);
|
||||||
emitter::emit_to_frontend_typed(&FriendDisconnected(data));
|
emitter::emit_to_frontend_typed(&FriendDisconnected(data));
|
||||||
|
if scene_friends_changed {
|
||||||
scene_friends::emit_scene_friends_updated();
|
scene_friends::emit_scene_friends_updated();
|
||||||
|
}
|
||||||
|
if presence_changed {
|
||||||
presence_state::emit_presence_state_updated();
|
presence_state::emit_presence_state_updated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handler for friend-doll-created event
|
/// Handler for friend-doll-created event
|
||||||
pub fn on_friend_doll_created(payload: Payload, _socket: RawClient) {
|
pub fn on_friend_doll_created(payload: Payload, _socket: RawClient) {
|
||||||
@@ -129,7 +135,6 @@ pub fn on_friend_active_doll_changed(payload: Payload, _socket: RawClient) {
|
|||||||
) {
|
) {
|
||||||
scene_friends::set_friend_active_doll(data.friend_id.clone(), data.doll.clone());
|
scene_friends::set_friend_active_doll(data.friend_id.clone(), data.doll.clone());
|
||||||
emitter::emit_to_frontend_typed(&FriendActiveDollChanged(data));
|
emitter::emit_to_frontend_typed(&FriendActiveDollChanged(data));
|
||||||
scene_friends::emit_scene_friends_updated();
|
|
||||||
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
refresh::refresh_app_data(AppDataRefreshScope::Friends);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,8 +144,11 @@ pub fn on_friend_user_status(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendUserStatusPayload>(payload, "friend-user-status")
|
utils::extract_and_parse::<FriendUserStatusPayload>(payload, "friend-user-status")
|
||||||
{
|
{
|
||||||
|
let presence_changed =
|
||||||
presence_state::set_friend_presence(data.user_id.clone(), data.status.clone());
|
presence_state::set_friend_presence(data.user_id.clone(), data.status.clone());
|
||||||
emitter::emit_to_frontend_typed(&FriendUserStatusChanged(data));
|
emitter::emit_to_frontend_typed(&FriendUserStatusChanged(data));
|
||||||
|
if presence_changed {
|
||||||
presence_state::emit_presence_state_updated();
|
presence_state::emit_presence_state_updated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ pub async fn report_user_status(status: UserStatusPayload) {
|
|||||||
warn!("Failed to emit user-status-changed event: {e}");
|
warn!("Failed to emit user-status-changed event: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
presence_state::set_current_presence(status.clone());
|
if presence_state::set_current_presence(status.clone()) {
|
||||||
presence_state::emit_presence_state_updated();
|
presence_state::emit_presence_state_updated();
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule new report after 500ms
|
// Schedule new report after 500ms
|
||||||
let handle = async_runtime::spawn(async move {
|
let handle = async_runtime::spawn(async move {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// in app-core/src/state.rs
|
// in app-core/src/state.rs
|
||||||
use crate::{
|
use crate::{
|
||||||
|
lock_r,
|
||||||
lock_w,
|
lock_w,
|
||||||
models::{app_data::UserData, dolls::DollDto},
|
models::{app_data::UserData, dolls::DollDto},
|
||||||
services::{
|
services::{
|
||||||
@@ -7,7 +8,7 @@ use crate::{
|
|||||||
presence_modules::models::ModuleMetadata,
|
presence_modules::models::ModuleMetadata,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::{Arc, LazyLock, RwLock};
|
use std::sync::{Arc, LazyLock, RwLock};
|
||||||
use tauri::tray::TrayIcon;
|
use tauri::tray::TrayIcon;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@@ -55,6 +56,22 @@ pub struct AppState {
|
|||||||
pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> =
|
pub static FDOLL: LazyLock<Arc<RwLock<AppState>>> =
|
||||||
LazyLock::new(|| Arc::new(RwLock::new(AppState::default())));
|
LazyLock::new(|| Arc::new(RwLock::new(AppState::default())));
|
||||||
|
|
||||||
|
pub fn get_known_friend_ids() -> HashSet<String> {
|
||||||
|
let guard = lock_r!(FDOLL);
|
||||||
|
|
||||||
|
guard
|
||||||
|
.user_data
|
||||||
|
.friends
|
||||||
|
.as_ref()
|
||||||
|
.map(|friends| {
|
||||||
|
friends
|
||||||
|
.iter()
|
||||||
|
.filter_map(|friendship| friendship.friend.as_ref().map(|friend| friend.id.clone()))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// Populate app state with initial
|
/// Populate app state with initial
|
||||||
/// values and necesary client instances.
|
/// values and necesary client instances.
|
||||||
pub fn init_app_state() {
|
pub fn init_app_state() {
|
||||||
|
|||||||
@@ -211,8 +211,14 @@ pub async fn init_app_data_scoped(scope: AppDataRefreshScope) {
|
|||||||
let app_data_clone = guard.user_data.clone();
|
let app_data_clone = guard.user_data.clone();
|
||||||
drop(guard); // Drop lock before emitting to prevent potential deadlocks
|
drop(guard); // Drop lock before emitting to prevent potential deadlocks
|
||||||
|
|
||||||
|
let refreshes_friend_runtime = matches!(
|
||||||
|
scope,
|
||||||
|
AppDataRefreshScope::All | AppDataRefreshScope::Friends
|
||||||
|
);
|
||||||
|
if refreshes_friend_runtime {
|
||||||
scene_friends::clear_missing_friends_from_runtime_state();
|
scene_friends::clear_missing_friends_from_runtime_state();
|
||||||
presence_state::clear_missing_friends_from_presence_state();
|
presence_state::clear_missing_friends_from_presence_state();
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = AppDataRefreshed(app_data_clone).emit(get_app_handle()) {
|
if let Err(e) = AppDataRefreshed(app_data_clone).emit(get_app_handle()) {
|
||||||
warn!("Failed to emit app-data-refreshed event: {}", e);
|
warn!("Failed to emit app-data-refreshed event: {}", e);
|
||||||
@@ -229,9 +235,13 @@ pub async fn init_app_data_scoped(scope: AppDataRefreshScope) {
|
|||||||
.show(|_| {});
|
.show(|_| {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if refreshes_friend_runtime {
|
||||||
scene_friends::emit_scene_friends_updated();
|
scene_friends::emit_scene_friends_updated();
|
||||||
|
}
|
||||||
|
if refreshes_friend_runtime {
|
||||||
presence_state::emit_presence_state_updated();
|
presence_state::emit_presence_state_updated();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import type { DollDto } from "$lib/bindings";
|
import type { DollColorSchemeDto, DollDto } from "$lib/bindings";
|
||||||
import type { RecolorOptions } from "$lib/utils/sprite-utils";
|
|
||||||
|
|
||||||
export function getSpriteOptions(
|
export function getDollColorScheme(
|
||||||
doll: DollDto | null | undefined,
|
doll: DollDto | null | undefined,
|
||||||
): RecolorOptions | undefined {
|
): DollColorSchemeDto | undefined {
|
||||||
if (!doll) {
|
if (!doll) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return doll.configuration.colorScheme;
|
||||||
bodyColor: doll.configuration.colorScheme.body,
|
|
||||||
outlineColor: doll.configuration.colorScheme.outline,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { commands, type DollColorSchemeDto } from "$lib/bindings";
|
import { commands, type DollColorSchemeDto } from "$lib/bindings";
|
||||||
import onekoGif from "../../assets/oneko/oneko.gif";
|
import onekoGif from "../../assets/oneko/oneko.gif";
|
||||||
|
|
||||||
export interface RecolorOptions {
|
const spriteSheetUrlCache = new Map<string, Promise<string>>();
|
||||||
bodyColor: string;
|
|
||||||
outlineColor: string;
|
function getSpriteCacheKey(options: DollColorSchemeDto): string {
|
||||||
applyTexture?: boolean;
|
return `${options.body}:${options.outline}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSpriteSheetUrl(
|
export async function getSpriteSheetUrl(
|
||||||
@@ -14,15 +14,26 @@ export async function getSpriteSheetUrl(
|
|||||||
return onekoGif;
|
return onekoGif;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const cacheKey = getSpriteCacheKey(options);
|
||||||
const result = await commands.recolorGifBase64(
|
const cachedSpriteSheet = spriteSheetUrlCache.get(cacheKey);
|
||||||
|
|
||||||
|
if (cachedSpriteSheet) {
|
||||||
|
return cachedSpriteSheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const spriteSheetPromise = commands
|
||||||
|
.recolorGifBase64(
|
||||||
options.body,
|
options.body,
|
||||||
options.outline,
|
options.outline,
|
||||||
true, // default texture to true at this stage, maybe one day open up more customization options
|
true, // default texture to true at this stage, maybe one day open up more customization options
|
||||||
);
|
)
|
||||||
return `data:image/gif;base64,${result}`;
|
.then((result) => `data:image/gif;base64,${result}`)
|
||||||
} catch (e) {
|
.catch((e) => {
|
||||||
console.error("Failed to recolor sprite:", e);
|
console.error("Failed to recolor sprite:", e);
|
||||||
|
spriteSheetUrlCache.delete(cacheKey);
|
||||||
return onekoGif;
|
return onekoGif;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
spriteSheetUrlCache.set(cacheKey, spriteSheetPromise);
|
||||||
|
return spriteSheetPromise;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { UnlistenFn } from "@tauri-apps/api/event";
|
||||||
|
import { onMount } from "svelte";
|
||||||
import { cursorPositionOnScreen } from "../../events/cursor";
|
import { cursorPositionOnScreen } from "../../events/cursor";
|
||||||
import { sceneFriends } from "../../events/friend-cursor";
|
import { sceneFriends } from "../../events/friend-cursor";
|
||||||
import { appData } from "../../events/app-data";
|
import { appData } from "../../events/app-data";
|
||||||
@@ -8,17 +10,21 @@
|
|||||||
currentPresenceState,
|
currentPresenceState,
|
||||||
} from "../../events/user-status";
|
} from "../../events/user-status";
|
||||||
import { commands, events, type SceneFriendNeko } from "$lib/bindings";
|
import { commands, events, type SceneFriendNeko } from "$lib/bindings";
|
||||||
|
import { getDollColorScheme } from "$lib/scene";
|
||||||
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
|
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
|
||||||
import DebugBar from "./components/debug-bar.svelte";
|
import DebugBar from "./components/debug-bar.svelte";
|
||||||
import Neko from "./components/neko/neko.svelte";
|
import Neko from "./components/neko/neko.svelte";
|
||||||
|
|
||||||
let userSpriteUrl = $state("");
|
let userSpriteUrl = $state("");
|
||||||
let friendSpriteUrls = $state<Record<string, string>>({});
|
let friendSpriteUrls = $state<Record<string, string>>({});
|
||||||
|
let latestFriendSpritesRequest = 0;
|
||||||
|
let cachedFriendSpriteUrls: Record<string, string> = {};
|
||||||
|
let cachedFriendSpriteKeys: Record<string, string> = {};
|
||||||
|
|
||||||
async function fetchUserSprite() {
|
async function fetchUserSprite() {
|
||||||
try {
|
try {
|
||||||
const doll = await commands.getUserActiveDoll();
|
const doll = await commands.getUserActiveDoll();
|
||||||
const url = await getSpriteSheetUrl(doll?.configuration.colorScheme);
|
const url = await getSpriteSheetUrl(getDollColorScheme(doll));
|
||||||
userSpriteUrl = url;
|
userSpriteUrl = url;
|
||||||
} catch {
|
} catch {
|
||||||
const url = await getSpriteSheetUrl();
|
const url = await getSpriteSheetUrl();
|
||||||
@@ -26,31 +32,87 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
function getFriendSpriteKey(friend: SceneFriendNeko): string {
|
||||||
fetchUserSprite();
|
const colorScheme = getDollColorScheme(friend.activeDoll);
|
||||||
|
|
||||||
events.userActiveDollUpdated.listen(() => {
|
if (!colorScheme) {
|
||||||
fetchUserSprite();
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${colorScheme.body}:${colorScheme.outline}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
void fetchUserSprite();
|
||||||
|
|
||||||
|
let isDisposed = false;
|
||||||
|
let unlisten: UnlistenFn | undefined;
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
const stopListening = await events.userActiveDollUpdated.listen(() => {
|
||||||
|
void fetchUserSprite();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isDisposed) {
|
||||||
|
stopListening();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlisten = stopListening;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isDisposed = true;
|
||||||
|
unlisten?.();
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const friends = $sceneFriends;
|
const friends = $sceneFriends;
|
||||||
|
const requestId = ++latestFriendSpritesRequest;
|
||||||
|
|
||||||
if (friends.length === 0) {
|
if (friends.length === 0) {
|
||||||
friendSpriteUrls = {};
|
friendSpriteUrls = {};
|
||||||
|
cachedFriendSpriteUrls = {};
|
||||||
|
cachedFriendSpriteKeys = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(
|
void Promise.all(
|
||||||
friends.map(async (friend: SceneFriendNeko) => {
|
friends.map(async (friend: SceneFriendNeko) => {
|
||||||
|
const spriteKey = getFriendSpriteKey(friend);
|
||||||
|
const existingSpriteUrl = cachedFriendSpriteUrls[friend.id];
|
||||||
|
|
||||||
|
if (existingSpriteUrl && cachedFriendSpriteKeys[friend.id] === spriteKey) {
|
||||||
|
return [friend.id, spriteKey, existingSpriteUrl] as const;
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
friend.id,
|
friend.id,
|
||||||
await getSpriteSheetUrl(friend.activeDoll.configuration.colorScheme),
|
spriteKey,
|
||||||
|
await getSpriteSheetUrl(getDollColorScheme(friend.activeDoll)),
|
||||||
] as const;
|
] as const;
|
||||||
}),
|
}),
|
||||||
).then((entries) => {
|
).then((entries) => {
|
||||||
friendSpriteUrls = Object.fromEntries(entries);
|
if (requestId !== latestFriendSpritesRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedFriendSpriteUrls = Object.fromEntries(
|
||||||
|
entries.map(
|
||||||
|
([friendId, , spriteUrl]: readonly [string, string, string]) => [
|
||||||
|
friendId,
|
||||||
|
spriteUrl,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
cachedFriendSpriteKeys = Object.fromEntries(
|
||||||
|
entries.map(([friendId, spriteKey]: readonly [string, string, string]) => [
|
||||||
|
friendId,
|
||||||
|
spriteKey,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
friendSpriteUrls = cachedFriendSpriteUrls;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -81,7 +143,7 @@
|
|||||||
cursorPosition={$cursorPositionOnScreen}
|
cursorPosition={$cursorPositionOnScreen}
|
||||||
presenceStatus={$currentPresenceState?.presenceStatus ?? null}
|
presenceStatus={$currentPresenceState?.presenceStatus ?? null}
|
||||||
friendsCursorPositions={Object.fromEntries(
|
friendsCursorPositions={Object.fromEntries(
|
||||||
$sceneFriends.map((friend) => [friend.id, friend.position]),
|
$sceneFriends.map((friend: SceneFriendNeko) => [friend.id, friend.position]),
|
||||||
)}
|
)}
|
||||||
friends={$appData?.friends ?? []}
|
friends={$appData?.friends ?? []}
|
||||||
friendsPresenceStates={$friendsPresenceStates}
|
friendsPresenceStates={$friendsPresenceStates}
|
||||||
|
|||||||
Reference in New Issue
Block a user