diff --git a/src/backends/xorg/mod.rs b/src/backends/xorg/mod.rs index c9beeb4b..8a760519 100644 --- a/src/backends/xorg/mod.rs +++ b/src/backends/xorg/mod.rs @@ -24,6 +24,7 @@ use uapi::c; use xcb_dl::{ffi, Xcb, XcbShm, XcbXinput, XcbXkb}; use xcb_dl_util::error::{XcbError, XcbErrorParser}; use xcb_dl_util::xcb_box::XcbBox; +use crate::utils::clonecell::CloneCell; #[derive(Debug, Error)] pub enum XorgBackendError { @@ -246,7 +247,7 @@ impl XorgBackend { width: Cell::new(0), height: Cell::new(0), image: RefCell::new(None), - cb: RefCell::new(None), + cb: CloneCell::new(None), }); { let class = "i4\0i4\0"; @@ -378,7 +379,7 @@ impl XorgBackend { _kb: info.deviceid, mouse: info.attachment, removed: Cell::new(false), - cb: RefCell::new(None), + cb: CloneCell::new(None), events: RefCell::new(Default::default()), button_map: Default::default(), last_position: Cell::new((0, 0)), @@ -685,9 +686,8 @@ impl XorgBackend { } } } - let buffer = surface.buffer.borrow(); - if let Some(buffer) = &*buffer { - self.render_buffer(image, buffer, x, y); + if let Some(buffer) = surface.buffer.get() { + self.render_buffer(image, &buffer, x, y); let mut fr = surface.frame_requests.borrow_mut(); for cb in fr.drain(..) { surface.client.dispatch_frame_requests.push(cb); @@ -776,7 +776,7 @@ struct XorgOutput { width: Cell, height: Cell, image: RefCell>>>, - cb: RefCell>>, + cb: CloneCell>>, } impl Drop for XorgOutput { @@ -790,7 +790,7 @@ impl Drop for XorgOutput { impl XorgOutput { fn changed(&self) { - if let Some(cb) = self.cb.borrow().clone() { + if let Some(cb) = self.cb.get() { cb(); } } @@ -814,7 +814,7 @@ impl Output for XorgOutput { } fn on_change(&self, cb: Rc) { - *self.cb.borrow_mut() = Some(cb); + self.cb.set(Some(cb)); } } @@ -824,7 +824,7 @@ struct XorgSeat { _kb: ffi::xcb_input_device_id_t, mouse: ffi::xcb_input_device_id_t, removed: Cell, - cb: RefCell>>, + cb: CloneCell>>, events: RefCell>, button_map: CopyHashMap, last_position: Cell<(ffi::xcb_input_fp1616_t, ffi::xcb_input_fp1616_t)>, @@ -832,7 +832,7 @@ struct XorgSeat { impl XorgSeat { fn changed(&self) { - if let Some(cb) = self.cb.borrow().clone() { + if let Some(cb) = self.cb.get() { cb(); } } @@ -889,6 +889,6 @@ impl Seat for XorgSeat { } fn on_change(&self, cb: Rc) { - *self.cb.borrow_mut() = Some(cb); + self.cb.set(Some(cb)); } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 72e663f4..9171d532 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -247,7 +247,7 @@ impl Clients { dispatch_frame_requests: AsyncQueue::new(), }); let display = Rc::new(WlDisplay::new(&data)); - *data.objects.display.borrow_mut() = Some(display.clone()); + data.objects.display.set(Some(display.clone())); data.objects.add_client_object(display).expect(""); let client = ClientHolder { _handler: global.eng.spawn(tasks::client(data.clone(), recv)), @@ -365,7 +365,7 @@ impl Client { } pub fn display(&self) -> Result, ClientError> { - match self.objects.display.borrow_mut().clone() { + match self.objects.display.get() { Some(d) => Ok(d), _ => Err(ClientError::NotADisplay(WL_DISPLAY_ID)), } diff --git a/src/client/objects.rs b/src/client/objects.rs index dc6c2c14..da4640dc 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -13,9 +13,10 @@ use ahash::AHashMap; use std::cell::{RefCell, RefMut}; use std::mem; use std::rc::Rc; +use crate::utils::clonecell::CloneCell; pub struct Objects { - pub display: RefCell>>, + pub display: CloneCell>>, registry: CopyHashMap>, registries: CopyHashMap>, pub surfaces: CopyHashMap>, @@ -33,7 +34,7 @@ const SEG_SIZE: usize = 8 * mem::size_of::(); impl Objects { pub fn new() -> Self { Self { - display: RefCell::new(None), + display: CloneCell::new(None), registry: Default::default(), registries: Default::default(), surfaces: Default::default(), @@ -54,7 +55,7 @@ impl Objects { } registry.clear(); } - *self.display.borrow_mut() = None; + self.display.set(None); self.regions.clear(); self.registries.clear(); self.surfaces.clear(); diff --git a/src/ifs/wl_buffer/mod.rs b/src/ifs/wl_buffer/mod.rs index fd3a0d48..9f991f78 100644 --- a/src/ifs/wl_buffer/mod.rs +++ b/src/ifs/wl_buffer/mod.rs @@ -70,7 +70,7 @@ impl WlBuffer { { let surfaces = self.surfaces.lock(); for surface in surfaces.values() { - *surface.buffer.borrow_mut() = None; + surface.buffer.set(None); } } self.client.remove_obj(self).await?; diff --git a/src/ifs/wl_seat/mod.rs b/src/ifs/wl_seat/mod.rs index 5a807640..0e07c653 100644 --- a/src/ifs/wl_seat/mod.rs +++ b/src/ifs/wl_seat/mod.rs @@ -16,13 +16,14 @@ use crate::utils::buffd::MsgParser; use crate::utils::copyhashmap::CopyHashMap; use crate::xkbcommon::XkbContext; use crate::State; -use ahash::AHashMap; +use ahash::{AHashMap, AHashSet}; use bstr::ByteSlice; use std::cell::{Cell, RefCell}; use std::io::Write; use std::rc::Rc; pub use types::*; use uapi::{c, OwnedFd}; +use crate::utils::clonecell::CloneCell; id!(WlSeatId); @@ -42,6 +43,8 @@ const TOUCH: u32 = 4; #[allow(dead_code)] const MISSING_CAPABILITY: u32 = 0; +const BTN_LEFT: u32 = 0x110; + pub struct WlSeatGlobal { name: GlobalName, state: Rc, @@ -50,7 +53,9 @@ pub struct WlSeatGlobal { move_start_pos: Cell<(Fixed, Fixed)>, extents_start_pos: Cell<(i32, i32)>, pos: Cell<(Fixed, Fixed)>, - cursor_node: RefCell>, + cursor_node: CloneCell>, + keyboard_node: CloneCell>, + pressed_keys: RefCell>, bindings: RefCell>>>, layout: Rc, layout_size: u32, @@ -82,7 +87,9 @@ impl WlSeatGlobal { move_start_pos: Cell::new((Fixed(0), Fixed(0))), extents_start_pos: Cell::new((0, 0)), pos: Cell::new((Fixed(0), Fixed(0))), - cursor_node: RefCell::new(state.root.clone()), + cursor_node: CloneCell::new(state.root.clone()), + keyboard_node: CloneCell::new(state.root.clone()), + pressed_keys: RefCell::new(Default::default()), bindings: Default::default(), layout, layout_size, @@ -90,7 +97,7 @@ impl WlSeatGlobal { } pub fn move_(&self, node: &Rc) { - let cursor = self.cursor_node.borrow().clone(); + let cursor = self.cursor_node.get(); if cursor.id() == node.id() { self.move_.set(true); self.move_start_pos.set(self.pos.get()); @@ -119,21 +126,42 @@ impl WlSeatGlobal { self.handle_new_position(x, y).await; } - fn for_each_pointer(&self, client: ClientId, mut f: C) + fn for_each_seat(&self, client: ClientId, mut f: C) where - C: FnMut(&Rc), + C: FnMut(&Rc), { let bindings = self.bindings.borrow(); if let Some(hm) = bindings.get(&client) { for seat in hm.values() { - let pointers = seat.pointers.lock(); - for pointer in pointers.values() { - f(pointer); - } + f(seat); } } } + fn for_each_pointer(&self, client: ClientId, mut f: C) + where + C: FnMut(&Rc), + { + self.for_each_seat(client, |seat| { + let pointers = seat.pointers.lock(); + for pointer in pointers.values() { + f(pointer); + } + }) + } + + fn for_each_kb(&self, client: ClientId, mut f: C) + where + C: FnMut(&Rc), + { + self.for_each_seat(client, |seat| { + let keyboards = seat.keyboards.lock(); + for keyboard in keyboards.values() { + f(keyboard); + } + }) + } + async fn tl_pointer_event(&self, tl: &ToplevelNode, mut f: F) where F: FnMut(&Rc) -> DynEventFormatter, @@ -145,9 +173,20 @@ impl WlSeatGlobal { let _ = client.flush().await; } + async fn tl_kb_event(&self, tl: &ToplevelNode, mut f: F) + where + F: FnMut(&Rc) -> DynEventFormatter, + { + let client = &tl.surface.surface.surface.client; + self.for_each_kb(client.id, |p| { + client.event_locked(f(p)); + }); + let _ = client.flush().await; + } + async fn handle_new_position(&self, x: Fixed, y: Fixed) { self.pos.set((x, y)); - let cur_node = self.cursor_node.borrow().clone(); + let cur_node = self.cursor_node.get(); if self.move_.get() { if let NodeKind::Toplevel(tn) = cur_node.into_kind() { let (move_start_x, move_start_y) = self.move_start_pos.get(); @@ -172,7 +211,7 @@ impl WlSeatGlobal { .await; } enter = true; - *self.cursor_node.borrow_mut() = node_dyn; + self.cursor_node.set(node_dyn); } if let NodeKind::Toplevel(tl) = &node { let ee = tl.surface.surface.surface.effective_extents.get(); @@ -196,19 +235,41 @@ impl WlSeatGlobal { if state == KeyState::Released { self.move_.set(false); } - let node = self.cursor_node.borrow().clone().into_kind(); - if let NodeKind::Toplevel(node) = node { + let node = self.cursor_node.get(); + let mut enter = false; + if button == BTN_LEFT { + let kb_node = self.keyboard_node.get(); + if kb_node.id() != node.id() { + enter = true; + if let NodeKind::Toplevel(tl) = kb_node.clone().into_kind() { + self.tl_kb_event(&tl, |k| k.leave(0, tl.surface.surface.surface.id)) + .await; + } + self.keyboard_node.set(node.clone()); + } + } + if let NodeKind::Toplevel(node) = node.into_kind() { let state = match state { KeyState::Released => wl_pointer::RELEASED, KeyState::Pressed => wl_pointer::PRESSED, }; self.tl_pointer_event(&node, |p| p.button(0, 0, button, state)) .await; + if enter { + self.tl_kb_event(&node, |k| { + k.enter( + 0, + node.surface.surface.surface.id, + self.pressed_keys.borrow().iter().cloned().collect(), + ) + }) + .await; + } } } async fn scroll_event(&self, delta: i32, axis: ScrollAxis) { - let node = self.cursor_node.borrow().clone().into_kind(); + let node = self.cursor_node.get().into_kind(); if let NodeKind::Toplevel(node) = node { let axis = match axis { ScrollAxis::Horizontal => wl_pointer::HORIZONTAL_SCROLL, @@ -219,7 +280,31 @@ impl WlSeatGlobal { } } - async fn key_event(&self, _key: u32, _state: KeyState) {} + async fn key_event(&self, key: u32, state: KeyState) { + let state = { + let mut pk = self.pressed_keys.borrow_mut(); + match state { + KeyState::Released => { + if !pk.remove(&key) { + return; + } + log::info!("release"); + wl_keyboard::RELEASED + } + KeyState::Pressed => { + if !pk.insert(key) { + return; + } + log::info!("press"); + wl_keyboard::PRESSED + } + } + }; + let node = self.keyboard_node.get().into_kind(); + if let NodeKind::Toplevel(node) = node { + self.tl_kb_event(&node, |k| k.key(0, 0, key, state)).await; + } + } async fn bind_( self: Rc, @@ -311,11 +396,7 @@ impl WlSeatObj { self.client.add_client_obj(&p)?; self.keyboards.set(req.id, p.clone()); self.client - .event(p.keymap( - wl_keyboard::XKB_V1, - p.keymap_fd()?, - self.global.layout_size, - )) + .event(p.keymap(wl_keyboard::XKB_V1, p.keymap_fd()?, self.global.layout_size)) .await?; Ok(()) } diff --git a/src/ifs/wl_seat/wl_keyboard/mod.rs b/src/ifs/wl_seat/wl_keyboard/mod.rs index 4d40886e..ebab27b6 100644 --- a/src/ifs/wl_seat/wl_keyboard/mod.rs +++ b/src/ifs/wl_seat/wl_keyboard/mod.rs @@ -22,10 +22,8 @@ const REPEAT_INFO: u32 = 5; const NO_KEYMAP: u32 = 0; pub(super) const XKB_V1: u32 = 1; -#[allow(dead_code)] -const RELEASED: u32 = 0; -#[allow(dead_code)] -const PRESSED: u32 = 1; +pub(super) const RELEASED: u32 = 0; +pub(super) const PRESSED: u32 = 1; id!(WlKeyboardId); @@ -65,7 +63,7 @@ impl WlKeyboard { rem as usize, ); match res { - Ok(_) | Err(Errno(c::EINTR)) => { }, + Ok(_) | Err(Errno(c::EINTR)) => {} Err(e) => return Err(WlKeyboardError::KeymapCopy(e.into())), } } diff --git a/src/ifs/wl_shm_pool/mod.rs b/src/ifs/wl_shm_pool/mod.rs index d6aadaa2..4153697a 100644 --- a/src/ifs/wl_shm_pool/mod.rs +++ b/src/ifs/wl_shm_pool/mod.rs @@ -5,10 +5,10 @@ use crate::clientmem::ClientMem; use crate::ifs::wl_buffer::WlBuffer; use crate::object::{Interface, Object, ObjectId}; use crate::utils::buffd::MsgParser; -use std::cell::RefCell; use std::rc::Rc; pub use types::*; use uapi::OwnedFd; +use crate::utils::clonecell::CloneCell; const CREATE_BUFFER: u32 = 0; const DESTROY: u32 = 1; @@ -20,7 +20,7 @@ pub struct WlShmPool { id: WlShmPoolId, client: Rc, fd: OwnedFd, - mem: RefCell>, + mem: CloneCell>, } impl WlShmPool { @@ -33,7 +33,7 @@ impl WlShmPool { Ok(Self { id, client: client.clone(), - mem: RefCell::new(Rc::new(ClientMem::new(fd.raw(), len)?)), + mem: CloneCell::new(Rc::new(ClientMem::new(fd.raw(), len)?)), fd, }) } @@ -47,7 +47,6 @@ impl WlShmPool { if req.height < 0 || req.width < 0 || req.stride < 0 || req.offset < 0 { return Err(CreateBufferError::NegativeParameters); } - let mem = self.mem.borrow(); let buffer = Rc::new(WlBuffer::new( req.id, &self.client, @@ -56,7 +55,7 @@ impl WlShmPool { req.height as u32, req.stride as u32, format, - &mem, + &self.mem.get(), )?); self.client.add_client_obj(&buffer)?; Ok(()) @@ -70,14 +69,13 @@ impl WlShmPool { async fn resize(&self, parser: MsgParser<'_, '_>) -> Result<(), ResizeError> { let req: Resize = self.client.parse(self, parser)?; - let mut mem = self.mem.borrow_mut(); if req.size < 0 { return Err(ResizeError::NegativeSize); } - if (req.size as usize) < mem.len() { + if (req.size as usize) < self.mem.get().len() { return Err(ResizeError::CannotShrink); } - *mem = Rc::new(ClientMem::new(self.fd.raw(), req.size as usize)?); + self.mem.set(Rc::new(ClientMem::new(self.fd.raw(), req.size as usize)?)); Ok(()) } diff --git a/src/ifs/wl_surface/mod.rs b/src/ifs/wl_surface/mod.rs index d97b10f2..e87f1a65 100644 --- a/src/ifs/wl_surface/mod.rs +++ b/src/ifs/wl_surface/mod.rs @@ -21,6 +21,7 @@ use std::mem; use std::ops::{Deref, DerefMut}; use std::rc::Rc; pub use types::*; +use crate::utils::clonecell::CloneCell; const DESTROY: u32 = 0; const ATTACH: u32 = 1; @@ -73,7 +74,7 @@ pub struct WlSurface { opaque_region: Cell>, pub extents: Cell, pub effective_extents: Cell, - pub buffer: RefCell>>, + pub buffer: CloneCell>>, pub children: RefCell>>, role_data: RefCell, pub frame_requests: RefCell>>, @@ -207,7 +208,7 @@ impl WlSurface { opaque_region: Cell::new(None), extents: Default::default(), effective_extents: Default::default(), - buffer: RefCell::new(None), + buffer: CloneCell::new(None), children: Default::default(), role_data: RefCell::new(RoleData::None), frame_requests: RefCell::new(vec![]), @@ -225,7 +226,7 @@ impl WlSurface { fn calculate_extents(&self) { { let mut extents = SurfaceExtents::default(); - if let Some(b) = self.buffer.borrow().deref() { + if let Some(b) = self.buffer.get() { extents.x2 = b.width as i32; extents.y2 = b.height as i32; } @@ -278,6 +279,9 @@ impl WlSurface { async fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), DestroyError> { let _req: Destroy = self.parse(parser)?; + if self.role_data.borrow().is_some() { + return Err(DestroyError::ReloObjectStillExists); + } { let mut children = self.children.borrow_mut(); if let Some(children) = &mut *children { @@ -287,49 +291,12 @@ impl WlSurface { } *children = None; } - let mut ss_parent = None; { - let mut data = self.role_data.borrow_mut(); - match &mut *data { - RoleData::None => {} - RoleData::Subsurface(ss) => { - ss_parent = Some(ss.subsurface.parent.clone()); - let mut children = ss.subsurface.parent.children.borrow_mut(); - if let Some(children) = &mut *children { - children.subsurfaces.remove(&self.id); - } - } - RoleData::XdgSurface(xdg) => { - let children = xdg.popups.lock(); - for child in children.values() { - let mut rd = child.surface.surface.role_data.borrow_mut(); - if let RoleData::XdgSurface(xdg) = &mut *rd { - if let XdgSurfaceRoleData::Popup(p) = &mut xdg.role_data { - p.parent = None; - } - } - } - if let XdgSurfaceRoleData::Popup(p) = &mut xdg.role_data { - if let Some(p) = &p.parent { - let mut rd = p.surface.role_data.borrow_mut(); - if let RoleData::XdgSurface(xdg) = &mut *rd { - xdg.popups.remove(&self.id); - } - } - } - } - } - *data = RoleData::None; - } - { - let buffer = self.buffer.borrow(); - if let Some(buffer) = &*buffer { + let buffer = self.buffer.get(); + if let Some(buffer) = &buffer { buffer.surfaces.remove(&self.id); } } - if let Some(parent) = ss_parent { - parent.calculate_extents(); - } self.client.remove_obj(self).await?; Ok(()) } @@ -337,14 +304,13 @@ impl WlSurface { async fn attach(&self, parser: MsgParser<'_, '_>) -> Result<(), AttachError> { let req: Attach = self.parse(parser)?; { - let mut buffer = self.buffer.borrow_mut(); - if let Some(buffer) = buffer.take() { + if let Some(buffer) = self.buffer.take() { self.client.event(buffer.release()).await?; buffer.surfaces.remove(&self.id); } let mut rd = self.role_data.borrow_mut(); if req.buffer.is_some() { - *buffer = Some(self.client.get_buffer(req.buffer)?); + self.buffer.set(Some(self.client.get_buffer(req.buffer)?)); if let RoleData::XdgSurface(xdg) = &mut *rd { if let XdgSurfaceRoleData::Toplevel(td) = &mut xdg.role_data { if td.node.is_none() { @@ -370,7 +336,7 @@ impl WlSurface { } } } else { - *buffer = None; + self.buffer.set(None); if let RoleData::XdgSurface(xdg) = &mut *rd { if let XdgSurfaceRoleData::Toplevel(td) = &mut xdg.role_data { td.node = None; @@ -574,6 +540,6 @@ impl Object for WlSurface { *self.children.borrow_mut() = None; *self.role_data.borrow_mut() = RoleData::None; mem::take(self.frame_requests.borrow_mut().deref_mut()); - *self.buffer.borrow_mut() = None; + self.buffer.set(None); } } diff --git a/src/ifs/wl_surface/types.rs b/src/ifs/wl_surface/types.rs index 4f95d2b6..4bcf4be9 100644 --- a/src/ifs/wl_surface/types.rs +++ b/src/ifs/wl_surface/types.rs @@ -49,6 +49,8 @@ pub enum DestroyError { ParseFailed(#[source] Box), #[error(transparent)] ClientError(Box), + #[error("Cannot destroy a `wl_surface` before its role object")] + ReloObjectStillExists, } efrom!(DestroyError, ParseFailed, MsgParserError); efrom!(DestroyError, ClientError, ClientError); diff --git a/src/tasks.rs b/src/tasks.rs index 08bc225c..aa8ceceb 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -6,6 +6,7 @@ use crate::utils::asyncevent::AsyncEvent; use crate::State; use std::cell::{Cell, RefCell}; use std::rc::Rc; +use crate::utils::clonecell::CloneCell; pub async fn handle_backend_events(state: Rc) { let mut beh = BackendEventHandler { state }; @@ -77,7 +78,7 @@ impl OutputHandler { floating_outputs: RefCell::new(Default::default()), }, backend: self.output.clone(), - child: RefCell::new(None), + child: CloneCell::new(None), floating: Default::default(), }); self.state.root.outputs.set(self.output.id(), on.clone()); diff --git a/src/tree.rs b/src/tree.rs index a812ba99..8648292c 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -6,6 +6,7 @@ use ahash::AHashMap; use std::cell::{Cell, RefCell}; use std::mem; use std::rc::Rc; +use crate::utils::clonecell::CloneCell; linear_ids!(NodeIds, NodeId); @@ -135,7 +136,7 @@ impl Node for DisplayNode { pub struct OutputNode { pub common: NodeCommon, pub backend: Rc, - pub child: RefCell>>, + pub child: CloneCell>>, pub floating: LinkedList>, } @@ -151,7 +152,7 @@ impl Node for OutputNode { for floating in self.floating.iter() { floating.clear(); } - if let Some(child) = self.child.borrow_mut().take() { + if let Some(child) = self.child.take() { child.clear(); } } diff --git a/src/utils/buffd/formatter.rs b/src/utils/buffd/formatter.rs index 85e0cd7c..6659c0c6 100644 --- a/src/utils/buffd/formatter.rs +++ b/src/utils/buffd/formatter.rs @@ -70,7 +70,7 @@ impl<'a> MsgFormatter<'a> { fds: self.fds, }; f(&mut fmt); - let len = self.buf.out_pos - pos + 4; + let len = self.buf.out_pos - pos - 4; let none = [MaybeUninit::new(0); 4]; self.buf.write(&none[..self.buf.out_pos.wrapping_neg() & 3]); len as u32 diff --git a/src/utils/clonecell.rs b/src/utils/clonecell.rs new file mode 100644 index 00000000..74b2d404 --- /dev/null +++ b/src/utils/clonecell.rs @@ -0,0 +1,49 @@ +use std::cell::UnsafeCell; +use std::mem; +use std::rc::Rc; +use crate::utils::ptr_ext::{MutPtrExt, PtrExt}; + +pub struct CloneCell { + data: UnsafeCell, +} + +impl CloneCell { + pub fn new(t: T) -> Self { + Self { + data: UnsafeCell::new(t), + } + } + + #[inline(always)] + pub fn get(&self) -> T { + unsafe { + self.data.get().deref().clone() + } + } + + #[inline(always)] + pub fn set(&self, t: T) { + unsafe { + let _ = mem::replace(self.data.get().deref_mut(), t); + } + } + + #[inline(always)] + pub fn take(&self) -> T where T: Default { + unsafe { + mem::take(self.data.get().deref_mut()) + } + } +} + +impl Default for CloneCell { + fn default() -> Self { + Self::new(Default::default()) + } +} + +pub unsafe trait UnsafeCellCloneSafe: Clone { } + +unsafe impl UnsafeCellCloneSafe for Option { } + +unsafe impl UnsafeCellCloneSafe for Rc { } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 63c2480f..98f15a2e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,3 +8,4 @@ pub mod oneshot; pub mod ptr_ext; pub mod queue; pub mod vec_ext; +pub mod clonecell;