use std::hash::Hash;
use std::sync::Arc;
use crate::{Animation, Duration, FrameRate, Node, Point, Random, Rect, Size, Time};
mod cache;
mod partition;
#[cfg(test)]
mod tests;
pub use cache::SceneCache;
pub use partition::{CacheLimits, CachePartition, RenderCache};
#[derive(Clone, Copy)]
pub struct SceneContext<'a> {
quality: crate::RenderQuality,
time: Time,
global_time: Time,
clip_duration: Option<Duration>,
frame_rate: FrameRate,
size: Size,
nested_scene: bool,
assets: Option<&'a crate::Assets>,
cache: &'a SceneCache,
diagnostics: Option<&'a crate::RequestDiagnostics>,
sounds: Option<&'a std::sync::Mutex<Vec<crate::AudioClip>>>,
}
impl<'a> SceneContext<'a> {
pub(crate) fn collecting_sounds(
mut self,
sounds: &'a std::sync::Mutex<Vec<crate::AudioClip>>,
) -> Self {
self.sounds = Some(sounds);
self
}
pub fn sound(&self, clip: crate::AudioClip) -> crate::Result {
if let Some(sounds) = self.sounds {
let mut sounds = sounds.lock().map_err(|_| {
crate::ValidationError::invalid(
"scene_audio",
"lock",
"Sound declaration lock is poisoned",
)
})?;
if sounds.len() >= 256 {
return Err(crate::ValidationError::invalid(
"scene_audio",
"count",
"At most 256 local sound declarations",
)
.into());
}
if let Some(clip) = clip.relocated(
self.global_time - self.time,
self.clip_duration.unwrap_or(Time::from_seconds(86400.0)),
)? {
sounds.push(clip);
}
}
Ok(())
}
pub fn new(time: Time, frame_rate: FrameRate, size: Size, cache: &'a SceneCache) -> Self {
Self::try_new(time, frame_rate, size, cache)
.unwrap_or_else(|error| panic!("{}", error.message))
}
pub fn try_new(
time: Time,
frame_rate: FrameRate,
size: Size,
cache: &'a SceneCache,
) -> Result<Self, crate::ValidationError> {
if !size.width.is_finite()
|| size.width <= 0.0
|| !size.height.is_finite()
|| size.height <= 0.0
{
return Err(crate::ValidationError::invalid(
"scene_context",
"size",
"scene design dimensions must be finite and positive",
));
}
Ok(Self {
quality: crate::RenderQuality::Final,
time,
global_time: time,
clip_duration: None,
frame_rate,
size,
nested_scene: false,
assets: None,
cache,
diagnostics: None,
sounds: None,
})
}
pub fn quality(mut self, quality: crate::RenderQuality) -> Self {
self.quality = quality;
self
}
pub fn render_quality(&self) -> crate::RenderQuality {
self.quality
}
pub fn assets(&self) -> &'a crate::Assets {
self.assets.unwrap_or(&self.cache.empty_assets)
}
pub fn with_assets(mut self, assets: &'a crate::Assets) -> Self {
self.assets = Some(assets);
self
}
pub fn with_diagnostics(mut self, diagnostics: &'a crate::RequestDiagnostics) -> Self {
self.diagnostics = Some(diagnostics);
self
}
pub fn diagnostics(&self) -> Option<&'a crate::RequestDiagnostics> {
self.diagnostics
}
pub fn with_clip_duration(mut self, duration: Duration) -> Self {
assert!(duration >= Time::ZERO, "clip duration must be nonnegative");
self.clip_duration = Some(duration);
self
}
pub fn try_with_clip_duration(
mut self,
duration: Duration,
) -> Result<Self, crate::ValidationError> {
if duration < Time::ZERO {
return Err(crate::ValidationError::invalid(
"scene_context",
"clip_duration",
"clip duration must be nonnegative",
));
}
self.clip_duration = Some(duration);
Ok(self)
}
pub fn time(&self) -> Time {
self.time
}
pub fn seconds(&self) -> f64 {
self.time.as_seconds()
}
pub fn global_time(&self) -> Time {
self.global_time
}
pub fn clip_duration(&self) -> Option<Duration> {
self.clip_duration
}
pub fn progress(&self) -> f64 {
self.clip_duration
.map_or(0.0, |duration| self.progress_in(Time::ZERO, duration))
}
pub fn progress_in(&self, start: Time, duration: Duration) -> f64 {
assert!(
duration >= Time::ZERO,
"interval duration must be nonnegative"
);
let elapsed = i128::from(self.time.ticks()) - i128::from(start.ticks());
if elapsed < 0 {
0.0
} else if elapsed >= i128::from(duration.ticks()) {
1.0
} else {
elapsed as f64 / duration.ticks() as f64
}
}
pub fn ease_in(&self, start: Duration, duration: Duration, easing: crate::Easing) -> f64 {
easing.apply(self.progress_in(start, duration))
}
pub fn frame_rate(&self) -> FrameRate {
self.frame_rate
}
pub(crate) fn for_timeline(&self, size: Size, frame_rate: FrameRate) -> Self {
Self {
frame_rate,
size,
nested_scene: true,
..*self
}
}
pub(crate) fn is_nested_scene(&self) -> bool {
self.nested_scene
}
pub fn frames(&self, count: i64) -> Duration {
Time::from_frame(count, self.frame_rate)
}
pub fn sample<Value>(&self, animation: &(impl Animation<Value> + ?Sized)) -> Value {
animation.sample(self.time)
}
pub fn shifted(&self, offset: Duration) -> Self {
Self {
time: self.time - offset,
..*self
}
}
pub fn try_shifted(&self, offset: Duration) -> Result<Self, crate::ValidationError> {
let ticks = self
.time
.ticks()
.checked_sub(offset.ticks())
.ok_or_else(|| {
crate::ValidationError::invalid(
"scene_context",
"time",
"Shifted local ticks overflow",
)
})?;
Ok(Self {
time: Time::from_ticks(ticks),
..*self
})
}
pub(crate) fn resampled(&self, offset: Duration) -> Result<Self, crate::ValidationError> {
let shift = |time: Time| {
time.ticks()
.checked_add(offset.ticks())
.map(Time::from_ticks)
.ok_or_else(|| {
crate::ValidationError::invalid(
"motion_blur",
"time",
"Subframe local/global ticks overflow",
)
})
};
Ok(Self {
time: shift(self.time)?,
global_time: shift(self.global_time)?,
..*self
})
}
pub fn try_motion_blur(
&self,
shutter: Duration,
samples: u32,
build: impl FnMut(&SceneContext<'_>) -> Node,
) -> Result<Node, crate::ValidationError> {
crate::authoring::sampling::capture(self, shutter, samples, build)
}
pub fn motion_blur(
&self,
shutter: Duration,
samples: u32,
build: impl FnMut(&SceneContext<'_>) -> Node,
) -> Node {
self.try_motion_blur(shutter, samples, build)
.unwrap_or_else(super::scene::failure)
}
pub fn during(
&self,
start: Time,
duration: Duration,
build: impl FnOnce(&SceneContext<'a>) -> Node,
) -> Node {
assert!(
duration >= Time::ZERO,
"interval duration must be nonnegative"
);
let elapsed = i128::from(self.time.ticks()) - i128::from(start.ticks());
if elapsed < 0 || elapsed >= i128::from(duration.ticks()) {
Node::empty()
} else {
build(&Self {
nested_scene: true,
..self.shifted(start).with_clip_duration(duration)
})
.with_scene_time(Time::from_ticks(elapsed as i64))
}
}
pub fn embed(&self, scene: &dyn crate::authoring::Scene, offset: Duration) -> Node {
match scene.duration() {
Some(duration) => self.during(offset, duration, |context| scene.render(context)),
None if self.time < offset => Node::empty(),
None => scene.render(&Self {
clip_duration: None,
nested_scene: true,
..self.shifted(offset)
}),
}
}
pub fn size(&self) -> Size {
self.size
}
pub fn bounds(&self) -> Rect {
self.size.to_rect()
}
pub fn center(&self) -> Point {
self.bounds().center()
}
pub fn measure(&self, node: &Node) -> Result<Rect, crate::ValidationError> {
let drawing = crate::DrawContext::default().with_assets(self.assets());
node.measure_with(&drawing, crate::ValidationLimits::default())
}
pub fn measure_or(&self, node: &Node, fallback: Rect) -> Rect {
self.measure(node).unwrap_or(fallback)
}
pub fn try_next_to(
&self,
node: impl Into<Node>,
reference: &Node,
side: crate::authoring::Side,
gap: f64,
) -> Result<Node, crate::ValidationError> {
if !gap.is_finite() {
return Err(crate::ValidationError::invalid(
"layout",
"gap",
"Relative layout gap must be finite",
));
}
let node = node.into();
let offset = side.adjacent(self.measure(&node)?, self.measure(reference)?, gap);
self.translated_layout(node, offset)
}
pub fn try_align_to(
&self,
node: impl Into<Node>,
reference: &Node,
edge: crate::authoring::Side,
) -> Result<Node, crate::ValidationError> {
let node = node.into();
let offset = edge.aligned(self.measure(&node)?, self.measure(reference)?);
self.translated_layout(node, offset)
}
pub fn next_to(
&self,
node: impl Into<Node>,
reference: &Node,
side: super::Side,
gap: f64,
) -> Node {
node.into().next_to(reference, side, gap)
}
pub fn align_to(&self, node: impl Into<Node>, reference: &Node, edge: super::Side) -> Node {
node.into().align_to(reference, edge)
}
fn translated_layout(
&self,
node: Node,
offset: crate::Vector2,
) -> Result<Node, crate::ValidationError> {
use crate::NodeModifiers;
if !offset.x.is_finite() || !offset.y.is_finite() {
return Err(crate::ValidationError::invalid(
"layout",
"translation",
"Relative layout offset must remain finite",
));
}
let node = node.translate(offset.x, offset.y);
self.measure(&node)?;
Ok(node)
}
pub fn random(&self, key: impl Hash) -> f64 {
self.random_generator(key).next_float()
}
pub fn random_generator(&self, key: impl Hash) -> Random {
Random::from_key(key)
}
#[track_caller]
pub fn cache<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
&self,
key: Key,
build: impl FnOnce() -> Value,
) -> Arc<Value> {
self.cache.get(key, build)
}
#[track_caller]
pub fn memo<Key: Eq + Hash + Send + Sync + 'static, Output: Into<Node>>(
&self,
key: Key,
build: impl FnOnce() -> Output,
) -> Node {
self.cache(key, || build().into()).as_ref().clone()
}
#[track_caller]
pub fn cache_with_cost<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
&self,
key: Key,
build: impl FnOnce() -> Value,
cost: impl FnOnce(&Value) -> usize,
) -> Arc<Value> {
self.cache.get_with_cost(key, build, cost)
}
}