jake
3 years ago
2 changed files with 385 additions and 0 deletions
@ -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<EG: EntityGateway>: 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<Box<dyn ItemAction<EG>>>,
|
||||
|
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<A: ItemAction<EG> + 'static>(&mut self, action: A) {
|
||||
|
//pub fn action<A: Into<Box<dyn ItemAction<EG>>>>(&mut self, action: A) {
|
||||
|
//pub fn action(&mut self, action: impl ItemAction<EG>) {
|
||||
|
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<U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> 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<E: std::fmt::Debug, U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> FinalizedItemTransaction<U, E, EG> {
|
||||
|
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<T, E, EG> {
|
||||
|
FinalizedItemTransaction {
|
||||
|
error: self.error,
|
||||
|
prev: self.prev,
|
||||
|
action_queue: self.actions.action_queue,
|
||||
|
}
|
||||
|
}
|
||||
|
*/
|
||||
|
}
|
||||
|
|
||||
|
|
||||
|
#[derive(Error, Debug)]
|
||||
|
#[error("")]
|
||||
|
pub enum TransactionError<E: std::fmt::Debug> {
|
||||
|
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<T, E: std::fmt::Debug, EG: EntityGateway> {
|
||||
|
value: Result<T, E>,
|
||||
|
action_queue: Vec<Box<dyn ItemAction<EG>>>,
|
||||
|
}
|
||||
|
|
||||
|
impl<T, E: std::fmt::Debug, EG: EntityGateway> FinalizedItemTransaction<T, E, EG> {
|
||||
|
pub async fn commit(self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<T, TransactionError<E>> {
|
||||
|
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<UserAccountEntity, GatewayError> {
|
||||
|
self.d1_set = user.username;
|
||||
|
Ok(UserAccountEntity::default())
|
||||
|
}
|
||||
|
|
||||
|
async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
|
||||
|
self.d2_inc += char.slot;
|
||||
|
Ok(CharacterEntity::default())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
|
||||
|
#[async_trait::async_trait]
|
||||
|
impl<EG: EntityGateway> ItemAction<EG> 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<EG: EntityGateway> ItemAction<EG> 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<CharacterEntity, GatewayError> {
|
||||
|
self.d2_inc += char.slot;
|
||||
|
Ok(CharacterEntity::default())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
|
||||
|
#[async_trait::async_trait]
|
||||
|
impl<EG: EntityGateway> ItemAction<EG> 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<EG: EntityGateway> ItemAction<EG> 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<CharacterEntity, GatewayError> {
|
||||
|
self.d2_inc += char.slot;
|
||||
|
Ok(CharacterEntity::default())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
|
||||
|
#[async_trait::async_trait]
|
||||
|
impl<EG: EntityGateway> ItemAction<EG> 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<EG: EntityGateway> ItemAction<EG> 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)))));
|
||||
|
}
|
||||
|
}
|
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue