logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
//! # 📨 Behavior API
//!
//! This API provides functionality for a behavior to communicate with the rest of the world and is mainly
//! used when a behavior is processing an incoming message. Incoming messages are sent from a "controller module",
//! which is a different Ark module type like an Applet or Cmdlet.
//!
//! ## Message passing
//!
//! Messages are the protocols through which behaviors communicate. At the lowest level they are
//! serializable Rust structures.
//!
//! The entire message passing flow between the controller module and behavior modules goes as follows:
//!
//! 1. The controller module creates messages and sends them to Ark
//! 2. Ark owns the actual behavior modules and will, in order, send these messages to the correct
//!    behaviors inside external behavior modules
//! 3. A behavior will then process the just received message
//!     - This is where the Behavior API comes in. During the processing of a message the behavior
//!       can create [`OutgoingMessage`]s and send them back to the controller module through
//!       [`Behavior::send_outgoing_messages`].
//!
//! ## Aspects
//!
//! Aspects are used to associate data with actors, and share it between behaviors. Aspects are just data and
//! don't implement any logic. From the point of view of a behavior, aspects can be used to eagerly
//! publish information/state, so that it doesn't need to be asynchronously queried via message passing.
//!
//! Aspect data can be retrieved through [`Behavior::aspect_get`] given a valid [`AspectAddr`].

use crate::{ffi::behavior_v0 as ffi, Error, ErrorCode};
pub use ffi::{
    ActorId, Guid, LocalBehaviorRegistration, LocalModuleRegistration, OutgoingMessageAddr,
};

#[doc(hidden)]
pub use ffi::API as FFI_API;

/// A unique identifier of a couple comprising a local behavior type id within a behavior module as
/// well as a local instance id.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ForeignBehaviorInstanceId(pub ffi::ForeignBehaviorInstanceId);

impl ForeignBehaviorInstanceId {
    /// Create a new instance id by packing a [`LocalBehaviorTypeId`] and [`LocalBehaviorInstanceId`]
    pub fn pack(type_id: LocalBehaviorTypeId, instance_id: LocalBehaviorInstanceId) -> Self {
        Self((u64::from(type_id.0) << 32) | u64::from(instance_id.0))
    }

    /// Unpack this id to retrieve the [`LocalBehaviorTypeId`] and [`LocalBehaviorInstanceId`]
    pub fn unpack(&self) -> (LocalBehaviorTypeId, LocalBehaviorInstanceId) {
        (
            LocalBehaviorTypeId(((self.0 >> 32) & ((1 << 16) - 1)) as u16),
            LocalBehaviorInstanceId(self.0 as u32),
        )
    }
}

/// A behavior instance index. May not be unique overall, but must form unique pairs with the
/// corresponding `LocalBehaviorTypeId` of the instance's type, resulting in a `ForeignBehaviorInstanceId`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct LocalBehaviorInstanceId(pub u32);

/// An index of the behavior type, local to a behavior module.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct LocalBehaviorTypeId(pub u16);

/// An aspect's address, comprising an aspect guid and an actor id
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct AspectAddr {
    /// A unique identifier for an aspect type
    pub aspect_guid: Guid,
    /// The actor this aspect lives on
    pub actor_id: ActorId,
}

/// An outgoing message that can be send from a behavior instance to the controller module
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OutgoingMessage {
    /// The address of the outgoing message
    pub addr: OutgoingMessageAddr,
    /// The serialized message payload
    pub serialized_message: Vec<u8>,
}

impl From<&OutgoingMessage> for ffi::OutgoingMessage {
    fn from(msg: &OutgoingMessage) -> Self {
        Self::new(msg.addr, &msg.serialized_message)
    }
}

/// Collection of actors with a specific aspect
///
/// This is created with [`Behavior::iter_actors_with_aspect`]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ActorsWithAspectIter {
    len: usize,
    bytes: Vec<u8>,
    offset: usize,
}

impl ActorsWithAspectIter {
    fn new(bytes: Vec<u8>) -> Self {
        let mut offset: usize = 0;
        let len = Self::read_u32(&bytes, &mut offset).unwrap_or_default() as usize;

        Self { len, bytes, offset }
    }

    /// Read a u32/[`ActorId`] value from the byte stream
    fn read_u32(bytes: &[u8], offset: &mut usize) -> Option<u32> {
        let result = bytes
            .get((*offset)..(*offset + std::mem::size_of::<ActorId>()))
            .map(|slice| u32::from_le_bytes(slice.try_into().unwrap()));
        *offset += std::mem::size_of::<ActorId>();
        result
    }
}

impl Iterator for ActorsWithAspectIter {
    type Item = ActorId;

    fn next(&mut self) -> Option<Self::Item> {
        Self::read_u32(&self.bytes, &mut self.offset)
    }
}

impl ExactSizeIterator for ActorsWithAspectIter {
    fn len(&self) -> usize {
        self.len
    }
}

/// Address of a single behavior.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct BehaviorAddr {
    /// The actor at which this message is targeted.
    pub actor_id: ActorId,
    /// The behavior instance at which this message is targeted.
    pub instance: ForeignBehaviorInstanceId,
}

/// Target address of an incoming message.
#[derive(Debug, Eq, PartialEq)]
pub enum IncomingMessageAddr {
    /// The message is targeted at a specific behavior instance.
    Behavior(BehaviorAddr),
    /// This incoming message is targeted at the behavior module itself.
    Module(Option<ForeignBehaviorInstanceId>),
}

impl IncomingMessageAddr {
    /// Creates a new `IncomingMessageAddr` given its raw components.
    pub fn from_raw(actor_id: ActorId, instance: ForeignBehaviorInstanceId) -> Self {
        if actor_id == ffi::CONTROLLER_SENTINEL_ACTOR_ID {
            // If the 48-bits are all-ones, it's the sentinel value indicating no-instance.
            let instance = if instance.0 == ffi::INCOMING_MESSAGE_NO_INSTANCE_SENTINEL {
                None
            } else {
                Some(instance)
            };
            Self::Module(instance)
        } else {
            Self::Behavior(BehaviorAddr { actor_id, instance })
        }
    }

    /// Assuming a message targeted at a behavior, unwraps it and extracts its components.
    pub fn unwrap_behavior(&self) -> BehaviorAddr {
        if let IncomingMessageAddr::Behavior(addr) = self {
            *addr
        } else {
            panic!("unexpected non-behavior incoming message");
        }
    }
}

/// An incoming message from a controller module
#[derive(Debug, Eq, PartialEq)]
pub struct IncomingMessage<'a> {
    /// Target address for this message.
    pub addr: IncomingMessageAddr,
    /// Serialized message payload
    pub serialized_message: &'a [u8],
}

impl<'a> From<ffi::IncomingMessage> for IncomingMessage<'a> {
    fn from(msg: ffi::IncomingMessage) -> Self {
        Self {
            addr: IncomingMessageAddr::from_raw(
                msg.actor_id,
                ForeignBehaviorInstanceId(msg.instance_id),
            ),
            serialized_message: unsafe {
                std::slice::from_raw_parts(
                    msg.serialized_message_ptr as *const u8,
                    msg.serialized_message_len as usize,
                )
            },
        }
    }
}

/// The `Behavior` API provides behavior modules functionality for interacting with the world
#[derive(Copy, Clone)]
pub struct Behavior;

impl Behavior {
    /// Retrieve a serialized representation of aspect data of the given actor and aspect type if it exists
    ///
    /// The serialization format has to be defined in a protocol that is agreed upon with the owning controller module
    pub fn aspect_get(&self, addr: AspectAddr) -> Option<Vec<u8>> {
        let (aspect_guid_hi, aspect_guid_lo) =
            ((addr.aspect_guid >> 64) as u64, addr.aspect_guid as u64);

        match ffi::aspect_get(aspect_guid_hi, aspect_guid_lo, addr.actor_id) {
            Ok(data) => Some(data),
            Err(ErrorCode::NotFound) => None,
            Err(error) => panic!("Unexpected error: {}", Error::from(error)),
        }
    }

    /// Batch send [`OutgoingMessage`] data 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
    ///
    /// TODO: Should return a custom error type through which a user can identify which
    /// messages/behavior modules failed and why
    pub fn send_outgoing_messages(
        &self,
        outgoing_messages: &[OutgoingMessage],
    ) -> Result<(), Error> {
        let outgoing_messages = outgoing_messages
            .iter()
            .map(ffi::OutgoingMessage::from)
            .collect::<Vec<ffi::OutgoingMessage>>();

        ffi::send_outgoing_messages(&outgoing_messages).map_err(Error::from)
    }

    /// Retrieve an iterator over all actors with the given aspect GUID
    pub fn iter_actors_with_aspect(&self, aspect_guid: Guid) -> Option<ActorsWithAspectIter> {
        let (aspect_guid_hi, aspect_guid_lo) = ((aspect_guid >> 64) as u64, aspect_guid as u64);

        match ffi::actors_with_aspect(aspect_guid_hi, aspect_guid_lo) {
            Ok(bytes) => Some(ActorsWithAspectIter::new(bytes)),
            Err(ErrorCode::NotFound) => None,
            Err(error) => panic!("Unexpected error: {}", Error::from(error)),
        }
    }

    /// Retrieves a fixed but random value (different each execution of the behavior module, but
    /// maintained on respawn).
    pub fn random_seed_value(&self) -> u128 {
        *ffi::random_seed_value()
    }
}