use core::{
any::{Any, TypeId},
fmt::Debug,
str::FromStr,
};
use alloc::{
borrow::ToOwned,
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use databoard::{Databoard, EntryReadGuard, EntryWriteGuard, RemappingList};
use tinyscript::{Environment, ScriptingValue};
use crate::{
BehaviorState, ConstString,
behavior::{BehaviorDataCollection, BehaviorTickCallback, behavior_description::BehaviorDescription},
port::error::Error,
};
#[derive(Default)]
pub struct BehaviorData {
uid: u16,
state: BehaviorState,
remappings: RemappingList,
blackboard: Databoard,
pre_state_change_hooks: Vec<(ConstString, Box<BehaviorTickCallback>)>,
description: BehaviorDescription,
}
impl BehaviorData {
#[must_use]
pub(crate) fn new(data: &BehaviorDataCollection) -> Self {
Self {
uid: data.uid,
state: BehaviorState::default(),
remappings: data.remappings.clone(),
blackboard: data.blackboard.clone(),
pre_state_change_hooks: Vec::default(),
description: data.bhvr_desc.clone(),
}
}
#[must_use]
pub fn contains_key(&self, key: &str) -> bool {
match self.remappings.find(key) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer)
| databoard::RemappingTarget::None(board_pointer) => self.blackboard.contains_key(&board_pointer),
databoard::RemappingTarget::StringAssignment(_) => false,
}
}
pub fn contains<T>(&self, key: &str) -> Result<bool, Error>
where
T: Any + Debug + FromStr + ToString + Send + Sync,
{
match self.remappings.find(key) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer)
| databoard::RemappingTarget::None(board_pointer) => Ok(self.blackboard.contains::<T>(&board_pointer)?),
databoard::RemappingTarget::StringAssignment(assignment) => Err(databoard::Error::Assignment {
key: key.into(),
value: assignment,
}
.into()),
}
}
pub fn delete<T>(&mut self, key: &str) -> Result<T, Error>
where
T: Any + Debug + FromStr + ToString + Send + Sync,
{
match self.remappings.find(key) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer)
| databoard::RemappingTarget::None(board_pointer) => Ok(self.blackboard.delete::<T>(&board_pointer)?),
databoard::RemappingTarget::StringAssignment(assignment) => Err(databoard::Error::Assignment {
key: key.into(),
value: assignment,
}
.into()),
}
}
#[allow(clippy::option_if_let_else)]
pub fn get<T>(&self, key: &str) -> Result<T, Error>
where
T: Any + Clone + Debug + FromStr + ToString + Send + Sync,
{
match self.remappings.find(key) {
databoard::RemappingTarget::LocalPointer(remapped_key)
| databoard::RemappingTarget::RootPointer(remapped_key)
| databoard::RemappingTarget::BoardPointer(remapped_key) => match self.blackboard.entry(&remapped_key) {
Ok(entry) => {
let en = &*entry.read();
let data = en.data().as_ref();
data.downcast_ref::<T>().map_or_else(
|| {
let s = data.downcast_ref::<String>().map_or_else(
|| {
self.get_env(&remapped_key)
.map_or_else(|_| Err(Error::NotFound { key: key.into() }), |val| Ok(val.to_string()))
},
|val| Ok(val.to_owned()),
)?;
T::from_str(&s).map_or_else(
|_| {
Err(Error::CouldNotConvert {
value: remapped_key,
port: key.into(),
})
},
|val| Ok(val),
)
},
|val| Ok(val.clone()),
)
}
Err(err) => match err {
databoard::Error::Assignment { key: _, value } => T::from_str(&value).map_or_else(
|_| {
Err(Error::CouldNotConvert {
value: remapped_key,
port: key.into(),
})
},
|val| Ok(val),
),
_ => Err(err.into()),
},
},
databoard::RemappingTarget::StringAssignment(assignment) => T::from_str(&assignment).map_or_else(
|_| {
Err(Error::CouldNotConvert {
value: assignment,
port: key.into(),
})
},
|val| Ok(val),
),
databoard::RemappingTarget::None(original_key) => match self.blackboard.get::<T>(&original_key) {
Ok(value) => Ok(value),
Err(err) => {
let entry = self.blackboard.entry(key)?;
let en = &*entry.read();
en.data().downcast_ref::<String>().map_or_else(
|| Err(err.into()),
|val| {
T::from_str(val).map_or_else(
|_| {
Err(Error::CouldNotConvert {
value: key.into(),
port: key.into(),
})
},
|res| Ok(res),
)
},
)
}
},
}
}
pub fn get_ref<T>(&self, key: &str) -> Result<EntryReadGuard<T>, Error>
where
T: Any + Debug + FromStr + ToString + Send + Sync,
{
match self.remappings.find(key) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer)
| databoard::RemappingTarget::None(board_pointer) => Ok(self.blackboard.get_ref::<T>(&board_pointer)?),
databoard::RemappingTarget::StringAssignment(assignment) => Err(databoard::Error::Assignment {
key: key.into(),
value: assignment,
}
.into()),
}
}
pub fn get_mut_ref<T>(&self, key: &str) -> Result<EntryWriteGuard<T>, Error>
where
T: Any + Debug + FromStr + ToString + Send + Sync,
{
match self.remappings.find(key) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer)
| databoard::RemappingTarget::None(board_pointer) => Ok(self.blackboard.get_mut_ref::<T>(&board_pointer)?),
databoard::RemappingTarget::StringAssignment(assignment) => Err(databoard::Error::Assignment {
key: key.into(),
value: assignment,
}
.into()),
}
}
pub fn set<T>(&mut self, key: &str, value: T) -> Result<Option<T>, Error>
where
T: Any + Debug + FromStr + ToString + Send + Sync,
{
match self.remappings.find(key) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer)
| databoard::RemappingTarget::None(board_pointer) => Ok(self.blackboard.set::<T>(&board_pointer, value)?),
databoard::RemappingTarget::StringAssignment(assignment) => Err(databoard::Error::Assignment {
key: key.into(),
value: assignment,
}
.into()),
}
}
#[inline]
pub fn sequence_id(&self, key: &str) -> Result<usize, Error> {
match self.remappings.find(key) {
databoard::RemappingTarget::BoardPointer(board_pointer)
| databoard::RemappingTarget::LocalPointer(board_pointer)
| databoard::RemappingTarget::RootPointer(board_pointer)
| databoard::RemappingTarget::None(board_pointer) => Ok(self.blackboard.sequence_id(&board_pointer)?),
databoard::RemappingTarget::StringAssignment(assignment) => Err(databoard::Error::Assignment {
key: key.into(),
value: assignment,
}
.into()),
}
}
#[must_use]
pub const fn blackboard(&self) -> &Databoard {
&self.blackboard
}
#[must_use]
pub const fn description(&self) -> &BehaviorDescription {
&self.description
}
#[must_use]
pub const fn description_mut(&mut self) -> &mut BehaviorDescription {
&mut self.description
}
#[must_use]
pub fn is_active(&self) -> bool {
self.state != BehaviorState::Idle && self.state != BehaviorState::Skipped
}
#[must_use]
pub const fn name(&self) -> &ConstString {
self.description.name()
}
#[must_use]
pub const fn uid(&self) -> u16 {
self.uid
}
#[must_use]
pub const fn state(&self) -> BehaviorState {
self.state
}
pub fn set_state(&mut self, state: BehaviorState) {
if state != self.state {
let mut state = state;
for (_, callback) in &self.pre_state_change_hooks {
callback(self, &mut state);
}
self.state = state;
}
}
pub fn add_pre_state_change_callback<T>(&mut self, name: ConstString, callback: T)
where
T: Fn(&Self, &mut BehaviorState) + Send + Sync + 'static,
{
self.pre_state_change_hooks
.push((name, Box::new(callback)));
}
pub fn remove_pre_state_change_callback(&mut self, name: &ConstString) {
let mut indices = Vec::new();
for (index, (cb_name, _)) in self.pre_state_change_hooks.iter().enumerate() {
if cb_name == name {
indices.push(index);
}
}
for index in indices {
let _ = self.pre_state_change_hooks.remove(index);
}
}
#[must_use]
pub const fn remappings(&self) -> &RemappingList {
&self.remappings
}
#[must_use]
pub const fn full_path(&self) -> &ConstString {
self.description.groot2_path()
}
}
impl Environment for BehaviorData {
fn define_env(&mut self, key: &str, value: ScriptingValue) -> Result<(), tinyscript::environment::Error> {
if self.contains_key(key) {
self.set_env(key, value)
} else {
match value {
ScriptingValue::Nil() => unreachable!(),
ScriptingValue::Boolean(b) => match self.set(key, b) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: key.into(),
cause: cause.to_string().into(),
});
}
},
ScriptingValue::Float64(f) => match self.set(key, f) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: key.into(),
cause: cause.to_string().into(),
});
}
},
ScriptingValue::Int64(i) => match self.set(key, i) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: key.into(),
cause: cause.to_string().into(),
});
}
},
ScriptingValue::String(s) => match self.set(key, s) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: key.into(),
cause: cause.to_string().into(),
});
}
},
}
Ok(())
}
}
#[allow(clippy::too_many_lines)]
fn get_env(&self, name: &str) -> Result<ScriptingValue, tinyscript::environment::Error> {
self.blackboard.entry(name).map_or_else(
|err| {
match err {
databoard::Error::Assignment { key: _, value } => i64::from_str(&value).map_or_else(
|_| {
f64::from_str(&value).map_or_else(
|_| {
bool::from_str(&value).map_or_else(
|_| Ok(ScriptingValue::String(value.to_string())),
|b| Ok(ScriptingValue::Boolean(b)),
)
},
|f| Ok(ScriptingValue::Float64(f)),
)
},
|i| Ok(ScriptingValue::Int64(i)),
),
_ => Err(tinyscript::environment::Error::EnvVarNotDefined { name: name.into() }),
}
},
|entry| {
let entry = entry.read();
let type_id = (**entry).as_ref().type_id();
if type_id == TypeId::of::<String>() {
let s =
entry
.downcast_ref::<String>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "String".into(),
})?;
Ok(ScriptingValue::String(s.to_owned()))
} else if type_id == TypeId::of::<f64>() {
let f = entry
.downcast_ref::<f64>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "f64".into(),
})?;
Ok(ScriptingValue::Float64(f.to_owned()))
} else if type_id == TypeId::of::<f32>() {
let f = entry
.downcast_ref::<f32>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "f32".into(),
})?;
Ok(ScriptingValue::Float64(f64::from(f.to_owned())))
} else if type_id == TypeId::of::<i64>() {
let i = entry
.downcast_ref::<i64>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "i64".into(),
})?;
Ok(ScriptingValue::Int64(i.to_owned()))
} else if type_id == TypeId::of::<i32>() {
let i = entry
.downcast_ref::<i32>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "i32".into(),
})?;
Ok(ScriptingValue::Int64(i64::from(i.to_owned())))
} else if type_id == TypeId::of::<u32>() {
let i = entry
.downcast_ref::<u32>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "u32".into(),
})?;
Ok(ScriptingValue::Int64(i64::from(i.to_owned())))
} else if type_id == TypeId::of::<i16>() {
let i = entry
.downcast_ref::<i16>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "i16".into(),
})?;
Ok(ScriptingValue::Int64(i64::from(i.to_owned())))
} else if type_id == TypeId::of::<u16>() {
let i = entry
.downcast_ref::<u16>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "u16".into(),
})?;
Ok(ScriptingValue::Int64(i64::from(i.to_owned())))
} else if type_id == TypeId::of::<u8>() {
let i = entry
.downcast_ref::<u8>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "u8".into(),
})?;
Ok(ScriptingValue::Int64(i64::from(i.to_owned())))
} else if type_id == TypeId::of::<i8>() {
let i = entry
.downcast_ref::<i8>()
.ok_or_else(|| tinyscript::environment::Error::EnvVarTypeCast {
name: name.into(),
var_type: "i8".into(),
})?;
Ok(ScriptingValue::Int64(i64::from(i.to_owned())))
} else {
Err(tinyscript::environment::Error::EnvVarUnknownType { name: name.into() })
}
},
)
}
#[allow(clippy::too_many_lines)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
fn set_env(&mut self, name: &str, value: ScriptingValue) -> Result<(), tinyscript::environment::Error> {
let entry_type_id = match self.blackboard.entry(name) {
Ok(entry) => {
let en = entry.read();
let data = en.as_ref();
data.type_id()
}
Err(_) => {
return Err(tinyscript::environment::Error::EnvVarNotDefined { name: name.into() });
}
};
match value {
ScriptingValue::Nil() => unreachable!(),
ScriptingValue::Boolean(b) => {
if TypeId::of::<bool>() == entry_type_id {
match self.set(name, b) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else {
return Err(tinyscript::environment::Error::EnvVarWrongType { name: name.into() });
}
}
ScriptingValue::Float64(f) => {
if TypeId::of::<f64>() == entry_type_id {
match self.set(name, f) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else if TypeId::of::<f32>() == entry_type_id {
if f > f64::from(f32::MAX) || f < f64::from(f32::MIN) {
return Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() });
}
match self.set(name, f) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else {
return Err(tinyscript::environment::Error::EnvVarWrongType { name: name.into() });
}
}
ScriptingValue::Int64(i) => {
if TypeId::of::<i64>() == entry_type_id {
match self.set(name, i) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else if TypeId::of::<i32>() == entry_type_id {
if i > i64::from(i32::MAX) || i < i64::from(i32::MIN) {
return Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() });
}
match self.set(name, i as i32) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else if TypeId::of::<u32>() == entry_type_id {
if i > i64::from(u32::MAX) || i < i64::from(u32::MIN) {
return Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() });
}
match self.set(name, i as u32) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else if TypeId::of::<i16>() == entry_type_id {
if i > i64::from(i16::MAX) || i < i64::from(i16::MIN) {
return Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() });
}
match self.set(name, i as i16) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else if TypeId::of::<u16>() == entry_type_id {
if i > i64::from(u16::MAX) || i < i64::from(u16::MIN) {
return Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() });
}
match self.set(name, i as u16) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else if TypeId::of::<i8>() == entry_type_id {
if i > i64::from(i8::MAX) || i < i64::from(i8::MIN) {
return Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() });
}
match self.set(name, i as i8) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else if TypeId::of::<u8>() == entry_type_id {
if i > i64::from(u8::MAX) || i < i64::from(u8::MIN) {
return Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() });
}
match self.set(name, i as u8) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else {
return Err(tinyscript::environment::Error::EnvVarWrongType { name: name.into() });
}
}
ScriptingValue::String(s) => {
if TypeId::of::<String>() == entry_type_id {
match self.set(name, s) {
Ok(_) => {}
Err(cause) => {
return Err(tinyscript::environment::Error::EnvVarSet {
name: name.into(),
cause: cause.to_string().into(),
});
}
}
} else {
return Err(tinyscript::environment::Error::EnvVarWrongType { name: name.into() });
}
}
}
Ok(())
}
}