use core::marker::PhantomData;
use arkhe_kernel::abi::{EntityId, Tick};
use serde::{Deserialize, Serialize};
use crate::brand::{ShellBrand, ShellId};
use crate::component::BoundedString;
use crate::user::UserId;
use crate::ArkheComponent;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ActorId(EntityId);
impl ActorId {
#[inline]
#[must_use]
pub fn new(id: EntityId) -> Self {
Self(id)
}
#[inline]
#[must_use]
pub fn get(self) -> EntityId {
self.0
}
}
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ActorKind {
Human = 0,
Bot = 1,
System = 2,
Anonymous = 3,
}
mod state_seal {
pub trait Sealed {}
}
pub trait ActorState: state_seal::Sealed + 'static {
const NAME: &'static str;
}
#[derive(Debug)]
pub enum Anonymous {}
#[derive(Debug)]
pub enum Authenticated {}
#[derive(Debug)]
pub enum Suspended {}
impl state_seal::Sealed for Anonymous {}
impl state_seal::Sealed for Authenticated {}
impl state_seal::Sealed for Suspended {}
impl ActorState for Anonymous {
const NAME: &'static str = "anonymous";
}
impl ActorState for Authenticated {
const NAME: &'static str = "authenticated";
}
impl ActorState for Suspended {
const NAME: &'static str = "suspended";
}
pub struct Actor<'s, S: ActorState> {
brand: ShellBrand<'s>,
id: ActorId,
_state: PhantomData<fn() -> S>,
}
impl<'s, S: ActorState> Clone for Actor<'s, S> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<'s, S: ActorState> Copy for Actor<'s, S> {}
impl<'s, S: ActorState> core::fmt::Debug for Actor<'s, S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Actor")
.field("id", &self.id)
.field("state", &S::NAME)
.finish()
}
}
impl<'s, S: ActorState> Actor<'s, S> {
#[inline]
#[must_use]
pub fn id(self) -> ActorId {
self.id
}
#[inline]
#[must_use]
pub fn brand(self) -> ShellBrand<'s> {
self.brand
}
}
impl<'s> Actor<'s, Anonymous> {
#[inline]
#[must_use]
pub fn new_anonymous(brand: ShellBrand<'s>, id: ActorId) -> Self {
Self {
brand,
id,
_state: PhantomData,
}
}
#[inline]
#[must_use]
pub fn authenticate(self, _user_id: UserId) -> Actor<'s, Authenticated> {
Actor {
brand: self.brand,
id: self.id,
_state: PhantomData,
}
}
}
impl<'s> Actor<'s, Authenticated> {
#[inline]
#[must_use]
pub fn suspend(self) -> Actor<'s, Suspended> {
Actor {
brand: self.brand,
id: self.id,
_state: PhantomData,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheComponent)]
#[arkhe(type_code = 0x0003_0101, schema_version = 1)]
pub struct ActorProfile {
pub schema_version: u16,
pub shell_id: ShellId,
pub handle: BoundedString<32>,
pub kind: ActorKind,
pub created_tick: Tick,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ArkheComponent)]
#[arkhe(type_code = 0x0003_0102, schema_version = 1)]
pub struct UserBinding {
pub schema_version: u16,
pub user_id: UserId,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::component::ArkheComponent;
fn ent(v: u64) -> EntityId {
EntityId::new(v).unwrap()
}
#[test]
fn actor_typestate_transitions_anonymous_authenticated_suspended() {
ShellBrand::run(|brand| {
let id = ActorId::new(ent(1));
let anon: Actor<'_, Anonymous> = Actor::new_anonymous(brand, id);
let user_id = UserId::new(ent(2));
let auth: Actor<'_, Authenticated> = anon.authenticate(user_id);
let susp: Actor<'_, Suspended> = auth.suspend();
assert_eq!(susp.id(), id);
});
}
#[test]
fn actor_state_names_are_distinct() {
assert_eq!(Anonymous::NAME, "anonymous");
assert_eq!(Authenticated::NAME, "authenticated");
assert_eq!(Suspended::NAME, "suspended");
}
#[test]
fn actor_profile_serde_roundtrip_postcard() {
let p = ActorProfile {
schema_version: 1,
shell_id: ShellId([0xAB; 16]),
handle: BoundedString::<32>::new("alice").unwrap(),
kind: ActorKind::Human,
created_tick: Tick(100),
};
let bytes = postcard::to_stdvec(&p).unwrap();
let back: ActorProfile = postcard::from_bytes(&bytes).unwrap();
assert_eq!(p, back);
}
#[test]
fn actor_profile_exposes_type_code_and_schema_version() {
assert_eq!(ActorProfile::TYPE_CODE, 0x0003_0101);
assert_eq!(ActorProfile::SCHEMA_VERSION, 1);
}
}