dvcompute 2.0.0

Discrete event simulation library (sequential simulation)
Documentation
// Copyright (c) 2020-2022  David Sorokin <davsor@mail.ru>, based in Yoshkar-Ola, Russia
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::marker::PhantomData;

#[cfg(feature="cons_mode")]
use std::mem;

#[cfg(feature="cons_mode")]
use std::ptr;

#[cfg(feature="cons_mode")]
use libc::*;

use crate::simulation;
use crate::simulation::point::Point;
use crate::simulation::event::Event;

#[cfg(feature="cons_mode")]
use crate::simulation::error::*;

/// Create a `Disposable` by the specified function.
#[inline]
pub fn cons_disposable<F>(f: F) -> Cons<F>
    where F: FnOnce(&Point) -> simulation::Result<()>
{
    Cons { f: f }
}

/// Create an empty `Disposable`.
#[inline]
pub fn empty_disposable() -> Empty {
    Empty {}
}

/// Concatenate `Disposable` objects.
#[inline]
pub fn concat_disposables<I, M>(disposables: I) -> Concat<I::IntoIter, M>
    where I: IntoIterator<Item = M>,
          M: Disposable
{
    Concat { disposables: disposables.into_iter(), _phantom: PhantomData }
}

/// Trace the computation.
#[inline]
pub fn trace_disposable<M>(msg: String, comp: M) -> Trace<M>
    where M: Disposable
{
    Trace { comp: comp, msg: msg}
}

/// The computation that disposes the observable subscription.
pub trait Disposable {

    /// Dispose something at the specified modeling time.
    #[doc(hidden)]
    fn dispose(self, p: &Point) -> simulation::Result<()>;

    /// Convert to the `Event` computation.
    #[inline]
    fn into_event(self) -> DisposableEvent<Self>
        where Self: Sized
    {
        DisposableEvent { disposable: self }
    }

    /// Merge with another `Disposable` computation.
    #[inline]
    fn merge<U>(self, other: U) -> Merge<Self, U>
        where Self: Sized,
              U: Disposable
    {
        Merge { disposable: self, other: other }
    }

    /// Convert into a boxed representation.
    #[inline]
    fn into_boxed(self) -> DisposableBox
        where Self: Sized + 'static
    {
        DisposableBox::new(move |p: &Point| { self.dispose(p) })
    }
}

/// Allows converting to `Disposable` computations.
pub trait IntoDisposable {

    type Disposable: Disposable;

    /// Convert to the `Disposable` computation.
    fn into_disposable(self) -> Self::Disposable;
}

impl<M: Disposable> IntoDisposable for M {

    type Disposable = M;

    #[inline]
    fn into_disposable(self) -> Self::Disposable {
        self
    }
}

/// The `Disposable` box value.
pub struct DisposableBox {
    f: Box<dyn DisposableFnBox>
}

impl DisposableBox {

    /// Create a new boxed computation.
    #[doc(hidden)]
    #[inline]
    fn new<F>(f: F) -> Self
        where F: FnOnce(&Point) -> simulation::Result<()> + 'static
    {
        DisposableBox {
            f: Box::new(f)
        }
    }

    /// Call the boxed function.
    #[doc(hidden)]
    #[inline]
    pub fn call_box(self, arg: (&Point,)) -> simulation::Result<()> {
        let DisposableBox { f } = self;
        f.call_box(arg)
    }
}

impl Disposable for DisposableBox {

    #[doc(hidden)]
    #[inline]
    fn dispose(self, p: &Point) -> simulation::Result<()> {
        self.call_box((p,))
    }

    #[inline]
    fn into_boxed(self) -> DisposableBox
        where Self: Sized + 'static
    {
        self
    }
}

/// A trait to support the stable version of Rust, where there is no `FnBox`.
trait DisposableFnBox {

    /// Call the corresponding function.
    fn call_box(self: Box<Self>, args: (&Point,)) -> simulation::Result<()>;
}

impl<F> DisposableFnBox for F
    where F: for<'a> FnOnce(&'a Point) -> simulation::Result<()>
{
    fn call_box(self: Box<Self>, args: (&Point,)) -> simulation::Result<()> {
        let this: Self = *self;
        this(args.0)
    }
}

/// It represents a raw trait object.
#[cfg(feature="cons_mode")]
#[repr(C)]
#[derive(Copy, Clone)]
struct DisposableTraitObject {

    field1: *mut c_void,
    field2: *mut c_void
}

/// A C-friendly representaton of the `Disposable` action.
#[cfg(feature="cons_mode")]
#[repr(C)]
pub struct DisposableRepr {

    /// Delete the object.
    delete: unsafe extern "C" fn(obj: *mut DisposableTraitObject),

    /// The callback.
    callback: unsafe extern "C" fn(obj: *mut DisposableTraitObject, p: *const Point) -> *mut ErrorRepr,

    /// The trait object.
    trait_object: DisposableTraitObject
}

#[cfg(feature="cons_mode")]
impl Drop for DisposableRepr {

    fn drop(&mut self) {
        unsafe {
            (self.delete)(&mut self.trait_object);
        }
    }
}

#[cfg(feature="cons_mode")]
impl DisposableRepr {

    /// Convert to a C-friendly representation.
    #[inline]
    pub fn into_repr(comp: DisposableBox) -> DisposableRepr {
        unsafe {
            DisposableRepr {
                delete: delete_disposable_repr,
                callback: call_disposable_repr,
                trait_object: mem::transmute(comp)
            }
        }
    }

    /// Call the representation.
    #[inline]
    fn call_repr(mut self, p: &Point) -> *mut ErrorRepr {
        unsafe {
            let x = (self.callback)(&mut self.trait_object, p);
            mem::forget(self);
            x
        }
    }
}

/// Call the `DisposableBox` representation.
#[cfg(feature="cons_mode")]
unsafe extern "C" fn call_disposable_repr(comp: *mut DisposableTraitObject, p: *const Point) -> *mut ErrorRepr {
    let comp: DisposableBox = mem::transmute(*comp);
    match comp.call_box((&*p,)) {
        Result::Ok(()) => ptr::null_mut(),
        Result::Err(e) => {
            let e = ErrorRepr::new(e);
            Box::into_raw(Box::new(e))
        }
    }
}

/// Delete the `DisposableBox` representation.
#[cfg(feature="cons_mode")]
unsafe extern "C" fn delete_disposable_repr(comp: *mut DisposableTraitObject) {
    let _: DisposableBox = mem::transmute(*comp);
}

#[cfg(feature="cons_mode")]
impl Disposable for DisposableRepr {

    #[doc(hidden)]
    #[inline]
    fn dispose(self, p: &Point) -> simulation::Result<()> {
        unsafe {
            let e = self.call_repr(p);
            if e == ptr::null_mut() {
                Result::Ok(())
            } else {
                let e = ffi_error_repr_into_error(e);
                Result::Err(e)
            }
        }
    }
}

/// The `Disposable` constructed by the specified function.
#[derive(Clone)]
pub struct Cons<F> {

    /// The function that disposes the subscription.
    f: F
}

impl<F> Disposable for Cons<F>
    where F: FnOnce(&Point) -> simulation::Result<()>
{
    #[doc(hidden)]
    #[inline]
    fn dispose(self, p: &Point) -> simulation::Result<()> {
        let Cons { f } = self;
        f(p)
    }
}

/// An empty `Disposable` that disposes nothing.
#[derive(Clone)]
pub struct Empty {}

impl Disposable for Empty {

    #[doc(hidden)]
    #[inline]
    fn dispose(self, _p: &Point) -> simulation::Result<()> {
        Result::Ok(())
    }
}

/// A merge of two `Disposable` computations.
#[derive(Clone)]
pub struct Merge<M, U> {

    /// The current computation.
    disposable: M,

    /// Another computation.
    other: U
}

impl<M, U> Disposable for Merge<M, U>
    where M: Disposable,
          U: Disposable
{
    #[doc(hidden)]
    #[inline]
    fn dispose(self, p: &Point) -> simulation::Result<()> {
        let Merge { disposable, other } = self;
        disposable.dispose(p)?;
        other.dispose(p)
    }
}

/// An event that calls the `Disposable` computation.
#[derive(Clone)]
pub struct DisposableEvent<D> {

    /// The disposable computation.
    disposable: D
}

impl<D> Event for DisposableEvent<D>
    where D: Disposable
{
    type Item = ();

    #[doc(hidden)]
    #[inline]
    fn call_event(self, p: &Point) -> simulation::Result<Self::Item> {
        let DisposableEvent { disposable } = self;
        disposable.dispose(p)
    }
}

/// A concatenation of the `Disposable` computations.
#[derive(Clone)]
pub struct Concat<I, M> {

    /// The disposable computations.
    disposables: I,

    /// To keep the type parameter.
    _phantom: PhantomData<M>
}

impl<I, M> Disposable for Concat<I, M>
    where I: Iterator<Item = M>,
          M: Disposable
{
    #[doc(hidden)]
    #[inline]
    fn dispose(self, p: &Point) -> simulation::Result<()> {
        let Concat { disposables, _phantom } = self;
        for disposable in disposables {
            disposable.dispose(p)?;
        }
        Result::Ok(())
    }
}

/// Trace the computation.
#[must_use = "computations are lazy and do nothing unless to be run"]
#[derive(Clone)]
pub struct Trace<M> {

    /// The computation.
    comp: M,

    /// The message to print.
    msg: String
}

impl<M> Disposable for Trace<M>
    where M: Disposable
{
    #[doc(hidden)]
    #[inline]
    fn dispose(self, p: &Point) -> simulation::Result<()> {
        let Trace { comp, msg } = self;
        p.trace(&msg);
        comp.dispose(p)
    }
}