ark-api 0.17.0-pre.15

Ark API
Documentation
use super::*;

/// A `ValueAccessor` is used to set/get/animate a component value.
/// `bool` and `i64` types will be set to target directly when using `animate`.
///
/// Example:
/// ```
/// let ball_mesh = ...;
/// let ball_entity = Entity::create("ball");
/// ball_entity.render().mesh().set(ball_mesh);
/// ball_entity
///     .transform()
///     .position() // ValueAccessor returned here
///     .set(Vec3::ZERO);
/// let duration = 1.0; // Duration is in seconds
/// ball_entity
///     .transform()
///     .position() // ValueAccessor returned here
///     .animate(Vec3::new(0.0, 2.5, -40.0), duration);
/// ```

pub struct ValueAccessorReadWriteAnimate<T> {
    id: Entity,
    component: ComponentType,
    param: u32,

    _marker: std::marker::PhantomData<T>,
}

/// Struct describing an animation request.

#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub struct AnimationRequest {
    pub id: Entity,
    pub component: ComponentType,
    pub param: u32,
    pub target: Value,
    pub options: AnimationOptions,
    pub enqueue: bool,
}

/// An `AnimationBuilder` is used to build an animation for a component value.
///
/// Set options on it such as curve / easing type, delay, target and finally submit the animation.
///
/// Note: bools and integers (also all kinds of entity references) will be set to their target directly,
/// however `delay` can be used to delay the set of the value.
pub struct AnimationBuilder<T> {
    request: AnimationRequest,
    _marker: std::marker::PhantomData<T>,
}

impl<T> AnimationBuilder<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Creates an `AnimationBuilder` from a `ValueAccessor`.
    pub fn new(accessor: &ValueAccessorReadWriteAnimate<T>) -> Self {
        Self {
            _marker: std::marker::PhantomData,
            request: AnimationRequest {
                id: accessor.id,
                component: accessor.component,
                param: accessor.param,
                options: AnimationOptions {
                    curve: AnimationCurve::Linear,
                    duration: 0.0,
                    loop_count: 1,
                    delay: 0.0,
                    smoothing: 0.0,
                },
                target: Value::none(),
                enqueue: false,
            },
        }
    }

    /// Sets the delay of the animation (how long to wait before it starts)
    pub fn delay(&mut self, seconds: f32) -> &mut Self {
        self.request.options.delay = seconds;
        self
    }

    /// Sets the `target` value that the animated value will approach.
    pub fn target(&mut self, target: T, duration: f32) -> &mut Self {
        self.request.target = <ValueConverter as ValueConverterTrait<T>>::into_value(target);
        self.request.options.duration = duration;
        self
    }

    /// Sets the curve shape of the value animation.
    pub fn curve(&mut self, curve: AnimationCurve) -> &mut Self {
        self.request.options.curve = curve;
        self
    }

    /// Sets the number of times the animation will loop. 0 is currently undefined.
    pub fn loop_count(&mut self, loop_count: u32) -> &mut Self {
        self.request.options.loop_count = loop_count;
        self
    }

    /// Sets the method of smoothing when the animation target changes.
    pub fn smoothing(&mut self, smoothing: f32) -> &mut Self {
        self.request.options.smoothing = smoothing;
        self
    }

    /// Enqueues the animation instead of cancelling a currently on-going animation (if any).
    pub fn enqueue(&mut self) -> &mut Self {
        self.request.enqueue = true;
        self
    }

    /// Finalizes the animation.
    pub fn submit(&self) {
        Animation::enqueue_request_in_global_queue(&self.request);
    }

    /// Finalizes the animation.
    ///
    /// Also gives you the opportunity to pass in a `callback` that will be called
    /// once the animation is finished. For this to work, you must call `entity_messenger().process_messages()`
    /// from your `world_update` function.
    pub fn submit_messenger(&self, callback: Option<Box<AnimationReplyFn<'static>>>) {
        if let Some(callback) = callback {
            EntityMessageDispatcher::global().animation_in_global_queue(&self.request, callback);
        } else {
            EntityMessenger::get()
                .global_queue()
                .animate_value_no_reply(&self.request);
        }
    }

    #[allow(unused)]
    #[inline]
    fn build_request(&self) -> AnimationRequest {
        self.request
    }
}

impl<T> std::ops::Deref for AnimationBuilder<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    type Target = AnimationRequest;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.request
    }
}

/// Value accessor with read-only access
pub struct ValueAccessorRead<T> {
    va: ValueAccessorReadWriteAnimate<T>,
}

impl<T> ValueAccessorRead<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Create a new value accessor with read-only access
    #[inline]
    pub fn new(id: Entity, component: ComponentType, param: u32) -> Self {
        Self {
            va: ValueAccessorReadWriteAnimate::new(id, component, param),
        }
    }

    /// Returns the current value.
    #[inline]
    pub fn get(&self) -> T {
        self.va.get()
    }
}

/// Value accessor with read-write-access
pub struct ValueAccessorReadWrite<T> {
    va: ValueAccessorReadWriteAnimate<T>,
}

impl<T> ValueAccessorReadWrite<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Create a new value accessor with read-write access
    #[inline]
    pub fn new(id: Entity, component: ComponentType, param: u32) -> Self {
        Self {
            va: ValueAccessorReadWriteAnimate::new(id, component, param),
        }
    }

    /// Sets the value to `v`.
    #[inline]
    pub fn set(&self, v: T) {
        self.va.set(v);
    }

    /// Returns the current value.
    #[inline]
    pub fn get(&self) -> T {
        self.va.get()
    }
}

/// Value accessor with read-write-access to data objects.
pub struct ValueAccessorDataReadWrite<T> {
    va: ValueAccessorReadWriteAnimate<T>,
}

impl<T> ValueAccessorDataReadWrite<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Create a new value accessor with read-write access
    #[inline]
    pub fn new(id: Entity, component: ComponentType, param: u32) -> Self {
        Self {
            va: ValueAccessorReadWriteAnimate::new(id, component, param),
        }
    }

    /// Returns the current data handle.
    #[inline]
    fn get_handle(&self) -> DataHandle {
        let v = World::get_entity_value(self.va.id, self.va.component, self.va.param);
        // This will never retain
        <ValueConverter as ValueConverterTrait<DataHandle>>::from_value(&v)
    }

    fn set_handle(&self, handle: DataHandle) {
        World::set_entity_value(
            self.va.id,
            self.va.component,
            self.va.param,
            &Value::from_i64(handle.as_ffi() as i64),
        );
    }

    /// Sets the value to `v`.
    pub fn set(&self, v: T) {
        let old_handle = self.get_handle();
        self.va.set(v); // moved here thus drop will never be called here.
        let new_handle = self.get_handle();
        if old_handle.is_valid() && (old_handle != new_handle) {
            World::destroy_data(old_handle); // We release the data we just overwrote.
        }
    }

    /// Resets the value to an invalid data handle.
    pub fn reset(&self) {
        let old_handle = self.get_handle();
        if old_handle.is_valid() {
            World::destroy_data(old_handle); // We release the data we will overwrite.
        }
        self.set_handle(DataHandle::invalid());
    }

    /// Returns the current value.
    #[inline]
    pub fn get(&self) -> T {
        self.va.get()
    }
}

/// Trait for writing component values
pub trait ValueAccessorWriteTrait<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Sets the value to `v`.
    fn set(&self, v: T);
}

/// Trait for reading component values
pub trait ValueAccessorReadTrait<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Returns the current value.
    fn get(&self) -> T;
}

/// Trait for animating component values
pub trait ValueAccessorAnimateTrait<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Will linearly animate / transition the value from the current value to the
    /// target `v` using `duration` (in seconds).
    ///
    /// Will stop and continue from any ongoing animation.
    ///
    /// Note: bools and integers (also all kinds of entity references) will be set to
    /// their target directly, however delay can be used to delay the set of the value.
    fn animate(&self, v: T, duration: f32);

    /// Will linearly animate / transition the value from the current value to the
    // target `v` using `duration` (in seconds) and apply a smooth filter to the output.
    ///
    /// Will stop and continue from any ongoing animation.
    /// A smoothing constant of 0 means no smoothing, higher value means more smoothing
    /// (i.e. the value will take a longer time to change).
    ///
    /// Useful if you want to smoothen out discontinuities when animating to new targets,
    /// cancelling the old animation.
    ///
    /// Note: bools and integers (also all kinds of entity references) will be set to their
    /// target directly, however delay can be used to delay the set of the value.
    fn animate_smooth(&self, v: T, duration: f32, smoothing: f32);

    /// Starts setting up an animation of this value.
    ///
    /// Call methods on the returned `AnimationBuilder` to configure it.
    fn build_animation(&self) -> AnimationBuilder<T>;
}

impl<T> ValueAccessorReadWriteAnimate<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Create a new value accessor for a specific entity, component and parameter.
    #[inline]
    pub fn new(id: Entity, component: ComponentType, param: u32) -> Self
    where
        T: Sized,
    {
        Self {
            id,
            component,
            param,

            _marker: std::marker::PhantomData,
        }
    }

    /// Sets the value to `v`.
    #[inline]
    pub fn set(&self, v: T) {
        World::set_entity_value(
            self.id,
            self.component,
            self.param,
            &<ValueConverter as ValueConverterTrait<T>>::into_value(v),
        );
    }

    /// Returns the current value.
    #[inline]
    pub fn get(&self) -> T {
        let v = World::get_entity_value(self.id, self.component, self.param);
        <ValueConverter as ValueConverterTrait<T>>::from_value(&v)
    }

    /// Will linearly animate / transition the value from the current value to the
    /// target `v` using `duration` (in seconds).
    ///
    /// Will stop and continue from any ongoing animation.
    ///
    /// Note: bools and integers (also all kinds of entity references) will be set to
    /// their target directly, however delay can be used to delay the set of the value.
    pub fn animate(&self, v: T, duration: f32) {
        AnimationBuilder::<T>::new(self)
            .target(v, duration)
            .submit();
    }

    /// Will linearly animate / transition the value from the current value to the
    // target `v` using `duration` (in seconds) and apply a smooth filter to the output.
    ///
    /// Will stop and continue from any ongoing animation.
    /// A smoothing constant of 0 means no smoothing, higher value means more smoothing
    /// (i.e. the value will take a longer time to change).
    ///
    /// Useful if you want to smoothen out discontinuities when animating to new targets,
    /// cancelling the old animation.
    ///
    /// Note: bools and integers (also all kinds of entity references) will be set to their
    /// target directly, however delay can be used to delay the set of the value.
    pub fn animate_smooth(&self, v: T, duration: f32, smoothing: f32) {
        AnimationBuilder::<T>::new(self)
            .target(v, duration)
            .smoothing(smoothing)
            .submit();
    }

    /// Starts setting up an animation of this value.
    ///
    /// Call methods on the returned `AnimationBuilder` to configure it.
    pub fn build_animation(&self) -> AnimationBuilder<T> {
        AnimationBuilder::<T>::new(self)
    }
}

impl<T> ValueAccessorReadTrait<T> for ValueAccessorRead<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    #[inline]
    fn get(&self) -> T {
        self.va.get()
    }
}

impl<T> ValueAccessorReadTrait<T> for ValueAccessorReadWrite<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    #[inline]
    fn get(&self) -> T {
        self.get()
    }
}

/// Implementation of trait for reading component values
impl<T> ValueAccessorReadTrait<T> for ValueAccessorReadWriteAnimate<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Returns the current value.
    #[inline]
    fn get(&self) -> T {
        self.get()
    }
}

impl<T> ValueAccessorWriteTrait<T> for ValueAccessorReadWrite<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    #[inline]
    fn set(&self, v: T) {
        self.set(v);
    }
}

/// Implementation of trait for writing component values
impl<T> ValueAccessorWriteTrait<T> for ValueAccessorReadWriteAnimate<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Sets the value to `v`.
    #[inline]
    fn set(&self, v: T) {
        self.set(v);
    }
}

/// Implementation of trait for animating component values
impl<T> ValueAccessorAnimateTrait<T> for ValueAccessorReadWriteAnimate<T>
where
    ValueConverter: ValueConverterTrait<T>,
{
    /// Will linearly animate / transition the value from the current value to the
    /// target `v` using `duration` (in seconds).
    ///
    /// Will stop and continue from any ongoing animation.
    ///
    /// Note: bools and integers (also all kinds of entity references) will be set to
    /// their target directly, however delay can be used to delay the set of the value.
    fn animate(&self, v: T, duration: f32) {
        self.animate(v, duration);
    }

    /// Will linearly animate / transition the value from the current value to the
    // target `v` using `duration` (in seconds) and apply a smooth filter to the output.
    ///
    /// Will stop and continue from any ongoing animation.
    /// A smoothing constant of 0 means no smoothing, higher value means more smoothing
    /// (i.e. the value will take a longer time to change).
    ///
    /// Useful if you want to smoothen out discontinuities when animating to new targets,
    /// cancelling the old animation.
    ///
    /// Note: bools and integers (also all kinds of entity references) will be set to their
    /// target directly, however delay can be used to delay the set of the value.
    fn animate_smooth(&self, v: T, duration: f32, smoothing: f32) {
        self.animate_smooth(v, duration, smoothing);
    }

    /// Starts setting up an animation of this value.
    ///
    /// Call methods on the returned `AnimationBuilder` to configure it.
    fn build_animation(&self) -> AnimationBuilder<T> {
        self.build_animation()
    }
}

#[macro_export]
/// Macro for implementing standard accessor trait
macro_rules! impl_world_accessor {
    ($(#[$attr: meta])* $component_type: ident, $param_name: ident, $param_type: ty, $accessor_name: ident, $visibility: ident) => {
        $(#[$attr])*
        #[inline]
        pub fn $accessor_name(&self) -> $visibility<$param_type> {
            use ffi::$component_type;
            $visibility::new(self.id, ffi::ComponentType::$component_type, $component_type::$param_name.into())
        }
    };
}

#[macro_export]
/// Macro for implementing standard accessor trait, but with an index parameter.
macro_rules! impl_world_accessor_indexed {
    ($(#[$attr: meta])* $component_type: ident, $param_name: ident, $param_type: ty, $accessor_name: ident, $index_type: ty, $visibility: ident) => {
        $(#[$attr])*
        #[inline]
        pub fn $accessor_name(&self, index: $index_type) -> $visibility<$param_type> {
            use ffi::$component_type;
            let param_index: u32 = $component_type::$param_name.into();
            $visibility::new(self.id, ffi::ComponentType::$component_type, param_index + index as u32)
            // TODO: If index is out of range, maybe do something?
        }
    };
}