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