ark-api 0.17.0-pre.15

Ark API
Documentation
//! # 📨 Behavior API
//!
//! This API provides functionality for a behavior to communicate with the rest of the world and is mainly
//! used when a behavior is processing an incoming message. Incoming messages are sent from a "controller module",
//! which is a different Ark module type like an Applet or Cmdlet.
//!
//! ## Message passing
//!
//! Messages are the protocols through which behaviors communicate. At the lowest level they are
//! serializable Rust structures.
//!
//! The entire message passing flow between the controller module and behavior modules goes as follows:
//!
//! 1. The controller module creates messages and sends them to Ark
//! 2. Ark owns the actual behavior modules and will, in order, send these messages to the correct
//!    behaviors inside external behavior modules
//! 3. A behavior will then process the just received message
//!     - This is where the Behavior API comes in. During the processing of a message the behavior
//!       can create [`OutgoingMessage`]s and send them back to the controller module through
//!       [`Behavior::send_outgoing_messages`].
//!
//! ## Aspects
//!
//! Aspects are used to associate data with actors, and share it between behaviors. Aspects are just data and
//! don't implement any logic. From the point of view of a behavior, aspects can be used to eagerly
//! publish information/state, so that it doesn't need to be asynchronously queried via message passing.
//!
//! Aspect data can be retrieved through [`Behavior::aspect_get`] given a valid [`AspectAddr`].

use crate::{ffi::behavior_v0 as ffi, Error, ErrorCode};
pub use ffi::{
    ActorId, Guid, LocalBehaviorRegistration, LocalModuleRegistration, OutgoingMessageAddr,
};

#[doc(hidden)]
pub use ffi::API as FFI_API;

/// A unique identifier of a couple comprising a local behavior type id within a behavior module as
/// well as a local instance id.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ForeignBehaviorInstanceId(pub ffi::ForeignBehaviorInstanceId);

impl ForeignBehaviorInstanceId {
    /// Create a new instance id by packing a [`LocalBehaviorTypeId`] and [`LocalBehaviorInstanceId`]
    pub fn pack(type_id: LocalBehaviorTypeId, instance_id: LocalBehaviorInstanceId) -> Self {
        Self((u64::from(type_id.0) << 32) | u64::from(instance_id.0))
    }

    /// Unpack this id to retrieve the [`LocalBehaviorTypeId`] and [`LocalBehaviorInstanceId`]
    pub fn unpack(&self) -> (LocalBehaviorTypeId, LocalBehaviorInstanceId) {
        (
            LocalBehaviorTypeId(((self.0 >> 32) & ((1 << 16) - 1)) as u16),
            LocalBehaviorInstanceId(self.0 as u32),
        )
    }
}

/// A behavior instance index. May not be unique overall, but must form unique pairs with the
/// corresponding `LocalBehaviorTypeId` of the instance's type, resulting in a `ForeignBehaviorInstanceId`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct LocalBehaviorInstanceId(pub u32);

/// An index of the behavior type, local to a behavior module.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct LocalBehaviorTypeId(pub u16);

/// An aspect's address, comprising an aspect guid and an actor id
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AspectAddr {
    /// A unique identifier for an aspect type
    pub aspect_guid: Guid,
    /// The actor this aspect lives on
    pub actor_id: ActorId,
}

/// An outgoing message that can be send from a behavior instance to the controller module
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OutgoingMessage {
    /// The address of the outgoing message
    pub addr: OutgoingMessageAddr,
    /// The serialized message payload
    pub serialized_message: Vec<u8>,
}

impl From<&OutgoingMessage> for ffi::OutgoingMessage {
    fn from(msg: &OutgoingMessage) -> Self {
        Self::new(msg.addr, &msg.serialized_message)
    }
}

/// Collection of actors with a specific aspect
///
/// This is created with [`Behavior::iter_actors_with_aspect`]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ActorsWithAspectIter {
    len: usize,
    bytes: Vec<u8>,
    offset: usize,
}

impl ActorsWithAspectIter {
    fn new(bytes: Vec<u8>) -> Self {
        let mut offset: usize = 0;
        let len = Self::read_u32(&bytes, &mut offset).unwrap_or_default() as usize;

        Self { len, bytes, offset }
    }

    /// Read a u32/[`ActorId`] value from the byte stream
    fn read_u32(bytes: &[u8], offset: &mut usize) -> Option<u32> {
        let result = bytes
            .get((*offset)..(*offset + std::mem::size_of::<ActorId>()))
            .map(|slice| u32::from_le_bytes(slice.try_into().unwrap()));
        *offset += std::mem::size_of::<ActorId>();
        result
    }
}

impl Iterator for ActorsWithAspectIter {
    type Item = ActorId;

    fn next(&mut self) -> Option<Self::Item> {
        Self::read_u32(&self.bytes, &mut self.offset)
    }
}

impl ExactSizeIterator for ActorsWithAspectIter {
    fn len(&self) -> usize {
        self.len
    }
}

/// Address of a single behavior.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct BehaviorAddr {
    /// The actor at which this message is targeted.
    pub actor_id: ActorId,
    /// The behavior instance at which this message is targeted.
    pub instance: ForeignBehaviorInstanceId,
}

/// Target address of an incoming message.
#[derive(Debug, Eq, PartialEq)]
pub enum IncomingMessageAddr {
    /// The message is targeted at a specific behavior instance.
    Behavior(BehaviorAddr),
    /// This incoming message is targeted at the behavior module itself.
    Module(Option<ForeignBehaviorInstanceId>),
}

impl IncomingMessageAddr {
    /// Creates a new `IncomingMessageAddr` given its raw components.
    pub fn from_raw(actor_id: ActorId, instance: ForeignBehaviorInstanceId) -> Self {
        if actor_id == ffi::CONTROLLER_SENTINEL_ACTOR_ID {
            // If the 48-bits are all-ones, it's the sentinel value indicating no-instance.
            let instance = if instance.0 == ffi::INCOMING_MESSAGE_NO_INSTANCE_SENTINEL {
                None
            } else {
                Some(instance)
            };
            Self::Module(instance)
        } else {
            Self::Behavior(BehaviorAddr { actor_id, instance })
        }
    }

    /// Assuming a message targeted at a behavior, unwraps it and extracts its components.
    pub fn unwrap_behavior(&self) -> BehaviorAddr {
        if let IncomingMessageAddr::Behavior(addr) = self {
            *addr
        } else {
            panic!("unexpected non-behavior incoming message");
        }
    }
}

/// An incoming message from a controller module
#[derive(Debug, Eq, PartialEq)]
pub struct IncomingMessage<'a> {
    /// Target address for this message.
    pub addr: IncomingMessageAddr,
    /// Serialized message payload
    pub serialized_message: &'a [u8],
}

impl<'a> From<ffi::IncomingMessage> for IncomingMessage<'a> {
    fn from(msg: ffi::IncomingMessage) -> Self {
        Self {
            addr: IncomingMessageAddr::from_raw(
                msg.actor_id,
                ForeignBehaviorInstanceId(msg.instance_id),
            ),
            serialized_message: unsafe {
                std::slice::from_raw_parts(
                    msg.serialized_message_ptr as *const u8,
                    msg.serialized_message_len as usize,
                )
            },
        }
    }
}

/// The `Behavior` API provides behavior modules functionality for interacting with the world
#[derive(Copy, Clone)]
pub struct Behavior;

impl Behavior {
    /// Retrieve a serialized representation of aspect data of the given actor and aspect type if it exists
    ///
    /// The serialization format has to be defined in a protocol that is agreed upon with the owning controller module
    pub fn aspect_get(&self, addr: AspectAddr) -> Option<Vec<u8>> {
        let (aspect_guid_hi, aspect_guid_lo) =
            ((addr.aspect_guid >> 64) as u64, addr.aspect_guid as u64);

        match ffi::aspect_get(aspect_guid_hi, aspect_guid_lo, addr.actor_id) {
            Ok(data) => Some(data),
            Err(ErrorCode::NotFound) => None,
            Err(error) => panic!("Unexpected error: {}", Error::from(error)),
        }
    }

    /// Batch send [`OutgoingMessage`] data back to the controller module
    ///
    /// It is important to understand that this doesn't send the message directly to a
    /// behavior instance for processing. It is sent to the controller module who then decides
    /// what to do with it.
    ///
    /// For performance reasons it is recommended to always batch as many outgoing messages together
    /// as that will reduce calls over FFI, which are expensive
    ///
    /// TODO: Should return a custom error type through which a user can identify which
    /// messages/behavior modules failed and why
    pub fn send_outgoing_messages(
        &self,
        outgoing_messages: &[OutgoingMessage],
    ) -> Result<(), Error> {
        let outgoing_messages = outgoing_messages
            .iter()
            .map(ffi::OutgoingMessage::from)
            .collect::<Vec<ffi::OutgoingMessage>>();

        ffi::send_outgoing_messages(&outgoing_messages).map_err(Error::from)
    }

    /// Retrieve an iterator over all actors with the given aspect GUID
    pub fn iter_actors_with_aspect(&self, aspect_guid: Guid) -> Option<ActorsWithAspectIter> {
        let (aspect_guid_hi, aspect_guid_lo) = ((aspect_guid >> 64) as u64, aspect_guid as u64);

        match ffi::actors_with_aspect(aspect_guid_hi, aspect_guid_lo) {
            Ok(bytes) => Some(ActorsWithAspectIter::new(bytes)),
            Err(ErrorCode::NotFound) => None,
            Err(error) => panic!("Unexpected error: {}", Error::from(error)),
        }
    }

    /// Retrieves a fixed but random value (different each execution of the behavior module, but
    /// maintained on respawn).
    pub fn random_seed_value(&self) -> u128 {
        *ffi::random_seed_value()
    }
}