Merge pull request #419 from Smithay/feature/swapchain_age

This commit is contained in:
Victoria Brekenfeld 2021-11-25 23:27:19 +01:00 committed by GitHub
commit c0216d15bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 452 additions and 145 deletions

View File

@ -25,6 +25,8 @@
- `render_texture` was removed from `Frame`, use `render_texture_at` or `render_texture_from_to` instead or use `Gles2Renderer::render_texture` as a direct replacement. - `render_texture` was removed from `Frame`, use `render_texture_at` or `render_texture_from_to` instead or use `Gles2Renderer::render_texture` as a direct replacement.
- Remove `InputBackend::dispatch_new_events`, turning `InputBackend` into a definition of backend event types. Future input backends should be a `calloop::EventSource`. - Remove `InputBackend::dispatch_new_events`, turning `InputBackend` into a definition of backend event types. Future input backends should be a `calloop::EventSource`.
- Remove `InputBackend::EventError` associated type as it is unneeded since `dispatch_new_events` was removed. - Remove `InputBackend::EventError` associated type as it is unneeded since `dispatch_new_events` was removed.
- `Swapchain` does not have a generic Userdata-parameter anymore, but utilizes `UserDataMap` instead
- `GbmBufferedSurface::next_buffer` now additionally returns the age of the buffer
### Additions ### Additions

View File

@ -34,6 +34,7 @@ libc = "0.2.103"
libseat= { version = "0.1.1", optional = true } libseat= { version = "0.1.1", optional = true }
libloading = { version="0.7.0", optional = true } libloading = { version="0.7.0", optional = true }
nix = "0.22" nix = "0.22"
once_cell = "1.8.0"
rand = "0.8.4" rand = "0.8.4"
slog = "2" slog = "2"
slog-stdlog = { version = "4", optional = true } slog-stdlog = { version = "4", optional = true }

View File

@ -738,7 +738,7 @@ fn render_surface(
return Ok(()); return Ok(());
}; };
let dmabuf = surface.surface.next_buffer()?; let (dmabuf, _age) = surface.surface.next_buffer()?;
renderer.bind(dmabuf)?; renderer.bind(dmabuf)?;
// and draw to our buffer // and draw to our buffer
@ -867,7 +867,7 @@ fn schedule_initial_render<Data: 'static>(
} }
fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> Result<(), SwapBuffersError> { fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> Result<(), SwapBuffersError> {
let dmabuf = surface.next_buffer()?; let (dmabuf, _age) = surface.next_buffer()?;
renderer.bind(dmabuf)?; renderer.bind(dmabuf)?;
// Does not matter if we render an empty frame // Does not matter if we render an empty frame
renderer renderer

View File

@ -105,9 +105,9 @@ fn main() {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut swapchain = Swapchain::new(allocator, w.into(), h.into(), Fourcc::Argb8888, mods); let mut swapchain = Swapchain::new(allocator, w.into(), h.into(), Fourcc::Argb8888, mods);
let first_buffer: Slot<DumbBuffer<FdWrapper>, _> = swapchain.acquire().unwrap().unwrap(); let first_buffer: Slot<DumbBuffer<FdWrapper>> = swapchain.acquire().unwrap().unwrap();
let framebuffer = surface.add_framebuffer(first_buffer.handle(), 32, 32).unwrap(); let framebuffer = surface.add_framebuffer(first_buffer.handle(), 32, 32).unwrap();
*first_buffer.userdata() = Some(framebuffer); first_buffer.userdata().insert_if_missing(|| framebuffer);
// Get the device as an allocator into the // Get the device as an allocator into the
let mut vblank_handler = VBlankHandler { let mut vblank_handler = VBlankHandler {
@ -137,8 +137,8 @@ fn main() {
} }
pub struct VBlankHandler { pub struct VBlankHandler {
swapchain: Swapchain<DrmDevice<FdWrapper>, DumbBuffer<FdWrapper>, framebuffer::Handle>, swapchain: Swapchain<DrmDevice<FdWrapper>, DumbBuffer<FdWrapper>>,
current: Slot<DumbBuffer<FdWrapper>, framebuffer::Handle>, current: Slot<DumbBuffer<FdWrapper>>,
surface: Rc<DrmSurface<FdWrapper>>, surface: Rc<DrmSurface<FdWrapper>>,
} }
@ -147,9 +147,9 @@ impl VBlankHandler {
{ {
// Next buffer // Next buffer
let next = self.swapchain.acquire().unwrap().unwrap(); let next = self.swapchain.acquire().unwrap().unwrap();
if next.userdata().is_none() { if next.userdata().get::<framebuffer::Handle>().is_none() {
let fb = self.surface.add_framebuffer(next.handle(), 32, 32).unwrap(); let fb = self.surface.add_framebuffer(next.handle(), 32, 32).unwrap();
*next.userdata() = Some(fb); next.userdata().insert_if_missing(|| fb);
} }
// now we could render to the mapping via software rendering. // now we could render to the mapping via software rendering.
@ -165,7 +165,7 @@ impl VBlankHandler {
self.current = next; self.current = next;
} }
let fb = self.current.userdata().unwrap(); let fb = *self.current.userdata().get::<framebuffer::Handle>().unwrap();
self.surface self.surface
.page_flip([(fb, self.surface.plane())].iter(), true) .page_flip([(fb, self.surface.plane())].iter(), true)
.unwrap(); .unwrap();

View File

@ -1,10 +1,11 @@
use std::ops::Deref; use std::ops::Deref;
use std::sync::{ use std::sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, AtomicU8, Ordering},
Arc, Mutex, MutexGuard, Arc,
}; };
use crate::backend::allocator::{Allocator, Buffer, Fourcc, Modifier}; use crate::backend::allocator::{Allocator, Buffer, Fourcc, Modifier};
use crate::utils::user_data::UserDataMap;
pub const SLOT_CAP: usize = 4; pub const SLOT_CAP: usize = 4;
@ -36,7 +37,7 @@ pub const SLOT_CAP: usize = 4;
/// you can store then in the `Slot`s userdata field. If a buffer is re-used, its userdata is preserved for the next time /// you can store then in the `Slot`s userdata field. If a buffer is re-used, its userdata is preserved for the next time
/// it is returned by `acquire()`. /// it is returned by `acquire()`.
#[derive(Debug)] #[derive(Debug)]
pub struct Swapchain<A: Allocator<B>, B: Buffer, U: 'static> { pub struct Swapchain<A: Allocator<B>, B: Buffer> {
/// Allocator used by the swapchain /// Allocator used by the swapchain
pub allocator: A, pub allocator: A,
@ -45,7 +46,7 @@ pub struct Swapchain<A: Allocator<B>, B: Buffer, U: 'static> {
fourcc: Fourcc, fourcc: Fourcc,
modifiers: Vec<Modifier>, modifiers: Vec<Modifier>,
slots: [Arc<InternalSlot<B, U>>; SLOT_CAP], slots: [Arc<InternalSlot<B>>; SLOT_CAP],
} }
/// Slot of a swapchain containing an allocated buffer and its userdata. /// Slot of a swapchain containing an allocated buffer and its userdata.
@ -53,50 +54,56 @@ pub struct Swapchain<A: Allocator<B>, B: Buffer, U: 'static> {
/// The buffer is marked for re-use once all copies are dropped. /// The buffer is marked for re-use once all copies are dropped.
/// Holding on to this struct will block the buffer in the swapchain. /// Holding on to this struct will block the buffer in the swapchain.
#[derive(Debug)] #[derive(Debug)]
pub struct Slot<B: Buffer, U: 'static>(Arc<InternalSlot<B, U>>); pub struct Slot<B: Buffer>(Arc<InternalSlot<B>>);
#[derive(Debug)] #[derive(Debug)]
struct InternalSlot<B: Buffer, U: 'static> { struct InternalSlot<B: Buffer> {
buffer: Option<B>, buffer: Option<B>,
acquired: AtomicBool, acquired: AtomicBool,
userdata: Mutex<Option<U>>, age: AtomicU8,
userdata: UserDataMap,
} }
impl<B: Buffer, U: 'static> Slot<B, U> { impl<B: Buffer> Slot<B> {
/// Retrieve userdata for this slot. /// Retrieve userdata for this slot.
pub fn userdata(&self) -> MutexGuard<'_, Option<U>> { pub fn userdata(&self) -> &UserDataMap {
self.0.userdata.lock().unwrap() &self.0.userdata
}
/// Retrieve the age of the buffer
pub fn age(&self) -> u8 {
self.0.age.load(Ordering::SeqCst)
} }
} }
impl<B: Buffer, U: 'static> Default for InternalSlot<B, U> { impl<B: Buffer> Default for InternalSlot<B> {
fn default() -> Self { fn default() -> Self {
InternalSlot { InternalSlot {
buffer: None, buffer: None,
acquired: AtomicBool::new(false), acquired: AtomicBool::new(false),
userdata: Mutex::new(None), age: AtomicU8::new(0),
userdata: UserDataMap::new(),
} }
} }
} }
impl<B: Buffer, U: 'static> Deref for Slot<B, U> { impl<B: Buffer> Deref for Slot<B> {
type Target = B; type Target = B;
fn deref(&self) -> &B { fn deref(&self) -> &B {
Option::as_ref(&self.0.buffer).unwrap() Option::as_ref(&self.0.buffer).unwrap()
} }
} }
impl<B: Buffer, U: 'static> Drop for Slot<B, U> { impl<B: Buffer> Drop for Slot<B> {
fn drop(&mut self) { fn drop(&mut self) {
self.0.acquired.store(false, Ordering::SeqCst); self.0.acquired.store(false, Ordering::SeqCst);
} }
} }
impl<A, B, U> Swapchain<A, B, U> impl<A, B> Swapchain<A, B>
where where
A: Allocator<B>, A: Allocator<B>,
B: Buffer, B: Buffer,
U: 'static,
{ {
/// Create a new swapchain with the desired allocator, dimensions and pixel format for the created buffers. /// Create a new swapchain with the desired allocator, dimensions and pixel format for the created buffers.
pub fn new( pub fn new(
@ -105,7 +112,7 @@ where
height: u32, height: u32,
fourcc: Fourcc, fourcc: Fourcc,
modifiers: Vec<Modifier>, modifiers: Vec<Modifier>,
) -> Swapchain<A, B, U> { ) -> Swapchain<A, B> {
Swapchain { Swapchain {
allocator, allocator,
width, width,
@ -120,7 +127,7 @@ where
/// ///
/// The swapchain has an internal maximum of four re-usable buffers. /// The swapchain has an internal maximum of four re-usable buffers.
/// This function returns the first free one. /// This function returns the first free one.
pub fn acquire(&mut self) -> Result<Option<Slot<B, U>>, A::Error> { pub fn acquire(&mut self) -> Result<Option<Slot<B>>, A::Error> {
if let Some(free_slot) = self if let Some(free_slot) = self
.slots .slots
.iter_mut() .iter_mut()
@ -144,6 +151,27 @@ where
Ok(None) Ok(None)
} }
/// Mark a given buffer as submitted.
///
/// This might effect internal data (e.g. buffer age) and may only be called,
/// if the buffer was actually used for display.
/// Buffers can always just be safely discarded by dropping them, but not
/// calling this function may affect performance characteristics
/// (e.g. by not tracking the buffer age).
pub fn submitted(&self, slot: Slot<B>) {
// don't mess up the state, if the user submitted and old buffer, after e.g. a resize
if !self.slots.iter().any(|other| Arc::ptr_eq(&slot.0, other)) {
return;
}
slot.0.age.store(1, Ordering::SeqCst);
for other_slot in &self.slots {
if !Arc::ptr_eq(other_slot, &slot.0) {
other_slot.age.fetch_add(1, Ordering::SeqCst);
}
}
}
/// Change the dimensions of newly returned buffers. /// Change the dimensions of newly returned buffers.
/// ///
/// Already obtained buffers are unaffected and will be cleaned up on drop. /// Already obtained buffers are unaffected and will be cleaned up on drop.
@ -156,4 +184,9 @@ where
self.height = height; self.height = height;
self.slots = Default::default(); self.slots = Default::default();
} }
/// Remove all internally cached buffers to e.g. reset age values
pub fn reset_buffers(&mut self) {
self.slots = Default::default();
}
} }

View File

@ -17,22 +17,16 @@ use crate::backend::SwapBuffersError;
use slog::{debug, error, o, trace, warn}; use slog::{debug, error, o, trace, warn};
/// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`]. /// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`].
#[derive(Debug)]
pub struct GbmBufferedSurface<D: AsRawFd + 'static> { pub struct GbmBufferedSurface<D: AsRawFd + 'static> {
buffers: Buffers<D>, current_fb: Slot<BufferObject<()>>,
swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>, pending_fb: Option<Slot<BufferObject<()>>>,
queued_fb: Option<Slot<BufferObject<()>>>,
next_fb: Option<Slot<BufferObject<()>>>,
swapchain: Swapchain<GbmDevice<D>, BufferObject<()>>,
drm: Arc<DrmSurface<D>>, drm: Arc<DrmSurface<D>>,
} }
// TODO: Replace with #[derive(Debug)] once gbm::BufferObject implements debug
impl<D: std::fmt::Debug + AsRawFd + 'static> std::fmt::Debug for GbmBufferedSurface<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GbmBufferedSurface")
.field("buffers", &self.buffers)
.field("drm", &self.drm)
.finish_non_exhaustive()
}
}
impl<D> GbmBufferedSurface<D> impl<D> GbmBufferedSurface<D>
where where
D: AsRawFd + 'static, D: AsRawFd + 'static,
@ -127,7 +121,7 @@ where
let mode = drm.pending_mode(); let mode = drm.pending_mode();
let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)> = Swapchain::new( let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>> = Swapchain::new(
allocator, allocator,
mode.size().0 as u32, mode.size().0 as u32,
mode.size().1 as u32, mode.size().1 as u32,
@ -148,14 +142,17 @@ where
let fb = attach_framebuffer(&drm, &*buffer)?; let fb = attach_framebuffer(&drm, &*buffer)?;
let dmabuf = buffer.export()?; let dmabuf = buffer.export()?;
let handle = fb.fb; let handle = fb.fb;
*buffer.userdata() = Some((dmabuf, fb)); buffer.userdata().insert_if_missing(|| dmabuf);
buffer.userdata().insert_if_missing(|| fb);
match drm.test_buffer(handle, &mode, true) { match drm.test_buffer(handle, &mode, true) {
Ok(_) => { Ok(_) => {
debug!(logger, "Choosen format: {:?}", format); debug!(logger, "Choosen format: {:?}", format);
let buffers = Buffers::new(drm.clone(), buffer);
Ok(GbmBufferedSurface { Ok(GbmBufferedSurface {
buffers, current_fb: buffer,
pending_fb: None,
queued_fb: None,
next_fb: None,
swapchain, swapchain,
drm, drm,
}) })
@ -170,12 +167,29 @@ where
} }
} }
/// Retrieves the next buffer to be rendered into. /// Retrieves the next buffer to be rendered into and it's age.
/// ///
/// *Note*: This function can be called multiple times and /// *Note*: This function can be called multiple times and
/// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]). /// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]).
pub fn next_buffer(&mut self) -> Result<Dmabuf, Error> { pub fn next_buffer(&mut self) -> Result<(Dmabuf, u8), Error> {
self.buffers.next(&mut self.swapchain) if self.next_fb.is_none() {
let slot = self.swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?;
let maybe_buffer = slot.userdata().get::<Dmabuf>().cloned();
if maybe_buffer.is_none() {
let dmabuf = slot.export().map_err(Error::AsDmabufError)?;
let fb_handle = attach_framebuffer(&self.drm, &*slot)?;
let userdata = slot.userdata();
userdata.insert_if_missing(|| dmabuf);
userdata.insert_if_missing(|| fb_handle);
}
self.next_fb = Some(slot);
}
let slot = self.next_fb.as_ref().unwrap();
Ok((slot.userdata().get::<Dmabuf>().unwrap().clone(), slot.age()))
} }
/// Queues the current buffer for rendering. /// Queues the current buffer for rendering.
@ -184,7 +198,11 @@ where
/// when a vblank event is received, that denotes successful scanout of the buffer. /// when a vblank event is received, that denotes successful scanout of the buffer.
/// Otherwise the underlying swapchain will eventually run out of buffers. /// Otherwise the underlying swapchain will eventually run out of buffers.
pub fn queue_buffer(&mut self) -> Result<(), Error> { pub fn queue_buffer(&mut self) -> Result<(), Error> {
self.buffers.queue() self.queued_fb = self.next_fb.take();
if self.pending_fb.is_none() && self.queued_fb.is_some() {
self.submit()?;
}
Ok(())
} }
/// Marks the current frame as submitted. /// Marks the current frame as submitted.
@ -193,7 +211,31 @@ where
/// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface. /// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
/// Otherwise the underlying swapchain will run out of buffers eventually. /// Otherwise the underlying swapchain will run out of buffers eventually.
pub fn frame_submitted(&mut self) -> Result<(), Error> { pub fn frame_submitted(&mut self) -> Result<(), Error> {
self.buffers.submitted() if let Some(mut pending) = self.pending_fb.take() {
std::mem::swap(&mut pending, &mut self.current_fb);
self.swapchain.submitted(pending);
if self.queued_fb.is_some() {
self.submit()?;
}
}
Ok(())
}
fn submit(&mut self) -> Result<(), Error> {
// yes it does not look like it, but both of these lines should be safe in all cases.
let slot = self.queued_fb.take().unwrap();
let fb = slot.userdata().get::<FbHandle<D>>().unwrap().fb;
let flip = if self.drm.commit_pending() {
self.drm.commit([(fb, self.drm.plane())].iter(), true)
} else {
self.drm.page_flip([(fb, self.drm.plane())].iter(), true)
};
if flip.is_ok() {
self.pending_fb = Some(slot);
}
flip.map_err(Error::DrmError)
} }
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface /// Returns the underlying [`crtc`](drm::control::crtc) of this surface
@ -284,103 +326,6 @@ impl<A: AsRawFd + 'static> Drop for FbHandle<A> {
} }
} }
type DmabufSlot<D> = Slot<BufferObject<()>, (Dmabuf, FbHandle<D>)>;
struct Buffers<D: AsRawFd + 'static> {
drm: Arc<DrmSurface<D>>,
_current_fb: DmabufSlot<D>,
pending_fb: Option<DmabufSlot<D>>,
queued_fb: Option<DmabufSlot<D>>,
next_fb: Option<DmabufSlot<D>>,
}
// TODO: Replace with #[derive(Debug)] once gbm::BufferObject implements debug
impl<D: std::fmt::Debug + AsRawFd + 'static> std::fmt::Debug for Buffers<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Buffers")
.field("drm", &self.drm)
.finish_non_exhaustive()
}
}
impl<D> Buffers<D>
where
D: AsRawFd + 'static,
{
pub fn new(drm: Arc<DrmSurface<D>>, slot: DmabufSlot<D>) -> Buffers<D> {
Buffers {
drm,
_current_fb: slot,
pending_fb: None,
queued_fb: None,
next_fb: None,
}
}
pub fn next(
&mut self,
swapchain: &mut Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
) -> Result<Dmabuf, Error> {
if let Some(slot) = self.next_fb.as_ref() {
return Ok(slot.userdata().as_ref().unwrap().0.clone());
}
let slot = swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?;
let maybe_buffer = slot.userdata().as_ref().map(|(buf, _)| buf.clone());
let dmabuf = match maybe_buffer {
Some(buf) => buf,
None => {
let dmabuf = slot.export()?;
let fb_handle = attach_framebuffer(&self.drm, &*slot)?;
*slot.userdata() = Some((dmabuf.clone(), fb_handle));
dmabuf
}
};
self.next_fb = Some(slot);
Ok(dmabuf)
}
pub fn queue(&mut self) -> Result<(), Error> {
self.queued_fb = self.next_fb.take();
if self.pending_fb.is_none() && self.queued_fb.is_some() {
self.submit()
} else {
Ok(())
}
}
pub fn submitted(&mut self) -> Result<(), Error> {
if self.pending_fb.is_none() {
return Ok(());
}
self._current_fb = self.pending_fb.take().unwrap();
if self.queued_fb.is_some() {
self.submit()
} else {
Ok(())
}
}
fn submit(&mut self) -> Result<(), Error> {
// yes it does not look like it, but both of these lines should be safe in all cases.
let slot = self.queued_fb.take().unwrap();
let fb = slot.userdata().as_ref().unwrap().1.fb;
let flip = if self.drm.commit_pending() {
self.drm.commit([(fb, self.drm.plane())].iter(), true)
} else {
self.drm.page_flip([(fb, self.drm.plane())].iter(), true)
};
if flip.is_ok() {
self.pending_fb = Some(slot);
}
flip.map_err(Error::DrmError)
}
}
fn attach_framebuffer<A>(drm: &Arc<DrmSurface<A>>, bo: &BufferObject<()>) -> Result<FbHandle<A>, Error> fn attach_framebuffer<A>(drm: &Arc<DrmSurface<A>>, bo: &BufferObject<()>) -> Result<FbHandle<A>, Error>
where where
A: AsRawFd + 'static, A: AsRawFd + 'static,

View File

@ -6,6 +6,8 @@ pub mod signaling;
#[cfg(feature = "x11rb_event_source")] #[cfg(feature = "x11rb_event_source")]
pub mod x11rb; pub mod x11rb;
pub mod user_data;
pub use self::geometry::{Buffer, Logical, Physical, Point, Raw, Rectangle, Size}; pub use self::geometry::{Buffer, Logical, Physical, Point, Raw, Rectangle, Size};
/// This resource is not managed by Smithay /// This resource is not managed by Smithay

324
src/utils/user_data.rs Normal file
View File

@ -0,0 +1,324 @@
//! Various utilities used for user data implementations
use once_cell::sync::OnceCell;
use std::any::Any;
use std::mem::ManuallyDrop;
use std::thread::{self, ThreadId};
use self::list::AppendList;
/// A wrapper for user data, able to store any type, and correctly
/// handling access from a wrong thread
#[derive(Debug)]
pub struct UserData {
inner: OnceCell<UserDataInner>,
}
#[derive(Debug)]
enum UserDataInner {
ThreadSafe(Box<dyn Any + Send + Sync + 'static>),
NonThreadSafe(Box<ManuallyDrop<dyn Any + 'static>>, ThreadId),
}
// UserData itself is always threadsafe, as it only gives access to its
// content if it is send+sync or we are on the right thread
unsafe impl Send for UserData {}
unsafe impl Sync for UserData {}
impl UserData {
/// Create a new UserData instance
pub const fn new() -> UserData {
UserData {
inner: OnceCell::new(),
}
}
/// Sets the UserData to a given value
///
/// The provided closure is called to init the UserData,
/// does nothing is the UserData had already been set.
pub fn set<T: Any + 'static, F: FnOnce() -> T>(&self, f: F) {
self.inner.get_or_init(|| {
UserDataInner::NonThreadSafe(Box::new(ManuallyDrop::new(f())), thread::current().id())
});
}
/// Sets the UserData to a given threadsafe value
///
/// The provided closure is called to init the UserData,
/// does nothing is the UserData had already been set.
pub fn set_threadsafe<T: Any + Send + Sync + 'static, F: FnOnce() -> T>(&self, f: F) {
self.inner
.get_or_init(|| UserDataInner::ThreadSafe(Box::new(f())));
}
/// Attempt to access the wrapped user data
///
/// Will return `None` if either:
///
/// - The requested type `T` does not match the type used for construction
/// - This `UserData` has been created using the non-threadsafe variant and access
/// is attempted from an other thread than the one it was created on
pub fn get<T: 'static>(&self) -> Option<&T> {
match self.inner.get() {
Some(&UserDataInner::ThreadSafe(ref val)) => <dyn Any>::downcast_ref::<T>(&**val),
Some(&UserDataInner::NonThreadSafe(ref val, threadid)) => {
// only give access if we are on the right thread
if threadid == thread::current().id() {
<dyn Any>::downcast_ref::<T>(&***val)
} else {
None
}
}
None => None,
}
}
}
impl Drop for UserData {
fn drop(&mut self) {
// only drop non-Send user data if we are on the right thread, leak it otherwise
if let Some(&mut UserDataInner::NonThreadSafe(ref mut val, threadid)) = self.inner.get_mut() {
if threadid == thread::current().id() {
unsafe {
ManuallyDrop::drop(&mut **val);
}
}
}
}
}
/// A storage able to store several values of `UserData`
/// of different types. It behaves similarly to a `TypeMap`.
#[derive(Debug)]
pub struct UserDataMap {
list: AppendList<UserData>,
}
impl UserDataMap {
/// Create a new map
pub fn new() -> UserDataMap {
UserDataMap {
list: AppendList::new(),
}
}
/// Attempt to access the wrapped user data of a given type
///
/// Will return `None` if no value of type `T` is stored in this `UserDataMap`
/// and accessible from this thread
pub fn get<T: 'static>(&self) -> Option<&T> {
for user_data in &self.list {
if let Some(val) = user_data.get::<T>() {
return Some(val);
}
}
None
}
/// Insert a value in the map if it is not already there
///
/// This is the non-threadsafe variant, the type you insert don't have to be
/// threadsafe, but they will not be visible from other threads (even if they are
/// actually threadsafe).
///
/// If the value does not already exists, the closure is called to create it and
/// this function returns `true`. If the value already exists, the closure is not
/// called, and this function returns `false`.
pub fn insert_if_missing<T: 'static, F: FnOnce() -> T>(&self, init: F) -> bool {
if self.get::<T>().is_some() {
return false;
}
let data = UserData::new();
data.set(init);
self.list.append(data);
true
}
/// Insert a value in the map if it is not already there
///
/// This is the threadsafe variant, the type you insert must be threadsafe and will
/// be visible from all threads.
///
/// If the value does not already exists, the closure is called to create it and
/// this function returns `true`. If the value already exists, the closure is not
/// called, and this function returns `false`.
pub fn insert_if_missing_threadsafe<T: Send + Sync + 'static, F: FnOnce() -> T>(&self, init: F) -> bool {
if self.get::<T>().is_some() {
return false;
}
let data = UserData::new();
data.set_threadsafe(init);
self.list.append(data);
true
}
}
impl Default for UserDataMap {
fn default() -> UserDataMap {
UserDataMap::new()
}
}
mod list {
/*
* This is a lock-free append-only list, it is used as an implementation
* detail of the UserDataMap.
*
* It was extracted from https://github.com/Diggsey/lockless under MIT license
* Copyright © Diggory Blake <diggsey@googlemail.com>
*/
use std::sync::atomic::{AtomicPtr, Ordering};
use std::{mem, ptr};
type NodePtr<T> = Option<Box<Node<T>>>;
#[derive(Debug)]
struct Node<T> {
value: T,
next: AppendList<T>,
}
#[derive(Debug)]
pub struct AppendList<T>(AtomicPtr<Node<T>>);
impl<T> AppendList<T> {
fn node_into_raw(ptr: NodePtr<T>) -> *mut Node<T> {
match ptr {
Some(b) => Box::into_raw(b),
None => ptr::null_mut(),
}
}
unsafe fn node_from_raw(ptr: *mut Node<T>) -> NodePtr<T> {
if ptr.is_null() {
None
} else {
Some(Box::from_raw(ptr))
}
}
fn new_internal(ptr: NodePtr<T>) -> Self {
AppendList(AtomicPtr::new(Self::node_into_raw(ptr)))
}
pub fn new() -> Self {
Self::new_internal(None)
}
pub fn append(&self, value: T) {
self.append_list(AppendList::new_internal(Some(Box::new(Node {
value,
next: AppendList::new(),
}))));
}
unsafe fn append_ptr(&self, p: *mut Node<T>) {
loop {
match self
.0
.compare_exchange_weak(ptr::null_mut(), p, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => return,
Err(head) => {
if !head.is_null() {
return (*head).next.append_ptr(p);
}
}
}
}
}
pub fn append_list(&self, other: AppendList<T>) {
let p = other.0.load(Ordering::Acquire);
mem::forget(other);
unsafe { self.append_ptr(p) };
}
pub fn iter(&self) -> AppendListIterator<'_, T> {
AppendListIterator(&self.0)
}
pub fn iter_mut(&mut self) -> AppendListMutIterator<'_, T> {
AppendListMutIterator(&mut self.0)
}
}
impl<'a, T> IntoIterator for &'a AppendList<T> {
type Item = &'a T;
type IntoIter = AppendListIterator<'a, T>;
fn into_iter(self) -> AppendListIterator<'a, T> {
self.iter()
}
}
impl<'a, T> IntoIterator for &'a mut AppendList<T> {
type Item = &'a mut T;
type IntoIter = AppendListMutIterator<'a, T>;
fn into_iter(self) -> AppendListMutIterator<'a, T> {
self.iter_mut()
}
}
impl<T> Drop for AppendList<T> {
fn drop(&mut self) {
unsafe { Self::node_from_raw(mem::replace(self.0.get_mut(), ptr::null_mut())) };
}
}
#[derive(Debug)]
pub struct AppendListIterator<'a, T>(&'a AtomicPtr<Node<T>>);
impl<'a, T: 'a> Iterator for AppendListIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<&'a T> {
let p = self.0.load(Ordering::Acquire);
if p.is_null() {
None
} else {
unsafe {
self.0 = &(*p).next.0;
Some(&(*p).value)
}
}
}
}
#[derive(Debug)]
pub struct AppendListMutIterator<'a, T>(&'a mut AtomicPtr<Node<T>>);
impl<'a, T: 'a> Iterator for AppendListMutIterator<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<&'a mut T> {
let p = self.0.load(Ordering::Acquire);
if p.is_null() {
None
} else {
unsafe {
self.0 = &mut (*p).next.0;
Some(&mut (*p).value)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::UserDataMap;
#[test]
fn insert_twice() {
let map = UserDataMap::new();
assert_eq!(map.get::<usize>(), None);
assert!(map.insert_if_missing(|| 42usize));
assert!(!map.insert_if_missing(|| 43usize));
assert_eq!(map.get::<usize>(), Some(&42));
}
}