mirror of
https://github.com/enso-org/enso.git
synced 2024-10-04 00:33:36 +03:00
Revert "Revert "Fixed rate animation loop fixes"" (#3399)
This commit is contained in:
parent
998d078b9a
commit
fbe28db1d7
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -1138,7 +1138,7 @@ dependencies = [
|
||||
"nalgebra 0.26.2",
|
||||
"num",
|
||||
"object 0.24.0",
|
||||
"paste 1.0.6",
|
||||
"paste 1.0.7",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shrinkwraprs 0.3.0",
|
||||
@ -1252,6 +1252,7 @@ dependencies = [
|
||||
"assert_approx_eq",
|
||||
"nalgebra 0.26.2",
|
||||
"num-traits",
|
||||
"paste 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3122,9 +3123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
|
||||
checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
|
||||
|
||||
[[package]]
|
||||
name = "paste-impl"
|
||||
@ -3851,7 +3852,7 @@ dependencies = [
|
||||
"approx 0.4.0",
|
||||
"num-complex 0.3.1",
|
||||
"num-traits",
|
||||
"paste 1.0.6",
|
||||
"paste 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2,6 +2,7 @@
|
||||
//! but can differ in all other aspects.
|
||||
|
||||
use crate::prelude::*;
|
||||
use ensogl_core::display::world::*;
|
||||
|
||||
use crate::typeface::font;
|
||||
|
||||
@ -12,13 +13,13 @@ use ensogl_core::display::layout::Alignment;
|
||||
use ensogl_core::display::scene::Scene;
|
||||
use ensogl_core::display::symbol::material::Material;
|
||||
use ensogl_core::display::symbol::shader::builder::CodeTemplate;
|
||||
use ensogl_core::display::world::*;
|
||||
use ensogl_core::system::gpu;
|
||||
use ensogl_core::system::gpu::texture;
|
||||
use font::Font;
|
||||
use font::GlyphRenderInfo;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
@ -14,12 +14,17 @@ use web::Closure;
|
||||
// === TimeInfo ===
|
||||
// ================
|
||||
|
||||
/// Note: the `start` field will be computed on first run. We cannot compute it upfront, as other
|
||||
/// time functions, like `performance.now()` can output not precise results. The exact results
|
||||
/// differ across browsers and browser versions. We have even observed that `performance.now()` can
|
||||
/// sometimes provide a bigger value than time provided to `requestAnimationFrame` callback later,
|
||||
/// which resulted in a negative frame time.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
|
||||
/// Time data of a given animation frame. Contains information about the animation loop start,
|
||||
/// previous frame time, and the time elapsed since the animation loop start till now.
|
||||
///
|
||||
/// Note: the [`animation_loop_start`] field will be computed on first loop iteration. We cannot
|
||||
/// compute it upfront, as other time functions, like `performance.now()`, can output results that
|
||||
/// can provide different values (both earlier or later than the time stamp provided to the
|
||||
/// `requestAnimationFrame` callback. The exact results differ across browsers and browser versions.
|
||||
/// We have even observed that `performance.now()` can sometimes provide a bigger value than time
|
||||
/// provided to `requestAnimationFrame` callback later, which resulted in a negative frame time.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct TimeInfo {
|
||||
pub animation_loop_start: Duration,
|
||||
@ -36,7 +41,15 @@ impl TimeInfo {
|
||||
/// Check whether the time info was initialized. See the documentation of the struct to learn
|
||||
/// more.
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.animation_loop_start != 0.0.ms()
|
||||
self.animation_loop_start != 0.ms()
|
||||
}
|
||||
|
||||
/// Creates a new [`TimeInfo`] for the next frame with the provided time. The frame time will
|
||||
/// be computed based on the current and the new frame time.
|
||||
pub fn new_frame(mut self, since_animation_loop_started: Duration) -> Self {
|
||||
self.previous_frame = since_animation_loop_started - self.since_animation_loop_started;
|
||||
self.since_animation_loop_started = since_animation_loop_started;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,35 +62,36 @@ impl TimeInfo {
|
||||
// === Types ===
|
||||
|
||||
/// Callback for `RawLoop`.
|
||||
pub trait RawLoopCallback = FnMut(Duration) + 'static;
|
||||
pub trait RawOnFrameCallback = FnMut(Duration) + 'static;
|
||||
|
||||
|
||||
// === Definition ===
|
||||
|
||||
/// The most performant animation loop possible. However, if you are looking for a way to define
|
||||
/// an animation loop, you are probably looking for the `Loop` which adds slight complexity
|
||||
/// an animation loop, you are probably looking for the [`Loop`] which adds slight complexity
|
||||
/// in order to provide better time information. The complexity is so small that it would not be
|
||||
/// noticeable in almost any use case.
|
||||
#[derive(CloneRef, Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
pub struct RawLoop<Callback> {
|
||||
data: Rc<RefCell<RawLoopData<Callback>>>,
|
||||
pub struct RawLoop<OnFrame> {
|
||||
data: Rc<RefCell<RawLoopData<OnFrame>>>,
|
||||
}
|
||||
|
||||
impl<Callback> RawLoop<Callback>
|
||||
where Callback: RawLoopCallback
|
||||
impl<OnFrame> RawLoop<OnFrame>
|
||||
where OnFrame: RawOnFrameCallback
|
||||
{
|
||||
/// Create and start a new animation loop.
|
||||
pub fn new(callback: Callback) -> Self {
|
||||
let data = Rc::new(RefCell::new(RawLoopData::new(callback)));
|
||||
pub fn new(on_frame: OnFrame) -> Self {
|
||||
let data = Rc::new(RefCell::new(RawLoopData::new(on_frame)));
|
||||
let weak_data = Rc::downgrade(&data);
|
||||
let on_frame = move |time: f64| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time));
|
||||
data.borrow_mut().on_frame = Some(Closure::new(on_frame));
|
||||
let handle_id = web::window.request_animation_frame_with_closure_or_panic(
|
||||
data.borrow_mut().on_frame.as_ref().unwrap(),
|
||||
let js_on_frame =
|
||||
move |time: f64| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time));
|
||||
data.borrow_mut().js_on_frame = Some(Closure::new(js_on_frame));
|
||||
let js_on_frame_handle_id = web::window.request_animation_frame_with_closure_or_panic(
|
||||
data.borrow_mut().js_on_frame.as_ref().unwrap(),
|
||||
);
|
||||
data.borrow_mut().handle_id = handle_id;
|
||||
data.borrow_mut().js_on_frame_handle_id = js_on_frame_handle_id;
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
@ -85,36 +99,36 @@ where Callback: RawLoopCallback
|
||||
/// The internal state of the `RawLoop`.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
pub struct RawLoopData<Callback> {
|
||||
pub struct RawLoopData<OnFrame> {
|
||||
#[derivative(Debug = "ignore")]
|
||||
callback: Callback,
|
||||
on_frame: Option<Closure<dyn FnMut(f64)>>,
|
||||
handle_id: i32,
|
||||
on_frame: OnFrame,
|
||||
js_on_frame: Option<Closure<dyn FnMut(f64)>>,
|
||||
js_on_frame_handle_id: i32,
|
||||
}
|
||||
|
||||
impl<Callback> RawLoopData<Callback> {
|
||||
impl<OnFrame> RawLoopData<OnFrame> {
|
||||
/// Constructor.
|
||||
fn new(callback: Callback) -> Self {
|
||||
let on_frame = default();
|
||||
let handle_id = default();
|
||||
Self { callback, on_frame, handle_id }
|
||||
fn new(on_frame: OnFrame) -> Self {
|
||||
let js_on_frame = default();
|
||||
let js_on_frame_handle_id = default();
|
||||
Self { on_frame, js_on_frame, js_on_frame_handle_id }
|
||||
}
|
||||
|
||||
/// Run the animation frame. The time will be converted to [`f32`] because of performance
|
||||
/// reasons. See https://hugotunius.se/2017/12/04/rust-f64-vs-f32.html to learn more.
|
||||
fn run(&mut self, current_time_ms: f64)
|
||||
where Callback: FnMut(Duration) {
|
||||
let callback = &mut self.callback;
|
||||
self.handle_id = self.on_frame.as_ref().map_or(default(), |on_frame| {
|
||||
callback((current_time_ms as f32).ms());
|
||||
web::window.request_animation_frame_with_closure_or_panic(on_frame)
|
||||
where OnFrame: FnMut(Duration) {
|
||||
let on_frame = &mut self.on_frame;
|
||||
self.js_on_frame_handle_id = self.js_on_frame.as_ref().map_or(default(), |js_on_frame| {
|
||||
on_frame((current_time_ms as f32).ms());
|
||||
web::window.request_animation_frame_with_closure_or_panic(js_on_frame)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Callback> Drop for RawLoopData<Callback> {
|
||||
impl<OnFrame> Drop for RawLoopData<OnFrame> {
|
||||
fn drop(&mut self) {
|
||||
web::window.cancel_animation_frame_or_panic(self.handle_id);
|
||||
web::window.cancel_animation_frame_or_panic(self.js_on_frame_handle_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,53 +140,52 @@ impl<Callback> Drop for RawLoopData<Callback> {
|
||||
|
||||
// === Types ===
|
||||
|
||||
pub trait LoopCallback = FnMut(TimeInfo) + 'static;
|
||||
/// Type of the function that will be called on every animation frame.
|
||||
pub trait OnFrameCallback = FnMut(TimeInfo) + 'static;
|
||||
|
||||
|
||||
// === Definition ===
|
||||
|
||||
/// An animation loop. Runs the provided `Callback` every animation frame. It uses the
|
||||
/// `RawLoop` under the hood. If you are looking for a more complex version where you can
|
||||
/// register new callbacks for every frame, take a look at the ``.
|
||||
/// An animation loop. Runs the provided [`OnFrame`] callback on every animation frame.
|
||||
#[derive(CloneRef, Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
pub struct Loop<Callback> {
|
||||
animation_loop: RawLoop<OnFrame<Callback>>,
|
||||
pub struct Loop<OnFrame> {
|
||||
animation_loop: RawLoop<OnFrameClosure<OnFrame>>,
|
||||
time_info: Rc<Cell<TimeInfo>>,
|
||||
}
|
||||
|
||||
impl<Callback> Loop<Callback>
|
||||
where Callback: LoopCallback
|
||||
impl<OnFrame> Loop<OnFrame>
|
||||
where OnFrame: OnFrameCallback
|
||||
{
|
||||
/// Constructor.
|
||||
pub fn new(callback: Callback) -> Self {
|
||||
pub fn new(on_frame: OnFrame) -> Self {
|
||||
let time_info = Rc::new(Cell::new(TimeInfo::new()));
|
||||
let animation_loop = RawLoop::new(on_frame(callback, time_info.clone_ref()));
|
||||
let animation_loop = RawLoop::new(on_frame_closure(on_frame, time_info.clone_ref()));
|
||||
Self { animation_loop, time_info }
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback for an animation frame.
|
||||
pub type OnFrame<Callback> = impl FnMut(Duration);
|
||||
fn on_frame<Callback>(
|
||||
mut callback: Callback,
|
||||
time_info_ref: Rc<Cell<TimeInfo>>,
|
||||
) -> OnFrame<Callback>
|
||||
pub type OnFrameClosure<OnFrame> = impl FnMut(Duration);
|
||||
fn on_frame_closure<OnFrame>(
|
||||
mut on_frame: OnFrame,
|
||||
time_info: Rc<Cell<TimeInfo>>,
|
||||
) -> OnFrameClosure<OnFrame>
|
||||
where
|
||||
Callback: LoopCallback,
|
||||
OnFrame: OnFrameCallback,
|
||||
{
|
||||
move |current_time: Duration| {
|
||||
let _profiler = profiler::start_debug!(profiler::APP_LIFETIME, "@on_frame");
|
||||
let prev_time = time_info_ref.get();
|
||||
let animation_loop_start =
|
||||
if prev_time.is_initialized() { prev_time.animation_loop_start } else { current_time };
|
||||
let prev_time = time_info.get();
|
||||
let is_initialized = prev_time.is_initialized();
|
||||
let prev_start = prev_time.animation_loop_start;
|
||||
let animation_loop_start = if is_initialized { prev_start } else { current_time };
|
||||
let since_animation_loop_started = current_time - animation_loop_start;
|
||||
let previous_frame = since_animation_loop_started - prev_time.since_animation_loop_started;
|
||||
let time_info =
|
||||
TimeInfo { animation_loop_start, previous_frame, since_animation_loop_started };
|
||||
time_info_ref.set(time_info);
|
||||
callback(time_info);
|
||||
let time = TimeInfo { animation_loop_start, previous_frame, since_animation_loop_started };
|
||||
time_info.set(time);
|
||||
on_frame(time);
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,52 +195,92 @@ where
|
||||
// === FixedFrameRateSampler ===
|
||||
// =============================
|
||||
|
||||
/// A callback `FnMut(TimeInfo) -> FnMut(TimeInfo)` transformer. Calls the inner callback with a
|
||||
/// constant frame rate.
|
||||
/// An animation loop transformer. Calls the provided [`OnFrame`] callback on every animation frame.
|
||||
/// If the real animation frames are too long, it will emit virtual frames in between. In case the
|
||||
/// delay is too long (more than [`max_skipped_frames`]), the [`OnTooManyFramesSkipped`] callback
|
||||
/// will be used instead.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""))]
|
||||
pub struct FixedFrameRateSampler<Callback> {
|
||||
frame_time: Duration,
|
||||
local_time: Duration,
|
||||
time_buffer: Duration,
|
||||
#[allow(missing_docs)]
|
||||
pub struct FixedFrameRateSampler<OnFrame, OnTooManyFramesSkipped> {
|
||||
pub max_skipped_frames: usize,
|
||||
frame_time: Duration,
|
||||
local_time: Duration,
|
||||
time_buffer: Duration,
|
||||
#[derivative(Debug = "ignore")]
|
||||
callback: Callback,
|
||||
callback: OnFrame,
|
||||
#[derivative(Debug = "ignore")]
|
||||
on_too_many_frames_skipped: OnTooManyFramesSkipped,
|
||||
}
|
||||
|
||||
impl<Callback> FixedFrameRateSampler<Callback> {
|
||||
impl<OnFrame, OnTooManyFramesSkipped> FixedFrameRateSampler<OnFrame, OnTooManyFramesSkipped> {
|
||||
/// Constructor.
|
||||
pub fn new(frame_rate: f32, callback: Callback) -> Self {
|
||||
let frame_time = (1000.0 / frame_rate).ms();
|
||||
pub fn new(
|
||||
fps: f32,
|
||||
callback: OnFrame,
|
||||
on_too_many_frames_skipped: OnTooManyFramesSkipped,
|
||||
) -> Self {
|
||||
let max_skipped_frames = 2;
|
||||
let frame_time = (1000.0 / fps).ms();
|
||||
let local_time = default();
|
||||
let time_buffer = default();
|
||||
Self { frame_time, local_time, time_buffer, callback }
|
||||
// The first call to this sampler will be with frame time 0, which would drop this
|
||||
// `time_buffer` to 0.
|
||||
let time_buffer = frame_time;
|
||||
Self {
|
||||
max_skipped_frames,
|
||||
frame_time,
|
||||
local_time,
|
||||
time_buffer,
|
||||
callback,
|
||||
on_too_many_frames_skipped,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Callback: FnOnce<(TimeInfo,)>> FnOnce<(TimeInfo,)> for FixedFrameRateSampler<Callback> {
|
||||
impl<OnFrame: FnOnce<(TimeInfo,)>, OnTooManyFramesSkipped> FnOnce<(TimeInfo,)>
|
||||
for FixedFrameRateSampler<OnFrame, OnTooManyFramesSkipped>
|
||||
{
|
||||
type Output = ();
|
||||
extern "rust-call" fn call_once(self, args: (TimeInfo,)) -> Self::Output {
|
||||
self.callback.call_once(args);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Callback: FnMut<(TimeInfo,)>> FnMut<(TimeInfo,)> for FixedFrameRateSampler<Callback> {
|
||||
impl<OnFrame, OnTooManyFramesSkipped> FnMut<(TimeInfo,)>
|
||||
for FixedFrameRateSampler<OnFrame, OnTooManyFramesSkipped>
|
||||
where
|
||||
OnFrame: FnMut(TimeInfo),
|
||||
OnTooManyFramesSkipped: FnMut(),
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, args: (TimeInfo,)) -> Self::Output {
|
||||
let time = args.0;
|
||||
self.time_buffer += time.previous_frame;
|
||||
loop {
|
||||
if self.time_buffer < 0.0.ms() {
|
||||
break;
|
||||
} else {
|
||||
let mut time = args.0;
|
||||
self.time_buffer += time.since_animation_loop_started - self.local_time;
|
||||
|
||||
let frame_time_2 = self.frame_time * 0.5;
|
||||
let skipped_frames = ((self.time_buffer - frame_time_2) / self.frame_time) as usize;
|
||||
let too_many_frames_skipped = skipped_frames > self.max_skipped_frames;
|
||||
if !too_many_frames_skipped {
|
||||
for _ in 0..skipped_frames {
|
||||
self.local_time += self.frame_time;
|
||||
self.time_buffer -= self.frame_time;
|
||||
let animation_loop_start = time.animation_loop_start;
|
||||
let previous_frame = self.frame_time;
|
||||
let since_animation_loop_started = self.local_time;
|
||||
let time2 =
|
||||
TimeInfo { animation_loop_start, previous_frame, since_animation_loop_started };
|
||||
self.local_time += self.frame_time;
|
||||
self.callback.call_mut((time2,));
|
||||
}
|
||||
let not_too_fast_refresh_rate = self.time_buffer >= -frame_time_2;
|
||||
if not_too_fast_refresh_rate {
|
||||
self.time_buffer -= self.frame_time;
|
||||
}
|
||||
time.previous_frame = time.since_animation_loop_started - self.local_time;
|
||||
self.local_time = time.since_animation_loop_started;
|
||||
(self.callback)(time);
|
||||
} else {
|
||||
self.local_time = time.since_animation_loop_started;
|
||||
self.time_buffer = 0.ms();
|
||||
(self.on_too_many_frames_skipped)();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,14 +291,115 @@ impl<Callback: FnMut<(TimeInfo,)>> FnMut<(TimeInfo,)> for FixedFrameRateSampler<
|
||||
// === FixedFrameRateLoop ===
|
||||
// ==========================
|
||||
|
||||
/// Loop with a `FixedFrameRateSampler` attached.
|
||||
pub type FixedFrameRateLoop<Callback> = Loop<FixedFrameRateSampler<Callback>>;
|
||||
/// Callback used if too many frames were skipped.
|
||||
pub trait OnTooManyFramesSkippedCallback = FnMut() + 'static;
|
||||
|
||||
impl<Callback> FixedFrameRateLoop<Callback>
|
||||
where Callback: LoopCallback
|
||||
/// Loop with a `FixedFrameRateSampler` attached.
|
||||
pub type FixedFrameRateLoop<OnFrame, OnTooManyFramesSkipped> =
|
||||
Loop<FixedFrameRateSampler<OnFrame, OnTooManyFramesSkipped>>;
|
||||
|
||||
impl<OnFrame: OnFrameCallback, OnTooManyFramesSkipped: OnTooManyFramesSkippedCallback>
|
||||
FixedFrameRateLoop<OnFrame, OnTooManyFramesSkipped>
|
||||
{
|
||||
/// Constructor.
|
||||
pub fn new_with_fixed_frame_rate(frame_rate: f32, callback: Callback) -> Self {
|
||||
Self::new(FixedFrameRateSampler::new(frame_rate, callback))
|
||||
pub fn new_with_fixed_frame_rate(
|
||||
fps: f32,
|
||||
on_frame: OnFrame,
|
||||
on_too_many_frames_skipped: OnTooManyFramesSkipped,
|
||||
) -> Self {
|
||||
Self::new(FixedFrameRateSampler::new(fps, on_frame, on_too_many_frames_skipped))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[test]
|
||||
fn fixed_frame_rate_sampler_test() {
|
||||
let mut count_check = 0;
|
||||
let count = Rc::new(Cell::new(0));
|
||||
let too_many_frames_skipped_count = Rc::new(Cell::new(0));
|
||||
let frame_times = Rc::new(RefCell::new(VecDeque::new()));
|
||||
let mut lp = FixedFrameRateSampler::new(
|
||||
10.0,
|
||||
|t| {
|
||||
frame_times.borrow_mut().push_back(t);
|
||||
count.set(count.get() + 1);
|
||||
},
|
||||
|| {
|
||||
too_many_frames_skipped_count.set(too_many_frames_skipped_count.get() + 1);
|
||||
},
|
||||
);
|
||||
let mut time = TimeInfo {
|
||||
animation_loop_start: 0.ms(),
|
||||
previous_frame: 0.ms(),
|
||||
since_animation_loop_started: 0.ms(),
|
||||
};
|
||||
|
||||
let mut step = |frame_time: Duration,
|
||||
sub_frames: &[Duration],
|
||||
offset: Duration,
|
||||
skipped_count: Option<usize>| {
|
||||
let time2 = time.new_frame(frame_time);
|
||||
lp(time2);
|
||||
for sub_frame in sub_frames {
|
||||
count_check += 1;
|
||||
time = time.new_frame(*sub_frame);
|
||||
assert_eq!(frame_times.borrow_mut().pop_front(), Some(time));
|
||||
}
|
||||
time = time.new_frame(time2.since_animation_loop_started);
|
||||
if skipped_count.is_none() {
|
||||
count_check += 1;
|
||||
assert_eq!(frame_times.borrow_mut().pop_front(), Some(time));
|
||||
}
|
||||
assert_eq!(frame_times.borrow_mut().pop_front(), None);
|
||||
assert_eq!(count.get(), count_check);
|
||||
assert_eq!(lp.time_buffer, offset);
|
||||
if let Some(skipped_count) = skipped_count {
|
||||
assert_eq!(too_many_frames_skipped_count.get(), skipped_count);
|
||||
}
|
||||
};
|
||||
|
||||
// Start frame.
|
||||
step(0.ms(), &[], 0.ms(), None);
|
||||
|
||||
// Perfectly timed next frame.
|
||||
step(100.ms(), &[], 0.ms(), None);
|
||||
|
||||
// Skipping 2 frames.
|
||||
step(400.ms(), &[200.ms(), 300.ms()], 0.ms(), None);
|
||||
|
||||
// Perfectly timed next frame.
|
||||
step(500.ms(), &[], 0.ms(), None);
|
||||
|
||||
// Next frame too slow.
|
||||
step(640.ms(), &[], 40.ms(), None);
|
||||
|
||||
// Next frame too slow.
|
||||
step(800.ms(), &[740.ms()], 0.ms(), None);
|
||||
|
||||
// Not-perfectly timed next frames.
|
||||
step(870.ms(), &[], -30.ms(), None);
|
||||
step(1010.ms(), &[], 10.ms(), None);
|
||||
step(1090.ms(), &[], -10.ms(), None);
|
||||
step(1200.ms(), &[], 0.ms(), None);
|
||||
|
||||
// Next frames way too fast.
|
||||
step(1210.ms(), &[], -90.ms(), None);
|
||||
// Time compression – we don't want to accumulate too much of negative time buffer for
|
||||
// monitors with bigger refresh-rate than assumed. The total accumulated time buffer would
|
||||
// be -180 here, so we add a frame time to it (100).
|
||||
step(1220.ms(), &[], -80.ms(), None);
|
||||
|
||||
// Too many frames skipped.
|
||||
step(2000.ms(), &[], 0.ms(), Some(1));
|
||||
}
|
||||
}
|
||||
|
@ -585,20 +585,20 @@ impl<T, OnStep, OnStart, OnEnd> Deref for SimulatorData<T, OnStep, OnStart, OnEn
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, OnStep, OnStart, OnEnd> SimulatorData<T, OnStep, OnStart, OnEnd>
|
||||
where
|
||||
T: Value,
|
||||
OnStep: Callback1<T>,
|
||||
OnStart: Callback0,
|
||||
OnEnd: Callback1<EndStatus>,
|
||||
{
|
||||
impl<T: Value, OnStep, OnStart, OnEnd> SimulatorData<T, OnStep, OnStart, OnEnd> {
|
||||
/// Constructor.
|
||||
pub fn new(on_step: OnStep, on_start: OnStart, on_end: OnEnd) -> Self {
|
||||
let simulation = SimulationDataCell::new();
|
||||
let frame_rate = Cell::new(60.0);
|
||||
Self { simulation, frame_rate, on_step, on_start, on_end }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Value, OnStep, OnStart, OnEnd> SimulatorData<T, OnStep, OnStart, OnEnd>
|
||||
where
|
||||
OnStep: Callback1<T>,
|
||||
OnEnd: Callback1<EndStatus>,
|
||||
{
|
||||
/// Proceed with the next simulation step for the given time delta.
|
||||
pub fn step(&self, delta_seconds: Duration) -> bool {
|
||||
let is_active = self.simulation.active();
|
||||
@ -628,7 +628,7 @@ pub type DynSimulator<T> = Simulator<T, Box<dyn Fn(T)>, (), Box<dyn Fn(EndStatus
|
||||
#[derivative(Clone(bound = ""))]
|
||||
pub struct Simulator<T, OnStep, OnStart, OnEnd> {
|
||||
data: Rc<SimulatorData<T, OnStep, OnStart, OnEnd>>,
|
||||
animation_loop: AnimationLoop<T, OnStep, OnStart, OnEnd>,
|
||||
animation_loop: AnimationLoopSlot<T, OnStep, OnStart, OnEnd>,
|
||||
}
|
||||
|
||||
impl<T, OnStep, OnStart, OnEnd> Deref for Simulator<T, OnStep, OnStart, OnEnd> {
|
||||
@ -707,7 +707,12 @@ where
|
||||
if self.animation_loop.get().is_none() {
|
||||
let frame_rate = self.frame_rate.get();
|
||||
let step = step(self);
|
||||
let animation_loop = animation::Loop::new_with_fixed_frame_rate(frame_rate, step);
|
||||
let on_too_many_frames_skipped = on_too_many_frames_skipped(self);
|
||||
let animation_loop = animation::Loop::new_with_fixed_frame_rate(
|
||||
frame_rate,
|
||||
step,
|
||||
on_too_many_frames_skipped,
|
||||
);
|
||||
self.animation_loop.set(Some(animation_loop));
|
||||
self.on_start.call();
|
||||
}
|
||||
@ -727,60 +732,106 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, OnStep, OnStart, OnEnd> Simulator<T, OnStep, OnStart, OnEnd> {
|
||||
/// Downgrade to a weak reference.
|
||||
pub fn downgrade(&self) -> WeakSimulator<T, OnStep, OnStart, OnEnd> {
|
||||
let data = self.data.clone_ref();
|
||||
let animation_loop = self.animation_loop.downgrade();
|
||||
WeakSimulator { data, animation_loop }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =====================
|
||||
// === AnimationLoop ===
|
||||
// === WeakSimulator ===
|
||||
// =====================
|
||||
|
||||
/// A wrapper over animation loop implementation. This type is defined mainly to make Rust type
|
||||
/// inferencer happy (not infer infinite, recursive types).
|
||||
/// Weak version of [`Simulator`].
|
||||
#[derive(CloneRef, Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
pub struct WeakSimulator<T, OnStep, OnStart, OnEnd> {
|
||||
data: Rc<SimulatorData<T, OnStep, OnStart, OnEnd>>,
|
||||
animation_loop: WeakAnimationLoopSlot<T, OnStep, OnStart, OnEnd>,
|
||||
}
|
||||
|
||||
impl<T, OnStep, OnStart, OnEnd> WeakSimulator<T, OnStep, OnStart, OnEnd> {
|
||||
/// Try upgrading to a string reference.
|
||||
pub fn upgrade(&self) -> Option<Simulator<T, OnStep, OnStart, OnEnd>> {
|
||||
let data = self.data.clone_ref();
|
||||
self.animation_loop.upgrade().map(|animation_loop| Simulator { data, animation_loop })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, OnStep, OnStart, OnEnd> Debug for WeakSimulator<T, OnStep, OnStart, OnEnd> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "WeakSimulator")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =========================
|
||||
// === AnimationLoopSlot ===
|
||||
// =========================
|
||||
|
||||
/// A slot for an animation loop. It will be empty if the animation is not active – either if it was
|
||||
/// not startd yet or it was already finished.
|
||||
#[derive(CloneRef, Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
#[derivative(Default(bound = ""))]
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct AnimationLoop<T, OnStep, OnStart, OnEnd> {
|
||||
animation_loop: Rc<CloneCell<Option<FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd>>>>,
|
||||
pub struct AnimationLoopSlot<T, OnStep, OnStart, OnEnd> {
|
||||
animation_loop: Rc<CloneCell<Option<FixedFrameRateLoop<T, OnStep, OnStart, OnEnd>>>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
impl<T, OnStep, OnStart, OnEnd> Deref for AnimationLoop<T, OnStep, OnStart, OnEnd> {
|
||||
type Target = Rc<CloneCell<Option<FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd>>>>;
|
||||
impl<T, OnStep, OnStart, OnEnd> Deref for AnimationLoopSlot<T, OnStep, OnStart, OnEnd> {
|
||||
type Target = Rc<CloneCell<Option<FixedFrameRateLoop<T, OnStep, OnStart, OnEnd>>>>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.animation_loop
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, OnStep, OnStart, OnEnd> AnimationLoop<T, OnStep, OnStart, OnEnd> {
|
||||
impl<T, OnStep, OnStart, OnEnd> AnimationLoopSlot<T, OnStep, OnStart, OnEnd> {
|
||||
/// Downgrade to a week reference.
|
||||
pub fn downgrade(&self) -> WeakAnimationLoop<T, OnStep, OnStart, OnEnd> {
|
||||
pub fn downgrade(&self) -> WeakAnimationLoopSlot<T, OnStep, OnStart, OnEnd> {
|
||||
let animation_loop = Rc::downgrade(&self.animation_loop);
|
||||
WeakAnimationLoop { animation_loop }
|
||||
WeakAnimationLoopSlot { animation_loop }
|
||||
}
|
||||
}
|
||||
|
||||
/// A weak wrapper over animation loop implementation. This type is defined mainly to make Rust type
|
||||
/// inferencer happy (not infer infinite, recursive types).
|
||||
|
||||
// =============================
|
||||
// === WeakAnimationLoopSlot ===
|
||||
// =============================
|
||||
|
||||
/// A weak version of [`AnimationLoopSlot`].
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct WeakAnimationLoop<T, OnStep, OnStart, OnEnd> {
|
||||
animation_loop: Weak<CloneCell<Option<FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd>>>>,
|
||||
#[derive(CloneRef, Derivative)]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
pub struct WeakAnimationLoopSlot<T, OnStep, OnStart, OnEnd> {
|
||||
animation_loop: Weak<CloneCell<Option<FixedFrameRateLoop<T, OnStep, OnStart, OnEnd>>>>,
|
||||
}
|
||||
|
||||
impl<T, OnStep, OnStart, OnEnd> WeakAnimationLoop<T, OnStep, OnStart, OnEnd> {
|
||||
impl<T, OnStep, OnStart, OnEnd> WeakAnimationLoopSlot<T, OnStep, OnStart, OnEnd> {
|
||||
/// Upgrade the weak reference.
|
||||
pub fn upgrade(&self) -> Option<AnimationLoop<T, OnStep, OnStart, OnEnd>> {
|
||||
self.animation_loop.upgrade().map(|animation_loop| AnimationLoop { animation_loop })
|
||||
pub fn upgrade(&self) -> Option<AnimationLoopSlot<T, OnStep, OnStart, OnEnd>> {
|
||||
self.animation_loop.upgrade().map(|animation_loop| AnimationLoopSlot { animation_loop })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Animation Step ===
|
||||
// ==========================
|
||||
// === FixedFrameRateLoop ===
|
||||
// ==========================
|
||||
|
||||
/// Alias for `FixedFrameRateLoop` with specified step callback.
|
||||
pub type FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd> =
|
||||
animation::FixedFrameRateLoop<Step<T, OnStep, OnStart, OnEnd>>;
|
||||
/// Alias for [`FixedFrameRateLoop`] with specified step callback.
|
||||
pub type FixedFrameRateLoop<T, OnStep, OnStart, OnEnd> = animation::FixedFrameRateLoop<
|
||||
Step<T, OnStep, OnStart, OnEnd>,
|
||||
OnTooManyFramesSkipped<T, OnStep, OnStart, OnEnd>,
|
||||
>;
|
||||
|
||||
/// Callback for an animation step.
|
||||
pub type Step<T, OnStep, OnStart, OnEnd> = impl Fn(animation::TimeInfo);
|
||||
@ -793,18 +844,36 @@ where
|
||||
OnStep: Callback1<T>,
|
||||
OnStart: Callback0,
|
||||
OnEnd: Callback1<EndStatus>, {
|
||||
let data = simulator.data.clone_ref();
|
||||
let animation_loop = simulator.animation_loop.downgrade();
|
||||
let weak_simulator = simulator.downgrade();
|
||||
move |time: animation::TimeInfo| {
|
||||
let delta_seconds = time.previous_frame / 1000.0;
|
||||
if !data.step(delta_seconds) {
|
||||
if let Some(animation_loop) = animation_loop.upgrade() {
|
||||
animation_loop.set(None)
|
||||
if let Some(simulator) = weak_simulator.upgrade() {
|
||||
let delta_seconds = time.previous_frame / 1000.0;
|
||||
if !simulator.step(delta_seconds) {
|
||||
simulator.animation_loop.set(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback for an animation step.
|
||||
pub type OnTooManyFramesSkipped<T, OnStep, OnStart, OnEnd> = impl Fn();
|
||||
|
||||
fn on_too_many_frames_skipped<T, OnStep, OnStart, OnEnd>(
|
||||
simulator: &Simulator<T, OnStep, OnStart, OnEnd>,
|
||||
) -> OnTooManyFramesSkipped<T, OnStep, OnStart, OnEnd>
|
||||
where
|
||||
T: Value,
|
||||
OnStep: Callback1<T>,
|
||||
OnStart: Callback0,
|
||||
OnEnd: Callback1<EndStatus>, {
|
||||
let weak_simulator = simulator.downgrade();
|
||||
move || {
|
||||
if let Some(simulator) = weak_simulator.upgrade() {
|
||||
simulator.skip()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
@ -836,6 +905,12 @@ impl Default for EndStatus {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#![warn(unsafe_code)]
|
||||
// === Non-Standard Linter Configuration ===
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::precedence)]
|
||||
#![allow(dead_code)]
|
||||
#![deny(unconditional_recursion)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
|
@ -28,6 +28,8 @@ use wasm_bindgen::prelude::*;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
|
||||
|
||||
|
||||
const CHARS_TO_TEST: &[&str] = &["abcdqwerty", "ABCDQUERTY"];
|
||||
|
||||
/// Main example runner.
|
||||
|
@ -21,12 +21,12 @@
|
||||
#![warn(unused_qualifications)]
|
||||
|
||||
use ensogl_core::prelude::*;
|
||||
use ensogl_text::traits::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use ensogl_core::application::Application;
|
||||
use ensogl_core::display::navigation::navigator::Navigator;
|
||||
use ensogl_text::style;
|
||||
use ensogl_text::traits::*;
|
||||
use ensogl_text::Area;
|
||||
use ensogl_text_msdf_sys::run_once_initialized;
|
||||
|
||||
|
@ -28,8 +28,8 @@
|
||||
#![warn(unused_import_braces)]
|
||||
|
||||
use enso_prelude::*;
|
||||
use enso_profiler_data as data;
|
||||
|
||||
use enso_profiler_data as data;
|
||||
use std::collections;
|
||||
|
||||
|
||||
|
@ -107,7 +107,6 @@ use std::error;
|
||||
use std::fmt;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
@ -9,6 +9,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
nalgebra = { version = "0.26.1" }
|
||||
num-traits = { version = "0.2" }
|
||||
paste = "1.0.7"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = { version = "1.1.0" }
|
||||
|
@ -6,6 +6,7 @@
|
||||
//! [`Duration`] or a number, respectfully. You are allowed to define any combination of operators
|
||||
//! and rules of how the result inference should be performed.
|
||||
|
||||
use paste::paste;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
|
||||
@ -110,9 +111,15 @@ impl<V, R> !IsNotUnit for UnitData<V, R> {}
|
||||
// === Default ===
|
||||
// ===============
|
||||
|
||||
impl<V, R: Default> Default for UnitData<V, R> {
|
||||
/// A default value of unit.
|
||||
#[allow(missing_docs)]
|
||||
pub trait UnitDefault<R> {
|
||||
fn unit_default() -> R;
|
||||
}
|
||||
|
||||
impl<V: UnitDefault<R>, R> Default for UnitData<V, R> {
|
||||
fn default() -> Self {
|
||||
R::default().unchecked_into()
|
||||
<V as UnitDefault<R>>::unit_default().unchecked_into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +159,6 @@ impl<V, R> AsRef<UnitData<V, R>> for UnitData<V, R> {
|
||||
// === Eq ===
|
||||
// ==========
|
||||
|
||||
impl<V, R: PartialEq> Eq for UnitData<V, R> {}
|
||||
impl<V, R: PartialEq> PartialEq for UnitData<V, R> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.repr.eq(&other.repr)
|
||||
@ -165,12 +171,6 @@ impl<V, R: PartialEq> PartialEq for UnitData<V, R> {
|
||||
// === Ord ===
|
||||
// ===========
|
||||
|
||||
impl<V, R: Ord> Ord for UnitData<V, R> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.repr.cmp(&other.repr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, R: PartialOrd> PartialOrd for UnitData<V, R> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.repr.partial_cmp(&other.repr)
|
||||
@ -354,8 +354,6 @@ macro_rules! gen_ops_mut {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
gen_ops!(RevAdd, Add, add);
|
||||
gen_ops!(RevSub, Sub, sub);
|
||||
gen_ops!(RevMul, Mul, mul);
|
||||
@ -368,6 +366,15 @@ gen_ops_mut!(RevSub, Sub, SubAssign, sub_assign);
|
||||
gen_ops_mut!(RevMul, Mul, MulAssign, mul_assign);
|
||||
gen_ops_mut!(RevDiv, Div, DivAssign, div_assign);
|
||||
|
||||
impl<V, R: ops::Neg<Output = R>> ops::Neg for UnitData<V, R> {
|
||||
type Output = UnitData<V, R>;
|
||||
|
||||
fn neg(mut self) -> Self::Output {
|
||||
self.repr = self.repr.neg();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============================
|
||||
@ -394,16 +401,23 @@ gen_ops_mut!(RevDiv, Div, DivAssign, div_assign);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! define {
|
||||
($(#$meta:tt)* $name:ident = $variant:ident ($tp:ident)) => {
|
||||
$(#$meta)*
|
||||
pub type $name = Unit<$variant>;
|
||||
($(#$meta:tt)* $name:ident: $tp:ident = $default:expr) => {
|
||||
paste!{
|
||||
$(#$meta)*
|
||||
pub type $name = $crate::unit2::Unit<[<$name:snake:upper>]>;
|
||||
$(#$meta)*
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct [<$name:snake:upper>];
|
||||
|
||||
$(#$meta)*
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct $variant;
|
||||
impl $crate::unit2::Variant for [<$name:snake:upper>] {
|
||||
type Repr = $tp;
|
||||
}
|
||||
|
||||
impl Variant for $variant {
|
||||
type Repr = $tp;
|
||||
impl $crate::unit2::UnitDefault<$tp> for [<$name:snake:upper>] {
|
||||
fn unit_default() -> $tp {
|
||||
$default
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -551,7 +565,7 @@ define! {
|
||||
/// Conversions between this type and the [`std::time::Duration`] are provided, however, please
|
||||
/// note that [`std::time::Duration`] internal representation is optimized for different cases,
|
||||
/// so losing precision is expected during the conversion.
|
||||
Duration = DURATION(f32)
|
||||
Duration: f32 = 0.0
|
||||
}
|
||||
define_ops![
|
||||
Duration [+,-] Duration = Duration,
|
||||
@ -618,6 +632,16 @@ impl const DurationNumberOps for f32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl const DurationNumberOps for i64 {
|
||||
fn ms(self) -> Duration {
|
||||
(self as f32).ms()
|
||||
}
|
||||
|
||||
fn s(self) -> Duration {
|
||||
(self as f32).s()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::time::Duration> for Duration {
|
||||
fn from(duration: std::time::Duration) -> Self {
|
||||
(duration.as_millis() as <DURATION as Variant>::Repr).ms()
|
||||
|
Loading…
Reference in New Issue
Block a user