ark-api-ffi 0.17.0-pre.15

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

use crate::ErrorCode;
use bytemuck::Pod;
use bytemuck::Zeroable;
use std::ops::Deref;

pub type ActorId = u32;
pub type Guid = u128;
/// A globally unique id representing a behavior instance that lives somewhere in an active behavior module
pub type BehaviorInstanceId = u64;
/// A globally unique id representing a behavior type
pub type BehaviorTypeIdInner = u32;

pub const INVALID_GUID_COMPONENT: u64 = !0u64;

/// 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_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_v0` API.
pub const INCOMING_MESSAGE_NO_INSTANCE_SENTINEL: u64 = 0xffff_ffff_ffff;

/// Represents a unique behavior type in an external behavior module
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BehaviorTypeId(pub BehaviorTypeIdInner);

/// An aspect's address, comprising of an aspect guid and an actor id
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct AspectAddr {
    aspect_guid_hi: u64,
    aspect_guid_lo: u64,
    actor_id: ActorId,
    _pad: u32,
}

impl AspectAddr {
    /// Construct a new `AspectAddr` from the given aspect guid and actor id
    ///
    /// # Errors
    ///
    /// Returns an [`ErrorCode::InvalidArguments`] if the lower or higher `u64` component
    /// of the provided guid is an [`INVALID_GUID_COMPONENT`]
    pub fn new(aspect_guid: Guid, actor_id: ActorId) -> Result<Self, ErrorCode> {
        let aspect_guid_hi = (aspect_guid >> 64) as u64;
        let aspect_guid_lo = aspect_guid as u64;

        if aspect_guid_hi != INVALID_GUID_COMPONENT || aspect_guid_lo != INVALID_GUID_COMPONENT {
            Ok(Self {
                actor_id,
                aspect_guid_hi,
                aspect_guid_lo,
                _pad: 0,
            })
        } else {
            Err(ErrorCode::InvalidArguments)
        }
    }

    /// Retrieve the address' aspect guid
    pub fn guid(&self) -> Guid {
        (u128::from(self.aspect_guid_hi) << 64) | u128::from(self.aspect_guid_lo)
    }

    /// Retrieve the address' actor id
    pub fn actor_id(&self) -> ActorId {
        self.actor_id
    }
}

/// A command used for `aspect_upsert` to issue an aspect insertion/update
/// that needs to be synced with the host
///
/// Note: it is up to the user to ensure that the serialized aspect data this structure points to is
/// alive when used in calls such as `aspect_upsert`
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct AspectUpsert {
    pub addr: AspectAddr,
    serialized_aspect_ptr: u32,
    serialized_aspect_len: u32,
}

impl AspectUpsert {
    /// Construct a new `AspectUpsert` given the provided address and serialized aspect data
    pub fn new(addr: AspectAddr, serialized_aspect_data: &[u8]) -> Self {
        Self {
            addr,
            serialized_aspect_ptr: serialized_aspect_data.as_ptr() as _,
            serialized_aspect_len: serialized_aspect_data.len() as _,
        }
    }

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

    /// Retrieve the length of the serialized aspect slice
    pub fn aspect_len(&self) -> u32 {
        self.serialized_aspect_len
    }
}

/// A message to be sent to a specific behavior instance for processing
///
/// It is "incoming" from the perspective of the behavior instance
///
/// Note: it is up to the user to ensure that the serialized message data this structure points to is still
/// alive when being used in calls such as `instances_handle_messages`
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct IncomingMessage {
    pub instance_id: BehaviorInstanceId,
    pub actor_id: ActorId,
    serialized_message_ptr: u32,
    serialized_message_len: u32,
    _pad: u32,
}

impl IncomingMessage {
    /// Constructs a new `IncomingMessage`
    ///
    /// - `instance_id`: the behavior instance that should process the provided serialized message
    /// - `actor_id`: the owning actor this behavior instance lives on
    pub fn new(
        instance_id: BehaviorInstanceId,
        actor_id: ActorId,
        serialized_message: &[u8],
    ) -> Self {
        Self {
            instance_id,
            actor_id,
            serialized_message_ptr: serialized_message.as_ptr() as _,
            serialized_message_len: serialized_message.len() as _,
            _pad: 0,
        }
    }

    /// 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 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 {
    /// Construct a new `OutgoingMessageAddr` from the provided arguments
    ///
    /// # 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_actor_id: ActorId, behavior_guid: Option<Guid>) -> Result<Self, ErrorCode> {
        let (behavior_guid_hi, behavior_guid_lo) = match behavior_guid {
            Some(guid) => {
                let hi = (guid >> 64) as u64;
                let lo = guid as u64;
                if hi == INVALID_GUID_COMPONENT || lo == INVALID_GUID_COMPONENT {
                    return Err(ErrorCode::InvalidArguments);
                }
                (hi, lo)
            }
            None => (INVALID_GUID_COMPONENT, INVALID_GUID_COMPONENT),
        };

        Ok(Self {
            to_actor_id,
            _pad: 0,
            behavior_guid_hi,
            behavior_guid_lo,
        })
    }

    /// 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 is being sent from a behavior instance to the controller module
///
/// It is "outgoing" from the perspective of the behavior instance
///
/// "controller module" is the module using the behavior controller FFI
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct OutgoingMessage {
    pub addr: OutgoingMessageAddr,
    pub serialized_message_len: u32,
    pub _pad: u32,
}

/// Identifier for a specific version of a module.
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct BehaviorModuleId(String);

impl BehaviorModuleId {
    pub fn new(id: String) -> Self {
        Self(id)
    }
}

impl Deref for BehaviorModuleId {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.0.as_str()
    }
}

/// Describes a behavior in an external behavior module
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BehaviorRegistration {
    pub type_id: BehaviorTypeId,
    pub name: String,
}

/// Contains all registration info for an external behavior module
///
/// # Usage
///
/// Through [`list_modules`](list_modules), a controller module can retrieve
/// a list of all registered behaviors. The type id contained in each `BehaviorRegistration` can
/// then be used with [`instance_create`](instance_create) to create new instances.
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BehaviorModuleRegistration {
    /// The name of the behavior module
    pub name: String,
    /// The `ModuleDescCid` of the module that implements the behavior(s)
    pub id: Option<BehaviorModuleId>,
    /// The list of behaviors implemented by the behavior module
    pub behavior_infos: Vec<BehaviorRegistration>,
}

#[ark_api_macros::ark_bindgen(imports = "ark-behavior-controller-v0")]
mod behavior_controller {
    use super::*;
    use crate::FFIResult;

    extern "C" {
        /// Returns a serialized JSON vector of bytes with a list of external registration info.
        ///
        /// Only registration info from behavior modules listed in a Cargo.toml's Ark annotation will be returned
        ///
        /// # Example
        ///
        /// At a bare minimum, the JSON vector can be deserialized into a vector of [`BehaviorModuleRegistration`]:
        ///
        /// ```ignore
        /// use ark_api_ffi::behavior_controller_v0::{BehaviorModuleRegistration};
        ///
        /// let list: Vec<BehaviorModuleRegistration> = serde_json::from_slice(list_modules()).unwrap();
        /// ```
        ///
        /// If behavior modules were registered with a custom registration format they can be deserialized similarly:
        ///
        /// ```ignore
        /// use ark_api_ffi::behavior_controller_v0::{BehaviorModuleRegistration};
        ///
        /// // Custom data format used in behavior module registration
        /// #[derive(serde::Deserialize)]
        /// pub struct CustomRegistrationInfo {
        ///    #[serde(flatten)]
        ///    pub info: BehaviorModuleRegistration,
        ///
        ///    pub module_name: String,
        ///    pub module_version: u32,
        /// }
        ///
        /// let list: Vec<CustomRegistrationInfo> = serde_json::from_slice(list_modules()).unwrap();
        /// ```
        fn list_modules() -> Vec<u8>;

        /// Create a behavior instance of the specific type
        fn instance_create(type_id: BehaviorTypeIdInner, ron_params: &str) -> BehaviorInstanceId;

        /// Destroy a behavior instance
        ///
        /// This frees the memory of the instance and the instance can no longer be accessed
        fn instance_destroy(instance_id: BehaviorInstanceId);

        /// Clone a behavior instance to create a new identical instance
        fn instance_clone(instance_id: BehaviorInstanceId) -> BehaviorInstanceId;

        /// Persist a behavior instance by serializing it to a blob
        fn instance_persist(instance_id: BehaviorInstanceId) -> Vec<u8>;

        /// Restore a behavior instance by deserializing it from a persistent representation
        fn instance_restore(type_id: BehaviorTypeIdInner, bytes: &[u8]) -> BehaviorInstanceId;

        /// Have the specified instances process the provided messages
        ///
        /// This is key functionality of the Behavior Controller API as it will kick off processing
        /// of messages for behavior instances in external behavior modules.
        ///
        /// The following will happen after a call to instances_handle_messages:
        ///
        /// 1. In order, Ark will extract the [`IncomingMessage`]'s address and send the message data to the
        ///    correct behavior instance owned by some external behavior module.
        /// 2. A behavior instance will then process the just received message.
        /// 3. During the processing of a message, a behavior instance can create new [`OutgoingMessage`]s that
        ///    will be returned to the user of this API.
        ///
        /// The user can then turn outgoing messages into incoming messages and repeat the above process.
        ///
        /// Due to limitations in our bindgen, outgoing messages are returned as an encoded byte stream.
        /// [`OutgoingMessage`] acts as a decoding target to help users decode the byte stream and turn it
        /// into a usable concrete type.
        ///
        /// The byte stream is encoded as follows:
        ///
        /// |----- [`OutgoingMessage`] -----| |----- serialized message -----| |---- repeat..
        ///
        /// [`OutgoingMessage`] can be safely transmuted through which you can receive the
        /// serialized message data length.
        #[with_memory]
        fn instances_handle_messages(messages: &[IncomingMessage]) -> Vec<u8>;

        /// Requests multiple insertions or updates in aspect stores; if an aspect doesn't exist, it's seamlessly
        /// created on the host
        #[with_memory]
        fn aspect_upsert(upserts: &[AspectUpsert]);

        /// Requests to remove aspect data associated to a single actor in a store. If the aspect
        /// store doesn't exist, nothing happens
        fn aspect_remove(removes: &[AspectAddr]);

        /// Reset all existing aspect stores to their initial states
        fn aspect_reset_all();

        // TODO: This function will block and cause stalls, should look into implement async version of it.
        /// Publishes the module with the given ID and returns a possibly different ID of the published version.
        ///
        /// If the module is already published nothing will happen and the function will return the same ID as the one
        /// provided as an argument.
        fn publish(id: &str) -> FFIResult<String>;

        // TODO: This function will block and cause stalls, should look into implement async version of it.
        /// Loads the module with the given ID.
        ///
        /// Returns a serialized JSON vector of bytes containing the
        /// external registration info for the loaded module.
        fn load_behavior_module(id: &str) -> FFIResult<Vec<u8>>;
    }
}

pub use behavior_controller::*;