use std::{sync::Arc, time::Instant};
use amethyst_core as core;
use amethyst_core::{
specs::prelude::{DispatcherBuilder, Read, Resources, System, Write},
SystemBundle, Time,
};
use crate::{Asset, Format, FormatValue, Loader, Result, Source};
#[derive(Default)]
pub struct HotReloadBundle {
strategy: HotReloadStrategy,
}
impl HotReloadBundle {
pub fn new(strategy: HotReloadStrategy) -> Self {
HotReloadBundle { strategy }
}
}
impl<'a, 'b> SystemBundle<'a, 'b> for HotReloadBundle {
fn build(self, dispatcher: &mut DispatcherBuilder<'a, 'b>) -> core::Result<()> {
dispatcher.add(HotReloadSystem::new(self.strategy), "hot_reload", &[]);
Ok(())
}
}
#[derive(Clone)]
pub struct HotReloadStrategy {
inner: HotReloadStrategyInner,
}
impl HotReloadStrategy {
pub fn every(n: u8) -> Self {
use std::u64::MAX;
HotReloadStrategy {
inner: HotReloadStrategyInner::Every {
interval: n,
last: Instant::now(),
frame_number: MAX,
},
}
}
pub fn when_triggered() -> Self {
use std::u64::MAX;
HotReloadStrategy {
inner: HotReloadStrategyInner::Trigger {
triggered: false,
frame_number: MAX,
},
}
}
pub fn never() -> Self {
HotReloadStrategy {
inner: HotReloadStrategyInner::Never,
}
}
pub fn trigger(&mut self) {
if let HotReloadStrategyInner::Trigger {
ref mut triggered, ..
} = self.inner
{
*triggered = true;
}
}
pub(crate) fn needs_reload(&self, current_frame: u64) -> bool {
match self.inner {
HotReloadStrategyInner::Every { frame_number, .. } => frame_number == current_frame,
HotReloadStrategyInner::Trigger { frame_number, .. } => frame_number == current_frame,
HotReloadStrategyInner::Never => false,
}
}
}
impl Default for HotReloadStrategy {
fn default() -> Self {
HotReloadStrategy::every(1)
}
}
#[derive(Clone)]
enum HotReloadStrategyInner {
Every {
interval: u8,
last: Instant,
frame_number: u64,
},
Trigger {
triggered: bool,
frame_number: u64,
},
Never,
}
pub struct HotReloadSystem {
initial_strategy: HotReloadStrategy,
}
impl HotReloadSystem {
pub fn new(strategy: HotReloadStrategy) -> Self {
HotReloadSystem {
initial_strategy: strategy,
}
}
}
impl<'a> System<'a> for HotReloadSystem {
type SystemData = (Read<'a, Time>, Write<'a, HotReloadStrategy>);
fn run(&mut self, (time, mut strategy): Self::SystemData) {
match strategy.inner {
HotReloadStrategyInner::Trigger {
ref mut triggered,
ref mut frame_number,
} => {
if *triggered {
*frame_number = time.frame_number() + 1;
}
*triggered = false;
}
HotReloadStrategyInner::Every {
interval,
ref mut last,
ref mut frame_number,
} => {
if last.elapsed().as_secs() > u64::from(interval) {
*frame_number = time.frame_number() + 1;
*last = Instant::now();
}
}
HotReloadStrategyInner::Never => {}
}
}
fn setup(&mut self, res: &mut Resources) {
use amethyst_core::specs::prelude::SystemData;
Self::SystemData::setup(res);
res.insert(self.initial_strategy.clone());
res.fetch_mut::<Loader>().set_hot_reload(true);
}
}
pub trait Reload<A: Asset>: ReloadClone<A> + Send + Sync + 'static {
fn needs_reload(&self) -> bool;
fn name(&self) -> String;
fn format(&self) -> &'static str;
fn reload(self: Box<Self>) -> Result<FormatValue<A>>;
}
pub trait ReloadClone<A> {
fn cloned(&self) -> Box<dyn Reload<A>>;
}
impl<A, T> ReloadClone<A> for T
where
A: Asset,
T: Clone + Reload<A>,
{
fn cloned(&self) -> Box<dyn Reload<A>> {
Box::new(self.clone())
}
}
impl<A: Asset> Clone for Box<dyn Reload<A>> {
fn clone(&self) -> Self {
self.cloned()
}
}
pub struct SingleFile<A: Asset, F: Format<A>> {
format: F,
modified: u64,
options: F::Options,
path: String,
source: Arc<dyn Source>,
}
impl<A: Asset, F: Format<A>> SingleFile<A, F> {
pub fn new(
format: F,
modified: u64,
options: F::Options,
path: String,
source: Arc<dyn Source>,
) -> Self {
SingleFile {
format,
modified,
options,
path,
source,
}
}
}
impl<A, F> Clone for SingleFile<A, F>
where
A: Asset,
F: Clone + Format<A>,
F::Options: Clone,
{
fn clone(&self) -> Self {
SingleFile {
format: self.format.clone(),
modified: self.modified,
options: self.options.clone(),
path: self.path.clone(),
source: self.source.clone(),
}
}
}
impl<A, F> Reload<A> for SingleFile<A, F>
where
A: Asset,
F: Clone + Format<A> + Sync,
<F as Format<A>>::Options: Clone + Sync,
{
fn needs_reload(&self) -> bool {
self.modified != 0 && (self.source.modified(&self.path).unwrap_or(0) > self.modified)
}
fn name(&self) -> String {
self.path.clone()
}
fn format(&self) -> &'static str {
F::NAME
}
fn reload(self: Box<Self>) -> Result<FormatValue<A>> {
#[cfg(feature = "profiler")]
profile_scope!("reload_single_file");
let this: SingleFile<_, _> = *self;
let SingleFile {
format,
path,
source,
options,
..
} = this;
format.import(path, source, options, true)
}
}