use super::color::Color;
use super::style::LineStyle;
use crate::core::Result;
use crate::core::types::Point2f;
use crate::data::{PooledVec, SharedMemoryPool};
#[cfg(feature = "simd")]
use wide::f32x4;
pub use crate::data::elements::LineSegment;
pub struct PooledRenderer {
f32_pool: SharedMemoryPool<f32>,
point_pool: SharedMemoryPool<Point2f>,
segment_pool: SharedMemoryPool<LineSegment>,
}
impl Default for PooledRenderer {
fn default() -> Self {
Self::new()
}
}
impl PooledRenderer {
pub fn new() -> Self {
Self {
f32_pool: SharedMemoryPool::new(1000),
point_pool: SharedMemoryPool::new(500),
segment_pool: SharedMemoryPool::new(500),
}
}
pub fn with_pool_sizes(f32_size: usize, point_size: usize, segment_size: usize) -> Self {
Self {
f32_pool: SharedMemoryPool::new(f32_size),
point_pool: SharedMemoryPool::new(point_size),
segment_pool: SharedMemoryPool::new(segment_size),
}
}
pub fn transform_x_coordinates_pooled<T>(
&self,
x_data: &T,
min: f64,
max: f64,
left: f32,
right: f32,
) -> Result<PooledVec<f32>>
where
T: crate::data::Data1D<f64>,
{
let len = x_data.len();
if len == 0 {
return Ok(PooledVec::new(self.f32_pool.clone()));
}
let mut result = PooledVec::with_capacity(len, self.f32_pool.clone());
let range = max - min;
if range == 0.0 {
let mid_x = left + (right - left) / 2.0;
result.resize(len, mid_x);
return Ok(result);
}
let scale = (right - left) as f64 / range;
let offset = left as f64 - min * scale;
#[cfg(feature = "simd")]
{
if len >= 4 {
let scale_vec = f32x4::splat(scale as f32);
let offset_vec = f32x4::splat(offset as f32);
let mut i = 0;
while i + 4 <= len {
let chunk = [
x_data.get(i).copied().unwrap_or(0.0) as f32,
x_data.get(i + 1).copied().unwrap_or(0.0) as f32,
x_data.get(i + 2).copied().unwrap_or(0.0) as f32,
x_data.get(i + 3).copied().unwrap_or(0.0) as f32,
];
let x_vec = f32x4::new(chunk);
let transformed = x_vec * scale_vec + offset_vec;
let output = transformed.to_array();
result.push(output[0]);
result.push(output[1]);
result.push(output[2]);
result.push(output[3]);
i += 4;
}
while i < len {
if let Some(x) = x_data.get(i) {
let transformed = (*x * scale + offset) as f32;
result.push(transformed);
}
i += 1;
}
} else {
for i in 0..len {
if let Some(x) = x_data.get(i) {
let transformed = (*x * scale + offset) as f32;
result.push(transformed);
}
}
}
}
#[cfg(not(feature = "simd"))]
{
for i in 0..len {
if let Some(x) = x_data.get(i) {
let transformed = (*x * scale + offset) as f32;
result.push(transformed);
}
}
}
Ok(result)
}
pub fn transform_y_coordinates_pooled<T>(
&self,
y_data: &T,
min: f64,
max: f64,
top: f32,
bottom: f32,
) -> Result<PooledVec<f32>>
where
T: crate::data::Data1D<f64>,
{
let len = y_data.len();
if len == 0 {
return Ok(PooledVec::new(self.f32_pool.clone()));
}
let mut result = PooledVec::with_capacity(len, self.f32_pool.clone());
let range = max - min;
if range == 0.0 {
let mid_y = top + (bottom - top) / 2.0;
result.resize(len, mid_y);
return Ok(result);
}
let scale = (top - bottom) as f64 / range;
let offset = bottom as f64 - min * scale;
#[cfg(feature = "simd")]
{
if len >= 4 {
let scale_vec = f32x4::splat(scale as f32);
let offset_vec = f32x4::splat(offset as f32);
let mut i = 0;
while i + 4 <= len {
let chunk = [
y_data.get(i).copied().unwrap_or(0.0) as f32,
y_data.get(i + 1).copied().unwrap_or(0.0) as f32,
y_data.get(i + 2).copied().unwrap_or(0.0) as f32,
y_data.get(i + 3).copied().unwrap_or(0.0) as f32,
];
let y_vec = f32x4::new(chunk);
let transformed = y_vec * scale_vec + offset_vec;
let output = transformed.to_array();
result.push(output[0]);
result.push(output[1]);
result.push(output[2]);
result.push(output[3]);
i += 4;
}
while i < len {
if let Some(y) = y_data.get(i) {
let transformed = (*y * scale + offset) as f32;
result.push(transformed);
}
i += 1;
}
} else {
for i in 0..len {
if let Some(y) = y_data.get(i) {
let transformed = (*y * scale + offset) as f32;
result.push(transformed);
}
}
}
}
#[cfg(not(feature = "simd"))]
{
for i in 0..len {
if let Some(y) = y_data.get(i) {
let transformed = (*y * scale + offset) as f32;
result.push(transformed);
}
}
}
Ok(result)
}
pub fn transform_coordinates_pooled<T>(
&self,
x_data: &T,
y_data: &T,
x_min: f64,
x_max: f64,
y_min: f64,
y_max: f64,
left: f32,
top: f32,
right: f32,
bottom: f32,
) -> Result<(PooledVec<f32>, PooledVec<f32>)>
where
T: crate::data::Data1D<f64>,
{
let x_result = self.transform_x_coordinates_pooled(x_data, x_min, x_max, left, right)?;
let y_result = self.transform_y_coordinates_pooled(y_data, y_min, y_max, top, bottom)?;
Ok((x_result, y_result))
}
pub fn generate_ticks_pooled(&self, min: f64, max: f64, target_count: usize) -> PooledVec<f64> {
let mut ticks = PooledVec::new(SharedMemoryPool::new(target_count.max(10)));
if min >= max {
return ticks;
}
if target_count <= 1 {
ticks.push(min);
return ticks;
}
if target_count == 2 {
ticks.push(min);
ticks.push(max);
return ticks;
}
let range = max - min;
let raw_step = range / (target_count - 1) as f64;
let magnitude = 10.0_f64.powf(raw_step.log10().floor());
let normalized_step = raw_step / magnitude;
let nice_step = if normalized_step <= 1.0 {
1.0
} else if normalized_step <= 2.0 {
2.0
} else if normalized_step <= 5.0 {
5.0
} else {
10.0
} * magnitude;
let start = (min / nice_step).ceil() * nice_step;
let mut current = start;
while current <= max + nice_step * 0.001 {
if current >= min - nice_step * 0.001 {
ticks.push(current);
}
current += nice_step;
}
ticks
}
pub fn create_line_segments_pooled<T>(
&self,
points: &T,
line_style: LineStyle,
color: Color,
line_width: f32,
) -> Result<PooledVec<LineSegment>>
where
T: crate::data::Data1D<Point2f>,
{
let len = points.len();
if len < 2 {
return Ok(PooledVec::new(self.segment_pool.clone()));
}
let mut segments = PooledVec::with_capacity(len - 1, self.segment_pool.clone());
for i in 0..len - 1 {
if let (Some(start), Some(end)) = (points.get(i), points.get(i + 1)) {
segments.push(LineSegment {
start: *start,
end: *end,
style: line_style.clone(),
color,
width: line_width,
});
}
}
Ok(segments)
}
pub fn get_pool_stats(&self) -> PooledRendererStats {
PooledRendererStats {
f32_pool_stats: self.f32_pool.statistics(),
point_pool_stats: self.point_pool.statistics(),
segment_pool_stats: self.segment_pool.statistics(),
}
}
pub fn shrink_pools(&self) {
self.f32_pool.shrink();
self.point_pool.shrink();
self.segment_pool.shrink();
}
}
#[derive(Debug, Clone)]
pub struct PooledRendererStats {
pub f32_pool_stats: crate::data::PoolStatistics,
pub point_pool_stats: crate::data::PoolStatistics,
pub segment_pool_stats: crate::data::PoolStatistics,
}
impl PooledRendererStats {
pub fn total_capacity(&self) -> usize {
self.f32_pool_stats.total_capacity
+ self.point_pool_stats.total_capacity
+ self.segment_pool_stats.total_capacity
}
pub fn total_in_use(&self) -> usize {
self.f32_pool_stats.in_use_count
+ self.point_pool_stats.in_use_count
+ self.segment_pool_stats.in_use_count
}
pub fn efficiency(&self) -> f32 {
let total_capacity = self.total_capacity();
if total_capacity == 0 {
return 1.0;
}
self.total_in_use() as f32 / total_capacity as f32
}
}
use std::sync::OnceLock;
static GLOBAL_POOLED_RENDERER: OnceLock<PooledRenderer> = OnceLock::new();
pub fn get_pooled_renderer() -> &'static PooledRenderer {
GLOBAL_POOLED_RENDERER.get_or_init(PooledRenderer::new)
}
impl std::fmt::Debug for PooledRenderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PooledRenderer")
.field("f32_pool", &"SharedMemoryPool<f32>")
.field("point_pool", &"SharedMemoryPool<Point2f>")
.field("segment_pool", &"SharedMemoryPool<LineSegment>")
.finish()
}
}
impl Clone for PooledRenderer {
fn clone(&self) -> Self {
Self {
f32_pool: self.f32_pool.clone(),
point_pool: self.point_pool.clone(),
segment_pool: self.segment_pool.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pooled_coordinate_transform() {
let renderer = PooledRenderer::new();
let x_data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = renderer
.transform_x_coordinates_pooled(&x_data, 1.0, 5.0, 0.0, 100.0)
.unwrap();
assert_eq!(result.len(), 5);
assert!((result[0] - 0.0).abs() < 0.001); assert!((result[4] - 100.0).abs() < 0.001); }
#[test]
fn test_pooled_tick_generation() {
let renderer = PooledRenderer::new();
let ticks = renderer.generate_ticks_pooled(0.0, 10.0, 6);
assert!(ticks.len() >= 2); assert!(ticks[0] >= 0.0);
assert!(ticks[ticks.len() - 1] <= 10.0);
}
#[test]
fn test_memory_pool_reuse() {
let renderer = PooledRenderer::new();
let x_data = vec![1.0, 2.0, 3.0];
let result1 = renderer
.transform_x_coordinates_pooled(&x_data, 0.0, 3.0, 0.0, 10.0)
.unwrap();
drop(result1);
let result2 = renderer
.transform_x_coordinates_pooled(&x_data, 0.0, 3.0, 0.0, 10.0)
.unwrap();
assert_eq!(result2.len(), 3);
let stats = renderer.get_pool_stats();
assert!(stats.f32_pool_stats.total_capacity > 0);
}
#[test]
fn test_simd_vs_scalar_consistency() {
let renderer = PooledRenderer::new();
let large_data: Vec<f64> = (0..100).map(|i| i as f64).collect();
let result_large = renderer
.transform_x_coordinates_pooled(&large_data, 0.0, 99.0, 0.0, 100.0)
.unwrap();
let small_data = vec![0.0, 50.0, 99.0];
let result_small = renderer
.transform_x_coordinates_pooled(&small_data, 0.0, 99.0, 0.0, 100.0)
.unwrap();
assert!((result_large[0] - result_small[0]).abs() < 0.001);
assert!((result_large[99] - 100.0).abs() < 0.001);
}
}