ark_api_ffi/ffi/
behavior_controller_v0.rs

1define_api_id!(0xbb0f_0abc_ff53_2c51, "behavior-controller-v0");
2
3use crate::ErrorCode;
4use bytemuck::Pod;
5use bytemuck::Zeroable;
6use std::ops::Deref;
7
8pub type ActorId = u32;
9pub type Guid = u128;
10/// A globally unique id representing a behavior instance that lives somewhere in an active behavior module
11pub type BehaviorInstanceId = u64;
12/// A globally unique id representing a behavior type
13pub type BehaviorTypeIdInner = u32;
14
15pub const INVALID_GUID_COMPONENT: u64 = !0u64;
16
17/// Sentinel value indicating that a message is a system message, with open interpretation of what
18/// this entails, by the behavior module.
19///
20/// Keep in sync with the same value in the `behavior_v0` API.
21pub const CONTROLLER_SENTINEL_ACTOR_ID: ActorId = !0u32;
22
23/// Sentinel value indicating that a system message has no instance tied to it.
24///
25/// Keep in sync with the same value in the `behavior_v0` API.
26pub const INCOMING_MESSAGE_NO_INSTANCE_SENTINEL: u64 = 0xffff_ffff_ffff;
27
28/// Represents a unique behavior type in an external behavior module
29#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
30#[derive(Copy, Clone, Debug, Eq, PartialEq)]
31pub struct BehaviorTypeId(pub BehaviorTypeIdInner);
32
33/// An aspect's address, comprising of an aspect guid and an actor id
34#[repr(C)]
35#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
36pub struct AspectAddr {
37    aspect_guid_hi: u64,
38    aspect_guid_lo: u64,
39    actor_id: ActorId,
40    _pad: u32,
41}
42
43impl AspectAddr {
44    /// Construct a new `AspectAddr` from the given aspect guid and actor id
45    ///
46    /// # Errors
47    ///
48    /// Returns an [`ErrorCode::InvalidArguments`] if the lower or higher `u64` component
49    /// of the provided guid is an [`INVALID_GUID_COMPONENT`]
50    pub fn new(aspect_guid: Guid, actor_id: ActorId) -> Result<Self, ErrorCode> {
51        let aspect_guid_hi = (aspect_guid >> 64) as u64;
52        let aspect_guid_lo = aspect_guid as u64;
53
54        if aspect_guid_hi != INVALID_GUID_COMPONENT || aspect_guid_lo != INVALID_GUID_COMPONENT {
55            Ok(Self {
56                actor_id,
57                aspect_guid_hi,
58                aspect_guid_lo,
59                _pad: 0,
60            })
61        } else {
62            Err(ErrorCode::InvalidArguments)
63        }
64    }
65
66    /// Retrieve the address' aspect guid
67    pub fn guid(&self) -> Guid {
68        (u128::from(self.aspect_guid_hi) << 64) | u128::from(self.aspect_guid_lo)
69    }
70
71    /// Retrieve the address' actor id
72    pub fn actor_id(&self) -> ActorId {
73        self.actor_id
74    }
75}
76
77/// A command used for `aspect_upsert` to issue an aspect insertion/update
78/// that needs to be synced with the host
79///
80/// Note: it is up to the user to ensure that the serialized aspect data this structure points to is
81/// alive when used in calls such as `aspect_upsert`
82#[repr(C)]
83#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
84pub struct AspectUpsert {
85    pub addr: AspectAddr,
86    serialized_aspect_ptr: u32,
87    serialized_aspect_len: u32,
88}
89
90impl AspectUpsert {
91    /// Construct a new `AspectUpsert` given the provided address and serialized aspect data
92    pub fn new(addr: AspectAddr, serialized_aspect_data: &[u8]) -> Self {
93        Self {
94            addr,
95            serialized_aspect_ptr: serialized_aspect_data.as_ptr() as _,
96            serialized_aspect_len: serialized_aspect_data.len() as _,
97        }
98    }
99
100    /// Retrieve a pointer to the serialized aspect slice
101    ///
102    /// Note: there is no check to ensure the data is still alive
103    pub fn aspect_ptr(&self) -> u32 {
104        self.serialized_aspect_ptr
105    }
106
107    /// Retrieve the length of the serialized aspect slice
108    pub fn aspect_len(&self) -> u32 {
109        self.serialized_aspect_len
110    }
111}
112
113/// A message to be sent to a specific behavior instance for processing
114///
115/// It is "incoming" from the perspective of the behavior instance
116///
117/// Note: it is up to the user to ensure that the serialized message data this structure points to is still
118/// alive when being used in calls such as `instances_handle_messages`
119#[repr(C)]
120#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
121pub struct IncomingMessage {
122    pub instance_id: BehaviorInstanceId,
123    pub actor_id: ActorId,
124    serialized_message_ptr: u32,
125    serialized_message_len: u32,
126    _pad: u32,
127}
128
129impl IncomingMessage {
130    /// Constructs a new `IncomingMessage`
131    ///
132    /// - `instance_id`: the behavior instance that should process the provided serialized message
133    /// - `actor_id`: the owning actor this behavior instance lives on
134    pub fn new(
135        instance_id: BehaviorInstanceId,
136        actor_id: ActorId,
137        serialized_message: &[u8],
138    ) -> Self {
139        Self {
140            instance_id,
141            actor_id,
142            serialized_message_ptr: serialized_message.as_ptr() as _,
143            serialized_message_len: serialized_message.len() as _,
144            _pad: 0,
145        }
146    }
147
148    /// Retrieve a pointer to the serialized message slice
149    ///
150    /// Note: there is no check to ensure the data is still alive
151    pub fn msg_ptr(&self) -> u32 {
152        self.serialized_message_ptr
153    }
154
155    /// Retrieve the length of the serialized message slice
156    pub fn msg_len(&self) -> u32 {
157        self.serialized_message_len
158    }
159}
160
161/// An outgoing message's address, targeting either an entire actor or a specific behavior
162#[repr(C)]
163#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
164pub struct OutgoingMessageAddr {
165    pub to_actor_id: ActorId,
166    _pad: u32,
167    behavior_guid_hi: u64,
168    behavior_guid_lo: u64,
169}
170
171impl OutgoingMessageAddr {
172    /// Construct a new `OutgoingMessageAddr` from the provided arguments
173    ///
174    /// # Errors
175    ///
176    /// Returns an [`ErrorCode::InvalidArguments`] if the lower or higher `u64` component
177    /// of the provided guid is an [`INVALID_GUID_COMPONENT`]
178    pub fn new(to_actor_id: ActorId, behavior_guid: Option<Guid>) -> Result<Self, ErrorCode> {
179        let (behavior_guid_hi, behavior_guid_lo) = match behavior_guid {
180            Some(guid) => {
181                let hi = (guid >> 64) as u64;
182                let lo = guid as u64;
183                if hi == INVALID_GUID_COMPONENT || lo == INVALID_GUID_COMPONENT {
184                    return Err(ErrorCode::InvalidArguments);
185                }
186                (hi, lo)
187            }
188            None => (INVALID_GUID_COMPONENT, INVALID_GUID_COMPONENT),
189        };
190
191        Ok(Self {
192            to_actor_id,
193            _pad: 0,
194            behavior_guid_hi,
195            behavior_guid_lo,
196        })
197    }
198
199    /// Retrieve the address' behavior guid if it was set
200    pub fn guid(&self) -> Option<Guid> {
201        if self.behavior_guid_hi == INVALID_GUID_COMPONENT
202            || self.behavior_guid_lo == INVALID_GUID_COMPONENT
203        {
204            None
205        } else {
206            Some((u128::from(self.behavior_guid_hi) << 64) | u128::from(self.behavior_guid_lo))
207        }
208    }
209}
210
211/// An outgoing message that is being sent from a behavior instance to the controller module
212///
213/// It is "outgoing" from the perspective of the behavior instance
214///
215/// "controller module" is the module using the behavior controller FFI
216#[repr(C)]
217#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
218pub struct OutgoingMessage {
219    pub addr: OutgoingMessageAddr,
220    pub serialized_message_len: u32,
221    pub _pad: u32,
222}
223
224/// Identifier for a specific version of a module.
225#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
226#[derive(Clone, Debug, Hash, Eq, PartialEq)]
227pub struct BehaviorModuleId(String);
228
229impl BehaviorModuleId {
230    pub fn new(id: String) -> Self {
231        Self(id)
232    }
233}
234
235impl Deref for BehaviorModuleId {
236    type Target = str;
237
238    fn deref(&self) -> &Self::Target {
239        self.0.as_str()
240    }
241}
242
243/// Describes a behavior in an external behavior module
244#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
245#[derive(Clone, Debug, Eq, PartialEq)]
246pub struct BehaviorRegistration {
247    pub type_id: BehaviorTypeId,
248    pub name: String,
249}
250
251/// Contains all registration info for an external behavior module
252///
253/// # Usage
254///
255/// Through [`list_modules`](list_modules), a controller module can retrieve
256/// a list of all registered behaviors. The type id contained in each `BehaviorRegistration` can
257/// then be used with [`instance_create`](instance_create) to create new instances.
258#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
259#[derive(Clone, Debug, Eq, PartialEq)]
260pub struct BehaviorModuleRegistration {
261    pub name: String,
262    pub id: Option<BehaviorModuleId>,
263    pub behavior_infos: Vec<BehaviorRegistration>,
264}
265
266#[ark_api_macros::ark_bindgen(imports = "ark-behavior-controller-v0")]
267mod behavior_controller {
268    use super::*;
269    use crate::FFIResult;
270
271    extern "C" {
272        /// Returns a serialized JSON vector of bytes with a list of external registration info.
273        ///
274        /// Only registration info from behavior modules listed in a Cargo.toml's Ark annotation will be returned
275        ///
276        /// # Example
277        ///
278        /// At a bare minimum, the JSON vector can be deserialized into a vector of [`BehaviorModuleRegistration`]:
279        ///
280        /// ```ignore
281        /// use ark_api_ffi::behavior_controller_v0::{BehaviorModuleRegistration};
282        ///
283        /// let list: Vec<BehaviorModuleRegistration> = serde_json::from_slice(list_modules()).unwrap();
284        /// ```
285        ///
286        /// If behavior modules were registered with a custom registration format they can be deserialized similarly:
287        ///
288        /// ```ignore
289        /// use ark_api_ffi::behavior_controller_v0::{BehaviorModuleRegistration};
290        ///
291        /// // Custom data format used in behavior module registration
292        /// #[derive(serde::Deserialize)]
293        /// pub struct CustomRegistrationInfo {
294        ///    #[serde(flatten)]
295        ///    pub info: BehaviorModuleRegistration,
296        ///
297        ///    pub module_name: String,
298        ///    pub module_version: u32,
299        /// }
300        ///
301        /// let list: Vec<CustomRegistrationInfo> = serde_json::from_slice(list_modules()).unwrap();
302        /// ```
303        fn list_modules() -> Vec<u8>;
304
305        /// Create a behavior instance of the specific type
306        fn instance_create(type_id: BehaviorTypeIdInner, ron_params: &str) -> BehaviorInstanceId;
307
308        /// Destroy a behavior instance
309        ///
310        /// This frees the memory of the instance and the instance can no longer be accessed
311        fn instance_destroy(instance_id: BehaviorInstanceId);
312
313        /// Clone a behavior instance to create a new identical instance
314        fn instance_clone(instance_id: BehaviorInstanceId) -> BehaviorInstanceId;
315
316        /// Persist a behavior instance by serializing it to a blob
317        fn instance_persist(instance_id: BehaviorInstanceId) -> Vec<u8>;
318
319        /// Restore a behavior instance by deserializing it from a persistent representation
320        fn instance_restore(type_id: BehaviorTypeIdInner, bytes: &[u8]) -> BehaviorInstanceId;
321
322        /// Have the specified instances process the provided messages
323        ///
324        /// This is key functionality of the Behavior Controller API as it will kick off processing
325        /// of messages for behavior instances in external behavior modules.
326        ///
327        /// The following will happen after a call to instances_handle_messages:
328        ///
329        /// 1. In order, Ark will extract the [`IncomingMessage`]'s address and send the message data to the
330        ///    correct behavior instance owned by some external behavior module.
331        /// 2. A behavior instance will then process the just received message.
332        /// 3. During the processing of a message, a behavior instance can create new [`OutgoingMessage`]s that
333        ///    will be returned to the user of this API.
334        ///
335        /// The user can then turn outgoing messages into incoming messages and repeat the above process.
336        ///
337        /// Due to limitations in our bindgen, outgoing messages are returned as an encoded byte stream.
338        /// [`OutgoingMessage`] acts as a decoding target to help users decode the byte stream and turn it
339        /// into a usable concrete type.
340        ///
341        /// The byte stream is encoded as follows:
342        ///
343        /// |----- [`OutgoingMessage`] -----| |----- serialized message -----| |---- repeat..
344        ///
345        /// [`OutgoingMessage`] can be safely transmuted through which you can receive the
346        /// serialized message data length.
347        #[with_memory]
348        fn instances_handle_messages(messages: &[IncomingMessage]) -> Vec<u8>;
349
350        /// Requests multiple insertions or updates in aspect stores; if an aspect doesn't exist, it's seamlessly
351        /// created on the host
352        #[with_memory]
353        fn aspect_upsert(upserts: &[AspectUpsert]);
354
355        /// Requests to remove aspect data associated to a single actor in a store. If the aspect
356        /// store doesn't exist, nothing happens
357        fn aspect_remove(removes: &[AspectAddr]);
358
359        /// Reset all existing aspect stores to their initial states
360        fn aspect_reset_all();
361
362        // TODO: This function will block and cause stalls, should look into implement async version of it.
363        /// Publishes the module with the given ID and returns a possibly different ID of the published version.
364        ///
365        /// If the module is already published nothing will happen and the function will return the same ID as the one
366        /// provided as an argument.
367        fn publish(id: &str) -> FFIResult<String>;
368
369        // TODO: This function will block and cause stalls, should look into implement async version of it.
370        /// Loads the module with the given ID.
371        ///
372        /// Returns a serialized JSON vector of bytes containing the
373        /// external registration info for the loaded module.
374        fn load_behavior_module(id: &str) -> FFIResult<Vec<u8>>;
375    }
376}
377
378pub use behavior_controller::*;