ark-api-ffi 0.16.0

Ark low-level Wasm FFI API
Documentation
define_api_id!(0xbb0f_0e2f_ff53_2a51, "behavior-v0");

use crate::ErrorCode;
use bytemuck::Pod;
use bytemuck::Zeroable;

pub type ActorId = u32;

/// Sentinel value indicating that a message is a system message, with open interpretation of what
/// this entails, by the behavior module.
///
/// Keep in sync with the same value in the `behavior_controller_v0` API.
pub const CONTROLLER_SENTINEL_ACTOR_ID: ActorId = !0u32;

/// Sentinel value indicating that a system message has no instance tied to it.
///
/// Keep in sync with the same value in the `behavior_controller_v0` API.
pub const INCOMING_MESSAGE_NO_INSTANCE_SENTINEL: u64 = 0xffff_ffff_ffff;

pub type Guid = u128;
/// A unique id local to a behavior module representing a behavior type
pub type LocalBehaviorTypeId = u16;
/// A unique id of a couple comprising a local behavior type id within a behavior module as well as an instance id
pub type ForeignBehaviorInstanceId = u64;

pub const INVALID_GUID_COMPONENT: u64 = !0u64;

/// An outgoing message's address, targeting either an entire actor or a specific behavior
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct OutgoingMessageAddr {
    pub to_actor_id: ActorId,
    _pad: u32,
    behavior_guid_hi: u64,
    behavior_guid_lo: u64,
}

impl OutgoingMessageAddr {
    /// Constructs a new `OutgoingMessageAddr` targeting an actor
    pub fn new(to_actor_id: ActorId) -> Self {
        Self {
            to_actor_id,
            _pad: 0,
            behavior_guid_hi: INVALID_GUID_COMPONENT,
            behavior_guid_lo: INVALID_GUID_COMPONENT,
        }
    }

    /// Constructs a new `OutgoingMessageAddr` targeting a specific behavior on an actor.
    ///
    /// # Errors
    ///
    /// Returns an [`ErrorCode::InvalidArguments`] if the lower or higher `u64` component
    /// of the provided guid is an [`INVALID_GUID_COMPONENT`]
    pub fn new_to_behavior(to_actor_id: ActorId, behavior_guid: Guid) -> Result<Self, ErrorCode> {
        let behavior_guid_hi = (behavior_guid >> 64) as u64;
        let behavior_guid_lo = behavior_guid as u64;

        if behavior_guid_hi != INVALID_GUID_COMPONENT || behavior_guid_lo != INVALID_GUID_COMPONENT
        {
            Ok(Self {
                to_actor_id,
                _pad: 0,
                behavior_guid_hi,
                behavior_guid_lo,
            })
        } else {
            Err(ErrorCode::InvalidArguments)
        }
    }

    /// A new message targeted at the controller module itself.
    pub fn new_to_controller() -> Self {
        Self {
            to_actor_id: CONTROLLER_SENTINEL_ACTOR_ID,
            _pad: 0,
            behavior_guid_hi: INVALID_GUID_COMPONENT,
            behavior_guid_lo: INVALID_GUID_COMPONENT,
        }
    }

    /// Retrieve the address' behavior guid if it was set
    pub fn guid(&self) -> Option<Guid> {
        if self.behavior_guid_hi == INVALID_GUID_COMPONENT
            && self.behavior_guid_lo == INVALID_GUID_COMPONENT
        {
            None
        } else {
            Some((u128::from(self.behavior_guid_hi) << 64) | u128::from(self.behavior_guid_lo))
        }
    }
}

/// An outgoing message that can be sent from a behavior instance to the controller module
///
/// Note: it is up to the user to ensure that the serialized message data this structure points to is still
/// alive when being used.
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct OutgoingMessage {
    pub addr: OutgoingMessageAddr,
    serialized_message_ptr: u32,
    serialized_message_len: u32,
}

impl OutgoingMessage {
    /// Construct a new `OutgoingMessage` given the provided address and serialized message data
    pub fn new(addr: OutgoingMessageAddr, serialized_message: &[u8]) -> Self {
        Self {
            addr,
            serialized_message_ptr: serialized_message.as_ptr() as _,
            serialized_message_len: serialized_message.len() as _,
        }
    }

    /// Retrieve a pointer to the serialized message slice
    ///
    /// Note: there is no check to ensure the data is still alive
    pub fn msg_ptr(&self) -> u32 {
        self.serialized_message_ptr
    }

    /// Retrieve the length of the serialized message slice
    pub fn msg_len(&self) -> u32 {
        self.serialized_message_len
    }
}

/// An incoming message sent from the controller module
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct IncomingMessage {
    pub instance_id: ForeignBehaviorInstanceId,
    pub actor_id: ActorId,
    pub serialized_message_ptr: u32,
    pub serialized_message_len: u32,
    pub _pad: u32,
}

/// Describes a behavior local to a behavior module
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LocalBehaviorRegistration {
    pub type_id: LocalBehaviorTypeId,
    pub name: String,
}

/// Contains all registration info for an external behavior module
///
/// During behavior module registration, behavior modules must return `RegistrationInfo` as a
/// serialized vector of JSON bytes. Ark will validate the returned registration info and store
/// it internally.
///
/// A controller module can retrieve the registration info from Ark and use it to, for example,
/// create behavior instances of a specific behavior type.
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LocalModuleRegistration {
    pub behavior_infos: Vec<LocalBehaviorRegistration>,
}

#[ark_api_macros::ark_bindgen(imports = "ark-behavior-v0")]
mod behavior {
    use super::ActorId;
    use super::OutgoingMessage;
    use crate::pod_helpers::Align16U128;
    use crate::FFIResult;

    extern "C" {
        /// Retrieve a serialized representation of aspect data of the given actor and aspect guid if it exists
        ///
        /// The serialization format has to be defined in a protocol that is agreed upon with the owning controller module
        ///
        /// # Errors
        ///
        /// Returns [`crate::ErrorCode::NotFound`] if no aspect data can be found for the provided arguments
        pub fn aspect_get(
            aspect_guid_hi: u64,
            aspect_guid_lo: u64,
            actor_id: ActorId,
        ) -> FFIResult<Vec<u8>>;

        /// Batch send [`OutgoingMessage`]s 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
        #[with_memory]
        pub fn send_outgoing_messages(outgoing_messages: &[OutgoingMessage]) -> FFIResult<()>;

        /// Retrieve a list of all actors with the given aspect GUID
        ///
        /// The returned byte stream is encoded as follows:
        ///
        /// |--- u32 ---| |--- [`ActorId`] ---| |--- [`ActorId`] ---| |--- repeat..
        ///
        ///  The first u32 in the byte stream indicates the total amount of [`ActorId`]s
        ///
        /// # Errors
        ///
        /// Returns [`crate::ErrorCode::NotFound`] on an unrecognized aspect guid
        pub fn actors_with_aspect(aspect_guid_hi: u64, aspect_guid_lo: u64) -> FFIResult<Vec<u8>>;

        /// A single 128-bit random value, unique to this run.
        ///
        /// Initialize your random number generators that need to be unpredictable from this.
        /// Not meant for cryptographic operations.
        pub fn random_seed_value() -> Align16U128;
    }
}

pub use behavior::*;