#[doc(hidden)]
#[macro_export]
macro_rules! update_env {
{} => {
use sketchbook::derive_sketch;
use macro_rules_attribute::apply;
use sketchbook::aspects::update::SketchExt;
mod single_aspect {
use sketchbook::aspects::update;
use sketchbook::aspects::update::*;
sketchbook::env_for_aspect!(update);
sketchbook::compose! {
pub enum Events {
#[part]
Aspect(update::Event),
}
}
sketchbook::compose! {
#[derive(Default)]
pub struct Page {
#[part]
pub aspect: update::EnvData<EnvSpecificMarker>,
}
}
impl update::EnvSpecific for EnvSpecificMarker {
type Time = i16;
type Duration = i16;
type Num = f32;
fn default_update_rate() -> Self::Num {
30.0
}
fn zero_duration() -> Self::Duration {
0
}
fn duration_between(start: &Self::Time, end: &Self::Time) -> Self::Duration {
end - start
}
}
impl update::EnvPageExt<EnvSpecificMarker> for Page {
fn get_time(&mut self) -> update::TimeFor<EnvSpecificMarker> {
0
}
}
}
}
}
use core::marker::PhantomData;
use crate::{compose::AsPart, Environment, PageOf, Sketch};
pub trait Handlers
where
Self: Sketch,
Self::Env: AssociatedEnvSpecificMarker,
{
#[allow(unused_variables)]
#[inline]
fn update(&mut self, delta_t: DurationFor<SpecificallyFor<Self::Env>>) {
}
}
pub trait SketchExt<M>
where
Self: AsPart<EnvData<M>>,
M: EnvSpecific,
{
#[inline]
fn stop(&mut self) {
self.as_part_mut().should_stop = true;
}
#[inline]
fn pause(&mut self) {
self.as_part_mut().is_paused = true;
}
#[inline]
fn resume(&mut self) {
self.as_part_mut().is_paused = false;
}
#[inline]
fn is_paused(&self) -> bool {
self.as_part().is_paused
}
#[inline]
fn update_rate<R: crate::real::ToReal<M::Num>>(&mut self, rate: R) {
self.as_part_mut().update_rate = rate.to_real();
}
}
impl<M, T> SketchExt<M> for T
where
Self: AsPart<EnvData<M>>,
M: EnvSpecific,
{
}
pub trait RunHandlers
where
Self: Sketch + Handlers,
Self::Env: AssociatedEnvSpecificMarker + Environment,
PageOf<Self::Env>:
EnvPageExt<SpecificallyFor<Self::Env>> + AsPart<EnvData<SpecificallyFor<Self::Env>>>,
{
fn run_handlers(&mut self, event: &Event) {
match event {
Event::Update => {
let page = self.get_page_mut();
let now = page.get_time();
let delta_t = if let Some(last_time) = &page.as_part().last_time {
SpecificallyFor::<Self::Env>::duration_between(last_time, &now)
} else {
SpecificallyFor::<Self::Env>::zero_duration()
};
self.update(delta_t);
self.get_page_mut().as_part_mut().last_time = Some(now);
}
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Event {
Update,
}
pub trait AssociatedEnvSpecificMarker {
type EnvSpecificMarker: EnvSpecific;
}
pub trait EnvSpecific {
type Time;
type Duration;
type Num;
fn default_update_rate() -> Self::Num;
fn zero_duration() -> Self::Duration;
fn duration_between(start: &Self::Time, end: &Self::Time) -> Self::Duration;
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "M::Num: serde::Serialize + serde::de::DeserializeOwned, M::Time: serde::Serialize + serde::de::DeserializeOwned"))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct EnvData<M>
where
M: EnvSpecific,
{
marker: PhantomData<M>,
update_rate: M::Num,
is_paused: bool,
should_stop: bool,
last_time: Option<M::Time>,
}
impl<M> EnvData<M>
where
M: EnvSpecific,
{
#[inline]
pub fn should_stop(&self) -> bool {
self.should_stop
}
#[inline]
pub fn update_rate(&self) -> &M::Num {
&self.update_rate
}
#[inline]
pub fn stop(&mut self) {
self.should_stop = true;
}
}
impl<M> Default for EnvData<M>
where
M: EnvSpecific,
{
#[inline]
fn default() -> Self {
Self {
marker: PhantomData,
update_rate: M::default_update_rate(),
is_paused: false,
should_stop: false,
last_time: None,
}
}
}
pub trait EnvPageExt<M>
where
M: EnvSpecific,
{
fn get_time(&mut self) -> M::Time;
}
pub type TimeFor<T> = <T as EnvSpecific>::Time;
pub type DurationFor<T> = <T as EnvSpecific>::Duration;
pub type NumFor<T> = <T as EnvSpecific>::Num;
pub type SpecificallyFor<T> = <T as AssociatedEnvSpecificMarker>::EnvSpecificMarker;
#[cfg(feature = "std")]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct UpdateUsingInstant {
last_update: Option<std::time::Instant>,
}
#[cfg(feature = "std")]
impl UpdateUsingInstant {
pub fn should_update(&mut self, update_rate: f32) -> bool {
let now = std::time::Instant::now();
if let Some(last_update) = self.last_update {
if now.duration_since(last_update).as_secs_f32() >= 1. / update_rate {
self.last_update = Some(now);
true
} else {
false
}
} else {
self.last_update = Some(now);
true
}
}
}
#[test]
fn test_update() {
use crate::aspects::update;
use crate::env::*;
use crate::*;
mod single_aspect {
use crate::aspects::update;
crate::env_for_aspect!(update);
crate::compose! {
pub enum Events {
#[part]
Aspect(super::Event),
}
}
crate::compose! {
#[derive(Default)]
pub struct Page {
#[part]
pub aspect: super::EnvData<EnvSpecificMarker>,
pub time: i16,
}
}
impl update::EnvSpecific for EnvSpecificMarker {
type Time = i16;
type Duration = i16;
type Num = f32;
fn default_update_rate() -> Self::Num {
30.0
}
fn zero_duration() -> Self::Duration {
0
}
fn duration_between(start: &Self::Time, end: &Self::Time) -> Self::Duration {
end - start
}
}
impl update::EnvPageExt<EnvSpecificMarker> for Page {
fn get_time(&mut self) -> update::TimeFor<EnvSpecificMarker> {
self.time
}
}
}
derive_sketch! {
#[sketch(
env = single_aspect,
aspects = (update),
)]
struct App {
#[page]
page: single_aspect::Page,
updated: bool,
}
}
impl Setup for App {
fn setup(page: single_aspect::Page, _: ()) -> Self {
App {
page,
updated: false,
}
}
}
impl Handlers for App {
fn update(&mut self, delta_t: i16) {
if self.updated {
assert_eq!(delta_t, 123);
} else {
assert_eq!(delta_t, 0);
self.updated = true;
}
}
}
let mut mill = single_aspect::Mill;
let mut app = App::setup(mill.new_page(), ());
app.handle_environment_event(&single_aspect::Events::Aspect(update::Event::Update));
assert!(app.updated);
app.page.time = 123;
app.handle_environment_event(&single_aspect::Events::Aspect(update::Event::Update));
assert_eq!(app.page.aspect.update_rate, 30.0);
assert!(!app.page.aspect.is_paused);
assert!(!app.page.aspect.should_stop);
assert_eq!(app.page.aspect.last_time, Some(123));
app.stop();
assert!(app.page.aspect.should_stop);
app.pause();
assert!(app.page.aspect.is_paused);
assert!(app.is_paused());
app.resume();
assert!(!app.page.aspect.is_paused);
assert!(!app.is_paused());
app.update_rate(10.0_f64);
assert_eq!(app.page.aspect.update_rate, 10.0);
}
#[cfg(feature = "std")]
#[test]
fn test_update_instant_impl() {
let mut update_impl = UpdateUsingInstant::default();
let rate = 1_000_000.0;
assert!(update_impl.should_update(rate));
assert!(!update_impl.should_update(rate));
std::thread::sleep(std::time::Duration::from_nanos(10));
assert!(update_impl.should_update(rate));
assert!(!update_impl.should_update(rate));
std::thread::sleep(std::time::Duration::from_nanos(10));
assert!(update_impl.should_update(rate));
assert!(!update_impl.should_update(rate));
}