Revert "Revert "Fixed rate animation loop fixes"" (#3399)

This commit is contained in:
Wojciech Daniło 2022-04-14 19:58:53 +02:00 committed by GitHub
parent 998d078b9a
commit fbe28db1d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 408 additions and 150 deletions

9
Cargo.lock generated
View File

@ -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]]

View File

@ -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 ===
// =================

View File

@ -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));
}
}

View File

@ -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::*;

View File

@ -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)]

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -107,7 +107,6 @@ use std::error;
use std::fmt;
// ==============
// === Export ===
// ==============

View File

@ -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" }

View File

@ -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()