use std::error::Error;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use all_is_cubes::behavior::{Behavior, BehaviorContext, BehaviorSetTransaction};
use all_is_cubes::math::GridAab;
use all_is_cubes::space::{self, Space, SpaceTransaction};
use all_is_cubes::time::Tick;
use all_is_cubes::transaction::{self, Merge as _, TransactionConflict};
use all_is_cubes::universe::{RefVisitor, VisitRefs};
use crate::vui::{validate_widget_transaction, LayoutGrant, Layoutable, Positioned};
pub type WidgetTransaction = SpaceTransaction;
pub trait Widget: Layoutable + Debug + Send + Sync {
fn controller(self: Arc<Self>, grant: &LayoutGrant) -> Box<dyn WidgetController>;
}
pub trait WidgetController: Debug + Send + Sync + 'static {
fn initialize(&mut self) -> Result<WidgetTransaction, InstallVuiError> {
Ok(WidgetTransaction::default())
}
fn step(&mut self, tick: Tick) -> Result<WidgetTransaction, Box<dyn Error + Send + Sync>> {
let _ = tick;
Ok(WidgetTransaction::default())
}
}
impl WidgetController for Box<dyn WidgetController> {
fn step(&mut self, tick: Tick) -> Result<WidgetTransaction, Box<dyn Error + Send + Sync>> {
(**self).step(tick)
}
fn initialize(&mut self) -> Result<WidgetTransaction, InstallVuiError> {
(**self).initialize()
}
}
#[derive(Debug)]
pub(super) struct WidgetBehavior {
widget: Positioned<Arc<dyn Widget>>,
controller: Mutex<Box<dyn WidgetController>>,
}
impl WidgetBehavior {
pub(crate) fn installation(
widget: Positioned<Arc<dyn Widget>>,
mut controller: Box<dyn WidgetController>,
) -> Result<SpaceTransaction, InstallVuiError> {
let init_txn = match controller.initialize() {
Ok(t) => t,
Err(e) => {
return Err(InstallVuiError::WidgetInitialization {
widget: controller,
error: Box::new(e),
});
}
};
let add_txn = BehaviorSetTransaction::insert(
space::SpaceBehaviorAttachment::new(widget.position.bounds),
Arc::new(WidgetBehavior {
widget,
controller: Mutex::new(controller),
}),
);
init_txn
.merge(SpaceTransaction::behaviors(add_txn))
.map_err(|error| InstallVuiError::Conflict { error })
}
}
impl VisitRefs for WidgetBehavior {
fn visit_refs(&self, _: &mut dyn RefVisitor) {
}
}
impl Behavior<Space> for WidgetBehavior {
fn step(
&self,
context: &BehaviorContext<'_, Space>,
tick: Tick,
) -> all_is_cubes::universe::UniverseTransaction {
let txn = self
.controller
.lock()
.unwrap()
.step(tick)
.expect("TODO: behaviors should have an error reporting path");
validate_widget_transaction(&self.widget.value, &txn, &self.widget.position)
.expect("transaction validation failed");
context.bind_host(txn)
}
fn alive(&self, _: &BehaviorContext<'_, Space>) -> bool {
true
}
fn ephemeral(&self) -> bool {
true
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum InstallVuiError {
#[error("error initializing widget ({:?})", .widget)]
WidgetInitialization {
widget: Box<dyn WidgetController>,
#[source]
error: Box<InstallVuiError>,
},
#[error("transaction conflict involving a widget")]
#[non_exhaustive]
Conflict {
#[source]
error: TransactionConflict,
},
#[error(
"widget attempted to write out of bounds\n\
grant: {grant:?}\n\
attempted write: {erroneous:?}\n\
widget: {widget:?}\n\
"
)]
OutOfBounds {
grant: LayoutGrant,
erroneous: GridAab,
widget: Arc<dyn Widget>,
},
#[error("installing widget tree failed")]
#[non_exhaustive]
ExecuteInstallation {
#[source]
error: transaction::ExecuteError,
},
}
impl From<InstallVuiError> for all_is_cubes::linking::InGenError {
fn from(value: InstallVuiError) -> Self {
all_is_cubes::linking::InGenError::other(value)
}
}
#[cfg(test)]
#[track_caller]
pub(crate) fn instantiate_widget<W: Widget + 'static>(
grant: LayoutGrant,
widget: W,
) -> (Option<GridAab>, Space) {
use crate::vui;
use all_is_cubes::transaction::Transaction as _;
let mut space = Space::builder(grant.bounds).build();
let txn = vui::install_widgets(grant, &vui::LayoutTree::leaf(Arc::new(widget)))
.expect("widget instantiation");
txn.execute(&mut space).expect("widget transaction");
(txn.bounds(), space)
}
#[cfg(test)]
mod tests {
use super::*;
use all_is_cubes::util::assert_send_sync;
#[test]
fn error_is_send_sync() {
assert_send_sync::<InstallVuiError>()
}
fn _assert_widget_trait_is_object_safe(_: &dyn Widget) {}
fn _assert_controller_trait_is_object_safe(_: &dyn WidgetController) {}
}