use std::hash::Hash;
use std::sync::Arc;
use crate::{
Duration, FrameRate, Interval, Node, Point, Random, Rect, Signal, Size, Time, TimeOffset,
};
mod cache;
mod partition;
#[cfg(test)]
mod tests;
pub use cache::SceneCache;
pub use partition::{CacheLimits, CachePartition, RenderCache};
#[derive(Clone)]
pub struct SceneContext<'a> {
evaluation: Arc<crate::Evaluation>,
time: Time,
global_time: Time,
clip_duration: Option<Duration>,
frame_rate: FrameRate,
size: Size,
nested_scene: bool,
cache: &'a SceneCache,
}
impl<'a> SceneContext<'a> {
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 {
evaluation: Arc::default(),
time,
global_time: time,
clip_duration: None,
frame_rate,
size,
nested_scene: false,
cache,
})
}
pub fn with_evaluation(mut self, evaluation: Arc<crate::Evaluation>) -> Self {
self.evaluation = evaluation;
self
}
pub fn evaluation(&self) -> &Arc<crate::Evaluation> {
&self.evaluation
}
fn edit(mut self, edit: impl FnOnce(crate::Evaluation) -> crate::Evaluation) -> Self {
let current = Arc::try_unwrap(std::mem::take(&mut self.evaluation))
.unwrap_or_else(|shared| crate::Evaluation::clone(&shared));
self.evaluation = Arc::new(edit(current));
self
}
pub fn with_quality(self, quality: crate::RenderQuality) -> Self {
self.edit(|evaluation| evaluation.with_quality(quality))
}
pub fn render_quality(&self) -> crate::RenderQuality {
self.evaluation.quality()
}
pub fn assets(&self) -> &crate::Assets {
self.evaluation.assets()
}
pub fn with_assets(self, assets: &crate::Assets) -> Self {
self.edit(|evaluation| evaluation.with_assets(assets))
}
pub fn with_diagnostics(self, diagnostics: &crate::RequestDiagnostics) -> Self {
self.edit(|evaluation| evaluation.with_diagnostics(diagnostics.clone()))
}
pub fn diagnostics(&self) -> &crate::RequestDiagnostics {
self.evaluation.diagnostics()
}
pub fn with_clip_duration(mut self, duration: Duration) -> Self {
self.clip_duration = Some(duration);
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 elapsed(&self) -> Duration {
self.time
.duration_since(Time::ZERO)
.unwrap_or(Duration::ZERO)
}
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 {
Interval::new(start, duration).progress(self.time)
}
pub fn ease_in(&self, start: Time, duration: Duration, easing: crate::Easing) -> f64 {
Interval::new(start, duration).ease(self.time, easing)
}
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.clone()
}
}
pub(crate) fn is_nested_scene(&self) -> bool {
self.nested_scene
}
pub fn frames(&self, count: u64) -> Duration {
Duration::from_frame(count, self.frame_rate)
}
pub fn sample<Value>(&self, animation: &(impl Signal<Value> + ?Sized)) -> Value {
animation.sample(self.time)
}
pub fn shifted(&self, offset: TimeOffset) -> Self {
Self {
time: self.time - offset,
..self.clone()
}
}
pub fn try_shifted(&self, offset: TimeOffset) -> 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.clone()
})
}
pub(crate) fn resampled(&self, offset: TimeOffset) -> 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.clone()
})
}
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 {
let interval = Interval::new(start, duration);
if !interval.contains(self.time) {
Node::empty()
} else {
build(&Self {
nested_scene: true,
..self
.shifted(start.since_origin())
.with_clip_duration(duration)
})
.with_scene_time(Time::from_ticks(
(i128::from(self.time.ticks()) - i128::from(start.ticks())) as i64,
))
}
}
pub fn embed(&self, scene: &dyn crate::authoring::Scene, offset: Time) -> 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.since_origin())
}),
}
}
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_evaluation(self.evaluation.clone())
.with_time(self.time);
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 try_cache<
Key: Eq + Hash + Send + Sync + 'static,
Value: Send + Sync + 'static,
Error,
>(
&self,
key: Key,
build: impl FnOnce() -> Result<Value, Error>,
) -> Result<Arc<Value>, Error> {
self.cache.try_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)
}
}