-
-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(animation): new animations engine
This commit is comprised of the following interactively rebased commits from PR #1002 by @thearturca. 1a184a4 refactor(animation): move animations to its own mod First step for more rusty version animations. The goal is to make animations more generic so its easier to add new animations to komorebi! d3ac6b7 refactor(animation): reduce mutex calls on `ANIMATION_STYLE` 8a42b73 refactor(animation): introduce `Lerp` trait e449861 refactor(animation): generalized ANIMATION_MANAGER Instead of a isize key for the ANIMATION_MANAGER HashMap, now we use a String key. For window move animation, the key would be `window_move:{hwnd}`. This allows us to use single manager for more types of animations. 67b2a7a feat(animation): introduce `AnimationPrefix` enum 8290f14 feat(animation): introduce `RenderDispatcher` trait 2400d75 feat(animation): implement window transparency animation This commit also fixes graceful shutdown of animations by disabling them before exit and wait for all remaining animations for 20 seconds. 44189d8 refactor(animation): move generation of `animation key` to `RenderDispatcher` e502cb3 refactor(animation): rename `animation` mod to `engine` Linter was upset about this: > error: module has the same name as its containing module 369107f feat(config): adds per animation configuration options Originally static config only allowed global config for animations. Since this refactor introduces the abilty to add more type of animations, this change allows us to configure `enabled`, `duration` and `style` state per animation type. Now each of them take either the raw value or a JSON object where keys are the animation types and values are desired config value. Also adds support for per animation configuration for komorebic commands.
- Loading branch information
1 parent
1d00196
commit 414860c
Showing
17 changed files
with
764 additions
and
347 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use std::collections::hash_map::Entry; | ||
use std::collections::HashMap; | ||
|
||
use super::prefix::AnimationPrefix; | ||
|
||
#[derive(Debug, Clone, Copy)] | ||
struct AnimationState { | ||
pub in_progress: bool, | ||
pub cancel_idx_counter: usize, | ||
pub pending_cancel_count: usize, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct AnimationManager { | ||
animations: HashMap<String, AnimationState>, | ||
} | ||
|
||
impl Default for AnimationManager { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
impl AnimationManager { | ||
pub fn new() -> Self { | ||
Self { | ||
animations: HashMap::new(), | ||
} | ||
} | ||
|
||
pub fn is_cancelled(&self, animation_key: &str) -> bool { | ||
if let Some(animation_state) = self.animations.get(animation_key) { | ||
animation_state.pending_cancel_count > 0 | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
pub fn in_progress(&self, animation_key: &str) -> bool { | ||
if let Some(animation_state) = self.animations.get(animation_key) { | ||
animation_state.in_progress | ||
} else { | ||
false | ||
} | ||
} | ||
|
||
pub fn init_cancel(&mut self, animation_key: &str) -> usize { | ||
if let Some(animation_state) = self.animations.get_mut(animation_key) { | ||
animation_state.pending_cancel_count += 1; | ||
animation_state.cancel_idx_counter += 1; | ||
|
||
// return cancel idx | ||
animation_state.cancel_idx_counter | ||
} else { | ||
0 | ||
} | ||
} | ||
|
||
pub fn latest_cancel_idx(&mut self, animation_key: &str) -> usize { | ||
if let Some(animation_state) = self.animations.get_mut(animation_key) { | ||
animation_state.cancel_idx_counter | ||
} else { | ||
0 | ||
} | ||
} | ||
|
||
pub fn end_cancel(&mut self, animation_key: &str) { | ||
if let Some(animation_state) = self.animations.get_mut(animation_key) { | ||
animation_state.pending_cancel_count -= 1; | ||
} | ||
} | ||
|
||
pub fn cancel(&mut self, animation_key: &str) { | ||
if let Some(animation_state) = self.animations.get_mut(animation_key) { | ||
animation_state.in_progress = false; | ||
} | ||
} | ||
|
||
pub fn start(&mut self, animation_key: &str) { | ||
if let Entry::Vacant(e) = self.animations.entry(animation_key.to_string()) { | ||
e.insert(AnimationState { | ||
in_progress: true, | ||
cancel_idx_counter: 0, | ||
pending_cancel_count: 0, | ||
}); | ||
|
||
return; | ||
} | ||
|
||
if let Some(animation_state) = self.animations.get_mut(animation_key) { | ||
animation_state.in_progress = true; | ||
} | ||
} | ||
|
||
pub fn end(&mut self, animation_key: &str) { | ||
if let Some(animation_state) = self.animations.get_mut(animation_key) { | ||
animation_state.in_progress = false; | ||
|
||
if animation_state.pending_cancel_count == 0 { | ||
self.animations.remove(animation_key); | ||
} | ||
} | ||
} | ||
|
||
pub fn count_in_progress(&self, animation_key_prefix: AnimationPrefix) -> usize { | ||
self.animations | ||
.keys() | ||
.filter(|key| key.starts_with(animation_key_prefix.to_string().as_str())) | ||
.count() | ||
} | ||
|
||
pub fn count(&self) -> usize { | ||
self.animations.len() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use color_eyre::Result; | ||
|
||
use schemars::JsonSchema; | ||
|
||
use serde::Deserialize; | ||
use serde::Serialize; | ||
use std::sync::atomic::Ordering; | ||
use std::time::Duration; | ||
use std::time::Instant; | ||
|
||
use super::RenderDispatcher; | ||
use super::ANIMATION_DURATION_GLOBAL; | ||
use super::ANIMATION_FPS; | ||
use super::ANIMATION_MANAGER; | ||
|
||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)] | ||
pub struct AnimationEngine; | ||
|
||
impl AnimationEngine { | ||
pub fn wait_for_all_animations() { | ||
let max_duration = Duration::from_secs(20); | ||
let spent_duration = Instant::now(); | ||
|
||
while ANIMATION_MANAGER.lock().count() > 0 { | ||
if spent_duration.elapsed() >= max_duration { | ||
break; | ||
} | ||
|
||
std::thread::sleep(Duration::from_millis( | ||
ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst), | ||
)); | ||
} | ||
} | ||
|
||
/// Returns true if the animation needs to continue | ||
pub fn cancel(animation_key: &str) -> bool { | ||
// should be more than 0 | ||
let cancel_idx = ANIMATION_MANAGER.lock().init_cancel(animation_key); | ||
let max_duration = Duration::from_secs(5); | ||
let spent_duration = Instant::now(); | ||
|
||
while ANIMATION_MANAGER.lock().in_progress(animation_key) { | ||
if spent_duration.elapsed() >= max_duration { | ||
ANIMATION_MANAGER.lock().end(animation_key); | ||
} | ||
|
||
std::thread::sleep(Duration::from_millis(250 / 2)); | ||
} | ||
|
||
let latest_cancel_idx = ANIMATION_MANAGER.lock().latest_cancel_idx(animation_key); | ||
|
||
ANIMATION_MANAGER.lock().end_cancel(animation_key); | ||
|
||
latest_cancel_idx == cancel_idx | ||
} | ||
|
||
#[allow(clippy::cast_precision_loss)] | ||
pub fn animate( | ||
render_dispatcher: (impl RenderDispatcher + Send + 'static), | ||
duration: Duration, | ||
) -> Result<()> { | ||
std::thread::spawn(move || { | ||
let animation_key = render_dispatcher.get_animation_key(); | ||
if ANIMATION_MANAGER.lock().in_progress(animation_key.as_str()) { | ||
let should_animate = Self::cancel(animation_key.as_str()); | ||
|
||
if !should_animate { | ||
return Ok(()); | ||
} | ||
} | ||
|
||
render_dispatcher.pre_render()?; | ||
|
||
ANIMATION_MANAGER.lock().start(animation_key.as_str()); | ||
|
||
let target_frame_time = | ||
Duration::from_millis(1000 / ANIMATION_FPS.load(Ordering::Relaxed)); | ||
let mut progress = 0.0; | ||
let animation_start = Instant::now(); | ||
|
||
// start animation | ||
while progress < 1.0 { | ||
// check if animation is cancelled | ||
if ANIMATION_MANAGER | ||
.lock() | ||
.is_cancelled(animation_key.as_str()) | ||
{ | ||
// cancel animation | ||
ANIMATION_MANAGER.lock().cancel(animation_key.as_str()); | ||
return Ok(()); | ||
} | ||
|
||
let frame_start = Instant::now(); | ||
// calculate progress | ||
progress = | ||
animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64; | ||
render_dispatcher.render(progress).ok(); | ||
|
||
// sleep until next frame | ||
let frame_time_elapsed = frame_start.elapsed(); | ||
|
||
if frame_time_elapsed < target_frame_time { | ||
std::thread::sleep(target_frame_time - frame_time_elapsed); | ||
} | ||
} | ||
|
||
ANIMATION_MANAGER.lock().end(animation_key.as_str()); | ||
|
||
// limit progress to 1.0 if animation took longer | ||
if progress != 1.0 { | ||
progress = 1.0; | ||
|
||
// process animation for 1.0 to set target position | ||
render_dispatcher.render(progress).ok(); | ||
} | ||
|
||
render_dispatcher.post_render() | ||
}); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use crate::core::Rect; | ||
use crate::AnimationStyle; | ||
|
||
use super::style::apply_ease_func; | ||
|
||
pub trait Lerp<T = Self> { | ||
fn lerp(self, end: T, time: f64, style: AnimationStyle) -> T; | ||
} | ||
|
||
impl Lerp for i32 { | ||
#[allow(clippy::cast_possible_truncation)] | ||
fn lerp(self, end: i32, time: f64, style: AnimationStyle) -> i32 { | ||
let time = apply_ease_func(time, style); | ||
|
||
f64::from(end - self).mul_add(time, f64::from(self)).round() as i32 | ||
} | ||
} | ||
|
||
impl Lerp for f64 { | ||
fn lerp(self, end: f64, time: f64, style: AnimationStyle) -> f64 { | ||
let time = apply_ease_func(time, style); | ||
|
||
(end - self).mul_add(time, self) | ||
} | ||
} | ||
|
||
impl Lerp for u8 { | ||
fn lerp(self, end: u8, time: f64, style: AnimationStyle) -> u8 { | ||
(self as f64).lerp(end as f64, time, style) as u8 | ||
} | ||
} | ||
|
||
impl Lerp for Rect { | ||
fn lerp(self, end: Rect, time: f64, style: AnimationStyle) -> Rect { | ||
Rect { | ||
left: self.left.lerp(end.left, time, style), | ||
top: self.top.lerp(end.top, time, style), | ||
right: self.right.lerp(end.right, time, style), | ||
bottom: self.bottom.lerp(end.bottom, time, style), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
use crate::animation::animation_manager::AnimationManager; | ||
use crate::core::animation::AnimationStyle; | ||
|
||
use lazy_static::lazy_static; | ||
use prefix::AnimationPrefix; | ||
use std::collections::HashMap; | ||
use std::sync::atomic::AtomicBool; | ||
use std::sync::atomic::AtomicU64; | ||
use std::sync::Arc; | ||
|
||
use parking_lot::Mutex; | ||
|
||
pub use engine::AnimationEngine; | ||
pub mod animation_manager; | ||
pub mod engine; | ||
pub mod lerp; | ||
pub mod prefix; | ||
pub mod render_dispatcher; | ||
pub use render_dispatcher::RenderDispatcher; | ||
pub mod style; | ||
use schemars::JsonSchema; | ||
use serde::Deserialize; | ||
use serde::Serialize; | ||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] | ||
#[serde(untagged)] | ||
pub enum PerAnimationPrefixConfig<T> { | ||
Prefix(HashMap<AnimationPrefix, T>), | ||
Global(T), | ||
} | ||
|
||
pub const DEFAULT_ANIMATION_ENABLED: bool = false; | ||
pub const DEFAULT_ANIMATION_STYLE: AnimationStyle = AnimationStyle::Linear; | ||
pub const DEFAULT_ANIMATION_DURATION: u64 = 250; | ||
pub const DEFAULT_ANIMATION_FPS: u64 = 60; | ||
|
||
lazy_static! { | ||
pub static ref ANIMATION_MANAGER: Arc<Mutex<AnimationManager>> = | ||
Arc::new(Mutex::new(AnimationManager::new())); | ||
pub static ref ANIMATION_STYLE_GLOBAL: Arc<Mutex<AnimationStyle>> = | ||
Arc::new(Mutex::new(DEFAULT_ANIMATION_STYLE)); | ||
pub static ref ANIMATION_ENABLED_GLOBAL: Arc<AtomicBool> = | ||
Arc::new(AtomicBool::new(DEFAULT_ANIMATION_ENABLED)); | ||
pub static ref ANIMATION_DURATION_GLOBAL: Arc<AtomicU64> = | ||
Arc::new(AtomicU64::new(DEFAULT_ANIMATION_DURATION)); | ||
pub static ref ANIMATION_STYLE_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, AnimationStyle>>> = | ||
Arc::new(Mutex::new(HashMap::new())); | ||
pub static ref ANIMATION_ENABLED_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, bool>>> = | ||
Arc::new(Mutex::new(HashMap::new())); | ||
pub static ref ANIMATION_DURATION_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, u64>>> = | ||
Arc::new(Mutex::new(HashMap::new())); | ||
} | ||
|
||
pub static ANIMATION_FPS: AtomicU64 = AtomicU64::new(DEFAULT_ANIMATION_FPS); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use clap::ValueEnum; | ||
use schemars::JsonSchema; | ||
use serde::Deserialize; | ||
use serde::Serialize; | ||
use strum::Display; | ||
use strum::EnumString; | ||
|
||
#[derive( | ||
Copy, | ||
Clone, | ||
Debug, | ||
Hash, | ||
PartialEq, | ||
Eq, | ||
Serialize, | ||
Deserialize, | ||
Display, | ||
EnumString, | ||
ValueEnum, | ||
JsonSchema, | ||
)] | ||
#[strum(serialize_all = "snake_case")] | ||
#[serde(rename_all = "snake_case")] | ||
pub enum AnimationPrefix { | ||
WindowMove, | ||
WindowTransparency, | ||
} | ||
|
||
pub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String { | ||
format!("{}:{}", prefix, key) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
use color_eyre::Result; | ||
|
||
pub trait RenderDispatcher { | ||
fn get_animation_key(&self) -> String; | ||
fn pre_render(&self) -> Result<()>; | ||
fn render(&self, delta: f64) -> Result<()>; | ||
fn post_render(&self) -> Result<()>; | ||
} |
Oops, something went wrong.