use super::{InteractivePlotSession, IntoPlot, Plot, PreparedPlot};
use std::{
hash::{Hash, Hasher},
sync::Arc,
};
pub trait IntoPlotSession {
fn into_plot_session(self) -> InteractivePlotSession;
}
impl<T> IntoPlotSession for T
where
T: IntoPlot,
{
fn into_plot_session(self) -> InteractivePlotSession {
self.into_plot().prepare_interactive()
}
}
impl IntoPlotSession for PreparedPlot {
fn into_plot_session(self) -> InteractivePlotSession {
self.into_interactive()
}
}
impl IntoPlotSession for InteractivePlotSession {
fn into_plot_session(self) -> InteractivePlotSession {
self
}
}
#[cfg(feature = "3d")]
pub trait TryIntoPlot3DSession {
fn try_into_plot3d_session(self) -> super::Result<super::InteractivePlot3DSession>;
}
#[cfg(feature = "3d")]
impl TryIntoPlot3DSession for super::InteractivePlot3DSession {
fn try_into_plot3d_session(self) -> super::Result<super::InteractivePlot3DSession> {
Ok(self)
}
}
#[cfg(feature = "3d")]
macro_rules! impl_try_into_plot3d_session {
($($builder:ty),+ $(,)?) => {
$(
impl TryIntoPlot3DSession for $builder {
fn try_into_plot3d_session(
self,
) -> super::Result<super::InteractivePlot3DSession> {
self.interactive_session()
}
}
)+
};
}
#[cfg(feature = "3d")]
impl_try_into_plot3d_session!(
super::Scatter3DBuilder,
super::Line3DBuilder,
super::Surface3DBuilder,
super::Wireframe3DBuilder,
);
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum ImageFit {
#[default]
Contain,
Cover,
Fill,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct LogicalRect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl LogicalRect {
pub const fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn contains(self, point: LogicalPoint) -> bool {
point.x >= self.x
&& point.y >= self.y
&& point.x <= self.x + self.width
&& point.y <= self.y + self.height
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct LogicalPoint {
pub x: f64,
pub y: f64,
}
impl LogicalPoint {
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
pub fn sanitize_scale_factor(scale_factor: f32) -> f32 {
if scale_factor.is_finite() && scale_factor > 0.0 {
scale_factor
} else {
1.0
}
}
pub fn physical_backing_size(
logical_width: f64,
logical_height: f64,
scale_factor: f32,
) -> (u32, u32) {
let scale = f64::from(sanitize_scale_factor(scale_factor));
(
physical_dimension(logical_width, scale),
physical_dimension(logical_height, scale),
)
}
fn physical_dimension(logical: f64, scale: f64) -> u32 {
if !logical.is_finite() || logical <= 0.0 {
return 1;
}
(logical * scale).ceil().clamp(1.0, f64::from(u32::MAX)) as u32
}
pub fn fitted_content_rect(
outer: LogicalRect,
image_size_px: (u32, u32),
fit: ImageFit,
) -> LogicalRect {
if !outer.width.is_finite()
|| !outer.height.is_finite()
|| outer.width <= 0.0
|| outer.height <= 0.0
|| image_size_px.0 == 0
|| image_size_px.1 == 0
|| matches!(fit, ImageFit::Fill)
{
return outer;
}
let image_aspect = f64::from(image_size_px.0) / f64::from(image_size_px.1);
let outer_aspect = outer.width / outer.height;
let (width, height) = match fit {
ImageFit::Contain if image_aspect > outer_aspect => {
(outer.width, outer.width / image_aspect)
}
ImageFit::Contain => (outer.height * image_aspect, outer.height),
ImageFit::Cover if image_aspect < outer_aspect => (outer.width, outer.width / image_aspect),
ImageFit::Cover => (outer.height * image_aspect, outer.height),
ImageFit::Fill => unreachable!("fill was returned above"),
};
LogicalRect {
x: outer.x + (outer.width - width) * 0.5,
y: outer.y + (outer.height - height) * 0.5,
width,
height,
}
}
pub fn logical_to_physical(
content: LogicalRect,
point: LogicalPoint,
image_size_px: (u32, u32),
) -> Option<(f64, f64)> {
if image_size_px.0 == 0
|| image_size_px.1 == 0
|| !content.width.is_finite()
|| !content.height.is_finite()
|| content.width <= 0.0
|| content.height <= 0.0
|| !content.contains(point)
{
return None;
}
Some((
(point.x - content.x) / content.width * f64::from(image_size_px.0),
(point.y - content.y) / content.height * f64::from(image_size_px.1),
))
}
#[derive(Debug)]
struct SchedulerIncarnation;
#[derive(Clone, Debug)]
pub struct ScheduledRequestId {
scheduler_incarnation: Arc<SchedulerIncarnation>,
generation: u64,
}
impl PartialEq for ScheduledRequestId {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.scheduler_incarnation, &other.scheduler_incarnation)
&& self.generation == other.generation
}
}
impl Eq for ScheduledRequestId {}
impl Hash for ScheduledRequestId {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.scheduler_incarnation).hash(state);
self.generation.hash(state);
}
}
impl ScheduledRequestId {
pub const fn generation(&self) -> u64 {
self.generation
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ScheduledRequest<T> {
id: ScheduledRequestId,
request: T,
}
impl<T> ScheduledRequest<T> {
pub fn id(&self) -> ScheduledRequestId {
self.id.clone()
}
pub const fn generation(&self) -> u64 {
self.id.generation()
}
pub const fn request(&self) -> &T {
&self.request
}
pub fn into_request(self) -> T {
self.request
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RequestCompletion<T> {
pub install: bool,
pub next: Option<ScheduledRequest<T>>,
}
#[derive(Debug)]
pub struct LatestRequestScheduler<T> {
scheduler_incarnation: Arc<SchedulerIncarnation>,
latest_generation: u64,
in_flight: Option<ScheduledRequestId>,
queued: Option<ScheduledRequest<T>>,
dropped_requests: u64,
}
impl<T> Default for LatestRequestScheduler<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> LatestRequestScheduler<T> {
pub fn new() -> Self {
Self {
scheduler_incarnation: Arc::new(SchedulerIncarnation),
latest_generation: 0,
in_flight: None,
queued: None,
dropped_requests: 0,
}
}
pub const fn latest_generation(&self) -> u64 {
self.latest_generation
}
pub const fn dropped_requests(&self) -> u64 {
self.dropped_requests
}
pub fn is_idle(&self) -> bool {
self.in_flight.is_none()
}
pub fn reset(&mut self) {
self.scheduler_incarnation = Arc::new(SchedulerIncarnation);
self.latest_generation = 0;
self.in_flight = None;
self.queued = None;
self.dropped_requests = 0;
}
}
impl<T> LatestRequestScheduler<T> {
pub fn request(&mut self, request: T) -> Option<ScheduledRequest<T>> {
if self.latest_generation == u64::MAX {
self.scheduler_incarnation = Arc::new(SchedulerIncarnation);
self.latest_generation = 1;
} else {
self.latest_generation += 1;
}
let scheduled = ScheduledRequest {
id: ScheduledRequestId {
scheduler_incarnation: Arc::clone(&self.scheduler_incarnation),
generation: self.latest_generation,
},
request,
};
if self.in_flight.is_some() {
if self.queued.replace(scheduled).is_some() {
self.dropped_requests = self.dropped_requests.saturating_add(1);
}
None
} else {
self.in_flight = Some(scheduled.id());
Some(scheduled)
}
}
pub fn complete(&mut self, id: ScheduledRequestId) -> Option<RequestCompletion<T>> {
if self.in_flight.as_ref() != Some(&id) {
return None;
}
self.in_flight = None;
let install = Arc::ptr_eq(&id.scheduler_incarnation, &self.scheduler_incarnation)
&& id.generation == self.latest_generation;
let next = self.queued.take();
if let Some(next) = &next {
self.in_flight = Some(next.id());
}
Some(RequestCompletion { install, next })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plot_values_convert_to_retained_sessions() {
let session = Plot::new()
.line(&[0.0, 1.0], &[1.0, 2.0])
.into_plot_session();
assert_eq!(session.displayed_frame_generation(), None);
}
#[test]
fn fractional_hidpi_dimensions_round_up() {
assert_eq!(physical_backing_size(100.25, 50.1, 1.25), (126, 63));
assert_eq!(physical_backing_size(100.25, 50.1, 1.5), (151, 76));
assert_eq!(physical_backing_size(100.25, 50.1, 2.0), (201, 101));
}
#[test]
fn contain_fit_and_mapping_use_actual_content_bounds() {
let outer = LogicalRect::new(10.0, 20.0, 400.0, 400.0);
let content = fitted_content_rect(outer, (400, 200), ImageFit::Contain);
assert_eq!(content, LogicalRect::new(10.0, 120.0, 400.0, 200.0));
assert_eq!(
logical_to_physical(content, LogicalPoint::new(210.0, 220.0), (800, 400)),
Some((400.0, 200.0))
);
assert_eq!(
logical_to_physical(content, LogicalPoint::new(210.0, 100.0), (800, 400)),
None
);
}
#[test]
fn newest_request_is_the_only_installable_completion() {
let mut scheduler = LatestRequestScheduler::default();
let first = scheduler.request("a").expect("first request starts");
assert!(scheduler.request("b").is_none());
assert!(scheduler.request("c").is_none());
assert_eq!(scheduler.dropped_requests(), 1);
let completion = scheduler
.complete(first.id())
.expect("first request was in flight");
assert!(!completion.install);
let newest = completion.next.expect("newest request starts next");
assert_eq!(newest.request(), &"c");
let completion = scheduler
.complete(newest.id())
.expect("newest request was in flight");
assert!(completion.install);
assert!(completion.next.is_none());
assert!(scheduler.is_idle());
}
#[test]
fn stale_completion_cannot_change_scheduler_state() {
let mut scheduler = LatestRequestScheduler::default();
let request = scheduler.request(1).unwrap();
let mut stale_id = request.id();
stale_id.generation += 1;
assert!(scheduler.complete(stale_id).is_none());
assert!(!scheduler.is_idle());
assert!(scheduler.complete(request.id()).is_some());
}
#[test]
fn scheduler_accepts_non_clone_requests_without_copying_jobs() {
#[derive(Debug, Eq, PartialEq)]
struct NonCloneRequest(&'static str);
let mut scheduler = LatestRequestScheduler::new();
let first = scheduler
.request(NonCloneRequest("first"))
.expect("first starts");
assert!(scheduler.request(NonCloneRequest("queued")).is_none());
let completion = scheduler.complete(first.id()).expect("first completion");
assert!(!completion.install);
let queued = completion.next.expect("queued request starts");
assert_eq!(queued.request(), &NonCloneRequest("queued"));
assert!(
scheduler
.complete(queued.id())
.expect("queued completion")
.install
);
}
#[test]
fn completion_from_a_dropped_scheduler_cannot_consume_new_work() {
let stale = {
let mut old_scheduler = LatestRequestScheduler::default();
old_scheduler.request("old scheduler").unwrap()
};
let mut new_scheduler = LatestRequestScheduler::default();
let current = new_scheduler.request("new scheduler").unwrap();
assert_eq!(stale.generation(), current.generation());
assert_ne!(stale.id(), current.id());
assert!(new_scheduler.complete(stale.id()).is_none());
assert!(!new_scheduler.is_idle());
assert!(
new_scheduler
.complete(current.id())
.expect("current completion")
.install
);
}
#[test]
fn reset_rotates_incarnation_before_reusing_generation() {
let mut scheduler = LatestRequestScheduler::default();
let cancelled = scheduler.request("old session").unwrap();
scheduler.reset();
let replacement = scheduler.request("new session").unwrap();
assert_eq!(cancelled.generation(), replacement.generation());
assert_ne!(cancelled.id(), replacement.id());
assert!(scheduler.complete(cancelled.id()).is_none());
assert!(!scheduler.is_idle());
assert!(
scheduler
.complete(replacement.id())
.expect("replacement completion")
.install
);
}
#[test]
fn scheduler_generation_exhaustion_rotates_incarnation() {
let mut scheduler = LatestRequestScheduler::<&'static str>::new();
let previous_incarnation = Arc::clone(&scheduler.scheduler_incarnation);
scheduler.latest_generation = u64::MAX;
let request = scheduler
.request("after exhaustion")
.expect("idle scheduler starts the request");
assert_eq!(request.generation(), 1);
assert!(!Arc::ptr_eq(
&request.id().scheduler_incarnation,
&previous_incarnation
));
}
}