diff --git a/CHANGELOG.md b/CHANGELOG.md index 96bfa7e68d..9b5396a712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Next Release +
![Bug Fixes](/docs/assets/tags/bug_fixes.svg) + +#### Visual Environment + +- [Fixed freezing after inactivity.][1776] When the IDE window was minimized or + covered by other windows or invisible for any other reason for a duration + around one minute or longer then it would often be frozen for some seconds on + return. Now it is possible to interact with the IDE instantly, no matter how + long it had been inactive. + +
+ +[1776]: https://github.com/enso-org/ide/pull/1776 + # Enso 2.0.0-alpha.17 (2021-09-23)
![Bug Fixes](/docs/assets/tags/bug_fixes.svg) @@ -152,11 +166,9 @@ these updates be shipped in a stable release before the end of the year. #### Visual Environment -- [Fixed a bug where edited node expression was sometimes altered.][1743]. When +- [Fixed a bug where edited node expression was sometimes altered.][1743] When editing node expression, the changes were occasionally reverted, or the - grayed-out parameter names were added to the actual expression. - -
+ grayed-out parameter names were added to the actual expression.
[1700]: https://github.com/enso-org/ide/pull/1700 [1742]: https://github.com/enso-org/ide/pull/1742 diff --git a/src/rust/ensogl/lib/core/src/animation/physics/inertia.rs b/src/rust/ensogl/lib/core/src/animation/physics/inertia.rs index 354b524168..16fc6c3fcb 100644 --- a/src/rust/ensogl/lib/core/src/animation/physics/inertia.rs +++ b/src/rust/ensogl/lib/core/src/animation/physics/inertia.rs @@ -220,14 +220,30 @@ impl Thresholds { /// ``` #[derive(Clone,Copy,Debug,Default)] pub struct SimulationData { - value : T, - target_value : T, - velocity : T, - mass : Mass, - spring : Spring, - drag : Drag, - thresholds : Thresholds, - active : bool, + // We store the current value as an offset from the target rather than an absolute value. This + // reduces numerical errors when animating floating point numbers: The offset will become very + // small towards the end of the animation. Small floating point numbers offer higher precision + // than large ones, because more digits can be used behind the point, for the fractional part of + // the number. This higher precision helps us to avoid non-termination that could otherwise + // happen due to rounding errors in our `step` function. + // + // For example: The precision of `f32` values is so low that we can only represent every second + // integer above 16 777 216. If we simulate values that large and represented the simulation's + // state by its current total value then this internal state would have to jump over those gaps. + // The animation would either become to fast (if we rounded the steps up) or slow down too early + // (if we rounded the steps down). Generally, it would be difficult to handle the rounding + // errors gracefully. By representing the state as an offset, we achieve the highest possible + // precision as the animation approaches its target. Large rounding errors might only happen + // when the simulation is still far away from the target. But in those situations, high + // precision is not as important. + offset_from_target : T, + target_value : T, + velocity : T, + mass : Mass, + spring : Spring, + drag : Drag, + thresholds : Thresholds, + active : bool, } impl SimulationData { @@ -240,29 +256,26 @@ impl SimulationData { fn step(&mut self, delta_seconds:f32) { if self.active { let velocity = self.velocity.magnitude(); - let distance = (self.value + self.target_value * -1.0).magnitude(); + let distance = self.offset_from_target.magnitude(); let snap_velocity = velocity < self.thresholds.speed; let snap_distance = distance < self.thresholds.distance; let should_snap = snap_velocity && snap_distance; - if should_snap { - self.value = self.target_value; - self.velocity = default(); - self.active = false; + if should_snap || distance.is_nan() { + self.offset_from_target = default(); + self.velocity = default(); + self.active = false; } else { - let force = self.spring_force() + self.drag_force(); - let acceleration = force * (1.0 / self.mass.value); - self.velocity = self.velocity + acceleration * delta_seconds; - self.value = self.value + self.velocity * delta_seconds; + let force = self.spring_force() + self.drag_force(); + let acceleration = force * (1.0 / self.mass.value); + self.velocity = self.velocity + acceleration * delta_seconds; + self.offset_from_target = self.offset_from_target + self.velocity * delta_seconds; } } } /// Compute spring force. fn spring_force(&self) -> T { - let value_delta = self.target_value + self.value * -1.0; - let spring_stretch = value_delta.magnitude(); - let coefficient = spring_stretch * self.spring.value; - value_delta.normalize() * coefficient + self.offset_from_target * -self.spring.value } /// Compute air drag force. Please note that this is physically incorrect. Read the docs of @@ -287,7 +300,7 @@ impl SimulationData { #[allow(missing_docs)] impl SimulationData { - pub fn value (&self) -> T { self.value } + pub fn value (&self) -> T { self.target_value + self.offset_from_target } pub fn target_value (&self) -> T { self.target_value } pub fn velocity (&self) -> T { self.velocity } pub fn mass (&self) -> Mass { self.mass } @@ -310,12 +323,14 @@ impl SimulationData { pub fn set_value(&mut self, value:T) { self.active = true; - self.value = value; + self.offset_from_target = value + self.target_value * -1.0; } pub fn set_target_value(&mut self, target_value:T) { self.active = true; + let old_target_value = self.target_value; self.target_value = target_value; + self.offset_from_target = old_target_value + self.offset_from_target + target_value * -1.0; } pub fn update_valueT>(&mut self, f:F) { @@ -350,9 +365,9 @@ impl SimulationData { /// Stop the animator and set it to the target value. pub fn skip(&mut self) { - self.active = false; - self.value = self.target_value; - self.velocity = default(); + self.active = false; + self.offset_from_target = default(); + self.velocity = default(); } } @@ -723,3 +738,31 @@ impl Default for EndStatus { Self::Normal } } + +#[cfg(test)] +mod tests { + use super::*; + + /// We test that simulations with target value `f32::NaN` terminate and that their final value + /// is in fact NaN. + #[test] + fn animation_to_nan() { + let mut data = SimulationData::::new(); + data.set_value(0.0); + data.set_target_value(f32::NAN); + data.step(1.0); + assert!(data.value().is_nan()); + assert!(!data.active); + } + + /// We test that simulations with start value `f32::NaN` terminate and reach their target. + #[test] + fn animation_from_nan() { + let mut data = SimulationData::::new(); + data.set_value(f32::NAN); + data.set_target_value(0.0); + data.step(1.0); + assert_eq!(data.value(),0.0); + assert!(!data.active); + } +} diff --git a/src/rust/ide/view/graph-editor/src/component/node/profiling.rs b/src/rust/ide/view/graph-editor/src/component/node/profiling.rs index bc74d5a10d..15673f0362 100644 --- a/src/rust/ide/view/graph-editor/src/component/node/profiling.rs +++ b/src/rust/ide/view/graph-editor/src/component/node/profiling.rs @@ -133,7 +133,7 @@ impl Status { }; let duration_delta = max_global_duration - min_global_duration; let hue_delta = theme.max_time_hue - theme.min_time_hue; - let relative_duration = if duration_delta != 0.0 { + let relative_duration = if duration_delta != 0.0 && !duration_delta.is_nan() { (duration - min_global_duration) / duration_delta } else { 0.0 diff --git a/src/rust/ide/view/graph-editor/src/profiling.rs b/src/rust/ide/view/graph-editor/src/profiling.rs index 5056c96f4c..6304b1d0ff 100644 --- a/src/rust/ide/view/graph-editor/src/profiling.rs +++ b/src/rust/ide/view/graph-editor/src/profiling.rs @@ -88,13 +88,16 @@ impl Statuses { frp.source.max_duration <+ min_and_max._1().on_change(); } + frp.source.min_duration.emit(Self::min_and_max(durations.borrow().deref()).0); + frp.source.max_duration.emit(Self::min_and_max(durations.borrow().deref()).1); + Self {frp,durations} } fn min_and_max(durations:&BiBTreeMap>) -> (f32,f32) { let mut durations = durations.right_values().copied(); - let min = durations.next().map(OrderedFloat::into_inner).unwrap_or(std::f32::INFINITY); + let min = durations.next().map(OrderedFloat::into_inner).unwrap_or(f32::INFINITY); let max = durations.last().map(OrderedFloat::into_inner).unwrap_or(0.0); (min, max) }