use { crate::{ backend::{ BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorId, ConnectorKernelId, Mode, }, backends::metal::MetalError, state::State, utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt}, video::drm::DrmError, }, ahash::AHashMap, std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, collections::hash_map::Entry, hash::{Hash, Hasher}, rc::Rc, }, thiserror::Error, }; pub trait BackendConnectorTransactionType: Hash + Eq + Any {} pub trait BackendConnectorTransactionTypeDyn: Any { fn eq(&self, other: &dyn BackendConnectorTransactionTypeDyn) -> bool; fn hash(&self, hasher: &mut dyn Hasher); } impl BackendConnectorTransactionTypeDyn for T where T: BackendConnectorTransactionType, { fn eq(&self, other: &dyn BackendConnectorTransactionTypeDyn) -> bool { let Some(other) = (other as &dyn Any).downcast_ref::() else { return false; }; self.eq(other) } fn hash(&self, hasher: &mut dyn Hasher) { struct BufHasher<'a> { buf: Vec, clear: Cell, any: Cell, hasher: RefCell<&'a mut dyn Hasher>, } impl Hasher for BufHasher<'_> { fn finish(&self) -> u64 { let hasher = &mut *self.hasher.borrow_mut(); if self.any.take() { self.clear.set(true); hasher.write(&self.buf); } hasher.finish() } fn write(&mut self, bytes: &[u8]) { if self.clear.take() { self.buf.clear(); } self.any.set(true); self.buf.extend_from_slice(bytes); } } let mut hasher = BufHasher { buf: Default::default(), clear: Cell::new(false), any: Cell::new(false), hasher: RefCell::new(hasher), }; TypeId::of::().hash(&mut hasher); self.hash(&mut hasher) } } impl PartialEq for dyn BackendConnectorTransactionTypeDyn { fn eq(&self, other: &Self) -> bool { self.eq(other) } } impl Eq for dyn BackendConnectorTransactionTypeDyn {} impl Hash for dyn BackendConnectorTransactionTypeDyn { fn hash(&self, state: &mut H) { self.hash(state) } } #[derive(Debug, Error)] pub enum BackendConnectorTransactionError { #[error("The underlying DRM device of connector {} no longer exists", .0)] MissingDrmDevice(ConnectorKernelId), #[error("Connector {} does not support transactions", .0)] TransactionsNotSupported(ConnectorKernelId), #[error("Connector {} is not supported by this transaction", .0)] UnsupportedConnectorType(ConnectorKernelId), #[error("Connector {} cannot be modified because it is leased", .0)] LeasedConnector(ConnectorKernelId), #[error("Connector {} does not exist", .0)] UnknownConnector(ConnectorKernelId), #[error("Cannot initialize connector {} because no CRTC is available", .0)] NoCrtcForConnector(ConnectorKernelId), #[error("Cannot initialize connector {} because no primary plane is available", .0)] NoPrimaryPlaneForConnector(ConnectorKernelId), #[error("Connector {} does not support the requested mode {}", .0, .1)] UnsupportedMode(ConnectorKernelId, Mode), #[error("Connector {} does not support VRR", .0)] NotVrrCapable(ConnectorKernelId), #[error("Connector {} does not support tearing", .0)] TearingNotSupported(ConnectorKernelId), #[error("Connector {} does not support color space {:?}", .0, .1)] ColorSpaceNotSupported(ConnectorKernelId, BackendColorSpace), #[error("Connector {} does not support EOTF {:?}", .0, .1)] EotfNotSupported(ConnectorKernelId, BackendEotfs), #[error("Could not create an hdr metadata blob")] CreateHdrMetadataBlob(#[source] DrmError), #[error("Could not create a mode blob")] CreateModeBlob(#[source] DrmError), #[error("Could not allocate buffers for connector {}", .0)] AllocateScanoutBuffers(ConnectorKernelId, #[source] Box), #[error("Test commit failed")] AtomicTestFailed(#[source] DrmError), #[error("Commit failed")] AtomicCommitFailed(#[source] DrmError), #[error("Could not create a gamma lut blob")] CreateGammaLutBlob(#[source] DrmError), } pub trait BackendConnectorTransaction { fn add( &mut self, connector: &Rc, change: BackendConnectorState, ) -> Result<(), BackendConnectorTransactionError>; fn prepare( self: Box, ) -> Result, BackendConnectorTransactionError>; } pub trait BackendPreparedConnectorTransaction { fn apply( self: Box, ) -> Result, BackendConnectorTransactionError>; } pub trait BackendAppliedConnectorTransaction { fn commit(self: Box); fn rollback(self: Box) -> Result<(), BackendConnectorTransactionError>; } struct Common { state: Rc, states: AHashMap, } pub struct ConnectorTransaction { common: Common, parts: AHashMap, Box>, } pub struct PreparedConnectorTransaction { common: Common, parts: Vec>, } pub struct AppliedConnectorTransaction { common: Common, parts: Vec>, } impl ConnectorTransaction { pub fn new(state: &Rc) -> Self { Self { common: Common { state: state.clone(), states: Default::default(), }, parts: Default::default(), } } pub fn add( &mut self, connector: &Rc, mut state: BackendConnectorState, ) -> Result<(), BackendConnectorTransactionError> { state.serial = self.common.state.backend_connector_state_serials.next(); let ty = connector.transaction_type(); let tran = match self.parts.entry(ty) { Entry::Occupied(v) => v.into_mut(), Entry::Vacant(v) => v.insert(connector.create_transaction()?), }; tran.add(connector, state.clone())?; self.common.states.insert(connector.id(), state); Ok(()) } pub fn prepare( mut self, ) -> Result { let mut new = vec![]; for tran in self.parts.drain_values() { new.push(tran.prepare()?); } Ok(PreparedConnectorTransaction { common: self.common, parts: new, }) } } impl PreparedConnectorTransaction { pub fn apply(self) -> Result { let mut applied = AppliedConnectorTransaction { common: self.common, parts: vec![], }; for tran in self.parts { applied.parts.push(tran.apply()?); } Ok(applied) } } impl AppliedConnectorTransaction { pub fn commit(mut self) { for tran in self.parts.drain(..) { tran.commit(); } for (connector_id, state) in self.common.states.drain() { if let Some(c) = self.common.state.connectors.get(&connector_id) { c.set_state(&self.common.state, state); } } } } impl Drop for AppliedConnectorTransaction { fn drop(&mut self) { for tran in self.parts.drain(..).rev() { if let Err(e) = tran.rollback() { log::error!("Could not roll back transaction: {}", ErrorFmt(e)); } } } }