vkobject_rs/
common.rs

1
2use std::{
3	collections::hash_map::DefaultHasher,
4	env,
5	ffi::CString,
6	fmt::Debug,
7	fs::{read, write},
8	hash::{Hash, Hasher},
9	io,
10	iter::FromIterator,
11	mem::{forget, size_of, size_of_val},
12	ops::{Deref, DerefMut},
13	slice,
14	path::PathBuf,
15	thread::sleep,
16	time::Duration,
17};
18use rand::prelude::*;
19
20/// A structure to hold owned C-type strings for ffi usages.
21#[derive(Debug)]
22pub struct CStringArray {
23	strings: Vec<CString>,
24	cstrarr: Vec<*const i8>,
25}
26
27impl CStringArray {
28	/// Create a new `CStringArray` from an array of `&str` or `String`.
29	pub fn new<T>(input: &[T]) -> Self
30	where
31		T: Clone + Into<Vec<u8>> {
32		let strings: Vec<CString> = input.iter().map(|s|CString::new(s.clone()).unwrap()).collect();
33		let cstrarr: Vec<*const i8> = strings.iter().map(|s|s.as_ptr()).collect();
34		Self {
35			strings,
36			cstrarr,
37		}
38	}
39
40	/// Get the number of the strings
41	pub fn len(&self) -> usize {
42		self.strings.len()
43	}
44
45	/// Get is empty
46	pub fn is_empty(&self) -> bool {
47		self.strings.is_empty()
48	}
49
50	/// Get the pointer to the string list.
51	pub fn as_ptr(&self) -> *const *const i8 {
52		self.cstrarr.as_ptr()
53	}
54}
55
56impl<'a> FromIterator<&'a String> for CStringArray {
57	fn from_iter<T>(input: T) -> Self
58	where
59		T: IntoIterator<Item = &'a String> {
60		let strings: Vec<CString> = input.into_iter().map(|s|CString::new(s.clone()).unwrap()).collect();
61		let cstrarr: Vec<*const i8> = strings.iter().map(|s|s.as_ptr()).collect();
62		Self {
63			strings,
64			cstrarr,
65		}
66	}
67}
68
69impl Clone for CStringArray {
70	fn clone(&self) -> Self {
71		let strings = self.strings.clone();
72		let cstrarr: Vec<*const i8> = strings.iter().map(|s|s.as_ptr()).collect();
73		Self {
74			strings,
75			cstrarr,
76		}
77	}
78}
79
80/// Sleep random nanoseconds, the nanosecond value could be bigger than a second.
81pub fn random_sleep<R: Rng>(max_nanos: u64, rng: &mut R) {
82	let sleep_nanos = rng.random_range(0..max_nanos);
83	let secs = sleep_nanos / 1000000000;
84	let nanos = (sleep_nanos % 1000000000) as u32;
85	sleep(Duration::new(secs, nanos));
86}
87
88/// The error returned from the spin function
89#[derive(Debug)]
90pub enum SpinError<E: Debug> {
91	/// The spin function could not acquire the lock
92	SpinFail,
93
94	/// The spin function failed for other reasons
95	OtherError(E)
96}
97
98pub fn spin_work_with_exp_backoff<T, E: Debug, W: FnMut() -> Result<T, SpinError<E>>>(mut spin_func: W, max_sleep_nanos: u64) -> Result<T, E> {
99	let mut sleep_nanos = 1000;
100	let mut rng = SmallRng::from_os_rng();
101	loop {
102		match spin_func() {
103			Ok(r) => return Ok(r),
104			Err(e) => match e {
105				SpinError::SpinFail => {}
106				SpinError::OtherError(e) => return Err(e),
107			}
108		}
109		random_sleep(sleep_nanos, &mut rng);
110		if sleep_nanos < max_sleep_nanos {
111			sleep_nanos = (sleep_nanos * 3) >> 1;
112		}
113	}
114}
115
116/// The resource guard ensures no resource is leaking. On `drop()`, the `destroyer` is used to free the `resource`.
117#[derive(Debug)]
118pub struct ResourceGuard<R, D: Fn(&R)> {
119	resource: Option<R>,
120	destroyer: D,
121}
122
123impl<R, D: Fn(&R)> ResourceGuard<R, D> {
124	/// Create the `ResourceGuard`, `resource` is the resource you want to handle, and `destroyer` is the closure you write to free the resource.
125	pub fn new(resource: R, destroyer: D) -> Self {
126		Self {
127			resource: Some(resource),
128			destroyer,
129		}
130	}
131
132	/// Release the resource from this structure, so it won't be freed when the guard is `drop()`ing.
133	pub fn release(mut self) -> R {
134		self.resource.take().unwrap()
135	}
136}
137
138impl<R, D: Fn(&R)> Deref for ResourceGuard<R, D> {
139	type Target = R;
140	fn deref(&self) -> &R {
141		self.resource.as_ref().unwrap()
142	}
143}
144
145impl<R, D: Fn(&R)> DerefMut for ResourceGuard<R, D> {
146	fn deref_mut(&mut self) -> &mut R {
147		self.resource.as_mut().unwrap()
148	}
149}
150
151impl<R, D: Fn(&R)> Drop for ResourceGuard<R, D> {
152	fn drop(&mut self) {
153		if let Some(resource) = &self.resource {
154			(self.destroyer)(resource);
155		}
156	}
157}
158
159/// Generate a temp file path for cache, the filename is hashed
160pub fn get_hashed_cache_file_path(cache_usage: &str, extension: Option<&str>) -> PathBuf {
161	let exe_path = env::current_exe().unwrap_or(PathBuf::from(env!("CARGO_PKG_NAME")));
162	let mut hasher = DefaultHasher::new();
163	exe_path.to_string_lossy().to_string().hash(&mut hasher);
164	cache_usage.hash(&mut hasher);
165	let hash = hasher.finish();
166	let mut path = std::env::temp_dir();
167	path.push(format!("{hash}"));
168	path.set_extension(extension.unwrap_or("tmp"));
169	path
170}
171
172pub fn load_cache<T: Clone + Copy + Sized>(cache_usage: &str, extension: Option<&str>) -> io::Result<Vec<T>> {
173	let path = get_hashed_cache_file_path(cache_usage, extension);
174	let mut data = read(&path)?;
175	let item_size = size_of::<T>();
176	let ptr = data.as_mut_ptr();
177	let len = data.len() / item_size;
178	let capacity = data.capacity() / item_size;
179	unsafe {
180		forget(data);
181		Ok(Vec::from_raw_parts(ptr as *mut T, len, capacity))
182	}
183}
184
185pub fn save_cache<T: Clone + Copy + Sized>(cache_usage: &str, extension: Option<&str>, data: &[T]) -> io::Result<()> {
186	let path = get_hashed_cache_file_path(cache_usage, extension);
187	let ptr = data.as_ptr() as *const u8;
188	let len = size_of_val(data);
189	let data: &[u8] = unsafe {slice::from_raw_parts(ptr, len)};
190	write(&path, data)
191}
192
193pub fn format_size(size: u64) -> String {
194	let eib = 1024 * 1024 * 1024 * 1024 * 1024;
195	let tib = 1024 * 1024 * 1024 * 1024;
196	let gib = 1024 * 1024 * 1024;
197	let mib = 1024 * 1024;
198	let kib = 1024;
199	if size >= eib {
200		format!("{:.3} EiB", size as f64 / eib as f64)
201	} else if size >= tib {
202		format!("{:.3} TiB", size as f64 / tib as f64)
203	} else if size >= gib {
204		format!("{:.3} GiB", size as f64 / gib as f64)
205	} else if size >= mib {
206		format!("{:.3} MiB", size as f64 / mib as f64)
207	} else if size >= kib {
208		format!("{:.3} KiB", size as f64 / kib as f64)
209	} else {
210		format!("{size} B")
211	}
212}