mirror of
https://github.com/enso-org/enso.git
synced 2024-12-27 22:53:17 +03:00
60 steps per second Physics Simulator (Kinematics, Spring, Air Dragging) (https://github.com/enso-org/ide/pull/91)
* 60 steps per second Physics Simulator (Kinematics, Spring, Air Dragging)
* Style fix
* Review refactoring
* Moved mass to Kinematics
* Linter fixes
* Vertical alignemnt update
* Removing std::mem::forget from physics test
* Physically correct interpolated simulator
* PhysicsSimulator implementation only with one Animator
* Added source commcents
* Safer Physics mod setters
* Removed use of performance.now() in set_time
Original commit: 0596cc5097
This commit is contained in:
parent
df8321d9da
commit
cd95b281e8
47
gui/lib/core/src/animation/animator.rs
Normal file
47
gui/lib/core/src/animation/animator.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use super::ContinuousTimeAnimator;
|
||||
use super::FnAnimation;
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === AnimatorData ===
|
||||
// ====================
|
||||
|
||||
struct AnimatorData {
|
||||
closure : Box<dyn FnMut(f32)>,
|
||||
previous_ms : Option<f32>
|
||||
}
|
||||
|
||||
impl AnimatorData {
|
||||
pub fn new<F:FnAnimation>(f:F) -> Self {
|
||||
let closure = Box::new(f);
|
||||
let previous_ms = None;
|
||||
Self { closure, previous_ms }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Animator ===
|
||||
// ================
|
||||
|
||||
/// This structure runs an animation every frame with the time difference from the last frame as
|
||||
/// its input.
|
||||
pub struct Animator {
|
||||
_continuous_animator: ContinuousTimeAnimator
|
||||
}
|
||||
|
||||
impl Animator {
|
||||
pub fn new<F:FnAnimation>(f:F) -> Self {
|
||||
let mut data = AnimatorData::new(f);
|
||||
let _continuous_animator = ContinuousTimeAnimator::new(move |current_ms| {
|
||||
if let Some(previous_ms) = data.previous_ms {
|
||||
let delta_ms = current_ms - previous_ms;
|
||||
(data.closure)(delta_ms);
|
||||
}
|
||||
data.previous_ms = Some(current_ms);
|
||||
});
|
||||
Self { _continuous_animator }
|
||||
}
|
||||
}
|
64
gui/lib/core/src/animation/continuous_time_animator.rs
Normal file
64
gui/lib/core/src/animation/continuous_time_animator.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::system::web::get_performance;
|
||||
use crate::system::web::animation_frame_loop::AnimationFrameLoop;
|
||||
use super::FnAnimation;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
||||
// ==================================
|
||||
// === ContinuousTimeAnimatorData ===
|
||||
// ==================================
|
||||
|
||||
struct ContinuousTimeAnimatorData {
|
||||
closure : Box<dyn FnMut(f32)>,
|
||||
start_time : f32,
|
||||
current_time : f32
|
||||
}
|
||||
|
||||
impl ContinuousTimeAnimatorData {
|
||||
pub fn new<F:FnAnimation>(f:F) -> Self {
|
||||
let closure = Box::new(f);
|
||||
let start_time = get_performance().expect("Couldn't get performance timer").now() as f32;
|
||||
let current_time = start_time;
|
||||
Self { closure,start_time,current_time }
|
||||
}
|
||||
|
||||
pub fn set_time(&mut self, time:f32) {
|
||||
self.start_time = self.current_time + time;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============================
|
||||
// === ContinuousTimeAnimator ===
|
||||
// ==============================
|
||||
|
||||
/// This structure runs an animation with continuous time as its input.
|
||||
pub struct ContinuousTimeAnimator {
|
||||
_animation_loop : AnimationFrameLoop,
|
||||
data : Rc<RefCell<ContinuousTimeAnimatorData>>
|
||||
}
|
||||
|
||||
impl ContinuousTimeAnimator {
|
||||
pub fn new<F:FnAnimation>(f:F) -> Self {
|
||||
let data = Rc::new(RefCell::new(ContinuousTimeAnimatorData::new(f)));
|
||||
let data_clone = data.clone();
|
||||
let _animation_loop = AnimationFrameLoop::new(move |current_time| {
|
||||
let mut data : &mut ContinuousTimeAnimatorData = &mut data_clone.borrow_mut();
|
||||
(data.closure)(current_time - data.start_time);
|
||||
data.current_time = current_time;
|
||||
});
|
||||
Self { _animation_loop, data }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Setters ===
|
||||
|
||||
impl ContinuousTimeAnimator {
|
||||
pub fn set_time(&mut self, time:f32) {
|
||||
self.data.borrow_mut().set_time(time);
|
||||
}
|
||||
}
|
79
gui/lib/core/src/animation/fixed_step_animator.rs
Normal file
79
gui/lib/core/src/animation/fixed_step_animator.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use super::Animator;
|
||||
use super::FnAnimation;
|
||||
|
||||
use nalgebra::zero;
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === IntervalCounter ===
|
||||
// =======================
|
||||
|
||||
/// This struct counts the intervals in a time period.
|
||||
pub struct IntervalCounter {
|
||||
pub interval_duration : f32,
|
||||
pub accumulated_time : f32
|
||||
}
|
||||
|
||||
impl IntervalCounter {
|
||||
pub fn new(interval_duration:f32) -> Self {
|
||||
let accumulated_time = zero();
|
||||
Self { interval_duration, accumulated_time }
|
||||
}
|
||||
|
||||
/// Adds time to the counter and returns the number of intervals it reached.
|
||||
pub fn add_time(&mut self, time:f32) -> u32 {
|
||||
self.accumulated_time += time;
|
||||
let count = (self.accumulated_time / self.interval_duration) as u32;
|
||||
self.accumulated_time -= count as f32 * self.interval_duration;
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
// =============================
|
||||
// === FixedStepAnimatorData ===
|
||||
// =============================
|
||||
|
||||
struct FixedStepAnimatorData {
|
||||
closure : Box<dyn FnMut(f32)>,
|
||||
counter : IntervalCounter
|
||||
}
|
||||
|
||||
impl FixedStepAnimatorData {
|
||||
pub fn new<F:FnAnimation>(steps_per_second:f32, f:F) -> Self {
|
||||
let closure = Box::new(f);
|
||||
let step_duration = 1000.0 / steps_per_second;
|
||||
let counter = IntervalCounter::new(step_duration);
|
||||
Self { closure,counter }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================
|
||||
// === Animator ===
|
||||
// ================
|
||||
|
||||
/// This structure attempts to run a closure at a fixed time rate.
|
||||
///
|
||||
/// # Internals
|
||||
/// If, for instance, we want to run FnAnimation once per second, it's delta_time
|
||||
/// (FnAnimation(delta_time)) will be 1 second. But keep in mind that if the actual frame takes
|
||||
/// longer, say 2 seconds, FnAnimation will be called twice in the same moment, but its delta_time
|
||||
/// parameter will always be fixed to 1 second.
|
||||
pub struct FixedStepAnimator {
|
||||
_animator: Animator
|
||||
}
|
||||
|
||||
impl FixedStepAnimator {
|
||||
pub fn new<F:FnAnimation>(steps_per_second:f32, f:F) -> Self {
|
||||
let mut data = FixedStepAnimatorData::new(steps_per_second, f);
|
||||
let _animator = Animator::new(move |delta_ms| {
|
||||
let intervals = data.counter.add_time(delta_ms);
|
||||
for _ in 0..intervals {
|
||||
(data.closure)(data.counter.interval_duration);
|
||||
}
|
||||
});
|
||||
Self { _animator }
|
||||
}
|
||||
}
|
35
gui/lib/core/src/animation/mod.rs
Normal file
35
gui/lib/core/src/animation/mod.rs
Normal file
@ -0,0 +1,35 @@
|
||||
// FIXME: Animators structs should get EventLoop as parameter. The whole application should have
|
||||
// only one RequestAnimationFrame loop going on to avoid its overhead.
|
||||
|
||||
pub mod physics;
|
||||
mod continuous_time_animator;
|
||||
mod animator;
|
||||
mod fixed_step_animator;
|
||||
|
||||
pub use continuous_time_animator::ContinuousTimeAnimator;
|
||||
pub use animator::Animator;
|
||||
pub use fixed_step_animator::FixedStepAnimator;
|
||||
pub use fixed_step_animator::IntervalCounter;
|
||||
|
||||
// ===================
|
||||
// === FnAnimation ===
|
||||
// ===================
|
||||
|
||||
pub trait FnAnimation = FnMut(f32) + 'static;
|
||||
|
||||
|
||||
|
||||
// FIXME: The objects in this section needs a better place.
|
||||
// =============
|
||||
// === Utils ===
|
||||
// =============
|
||||
|
||||
use nalgebra::clamp;
|
||||
use std::ops::Mul;
|
||||
use std::ops::Add;
|
||||
|
||||
pub fn linear_interpolation<T>(a:T, b:T, t:f32) -> T
|
||||
where T : Mul<f32, Output = T> + Add<T, Output = T> {
|
||||
let t = clamp(t, 0.0, 1.0);
|
||||
a * (1.0 - t) + b * t
|
||||
}
|
320
gui/lib/core/src/animation/physics.rs
Normal file
320
gui/lib/core/src/animation/physics.rs
Normal file
@ -0,0 +1,320 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::animation::{Animator, IntervalCounter, linear_interpolation};
|
||||
use crate::traits::HasPosition;
|
||||
|
||||
use nalgebra::Vector3;
|
||||
use nalgebra::zero;
|
||||
|
||||
// ====================
|
||||
// === PhysicsForce ===
|
||||
// ====================
|
||||
|
||||
pub trait PhysicsForce {
|
||||
fn force(&self, kinematics:&KinematicsProperties) -> Vector3<f32>;
|
||||
}
|
||||
|
||||
// ======================
|
||||
// === DragProperties ===
|
||||
// ======================
|
||||
|
||||
/// This structure contains air dragging properties.
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct DragProperties {
|
||||
coefficient: f32
|
||||
}
|
||||
|
||||
impl DragProperties {
|
||||
pub fn new(amount:f32) -> Self {
|
||||
Self { coefficient: amount }
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicsForce for DragProperties {
|
||||
fn force(&self, kinematics:&KinematicsProperties) -> Vector3<f32> {
|
||||
let velocity = kinematics.velocity;
|
||||
let speed = velocity.norm();
|
||||
if speed > 0.0 {
|
||||
velocity.normalize() * speed * speed * -0.5 * self.coefficient
|
||||
} else {
|
||||
zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Getters ===
|
||||
|
||||
impl DragProperties {
|
||||
pub fn coefficient(self) -> f32 {
|
||||
self.coefficient
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Setters ===
|
||||
|
||||
impl DragProperties {
|
||||
pub fn set_coefficient(&mut self, coefficient:f32) {
|
||||
self.coefficient = coefficient
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === SpringProperties ===
|
||||
// ========================
|
||||
|
||||
/// This structure contains spring physics properties.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SpringProperties {
|
||||
coefficient : f32,
|
||||
fixed_point : Vector3<f32>
|
||||
}
|
||||
|
||||
impl Default for SpringProperties {
|
||||
fn default() -> Self {
|
||||
Self::new(zero(),zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl SpringProperties {
|
||||
pub fn new(coefficient:f32, fixed_point:Vector3<f32>) -> Self {
|
||||
Self { coefficient,fixed_point }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Getters ===
|
||||
|
||||
impl SpringProperties {
|
||||
pub fn coefficient(&self) -> f32 { self.coefficient }
|
||||
pub fn fixed_point(&self) -> Vector3<f32> { self.fixed_point }
|
||||
}
|
||||
|
||||
impl PhysicsForce for SpringProperties {
|
||||
fn force(&self, kinematics:&KinematicsProperties) -> Vector3<f32> {
|
||||
let delta = self.fixed_point() - kinematics.position();
|
||||
let delta_len = delta.magnitude();
|
||||
if delta_len > 0.0 {
|
||||
let force_val = delta_len * self.coefficient();
|
||||
delta.normalize() * force_val
|
||||
} else {
|
||||
zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Setters ===
|
||||
|
||||
impl SpringProperties {
|
||||
pub fn set_coefficient(&mut self, coefficient:f32) { self.coefficient = coefficient }
|
||||
pub fn set_fixed_point(&mut self, fixed_point:Vector3<f32>) { self.fixed_point = fixed_point }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================
|
||||
// === KinematicProperties ===
|
||||
// ============================
|
||||
|
||||
/// This structure contains kinematics properties.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct KinematicsProperties {
|
||||
position : Vector3<f32>,
|
||||
velocity : Vector3<f32>,
|
||||
acceleration : Vector3<f32>,
|
||||
mass : f32
|
||||
}
|
||||
|
||||
impl Default for KinematicsProperties {
|
||||
fn default() -> Self {
|
||||
Self::new(zero(),zero(),zero(),zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl KinematicsProperties {
|
||||
pub fn new
|
||||
(position:Vector3<f32>, velocity:Vector3<f32>, acceleration:Vector3<f32>, mass:f32) -> Self {
|
||||
Self { position,velocity,acceleration,mass }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Getters ===
|
||||
|
||||
impl KinematicsProperties {
|
||||
pub fn velocity (&self) -> Vector3<f32> { self.velocity }
|
||||
pub fn acceleration(&self) -> Vector3<f32> { self.acceleration }
|
||||
pub fn mass (&self) -> f32 { self.mass }
|
||||
}
|
||||
|
||||
|
||||
// === Setters ===
|
||||
|
||||
impl KinematicsProperties {
|
||||
pub fn set_velocity(&mut self, velocity:Vector3<f32>) {
|
||||
self.velocity = velocity
|
||||
}
|
||||
|
||||
pub fn set_acceleration(&mut self, acceleration:Vector3<f32>) {
|
||||
self.acceleration = acceleration
|
||||
}
|
||||
|
||||
pub fn set_mass(&mut self, mass:f32) {
|
||||
self.mass = mass
|
||||
}
|
||||
}
|
||||
|
||||
impl HasPosition for KinematicsProperties {
|
||||
fn position (&self) -> Vector3<f32> { self.position }
|
||||
fn set_position(&mut self, position:Vector3<f32>) { self.position = position }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============================
|
||||
// === PhysicsPropertiesData ===
|
||||
// =============================
|
||||
|
||||
struct PhysicsPropertiesData {
|
||||
kinematics : KinematicsProperties,
|
||||
spring : SpringProperties,
|
||||
drag : DragProperties
|
||||
}
|
||||
|
||||
impl PhysicsPropertiesData {
|
||||
pub fn new
|
||||
(kinematics: KinematicsProperties, spring:SpringProperties, drag:DragProperties) -> Self {
|
||||
Self { kinematics,spring,drag }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =========================
|
||||
// === PhysicsProperties ===
|
||||
// =========================
|
||||
|
||||
/// A structure including kinematics, drag and spring properties.
|
||||
#[derive(Clone)]
|
||||
pub struct PhysicsProperties {
|
||||
data : Rc<RefCell<PhysicsPropertiesData>>
|
||||
}
|
||||
|
||||
impl PhysicsProperties {
|
||||
pub fn new
|
||||
(kinematics: KinematicsProperties, spring:SpringProperties, drag:DragProperties) -> Self {
|
||||
let data = Rc::new(RefCell::new(PhysicsPropertiesData::new(kinematics, spring, drag)));
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Getters ===
|
||||
|
||||
impl PhysicsProperties {
|
||||
pub fn kinematics(&self) -> KinematicsProperties { self.data.borrow().kinematics }
|
||||
pub fn spring (&self) -> SpringProperties { self.data.borrow().spring }
|
||||
pub fn drag (&self) -> DragProperties { self.data.borrow().drag }
|
||||
}
|
||||
|
||||
|
||||
// === Setters ===
|
||||
|
||||
impl PhysicsProperties {
|
||||
pub fn mod_kinematics<F:FnOnce(&mut KinematicsProperties)>(&mut self, f:F) {
|
||||
let mut kinematics = self.kinematics();
|
||||
f(&mut kinematics);
|
||||
self.set_kinematics(kinematics);
|
||||
}
|
||||
|
||||
pub fn set_kinematics(&mut self, kinematics:KinematicsProperties) {
|
||||
self.data.borrow_mut().kinematics = kinematics;
|
||||
}
|
||||
|
||||
pub fn mod_spring<F:FnOnce(&mut SpringProperties)>(&mut self, f:F) {
|
||||
let mut spring = self.spring();
|
||||
f(&mut spring);
|
||||
self.set_spring(spring);
|
||||
}
|
||||
|
||||
pub fn set_spring(&mut self, spring:SpringProperties) {
|
||||
self.data.borrow_mut().spring = spring;
|
||||
}
|
||||
|
||||
pub fn mod_drag<F:FnOnce(&mut DragProperties)>(&mut self, f:F) {
|
||||
let mut drag = self.drag();
|
||||
f(&mut drag);
|
||||
self.set_drag(drag);
|
||||
}
|
||||
|
||||
pub fn set_drag(&mut self, drag:DragProperties) {
|
||||
self.data.borrow_mut().drag = drag;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === SimulationObject ===
|
||||
// ========================
|
||||
|
||||
pub trait SimulationObject = HasPosition + 'static;
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === PhysicsSimulator ===
|
||||
// ========================
|
||||
|
||||
/// A 60 steps per second physics simulator used to simulate `Properties`.
|
||||
pub struct PhysicsSimulator {
|
||||
_animator : Animator
|
||||
}
|
||||
|
||||
impl PhysicsSimulator {
|
||||
/// Simulates `Properties` on `object`.
|
||||
pub fn new<T:SimulationObject>(mut object:T, mut properties:PhysicsProperties) -> Self {
|
||||
properties.mod_kinematics(|kinematics| { kinematics.set_position(object.position()); });
|
||||
let steps_per_second = 60.0;
|
||||
let step_ms = 1000.0 / steps_per_second;
|
||||
let mut current_position = object.position();
|
||||
let mut next_position = simulate(&mut properties, step_ms);
|
||||
let mut interval_counter = IntervalCounter::new(step_ms);
|
||||
let _animator = Animator::new(move |delta_ms| {
|
||||
let intervals = interval_counter.add_time(delta_ms);
|
||||
for _ in 0..intervals {
|
||||
current_position = next_position;
|
||||
next_position = simulate(&mut properties, step_ms);
|
||||
}
|
||||
|
||||
let transition = interval_counter.accumulated_time / interval_counter.interval_duration;
|
||||
let position = linear_interpolation(current_position, next_position, transition);
|
||||
object.set_position(position);
|
||||
});
|
||||
|
||||
Self { _animator }
|
||||
}
|
||||
}
|
||||
|
||||
/// Simulate the `KinematicProperties`.
|
||||
fn simulate_kinematics(kinematics:&mut KinematicsProperties, force:&Vector3<f32>, dt:f32) {
|
||||
kinematics.set_acceleration(force / kinematics.mass);
|
||||
kinematics.set_velocity(kinematics.velocity() + kinematics.acceleration() * dt);
|
||||
kinematics.set_position(kinematics.position() + kinematics.velocity() * dt);
|
||||
}
|
||||
|
||||
/// Runs a simulation step.
|
||||
fn simulate(properties:&mut PhysicsProperties, delta_ms:f32) -> Vector3<f32> {
|
||||
let spring = properties.spring();
|
||||
let drag = properties.drag();
|
||||
let mut net_force = zero();
|
||||
properties.mod_kinematics(|mut kinematics| {
|
||||
net_force += spring.force(&kinematics);
|
||||
net_force += drag.force(&kinematics);
|
||||
let delta_seconds = delta_ms / 1000.0;
|
||||
simulate_kinematics(&mut kinematics, &net_force, delta_seconds);
|
||||
});
|
||||
properties.kinematics().position()
|
||||
}
|
@ -35,7 +35,7 @@ impl EventLoop {
|
||||
/// Init the event loop.
|
||||
fn init(self) -> Self {
|
||||
let data = Rc::downgrade(&self.rc);
|
||||
let main = move || { data.upgrade().map(|t| t.borrow_mut().run()); };
|
||||
let main = move |_| { data.upgrade().map(|t| t.borrow_mut().run()); };
|
||||
with(self.rc.borrow_mut(), |mut data| {
|
||||
data.main = Some(Closure::new(main));
|
||||
data.run();
|
||||
@ -60,7 +60,7 @@ impl EventLoop {
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug, Default)]
|
||||
pub struct EventLoopData {
|
||||
main : Option<Closure<dyn FnMut()>>,
|
||||
main : Option<Closure<dyn FnMut(f32)>>,
|
||||
callbacks : CallbackRegistry,
|
||||
main_id : i32,
|
||||
}
|
||||
|
@ -6,16 +6,19 @@ use crate::system::web::dyn_into;
|
||||
use crate::system::web::Result;
|
||||
use crate::system::web::Error;
|
||||
use crate::system::web::StyleSetter;
|
||||
use crate::traits::HasPosition;
|
||||
|
||||
use nalgebra::Vector2;
|
||||
use nalgebra::{Vector2, Vector3};
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === HTMLObject ===
|
||||
// ==================
|
||||
|
||||
/// A structure for representing a 3D HTMLElement in a `HTMLScene`.
|
||||
#[derive(Shrinkwrap, Debug)]
|
||||
#[derive(Shrinkwrap, Debug, Clone)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct HTMLObject {
|
||||
#[shrinkwrap(main_field)]
|
||||
@ -24,6 +27,16 @@ pub struct HTMLObject {
|
||||
dimensions : Vector2<f32>,
|
||||
}
|
||||
|
||||
impl HasPosition for HTMLObject {
|
||||
fn position(&self) -> Vector3<f32> {
|
||||
self.object.position()
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position:Vector3<f32>) {
|
||||
self.object.set_position(position)
|
||||
}
|
||||
}
|
||||
|
||||
impl HTMLObject {
|
||||
/// Creates a HTMLObject from element name.
|
||||
pub fn new(dom_name: &str) -> Result<Self> {
|
||||
|
@ -18,6 +18,12 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Js Bindings ===
|
||||
// ===================
|
||||
|
||||
mod js {
|
||||
use super::*;
|
||||
#[wasm_bindgen(module = "/src/display/render/css3d/html/snippets.js")]
|
||||
@ -74,6 +80,8 @@ fn setup_camera_transform
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === HTMLRendererData ===
|
||||
// ========================
|
||||
@ -145,7 +153,7 @@ impl HTMLRenderer {
|
||||
|
||||
/// Renders the `Scene` from `Camera`'s point of view.
|
||||
pub fn render(&self, camera: &mut Camera, scene: &Scene<HTMLObject>) {
|
||||
let trans_cam = camera.transform.to_homogeneous().try_inverse();
|
||||
let trans_cam = camera.transform().to_homogeneous().try_inverse();
|
||||
let trans_cam = trans_cam.expect("Camera's matrix is not invertible.");
|
||||
let trans_cam = trans_cam.map(eps);
|
||||
let trans_cam = invert_y(trans_cam);
|
||||
@ -166,7 +174,7 @@ impl HTMLRenderer {
|
||||
|
||||
let scene : &Scene<HTMLObject> = &scene;
|
||||
for object in &mut scene.into_iter() {
|
||||
let mut transform = object.transform.to_homogeneous();
|
||||
let mut transform = object.transform().to_homogeneous();
|
||||
transform.iter_mut().for_each(|a| *a = eps(*a));
|
||||
|
||||
let parent_node = object.dom.parent_node();
|
||||
|
@ -5,67 +5,77 @@ use crate::display::render::css3d::Transform;
|
||||
use nalgebra::UnitQuaternion;
|
||||
use nalgebra::Vector3;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use crate::traits::HasPosition;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Object ===
|
||||
// ==============
|
||||
|
||||
/// Base structure for representing a 3D object in a `Scene`.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Object {
|
||||
pub transform : Transform
|
||||
transform : Rc<RefCell<Transform>>
|
||||
}
|
||||
|
||||
impl Object {
|
||||
/// Creates a Default Object.
|
||||
pub fn new() -> Object { default() }
|
||||
|
||||
/// Sets the object's position.
|
||||
pub fn set_position(&mut self, x:f32, y:f32, z:f32) {
|
||||
self.transform.set_translation(x, y, z)
|
||||
}
|
||||
|
||||
/// Gets the object's position.
|
||||
pub fn position(&self) -> &Vector3<f32> {
|
||||
self.transform.translation()
|
||||
}
|
||||
/// Gets object's transform.
|
||||
pub fn transform(&self) -> Transform { *self.transform.borrow() }
|
||||
|
||||
/// Sets the object's rotation in YXZ (yaw -> roll -> pitch) order.
|
||||
pub fn set_rotation(&mut self, roll:f32, pitch:f32, yaw:f32) {
|
||||
self.transform.set_rotation(roll, pitch, yaw)
|
||||
self.transform.borrow_mut().set_rotation(roll, pitch, yaw)
|
||||
}
|
||||
|
||||
/// Gets the object's rotation UnitQuaternion.
|
||||
pub fn rotation(&self) -> &UnitQuaternion<f32> {
|
||||
self.transform.rotation()
|
||||
pub fn rotation(&self) -> UnitQuaternion<f32> {
|
||||
*self.transform.borrow().rotation()
|
||||
}
|
||||
|
||||
/// Sets the object's scale.
|
||||
pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
|
||||
self.transform.set_scale(x, y, z);
|
||||
self.transform.borrow_mut().set_scale(x, y, z);
|
||||
}
|
||||
|
||||
/// Gets the object's scale.
|
||||
pub fn scale(&self) -> &Vector3<f32> {
|
||||
self.transform.scale()
|
||||
pub fn scale(&self) -> Vector3<f32> {
|
||||
*self.transform.borrow().scale()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasPosition for Object {
|
||||
fn position(&self) -> Vector3<f32> {
|
||||
*self.transform.borrow().translation()
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position:Vector3<f32>) {
|
||||
self.transform.borrow_mut().set_translation(position)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Object;
|
||||
use crate::traits::HasPosition;
|
||||
use nalgebra::Vector3;
|
||||
use nalgebra::Quaternion;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
#[test]
|
||||
fn set_transform() {
|
||||
use super::Object;
|
||||
use nalgebra::Vector3;
|
||||
use nalgebra::Quaternion;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
let mut object = Object::new();
|
||||
object.set_position(1.0, 2.0, 3.0);
|
||||
object.set_position(Vector3::new(1.0, 2.0, 3.0));
|
||||
object.set_scale(3.0, 2.0, 1.0);
|
||||
object.set_rotation(PI * 2.0, PI, PI / 2.0);
|
||||
|
||||
assert_eq!(*object.position(), Vector3::new(1.0, 2.0, 3.0));
|
||||
assert_eq!(*object.scale(), Vector3::new(3.0, 2.0, 1.0));
|
||||
assert_eq!(object.position(), Vector3::new(1.0, 2.0, 3.0));
|
||||
assert_eq!(object.scale(), Vector3::new(3.0, 2.0, 1.0));
|
||||
|
||||
let expected = Quaternion::new
|
||||
( 0.00000009272586
|
||||
|
@ -3,6 +3,8 @@ use crate::data::opt_vec::*;
|
||||
|
||||
type Index = usize;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Scene ===
|
||||
// =============
|
||||
@ -38,20 +40,24 @@ impl<T> Scene<T> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.objects.is_empty()
|
||||
}
|
||||
|
||||
/// Gets mutable iterator.
|
||||
pub fn iter_mut(&mut self) -> IterMut<'_, T> { self.objects.iter_mut() }
|
||||
|
||||
/// Gets iterator.
|
||||
pub fn iter(&self) -> Iter<'_, T> { self.objects.iter() }
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a Scene<T> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = Iter<'a, T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.objects.into_iter()
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a mut Scene<T> {
|
||||
type Item = &'a mut T;
|
||||
type IntoIter = IterMut<'a, T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
(&mut self.objects).into_iter()
|
||||
}
|
||||
type IntoIter = IterMut<'a, T> ;
|
||||
fn into_iter(self) -> Self::IntoIter { self.iter_mut() }
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ use nalgebra::Quaternion;
|
||||
use nalgebra::UnitQuaternion;
|
||||
use nalgebra::Vector3;
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Utils ===
|
||||
// =============
|
||||
@ -22,12 +24,14 @@ fn from_euler_angles_pry(roll:f32, pitch:f32, yaw:f32) -> UnitQuaternion<f32> {
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === Transform ===
|
||||
// =================
|
||||
|
||||
/// A structure representing 3D Position, Rotation and Scale.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Transform {
|
||||
pub translation : Vector3<f32>,
|
||||
pub rotation : UnitQuaternion<f32>,
|
||||
@ -48,8 +52,8 @@ impl Transform {
|
||||
pub fn identity() -> Self { default() }
|
||||
|
||||
/// Sets Transform's translation.
|
||||
pub fn set_translation(&mut self, x:f32, y:f32, z:f32) {
|
||||
self.translation = Vector3::new(x, y, z);
|
||||
pub fn set_translation(&mut self, translation:Vector3<f32>) {
|
||||
self.translation = translation;
|
||||
}
|
||||
|
||||
/// Gets Transform's translation.
|
||||
@ -121,6 +125,8 @@ impl Transform {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
@ -147,7 +153,7 @@ mod test {
|
||||
use std::f32::consts::PI;
|
||||
|
||||
let mut transform = Transform::identity();
|
||||
transform.set_translation(1.0, 2.0, 3.0);
|
||||
transform.set_translation(Vector3::new(1.0, 2.0, 3.0));
|
||||
transform.set_scale(3.0, 2.0, 1.0);
|
||||
transform.set_rotation(PI * 2.0, PI, PI / 2.0);
|
||||
|
||||
|
@ -26,6 +26,7 @@ pub mod control;
|
||||
pub mod data;
|
||||
pub mod debug;
|
||||
pub mod display;
|
||||
pub mod traits;
|
||||
|
||||
pub use basegl_prelude as prelude;
|
||||
pub mod system {
|
||||
|
8
gui/lib/core/src/traits.rs
Normal file
8
gui/lib/core/src/traits.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use nalgebra::Vector3;
|
||||
|
||||
pub trait HasPosition {
|
||||
/// Gets self's position.
|
||||
fn position(&self) -> Vector3<f32>;
|
||||
/// Sets self's position.
|
||||
fn set_position(&mut self, position:Vector3<f32>);
|
||||
}
|
@ -24,8 +24,10 @@ mod tests {
|
||||
use basegl::display::render::css3d::html::HTMLRenderer;
|
||||
use basegl::system::web::StyleSetter;
|
||||
use basegl::system::web::get_performance;
|
||||
use basegl::traits::HasPosition;
|
||||
use web_test::*;
|
||||
use web_sys::Performance;
|
||||
use nalgebra::Vector3;
|
||||
|
||||
#[web_test(no_container)]
|
||||
fn invalid_container() {
|
||||
@ -44,7 +46,7 @@ mod tests {
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let mut object = HTMLObject::new("div").unwrap();
|
||||
object.set_position(0.0, 0.0, 0.0);
|
||||
object.set_position(Vector3::new(0.0, 0.0, 0.0));
|
||||
object.dom.set_property_or_panic("background-color", "black");
|
||||
object.set_dimensions(100.0, 100.0);
|
||||
scene.add(object);
|
||||
@ -52,7 +54,7 @@ mod tests {
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
// We move the Camera behind the object so we don't see it.
|
||||
camera.set_position(0.0, 0.0, -100.0);
|
||||
camera.set_position(Vector3::new(0.0, 0.0, -100.0));
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
@ -76,7 +78,7 @@ mod tests {
|
||||
let x = (i * axis.0) as f32;
|
||||
let y = (i * axis.1) as f32;
|
||||
let z = (i * axis.2) as f32;
|
||||
object.set_position(x, y, z);
|
||||
object.set_position(Vector3::new(x, y, z));
|
||||
|
||||
// Creates a gradient color based on the axis.
|
||||
let r = (x * 25.5) as u8;
|
||||
@ -105,7 +107,7 @@ mod tests {
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, 29.0);
|
||||
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
@ -125,7 +127,7 @@ mod tests {
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
|
||||
// We move the Camera -29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, -29.0);
|
||||
camera.set_position(Vector3::new(0.0, 0.0, -29.0));
|
||||
// We rotate it 180 degrees so we can see the center of the scene
|
||||
// from behind.
|
||||
camera.set_rotation(0.0, PI, 0.0);
|
||||
@ -150,7 +152,7 @@ mod tests {
|
||||
b.iter(move || {
|
||||
let t = (performance.now() / 1000.0) as f32;
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(t.sin() * 5.0, t.cos() * 5.0, 29.0);
|
||||
camera.set_position(Vector3::new(t.sin() * 5.0, t.cos() * 5.0, 29.0));
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
})
|
||||
@ -174,7 +176,7 @@ mod tests {
|
||||
x += (y * 1.25 + t * 2.50).cos() * 0.5;
|
||||
y += (z * 1.25 + t * 2.00).cos() * 0.5;
|
||||
z += (x * 1.25 + t * 3.25).cos() * 0.5;
|
||||
object.set_position(x * 5.0, y * 5.0, z * 5.0);
|
||||
object.set_position(Vector3::new(x * 5.0, y * 5.0, z * 5.0));
|
||||
|
||||
let faster_t = t * 100.0;
|
||||
let r = (i + 0.0 + faster_t) as u8 % 255;
|
||||
@ -208,7 +210,7 @@ mod tests {
|
||||
.expect("Couldn't get performance obj");
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, 29.0);
|
||||
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
|
||||
|
||||
make_sphere(&mut scene, &performance);
|
||||
|
||||
@ -241,7 +243,7 @@ mod tests {
|
||||
.expect("Couldn't get performance obj");
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, 29.0);
|
||||
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
|
||||
|
||||
b.iter(move || {
|
||||
make_sphere(&mut scene, &performance);
|
||||
|
75
gui/lib/core/tests/physics_simulator.rs
Normal file
75
gui/lib/core/tests/physics_simulator.rs
Normal file
@ -0,0 +1,75 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use web_test::web_configure;
|
||||
web_configure!(run_in_browser);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use basegl::system::web::StyleSetter;
|
||||
use basegl::animation::physics::DragProperties;
|
||||
use basegl::animation::physics::SpringProperties;
|
||||
use basegl::animation::physics::KinematicsProperties;
|
||||
use basegl::animation::FixedStepAnimator;
|
||||
use basegl::animation::physics::PhysicsSimulator;
|
||||
use basegl::animation::physics::PhysicsProperties;
|
||||
use basegl::display::render::css3d::html::HTMLRenderer;
|
||||
use basegl::display::render::css3d::html::HTMLObject;
|
||||
use basegl::display::render::css3d::Scene;
|
||||
use basegl::display::render::css3d::Camera;
|
||||
use basegl::traits::HasPosition;
|
||||
use web_test::*;
|
||||
use nalgebra::{zero, Vector3};
|
||||
use js_sys::Math::random;
|
||||
|
||||
#[web_bench]
|
||||
fn simulator(b : &mut Bencher) {
|
||||
let renderer = HTMLRenderer::new("simulator").expect("Renderer couldn't be created");
|
||||
renderer.container.dom.set_property_or_panic("background-color", "black");
|
||||
|
||||
let mut scene : Scene<HTMLObject> = Scene::new();
|
||||
|
||||
let mut target = HTMLObject::new("div").unwrap();
|
||||
target.set_dimensions(1.0, 1.0);
|
||||
target.dom.set_property_or_panic("background-color", "green");
|
||||
scene.add(target.clone());
|
||||
|
||||
let mut object = HTMLObject::new("div").unwrap();
|
||||
object.set_dimensions(1.0, 1.0);
|
||||
object.dom.set_property_or_panic("background-color", "red");
|
||||
scene.add(object.clone());
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
|
||||
|
||||
let mass = 2.0;
|
||||
let kinematics = KinematicsProperties::new(zero(), zero(), zero(), mass);
|
||||
let coefficient = 10.0;
|
||||
let fixed_point = zero();
|
||||
let spring = SpringProperties::new(coefficient, fixed_point);
|
||||
let drag = DragProperties::new(0.8);
|
||||
let mut properties = PhysicsProperties::new(kinematics, spring, drag);
|
||||
let simulator = PhysicsSimulator::new(object, properties.clone());
|
||||
|
||||
// Updates spring's fixed point every two seconds.
|
||||
let every = 2.0;
|
||||
let animator = FixedStepAnimator::new(1.0 / every, move |_| {
|
||||
let x = 32.0 * (random() - 0.5) as f32;
|
||||
let y = 24.0 * (random() - 0.5) as f32;
|
||||
let z = 0.0;
|
||||
let position = Vector3::new(x, y, z);
|
||||
properties.mod_spring(|spring| spring.set_fixed_point(position));
|
||||
target.set_position(position);
|
||||
});
|
||||
|
||||
b.iter(move || {
|
||||
let _keep_alive = &simulator;
|
||||
let _keep_alive = &animator;
|
||||
renderer.render(&mut camera, &scene);
|
||||
});
|
||||
}
|
||||
}
|
@ -5,6 +5,14 @@ use std::cell::RefCell;
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
|
||||
|
||||
// =======================
|
||||
// === FnAnimationLoop ===
|
||||
// =======================
|
||||
|
||||
pub trait FnAnimationLoop = FnMut(f32) + 'static;
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === AnimationFrameData ===
|
||||
// ==========================
|
||||
@ -19,13 +27,14 @@ pub struct AnimationFrameLoop {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==========================
|
||||
// === AnimationFrameLoop ===
|
||||
// ==========================
|
||||
|
||||
impl AnimationFrameLoop {
|
||||
pub fn new(mut func:Box<dyn FnMut()>) -> Self {
|
||||
let nop_func = Box::new(|| ()) as Box<dyn FnMut()>;
|
||||
pub fn new<F:FnAnimationLoop>(mut f:F) -> Self {
|
||||
let nop_func = Box::new(|_| ()) as Box<dyn FnMut(f32)>;
|
||||
let nop_closure = Closure::once(nop_func);
|
||||
let callback = Rc::new(RefCell::new(nop_closure));
|
||||
let run = true;
|
||||
@ -33,13 +42,13 @@ impl AnimationFrameLoop {
|
||||
let callback_clone = callback.clone();
|
||||
let data_clone = data.clone();
|
||||
|
||||
*callback.borrow_mut() = Closure::wrap(Box::new(move || {
|
||||
*callback.borrow_mut() = Closure::wrap(Box::new(move |delta_time| {
|
||||
if data_clone.borrow().run {
|
||||
func();
|
||||
f(delta_time);
|
||||
let clb = &callback_clone.borrow();
|
||||
request_animation_frame(&clb).expect("Request Animation Frame");
|
||||
}
|
||||
}) as Box<dyn FnMut()>);
|
||||
}) as Box<dyn FnMut(f32)>);
|
||||
request_animation_frame(&callback.borrow()).unwrap();
|
||||
|
||||
let forget = false;
|
@ -1,8 +1,7 @@
|
||||
#![feature(trait_alias)]
|
||||
|
||||
pub mod resize_observer;
|
||||
mod animationframeloop;
|
||||
pub use animationframeloop::AnimationFrameLoop;
|
||||
pub mod animation_frame_loop;
|
||||
|
||||
use basegl_prelude::*;
|
||||
|
||||
@ -113,24 +112,24 @@ impl Logger {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl Logger {
|
||||
pub fn trace<M: LogMsg>(&self, msg: M) {
|
||||
pub fn trace<M: LogMsg>(&self, _msg: M) {
|
||||
// console::debug_1(&self.format(msg));
|
||||
}
|
||||
|
||||
pub fn info<M: LogMsg>(&self, msg: M) {
|
||||
pub fn info<M: LogMsg>(&self, _msg: M) {
|
||||
// console::group_1(&self.format(msg));
|
||||
// console::group_end();
|
||||
}
|
||||
|
||||
pub fn warning<M: LogMsg>(&self, msg: M) {
|
||||
pub fn warning<M: LogMsg>(&self, _msg: M) {
|
||||
// console::warn_1(&self.format(msg));
|
||||
}
|
||||
|
||||
pub fn error<M: LogMsg>(&self, msg: M) {
|
||||
pub fn error<M: LogMsg>(&self, _msg: M) {
|
||||
// console::error_1(&self.format(msg));
|
||||
}
|
||||
|
||||
pub fn group_begin<M: LogMsg>(&self, msg: M) {
|
||||
pub fn group_begin<M: LogMsg>(&self, _msg: M) {
|
||||
// console::group_1(&self.format(msg));
|
||||
}
|
||||
|
||||
@ -232,7 +231,7 @@ pub fn get_webgl2_context
|
||||
context.dyn_into().map_err(|_| no_webgl())
|
||||
}
|
||||
|
||||
pub fn request_animation_frame(f:&Closure<dyn FnMut()>) -> Result<i32> {
|
||||
pub fn request_animation_frame(f:&Closure<dyn FnMut(f32)>) -> Result<i32> {
|
||||
let req = window()?.request_animation_frame(f.as_ref().unchecked_ref());
|
||||
req.map_err(|_| Error::missing("requestAnimationFrame"))
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::prelude::*;
|
||||
|
||||
use super::BenchContainer;
|
||||
pub use crate::system::web::get_performance;
|
||||
pub use crate::system::web::AnimationFrameLoop;
|
||||
pub use crate::system::web::animation_frame_loop::AnimationFrameLoop;
|
||||
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
@ -10,6 +10,7 @@ use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === BencherCell ===
|
||||
// ===================
|
||||
@ -46,6 +47,7 @@ impl BencherCell {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === BencherData ===
|
||||
// ===================
|
||||
@ -66,7 +68,7 @@ impl BencherData {
|
||||
let data_clone = self.clone();
|
||||
let performance = get_performance().expect("Performance object");
|
||||
let mut t0 = performance.now();
|
||||
let anim_loop = AnimationFrameLoop::new(Box::new(move || {
|
||||
let anim_loop = AnimationFrameLoop::new(Box::new(move |_| {
|
||||
let mut data = data_clone.borrow_mut();
|
||||
|
||||
(&mut data.func)();
|
||||
@ -91,6 +93,8 @@ impl BencherData {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Bencher ===
|
||||
// ===============
|
||||
|
Loading…
Reference in New Issue
Block a user