use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
use std::cell::RefCell;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
const INITIAL_POOL_CAPACITY: usize = 16;
const MAX_POOL_SIZE: usize = 128;
pub struct Pool<T> {
objects: Vec<T>,
capacity: usize,
}
impl<T> Pool<T> {
pub fn new(capacity: usize) -> Self {
Self {
objects: Vec::with_capacity(capacity),
capacity,
}
}
pub fn get<F>(&mut self, create: F) -> T
where
F: FnOnce() -> T,
{
self.objects.pop().unwrap_or_else(create)
}
pub fn put(&mut self, obj: T) {
if self.objects.len() < MAX_POOL_SIZE {
self.objects.push(obj);
}
}
pub fn clear(&mut self) {
self.objects.clear();
}
pub fn len(&self) -> usize {
self.objects.len()
}
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
}
impl<T> Default for Pool<T> {
fn default() -> Self {
Self::new(INITIAL_POOL_CAPACITY)
}
}
pub struct PoolGuard<'a, T> {
object: ManuallyDrop<T>,
pool: &'a RefCell<Pool<T>>,
consumed: bool,
}
impl<'a, T> PoolGuard<'a, T> {
fn new(object: T, pool: &'a RefCell<Pool<T>>) -> Self {
Self {
object: ManuallyDrop::new(object),
pool,
consumed: false,
}
}
#[allow(unsafe_code)]
pub fn into_inner(mut self) -> T {
self.consumed = true;
unsafe { ManuallyDrop::take(&mut self.object) }
}
}
impl<'a, T> Deref for PoolGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.object
}
}
impl<'a, T> DerefMut for PoolGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.object
}
}
impl<'a, T> Drop for PoolGuard<'a, T> {
#[allow(unsafe_code)]
fn drop(&mut self) {
if !self.consumed {
let object = unsafe { ManuallyDrop::take(&mut self.object) };
if let Ok(mut pool) = self.pool.try_borrow_mut() {
pool.put(object);
}
}
}
}
thread_local! {
static POINT_POOL: RefCell<Pool<Point>> = RefCell::new(Pool::default());
static LINESTRING_POOL: RefCell<Pool<LineString>> = RefCell::new(Pool::default());
static POLYGON_POOL: RefCell<Pool<Polygon>> = RefCell::new(Pool::default());
static COORDINATE_VEC_POOL: RefCell<Pool<Vec<Coordinate>>> = RefCell::new(Pool::default());
}
#[allow(unsafe_code)]
pub fn get_pooled_point(x: f64, y: f64) -> PoolGuard<'static, Point> {
POINT_POOL.with(|pool| {
let mut pool_ref = pool.borrow_mut();
let mut point = pool_ref.get(|| Point::new(0.0, 0.0));
point.coord.x = x;
point.coord.y = y;
point.coord.z = None;
drop(pool_ref);
PoolGuard::new(point, unsafe { &*(pool as *const RefCell<Pool<Point>>) })
})
}
#[allow(unsafe_code)]
pub fn get_pooled_linestring() -> PoolGuard<'static, LineString> {
LINESTRING_POOL.with(|pool| {
let mut pool_ref = pool.borrow_mut();
let mut linestring = pool_ref.get(|| {
match LineString::new(vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(0.0, 0.0),
]) {
Ok(ls) => ls,
Err(_) => {
match LineString::new(vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(1.0, 1.0),
]) {
Ok(ls) => ls,
Err(_) => {
LineString {
coords: vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(1.0, 1.0),
],
}
}
}
}
}
});
if linestring.len() < 2 {
linestring.coords.clear();
linestring.coords.push(Coordinate::new_2d(0.0, 0.0));
linestring.coords.push(Coordinate::new_2d(0.0, 0.0));
}
drop(pool_ref);
PoolGuard::new(linestring, unsafe {
&*(pool as *const RefCell<Pool<LineString>>)
})
})
}
#[allow(unsafe_code)]
pub fn get_pooled_polygon() -> PoolGuard<'static, Polygon> {
POLYGON_POOL.with(|pool| {
let mut pool_ref = pool.borrow_mut();
let mut polygon = pool_ref.get(|| {
let ring = match LineString::new(vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(1.0, 0.0),
Coordinate::new_2d(0.0, 1.0),
Coordinate::new_2d(0.0, 0.0),
]) {
Ok(ls) => ls,
Err(_) => {
LineString {
coords: vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(1.0, 0.0),
Coordinate::new_2d(0.0, 1.0),
Coordinate::new_2d(0.0, 0.0),
],
}
}
};
match Polygon::new(ring, vec![]) {
Ok(poly) => poly,
Err(_) => {
Polygon {
exterior: LineString {
coords: vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(1.0, 0.0),
Coordinate::new_2d(0.0, 1.0),
Coordinate::new_2d(0.0, 0.0),
],
},
interiors: vec![],
}
}
}
});
if polygon.exterior.len() < 4 {
polygon.exterior.coords.clear();
polygon.exterior.coords.push(Coordinate::new_2d(0.0, 0.0));
polygon.exterior.coords.push(Coordinate::new_2d(1.0, 0.0));
polygon.exterior.coords.push(Coordinate::new_2d(0.0, 1.0));
polygon.exterior.coords.push(Coordinate::new_2d(0.0, 0.0));
}
polygon.interiors.clear();
drop(pool_ref);
PoolGuard::new(polygon, unsafe {
&*(pool as *const RefCell<Pool<Polygon>>)
})
})
}
#[allow(unsafe_code)]
pub fn get_pooled_coordinate_vec() -> PoolGuard<'static, Vec<Coordinate>> {
COORDINATE_VEC_POOL.with(|pool| {
let mut pool_ref = pool.borrow_mut();
let mut vec = pool_ref.get(Vec::new);
vec.clear();
drop(pool_ref);
PoolGuard::new(vec, unsafe {
&*(pool as *const RefCell<Pool<Vec<Coordinate>>>)
})
})
}
pub fn clear_all_pools() {
POINT_POOL.with(|pool| pool.borrow_mut().clear());
LINESTRING_POOL.with(|pool| pool.borrow_mut().clear());
POLYGON_POOL.with(|pool| pool.borrow_mut().clear());
COORDINATE_VEC_POOL.with(|pool| pool.borrow_mut().clear());
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub points_pooled: usize,
pub linestrings_pooled: usize,
pub polygons_pooled: usize,
pub coordinate_vecs_pooled: usize,
}
pub fn get_pool_stats() -> PoolStats {
PoolStats {
points_pooled: POINT_POOL.with(|pool| pool.borrow().len()),
linestrings_pooled: LINESTRING_POOL.with(|pool| pool.borrow().len()),
polygons_pooled: POLYGON_POOL.with(|pool| pool.borrow().len()),
coordinate_vecs_pooled: COORDINATE_VEC_POOL.with(|pool| pool.borrow().len()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_basic_operations() {
clear_all_pools();
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 0);
{
let _point = get_pooled_point(1.0, 2.0);
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 0);
}
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 1);
}
#[test]
fn test_pool_guard_deref() {
clear_all_pools();
let point = get_pooled_point(3.0, 4.0);
assert_eq!(point.x(), 3.0);
assert_eq!(point.y(), 4.0);
}
#[test]
fn test_pool_guard_deref_mut() {
clear_all_pools();
let mut point = get_pooled_point(1.0, 1.0);
point.coord.x = 5.0;
point.coord.y = 6.0;
assert_eq!(point.x(), 5.0);
assert_eq!(point.y(), 6.0);
}
#[test]
fn test_pool_reuse() {
clear_all_pools();
for i in 0..5 {
let _point = get_pooled_point(i as f64, i as f64);
}
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 1);
let _point = get_pooled_point(100.0, 100.0);
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 0);
}
#[test]
fn test_linestring_pool() {
clear_all_pools();
let mut linestring = get_pooled_linestring();
linestring.coords.clear();
linestring.coords.push(Coordinate::new_2d(0.0, 0.0));
linestring.coords.push(Coordinate::new_2d(1.0, 1.0));
assert_eq!(linestring.len(), 2);
drop(linestring);
let stats = get_pool_stats();
assert_eq!(stats.linestrings_pooled, 1);
let linestring = get_pooled_linestring();
assert_eq!(linestring.len(), 2);
}
#[test]
fn test_polygon_pool() {
clear_all_pools();
let polygon = get_pooled_polygon();
assert_eq!(polygon.exterior().len(), 4);
assert_eq!(polygon.interiors().len(), 0);
drop(polygon);
let stats = get_pool_stats();
assert_eq!(stats.polygons_pooled, 1);
}
#[test]
fn test_coordinate_vec_pool() {
clear_all_pools();
let mut coords = get_pooled_coordinate_vec();
coords.push(Coordinate::new_2d(1.0, 2.0));
coords.push(Coordinate::new_2d(3.0, 4.0));
assert_eq!(coords.len(), 2);
drop(coords);
let stats = get_pool_stats();
assert_eq!(stats.coordinate_vecs_pooled, 1);
let coords = get_pooled_coordinate_vec();
assert_eq!(coords.len(), 0);
}
#[test]
fn test_pool_max_size() {
clear_all_pools();
for i in 0..(MAX_POOL_SIZE + 10) {
let _point = get_pooled_point(i as f64, i as f64);
}
let stats = get_pool_stats();
assert!(stats.points_pooled <= MAX_POOL_SIZE);
}
#[test]
fn test_into_inner() {
clear_all_pools();
let guard = get_pooled_point(7.0, 8.0);
let point = guard.into_inner();
assert_eq!(point.x(), 7.0);
assert_eq!(point.y(), 8.0);
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 0);
}
#[test]
fn test_clear_all_pools() {
let _p = get_pooled_point(1.0, 1.0);
let _l = get_pooled_linestring();
let _poly = get_pooled_polygon();
let _coords = get_pooled_coordinate_vec();
drop(_p);
drop(_l);
drop(_poly);
drop(_coords);
let stats = get_pool_stats();
assert!(stats.points_pooled > 0);
assert!(stats.linestrings_pooled > 0);
assert!(stats.polygons_pooled > 0);
assert!(stats.coordinate_vecs_pooled > 0);
clear_all_pools();
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 0);
assert_eq!(stats.linestrings_pooled, 0);
assert_eq!(stats.polygons_pooled, 0);
assert_eq!(stats.coordinate_vecs_pooled, 0);
}
#[test]
fn test_thread_local_isolation() {
use std::thread;
clear_all_pools();
{
let _point = get_pooled_point(1.0, 1.0);
}
let main_stats = get_pool_stats();
assert_eq!(main_stats.points_pooled, 1);
let handle = thread::spawn(|| {
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 0);
{
let _point = get_pooled_point(2.0, 2.0);
}
let stats = get_pool_stats();
assert_eq!(stats.points_pooled, 1);
});
handle.join().expect("Thread panicked");
let main_stats = get_pool_stats();
assert_eq!(main_stats.points_pooled, 1);
}
}