use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
mem, ops,
};
use crate::{
Item, action,
common::error::RecoverableError,
interactor,
item::{self, IdT, storage},
player,
record::{self, Records},
};
use derive_where::derive_where;
#[doc(inline)]
pub use spru_macro::with;
#[derive(Debug)]
struct ItemStatus<Action> {
version_change: item::version::Change,
flushed_do: Vec<Action>,
flushed_undo: Vec<Action>,
pending_do: RefCell<VecDeque<Action>>,
}
impl<Action> ItemStatus<Action> {
fn pending() -> Self {
Self {
version_change: item::version::Change::create(),
flushed_do: vec![],
flushed_undo: vec![],
pending_do: RefCell::default(),
}
}
fn existing(version: item::Version) -> Self {
Self {
version_change: item::version::Change::update(version),
flushed_do: vec![],
flushed_undo: vec![],
pending_do: RefCell::default(),
}
}
fn enqueue(&self, action: Action) {
self.pending_do.borrow_mut().push_back(action);
}
fn version_change(&self) -> item::version::Change {
if self.flushed_do.is_empty() {
self.version_change
} else {
self.version_change.into_noop()
}
}
fn update_immediate<'l, Storage, Update>(
&mut self,
id: item::Id,
storage: &'l mut Storage,
update: Update,
) -> action::Result<&'l Update::T>
where
Storage: item::Storage,
Action: crate::Action<State = Storage::State>,
Update: Into<Action>
+ action::Update<T: item::storage::Storable<Storage::State>, Undo: Into<Action>>,
{
self.flush(id, storage)?;
let context = action::Context::new(storage, id, self.version_change());
let undo = context.update(&update)?;
if let Some(undo) = undo {
self.flushed_do.push(update.into());
self.flushed_undo.push(undo.into());
}
let item = storage
.get::<Update::T>(id.force_type())
.expect("Item must still exist");
Ok(item.get())
}
fn flush<Storage>(&mut self, id: item::Id, storage: &mut Storage) -> action::Result<()>
where
Action: crate::Action<State = Storage::State>,
Storage: item::Storage,
{
for pending_do in mem::take(self.pending_do.get_mut()) {
let context = action::Context::new(storage, id, self.version_change());
let undo = pending_do.apply(context)?;
if let Some(undo) = undo {
self.flushed_do.push(pending_do);
self.flushed_undo.push(undo);
}
}
Ok(())
}
fn is_flushed(&self) -> bool {
self.pending_do.borrow().is_empty()
}
fn revert<Storage>(self, id: item::Id, storage: &mut Storage) -> action::Result<()>
where
Action: crate::Action<State = Storage::State>,
Storage: item::Storage,
{
let mut version_change = self.version_change.undo();
for undo in self.flushed_undo.into_iter().rev() {
let context = action::Context::new(storage, id, version_change);
let _redo = undo.apply(context)?;
version_change = version_change.into_noop();
}
Ok(())
}
fn expected_version(&self) -> item::Version {
self.version_change.before
}
fn into_records(
self,
id: item::Id,
) -> Option<(record::Packed<Action>, record::Packed<Action>)> {
if !self.is_flushed() {
panic!("Interactor must be flushed first");
}
let mut flushed_do = self.flushed_do.into_iter();
if let Some(first_do) = flushed_do.next() {
let mut packed_do = record::Packed::new(id, self.version_change, first_do);
for do_action in flushed_do {
packed_do.append(do_action);
}
let mut flushed_undo = self.flushed_undo.into_iter();
let first_undo = flushed_undo
.next()
.expect("do and undo must have same length");
let mut packed_undo = record::Packed::new(id, self.version_change.undo(), first_undo);
for undo_action in flushed_undo {
packed_undo.append(undo_action);
}
Some((packed_do, packed_undo))
} else {
None
}
}
}
#[derive(Debug)]
#[derive_where(Default)]
struct ItemsStatus<Action> {
items: RefCell<HashMap<item::Id, ItemStatus<Action>>>,
}
impl<Action> ItemsStatus<Action> {
fn register_read(&self, id: item::Id, version: item::Version) {
self.items
.borrow_mut()
.entry(id)
.or_insert(ItemStatus::existing(version));
}
fn enqueue_create(&self, id: item::Id, action: Action) {
self.items
.borrow_mut()
.entry(id)
.or_insert(ItemStatus::pending())
.enqueue(action);
}
fn enqueue(&self, id: item::Id, action: Action) {
self.items
.borrow_mut()
.get_mut(&id)
.expect("id must be added as read first")
.enqueue(action);
}
fn update_immediate<'l, Storage, Update>(
&mut self,
id: item::Id,
storage: &'l mut Storage,
update: Update,
) -> action::Result<&'l Update::T>
where
Storage: item::Storage,
Action: crate::Action<State = Storage::State>,
Update: Into<Action>
+ action::Update<T: item::storage::Storable<Storage::State>, Undo: Into<Action>>,
{
self.items
.borrow_mut()
.get_mut(&id)
.expect("id must be added as read first")
.update_immediate(id, storage, update)
}
fn flush<Storage>(&mut self, storage: &mut Storage) -> action::Result<()>
where
Action: crate::Action<State = Storage::State>,
Storage: item::Storage,
{
for (&id, item) in self.items.get_mut() {
item.flush(id, storage)?;
}
Ok(())
}
fn revert<Storage>(self, storage: &mut Storage) -> action::Result<()>
where
Action: crate::Action<State = Storage::State>,
Storage: item::Storage,
{
for (id, item) in self.items.into_inner() {
item.revert(id, storage)?;
}
Ok(())
}
fn expected_versions(&self) -> item::version::Expected {
let mut versions = vec![];
for (&item_id, item) in self.items.borrow().iter() {
versions.push((item_id, item.expected_version()));
}
item::version::Expected::new(versions.into_iter())
}
fn into_records(self) -> (Records<Action>, Records<Action>) {
let mut packed_do = Records::new();
let mut packed_undo = Records::new();
for (item_id, item) in self.items.into_inner() {
if let Some((item_do, item_undo)) = item.into_records(item_id) {
packed_do.push(item_do);
packed_undo.push(item_undo);
}
}
(packed_do, packed_undo)
}
}
#[derive(Debug)]
struct Inner<'l, Storage, Action> {
storage: &'l mut Storage,
items_status: ItemsStatus<Action>,
reservation: &'l item::id::Reservation,
}
impl<'l, Storage, Action> Inner<'l, Storage, Action> {
fn get<T>(&self, id: IdT<T>) -> storage::Result<Existing<'_, Storage, Action, T>>
where
Storage: item::Storage,
T: item::storage::Storable<Storage::State>,
{
let item = self.storage.get(id).map_err(|e| e.with_context(id))?;
self.items_status
.register_read(id.untyped(), item.version());
Ok(Existing { inner: self, item })
}
}
#[derive(Debug)]
pub struct Interactor<'l, Storage, Action, Context, Output> {
inner: Inner<'l, Storage, Action>,
context: Context,
output: RefCell<Output>,
}
impl<'l, Storage, Action, Context, Output> Interactor<'l, Storage, Action, Context, Output> {
pub(crate) fn new(
storage: &'l mut Storage,
reservation: &'l item::id::Reservation,
context: Context,
) -> Self
where
Output: Default,
{
Self {
inner: Inner {
storage,
items_status: ItemsStatus::default(),
reservation,
},
context,
output: RefCell::new(Output::default()),
}
}
pub fn context(&self) -> &Context {
&self.context
}
pub(crate) fn context_mut(&mut self) -> &mut Context {
&mut self.context
}
pub fn create<Create>(&self, create: Create) -> Pending<'_, Storage, Action, Create::T>
where
Create: Into<Action> + action::Create,
{
let item_id = self
.inner
.reservation
.claim_id()
.unwrap_or_else(|| unimplemented!("Out of ids"));
self.inner
.items_status
.enqueue_create(item_id, create.into());
Pending {
inner: &self.inner,
item_id: item_id.force_type(),
}
}
pub fn get<T>(&self, id: IdT<T>) -> storage::Result<Existing<'_, Storage, Action, T>>
where
Storage: item::Storage,
T: item::storage::Storable<Storage::State>,
{
self.inner.get(id)
}
pub fn root(&self) -> &Context::Root
where
Context: GetRoot,
{
self.context.get_root()
}
pub fn get_root<Root>(&self) -> storage::Result<Existing<'_, Storage, Action, Root>>
where
Storage: item::Storage,
Root: item::storage::Storable<Storage::State>,
Context: GetRoot<Root = IdT<Root>>,
{
let root_id = *self.context.get_root();
self.get(root_id)
}
#[doc(hidden)]
pub fn update_immediate<Update>(
&mut self,
update: WithUpdate<Update::T, Update>,
) -> action::Result<&Update::T>
where
Storage: item::Storage,
Action: crate::Action<State = Storage::State>,
Update: Into<Action>
+ action::Update<T: item::storage::Storable<Storage::State>, Undo: Into<Action>>,
{
let WithUpdate { id, update } = update;
self.flush()?;
self.inner
.items_status
.update_immediate(id.untyped(), self.inner.storage, update)
}
pub fn enqueue_trigger(&self, trigger: Output::Trigger)
where
Output: EnqueueTrigger,
{
self.output.borrow_mut().enqueue_trigger(trigger);
}
pub fn set_game_outcome(&self, game_outcome: Output::GameOutcome)
where
Output: SetGameOutcome,
{
self.output.borrow_mut().set_game_outcome(game_outcome);
}
pub fn flush(&mut self) -> action::Result<()>
where
Action: crate::Action<State = Storage::State>,
Storage: item::Storage,
{
self.inner.items_status.flush(self.inner.storage)
}
pub(crate) fn revert<E>(self, err: E) -> RecoverableError<E>
where
Action: crate::Action<State = Storage::State>,
Storage: item::Storage,
{
let mut recoverable_error = RecoverableError::new(err);
if let Err(recovery_err) = self.inner.items_status.revert(self.inner.storage) {
recoverable_error.set_recovery_error(recovery_err);
}
recoverable_error
}
pub(crate) fn complete<E>(
mut self,
error: Option<E>,
) -> Result<interactor::Complete<Action, Context, Output>, RecoverableError<E>>
where
Action: crate::Action<State = Storage::State>,
action::Error: Into<E>,
Storage: item::Storage,
{
let result = match error {
None => self.flush().map_err(Into::into),
Some(err) => Err(err),
};
match result {
Ok(_) => Ok(self.complete_internal()),
Err(err) => Err(self.revert(err)),
}
}
fn complete_internal(self) -> Complete<Action, Context, Output> {
let Self {
inner:
Inner {
storage: _st,
items_status,
reservation: _reservation,
},
context,
output,
} = self;
let expected_versions = items_status.expected_versions();
let (do_records, undo_records) = items_status.into_records();
Complete {
expected_versions,
do_records,
undo_records,
context,
output: output.into_inner(),
}
}
}
pub(crate) struct Complete<Action, Context, Output> {
pub(crate) expected_versions: item::version::Expected,
pub(crate) do_records: Records<Action>,
pub(crate) undo_records: Records<Action>,
pub(crate) context: Context,
pub(crate) output: Output,
}
#[doc(hidden)]
pub trait EnqueueTrigger {
type Trigger;
fn enqueue_trigger(&mut self, trigger: Self::Trigger);
}
#[doc(hidden)]
pub trait SetGameOutcome {
type GameOutcome;
fn set_game_outcome(&mut self, game_outcome: Self::GameOutcome);
}
pub(crate) trait TakeTriggers<Trigger> {
fn take_triggers(&mut self) -> VecDeque<Trigger>;
}
pub(crate) trait TakeGameOutcome<GameOutcome> {
fn take_game_outcome(&mut self) -> Option<GameOutcome>;
}
pub(crate) trait PlayerContext {
fn player_context(&self) -> Option<player::Id>;
}
#[doc(hidden)]
pub trait GetRoot {
type Root;
fn get_root(&self) -> &Self::Root;
}
#[derive(Debug)]
pub struct Pending<'i, Storage, Action, T> {
inner: &'i Inner<'i, Storage, Action>,
item_id: IdT<T>,
}
impl<'i, Storage, Action, T> Pending<'i, Storage, Action, T> {
pub fn id(&self) -> IdT<T> {
self.item_id
}
pub fn update<Update>(&self, update: Update) -> &Self
where
Update: Into<Action> + action::Update<T = T>,
{
self.inner
.items_status
.enqueue(self.item_id.untyped(), update.into());
self
}
}
#[derive(Debug)]
pub struct Existing<'i, Storage, Action, T> {
inner: &'i Inner<'i, Storage, Action>,
item: &'i Item<T>,
}
impl<'i, Storage, Action, T> Existing<'i, Storage, Action, T> {
pub fn id(&self) -> IdT<T> {
self.item.id()
}
pub fn update<Update>(&self, update: Update) -> &Self
where
Update: Into<Action> + action::Update<T = T>,
{
self.inner
.items_status
.enqueue(self.item.id().untyped(), update.into());
self
}
#[doc(hidden)]
pub fn update_immediate<Update>(self, update: Update) -> WithUpdate<T, Update> {
WithUpdate {
id: self.id(),
update,
}
}
pub fn destroy<Destroy>(self, destroy: Destroy)
where
Destroy: Into<Action> + action::Destroy<T = T>,
{
self.inner
.items_status
.enqueue(self.item.id().untyped(), destroy.into());
}
}
impl<'i, Storage, Action, T> ops::Deref for Existing<'i, Storage, Action, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.item.get()
}
}
#[doc(hidden)]
#[must_use]
#[derive(Debug)]
pub struct WithUpdate<T, Update> {
id: IdT<T>,
update: Update,
}