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::*;