use crate::{
core::types::{BoundingBox, Point2f},
data::{ManagedBuffer, get_memory_manager},
render::{Color, LineStyle, MarkerStyle},
};
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub struct PlotElementStorage {
line_pool: Arc<Mutex<ElementPool<LineSegment>>>,
marker_pool: Arc<Mutex<ElementPool<MarkerInstance>>>,
polygon_pool: Arc<Mutex<ElementPool<Polygon>>>,
text_pool: Arc<Mutex<ElementPool<TextElement>>>,
error_bar_pool: Arc<Mutex<ElementPool<ErrorBar>>>,
}
impl PlotElementStorage {
pub fn new() -> Self {
Self {
line_pool: Arc::new(Mutex::new(ElementPool::new(1000))),
marker_pool: Arc::new(Mutex::new(ElementPool::new(5000))),
polygon_pool: Arc::new(Mutex::new(ElementPool::new(100))),
text_pool: Arc::new(Mutex::new(ElementPool::new(50))),
error_bar_pool: Arc::new(Mutex::new(ElementPool::new(1000))),
}
}
pub fn get_line_storage(&self, capacity: usize) -> ManagedElementStorage<LineSegment> {
let mut pool = self.line_pool.lock().unwrap();
let storage = pool.get_storage(capacity);
ManagedElementStorage::new(storage, self.line_pool.clone())
}
pub fn get_marker_storage(&self, capacity: usize) -> ManagedElementStorage<MarkerInstance> {
let mut pool = self.marker_pool.lock().unwrap();
let storage = pool.get_storage(capacity);
ManagedElementStorage::new(storage, self.marker_pool.clone())
}
pub fn get_polygon_storage(&self, capacity: usize) -> ManagedElementStorage<Polygon> {
let mut pool = self.polygon_pool.lock().unwrap();
let storage = pool.get_storage(capacity);
ManagedElementStorage::new(storage, self.polygon_pool.clone())
}
pub fn get_text_storage(&self, capacity: usize) -> ManagedElementStorage<TextElement> {
let mut pool = self.text_pool.lock().unwrap();
let storage = pool.get_storage(capacity);
ManagedElementStorage::new(storage, self.text_pool.clone())
}
pub fn get_error_bar_storage(&self, capacity: usize) -> ManagedElementStorage<ErrorBar> {
let mut pool = self.error_bar_pool.lock().unwrap();
let storage = pool.get_storage(capacity);
ManagedElementStorage::new(storage, self.error_bar_pool.clone())
}
pub fn get_pool_stats(&self) -> PlotElementStats {
PlotElementStats {
line_segments: self.line_pool.lock().unwrap().stats(),
markers: self.marker_pool.lock().unwrap().stats(),
polygons: self.polygon_pool.lock().unwrap().stats(),
text_elements: self.text_pool.lock().unwrap().stats(),
error_bars: self.error_bar_pool.lock().unwrap().stats(),
}
}
}
impl Default for PlotElementStorage {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
struct ElementPool<T> {
available: Vec<Vec<T>>,
target_capacity: usize,
stats: PoolStats,
}
impl<T> ElementPool<T> {
fn new(target_capacity: usize) -> Self {
Self {
available: Vec::new(),
target_capacity,
stats: PoolStats::default(),
}
}
fn get_storage(&mut self, min_capacity: usize) -> Vec<T> {
for (i, storage) in self.available.iter().enumerate() {
if storage.capacity() >= min_capacity {
let mut storage = self.available.swap_remove(i);
storage.clear();
self.stats.hits += 1;
return storage;
}
}
let capacity = min_capacity.max(self.target_capacity);
self.stats.misses += 1;
self.stats.total_allocated += 1;
Vec::with_capacity(capacity)
}
fn return_storage(&mut self, mut storage: Vec<T>) {
if storage.capacity() <= self.target_capacity * 4 {
storage.clear();
self.available.push(storage);
}
}
fn stats(&self) -> PoolStats {
self.stats.clone()
}
}
#[derive(Debug, Clone, Default)]
pub struct PoolStats {
pub hits: usize,
pub misses: usize,
pub total_allocated: usize,
}
pub struct ManagedElementStorage<T> {
storage: Option<Vec<T>>,
pool: Arc<Mutex<ElementPool<T>>>,
}
impl<T> ManagedElementStorage<T> {
fn new(storage: Vec<T>, pool: Arc<Mutex<ElementPool<T>>>) -> Self {
Self {
storage: Some(storage),
pool,
}
}
pub fn get_mut(&mut self) -> &mut Vec<T> {
self.storage.as_mut().unwrap()
}
pub fn get(&self) -> &Vec<T> {
self.storage.as_ref().unwrap()
}
pub fn into_inner(mut self) -> Vec<T> {
self.storage.take().unwrap()
}
}
impl<T> Drop for ManagedElementStorage<T> {
fn drop(&mut self) {
if let Some(storage) = self.storage.take() {
if let Ok(mut pool) = self.pool.lock() {
pool.return_storage(storage);
}
}
}
}
#[derive(Debug, Clone)]
pub struct LineSegment {
pub start: Point2f,
pub end: Point2f,
pub style: LineStyle,
pub color: Color,
pub width: f32,
}
impl LineSegment {
pub fn new(start: Point2f, end: Point2f, style: LineStyle, color: Color, width: f32) -> Self {
Self {
start,
end,
style,
color,
width,
}
}
pub fn length(&self) -> f32 {
let dx = self.end.x - self.start.x;
let dy = self.end.y - self.start.y;
(dx * dx + dy * dy).sqrt()
}
pub fn bounds(&self) -> BoundingBox {
BoundingBox::new(
self.start.x.min(self.end.x),
self.start.x.max(self.end.x),
self.start.y.min(self.end.y),
self.start.y.max(self.end.y),
)
}
}
#[derive(Debug, Clone)]
pub struct MarkerInstance {
pub position: Point2f,
pub style: MarkerStyle,
pub color: Color,
pub size: f32,
}
impl MarkerInstance {
pub fn new(position: Point2f, style: MarkerStyle, color: Color, size: f32) -> Self {
Self {
position,
style,
color,
size,
}
}
pub fn bounds(&self) -> BoundingBox {
let half_size = self.size * 0.5;
BoundingBox::new(
self.position.x - half_size,
self.position.x + half_size,
self.position.y - half_size,
self.position.y + half_size,
)
}
}
#[derive(Debug)]
pub struct Polygon {
vertices: ManagedBuffer<Point2f>,
pub fill_color: Color,
pub stroke_color: Color,
pub stroke_width: f32,
}
impl Polygon {
pub fn new(capacity: usize, fill_color: Color, stroke_color: Color, stroke_width: f32) -> Self {
let memory_manager = get_memory_manager();
Self {
vertices: memory_manager.get_point_buffer(capacity),
fill_color,
stroke_color,
stroke_width,
}
}
pub fn add_vertex(&mut self, point: Point2f) {
self.vertices.get_mut().push(point);
}
pub fn vertices(&self) -> &[Point2f] {
self.vertices.get()
}
pub fn vertices_mut(&mut self) -> &mut Vec<Point2f> {
self.vertices.get_mut()
}
pub fn bounds(&self) -> BoundingBox {
let vertices = self.vertices();
if vertices.is_empty() {
return BoundingBox::new(0.0, 0.0, 0.0, 0.0);
}
let mut min_x = vertices[0].x;
let mut max_x = vertices[0].x;
let mut min_y = vertices[0].y;
let mut max_y = vertices[0].y;
for vertex in vertices.iter().skip(1) {
min_x = min_x.min(vertex.x);
max_x = max_x.max(vertex.x);
min_y = min_y.min(vertex.y);
max_y = max_y.max(vertex.y);
}
BoundingBox::new(min_x, max_x, min_y, max_y)
}
}
#[derive(Debug, Clone)]
pub struct TextElement {
pub text: String,
pub position: Point2f,
pub font_size: f32,
pub color: Color,
pub alignment: TextAlignment,
pub rotation: f32,
}
impl TextElement {
pub fn new(text: String, position: Point2f, font_size: f32, color: Color) -> Self {
Self {
text,
position,
font_size,
color,
alignment: TextAlignment::Left,
rotation: 0.0,
}
}
pub fn bounds(&self) -> BoundingBox {
let char_width = self.font_size * 0.6; let text_width = self.text.len() as f32 * char_width;
let text_height = self.font_size;
BoundingBox::new(
self.position.x,
self.position.x + text_width,
self.position.y,
self.position.y + text_height,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextAlignment {
Left,
Center,
Right,
}
#[derive(Debug, Clone)]
pub struct ErrorBar {
pub center: Point2f,
pub error_x_pos: f32,
pub error_x_neg: f32,
pub error_y_pos: f32,
pub error_y_neg: f32,
pub color: Color,
pub width: f32,
pub cap_size: f32,
}
impl ErrorBar {
pub fn symmetric(
center: Point2f,
x_error: f32,
y_error: f32,
color: Color,
width: f32,
) -> Self {
Self {
center,
error_x_pos: x_error,
error_x_neg: x_error,
error_y_pos: y_error,
error_y_neg: y_error,
color,
width,
cap_size: width * 2.0,
}
}
pub fn asymmetric(
center: Point2f,
x_pos: f32,
x_neg: f32,
y_pos: f32,
y_neg: f32,
color: Color,
width: f32,
) -> Self {
Self {
center,
error_x_pos: x_pos,
error_x_neg: x_neg,
error_y_pos: y_pos,
error_y_neg: y_neg,
color,
width,
cap_size: width * 2.0,
}
}
pub fn bounds(&self) -> BoundingBox {
BoundingBox::new(
self.center.x - self.error_x_neg,
self.center.x + self.error_x_pos,
self.center.y - self.error_y_neg,
self.center.y + self.error_y_pos,
)
}
}
#[derive(Debug, Clone)]
pub struct PlotElementStats {
pub line_segments: PoolStats,
pub markers: PoolStats,
pub polygons: PoolStats,
pub text_elements: PoolStats,
pub error_bars: PoolStats,
}
impl PlotElementStats {
pub fn total_efficiency(&self) -> f32 {
let total_hits = self.line_segments.hits
+ self.markers.hits
+ self.polygons.hits
+ self.text_elements.hits
+ self.error_bars.hits;
let total_requests = total_hits
+ self.line_segments.misses
+ self.markers.misses
+ self.polygons.misses
+ self.text_elements.misses
+ self.error_bars.misses;
if total_requests > 0 {
total_hits as f32 / total_requests as f32
} else {
0.0
}
}
pub fn total_allocations(&self) -> usize {
self.line_segments.total_allocated
+ self.markers.total_allocated
+ self.polygons.total_allocated
+ self.text_elements.total_allocated
+ self.error_bars.total_allocated
}
}
static PLOT_ELEMENT_STORAGE: std::sync::OnceLock<PlotElementStorage> = std::sync::OnceLock::new();
pub fn get_plot_element_storage() -> &'static PlotElementStorage {
PLOT_ELEMENT_STORAGE.get_or_init(PlotElementStorage::new)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_segment_creation() {
let start = Point2f::new(0.0, 0.0);
let end = Point2f::new(3.0, 4.0);
let line = LineSegment::new(start, end, LineStyle::Solid, Color::BLACK, 1.0);
assert_eq!(line.length(), 5.0); let bounds = line.bounds();
assert_eq!(bounds.min_x, 0.0);
assert_eq!(bounds.max_x, 3.0);
}
#[test]
fn test_element_storage_pooling() {
let storage = PlotElementStorage::new();
{
let mut lines1 = storage.get_line_storage(100);
lines1.get_mut().push(LineSegment::new(
Point2f::zero(),
Point2f::new(1.0, 1.0),
LineStyle::Solid,
Color::RED,
1.0,
));
}
{
let lines2 = storage.get_line_storage(50);
assert!(lines2.get().capacity() >= 50);
}
let stats = storage.get_pool_stats();
assert!(stats.line_segments.hits > 0 || stats.line_segments.misses > 0);
}
#[test]
fn test_polygon_bounds() {
let mut polygon = Polygon::new(10, Color::RED, Color::BLACK, 1.0);
polygon.add_vertex(Point2f::new(0.0, 0.0));
polygon.add_vertex(Point2f::new(5.0, 0.0));
polygon.add_vertex(Point2f::new(2.5, 5.0));
let bounds = polygon.bounds();
assert_eq!(bounds.min_x, 0.0);
assert_eq!(bounds.max_x, 5.0);
assert_eq!(bounds.min_y, 0.0);
assert_eq!(bounds.max_y, 5.0);
}
#[test]
fn test_error_bar_creation() {
let center = Point2f::new(5.0, 10.0);
let error_bar = ErrorBar::symmetric(center, 1.0, 2.0, Color::BLUE, 0.5);
let bounds = error_bar.bounds();
assert_eq!(bounds.min_x, 4.0); assert_eq!(bounds.max_x, 6.0); assert_eq!(bounds.min_y, 8.0); assert_eq!(bounds.max_y, 12.0); }
}