diff --git a/src/ship/items/mod.rs b/src/ship/items/mod.rs index d70a7f4..7a259ae 100644 --- a/src/ship/items/mod.rs +++ b/src/ship/items/mod.rs @@ -2,6 +2,7 @@ mod bank; mod floor; pub mod inventory; mod manager; +mod transaction; pub mod use_tool; use serde::{Serialize, Deserialize}; diff --git a/src/ship/items/transaction.rs b/src/ship/items/transaction.rs new file mode 100644 index 0000000..a161a2a --- /dev/null +++ b/src/ship/items/transaction.rs @@ -0,0 +1,384 @@ +use crate::entity::gateway::EntityGateway; +use thiserror::Error; +use std::borrow::Cow; +use crate::ship::items::manager::{ItemManager, ItemManagerError}; +use std::collections::HashMap; +use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel}; +use crate::ship::items::bank::*; +use crate::ship::items::floor::*; +use crate::ship::items::inventory::*; +use crate::ship::items::ClientItemId; +use crate::entity::gateway::GatewayError; + +use crate::ship::location::{AreaClient, RoomId}; + +#[derive(Error, Debug)] +#[error("")] +pub enum TransactionCommitError { + Gateway(#[from] GatewayError), + ItemManager(#[from] ItemManagerError), +} + +#[async_trait::async_trait] +pub trait ItemAction: std::marker::Send + std::marker::Sync { + async fn commit(&self, manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError>; +} + +pub struct ItemTransactionActions<'a, EG: EntityGateway> { + action_queue: Vec>>, + pub manager: &'a ItemManager, +} + + +impl<'a, EG: EntityGateway> ItemTransactionActions<'a, EG> { + fn new(manager: &'a ItemManager) -> ItemTransactionActions<'a, EG> { + ItemTransactionActions { + action_queue: Vec::new(), + manager + } + } + + pub fn action + 'static>(&mut self, action: A) { + //pub fn action>>>(&mut self, action: A) { + //pub fn action(&mut self, action: impl ItemAction) { + self.action_queue.push(Box::new(action)) + //self.action_queue.push(action.into()) + } +} + + +pub struct ItemTransaction<'a, T, EG: EntityGateway> { + data: T, + actions: ItemTransactionActions<'a, EG>, +} + +impl<'a, T, EG: EntityGateway> ItemTransaction<'a, T, EG> { + pub fn new(manager: &'a ItemManager, arg: T) -> ItemTransaction<'a, T, EG> { + ItemTransaction { + data: arg, + actions: ItemTransactionActions::new(manager), + } + } + + /* + pub fn act(mut self, action: fn(&mut ItemTransactionActions, &T) -> Result) -> ItemTransaction<'a, U, E, EG> { + if self.error.is_none() { + let k = action(&mut self.actions, &self.prev.unwrap()); + match k { + Ok(k) => { + ItemTransaction { + error: None, + prev: Some(k), + actions: self.actions, + } + }, + Err(err) => { + ItemTransaction { + error: Some(err), + prev: None, + actions: self.actions, + } + } + } + } + else { + ItemTransaction { + error: self.error, + prev: None, + actions: self.actions, + } + } + } +*/ + + pub fn act(mut self, action: fn(&mut ItemTransactionActions, &T) -> Result) -> FinalizedItemTransaction { + match action(&mut self.actions, &self.data) { + Ok(k) => { + FinalizedItemTransaction { + value: Ok(k), + action_queue: self.actions.action_queue, + } + }, + Err(err) => { + FinalizedItemTransaction { + value: Err(err), + action_queue: Vec::new(), + } + } + } + } + + /* + pub fn finalize(self) -> FinalizedItemTransaction { + FinalizedItemTransaction { + error: self.error, + prev: self.prev, + action_queue: self.actions.action_queue, + } + } +*/ +} + + +#[derive(Error, Debug)] +#[error("")] +pub enum TransactionError { + Action(E), + Commit(#[from] TransactionCommitError), + +} + +// this only exists to drop the ItemManager borrow of ItemTransaction so a mutable ItemTransaction can be passed in later +pub struct FinalizedItemTransaction { + value: Result, + action_queue: Vec>>, +} + +impl FinalizedItemTransaction { + pub async fn commit(self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result> { + match self.value { + Ok(value) => { + for action in self.action_queue.into_iter() { + // TODO: better handle rolling back if this ever errors out + //let result = action.item_action(item_manager).await.map_err(|err| TransactionError::Commit(err.into()))?; + //action.gateway_action(entity_gateway, result).await.map_err(|err| TransactionError::Commit(err.into()))?; + action.commit(item_manager, entity_gateway).await.map_err(|err| TransactionError::Commit(err))?; + } + Ok(value) + }, + Err(err) => Err(TransactionError::Action(err)), + } + } +} + + +#[cfg(test)] +mod test { + use super::*; + use crate::entity::account::{UserAccountId, NewUserAccountEntity, UserAccountEntity}; + use crate::entity::character::{NewCharacterEntity, CharacterEntity}; + use crate::entity::gateway::GatewayError; + use thiserror::Error; + + #[async_std::test] + async fn test_item_transaction() { + struct DummyAction1 { + name: String, + } + struct DummyAction2 { + value: u32, + } + + #[derive(Error, Debug)] + #[error("")] + enum DummyError { + Error + } + + #[derive(Default, Clone)] + struct DummyGateway { + d1_set: String, + d2_inc: u32, + } + + #[async_trait::async_trait] + impl EntityGateway for DummyGateway { + async fn create_user(&mut self, user: NewUserAccountEntity) -> Result { + self.d1_set = user.username; + Ok(UserAccountEntity::default()) + } + + async fn create_character(&mut self, char: NewCharacterEntity) -> Result { + self.d2_inc += char.slot; + Ok(CharacterEntity::default()) + } + } + + + #[async_trait::async_trait] + impl ItemAction for DummyAction1 { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + item_manager.id_counter = 55555; + entity_gateway.create_user(NewUserAccountEntity { + username: self.name.clone(), + ..NewUserAccountEntity::default() + }) + .await?; + Ok(()) + } + } + + #[async_trait::async_trait] + impl ItemAction for DummyAction2 { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + item_manager.id_counter += self.value; + entity_gateway.create_character(NewCharacterEntity { + slot: self.value, + ..NewCharacterEntity::new(UserAccountId(0)) + }) + .await?; + Ok(()) + } + } + + let mut item_manager = ItemManager::default(); + let mut entity_gateway = DummyGateway::default(); + + let result = ItemTransaction::new(&item_manager, 12) + .act(|it, k| { + it.action(DummyAction1 {name: "asdf".into()}); + it.action(DummyAction2 {value: 11}); + it.action(DummyAction2 {value: *k}); + if *k == 99 { + return Err(DummyError::Error) + } + Ok(String::from("hello")) + }) + .commit(&mut item_manager, &mut entity_gateway) + .await; + + assert!(entity_gateway.d1_set == "asdf"); + assert!(entity_gateway.d2_inc == 23); + assert!(item_manager.id_counter == 55578); + assert!(result.unwrap() == "hello"); + } + + #[async_std::test] + async fn test_item_transaction_with_action_error() { + struct DummyAction1 { + } + struct DummyAction2 { + } + + #[derive(Error, Debug, PartialEq, Eq)] + #[error("")] + enum DummyError { + Error + } + + #[derive(Default, Clone)] + struct DummyGateway { + d1_set: String, + d2_inc: u32, + } + + #[async_trait::async_trait] + impl EntityGateway for DummyGateway { + async fn create_character(&mut self, char: NewCharacterEntity) -> Result { + self.d2_inc += char.slot; + Ok(CharacterEntity::default()) + } + } + + + #[async_trait::async_trait] + impl ItemAction for DummyAction1 { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + entity_gateway.create_character(NewCharacterEntity { + slot: 1, + ..NewCharacterEntity::new(UserAccountId(0)) + }) + .await?; + Ok(()) + } + } + + #[async_trait::async_trait] + impl ItemAction for DummyAction2 { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + entity_gateway.create_character(NewCharacterEntity { + slot: 1, + ..NewCharacterEntity::new(UserAccountId(0)) + }) + .await?; + Ok(()) + } + } + + let mut item_manager = ItemManager::default(); + let mut entity_gateway = DummyGateway::default(); + + let result = ItemTransaction::new(&item_manager, 12) + .act(|it, _| -> Result<(), _> { + it.action(DummyAction1 {}); + it.action(DummyAction2 {}); + it.action(DummyAction2 {}); + Err(DummyError::Error) + }) + .commit(&mut item_manager, &mut entity_gateway) + .await; + + assert!(entity_gateway.d2_inc == 0); + assert!(matches!(result, Err(TransactionError::Action(DummyError::Error)))); + } + + #[async_std::test] + async fn test_item_transaction_with_commit_error() { + struct DummyAction1 { + } + struct DummyAction2 { + } + + #[derive(Error, Debug, PartialEq, Eq)] + #[error("")] + enum DummyError { + } + + #[derive(Default, Clone)] + struct DummyGateway { + d1_set: String, + d2_inc: u32, + } + + #[async_trait::async_trait] + impl EntityGateway for DummyGateway { + async fn create_character(&mut self, char: NewCharacterEntity) -> Result { + self.d2_inc += char.slot; + Ok(CharacterEntity::default()) + } + } + + + #[async_trait::async_trait] + impl ItemAction for DummyAction1 { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + entity_gateway.create_character(NewCharacterEntity { + slot: 1, + ..NewCharacterEntity::new(UserAccountId(0)) + }) + .await?; + Err(GatewayError::Error.into()) + } + } + + #[async_trait::async_trait] + impl ItemAction for DummyAction2 { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + entity_gateway.create_character(NewCharacterEntity { + slot: 1, + ..NewCharacterEntity::new(UserAccountId(0)) + }) + .await?; + Ok(()) + } + } + + let mut item_manager = ItemManager::default(); + let mut entity_gateway = DummyGateway::default(); + + let result = ItemTransaction::new(&item_manager, 12) + .act(|it, _| -> Result<_, DummyError> { + it.action(DummyAction1 {}); + it.action(DummyAction2 {}); + it.action(DummyAction2 {}); + Ok(()) + }) + .commit(&mut item_manager, &mut entity_gateway) + .await; + + // in an ideal world this would be 0 as rollbacks would occur + assert!(entity_gateway.d2_inc == 1); + assert!(matches!(result, Err(TransactionError::Commit(TransactionCommitError::Gateway(GatewayError::Error))))); + } +} +