#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct BindingConfig {
pub target: u8,
pub param_a: u8,
pub param_b: u8,
pub flags: u8,
}
impl BindingConfig {
pub fn sensory(field_id: u8, offset: u16) -> Self {
Self {
target: field_id,
param_a: offset as u8,
param_b: (offset >> 8) as u8,
flags: 0,
}
}
pub fn motor(channel_id: u8, scale: u8) -> Self {
Self {
target: channel_id,
param_a: scale,
param_b: 0,
flags: 0,
}
}
pub fn memory_reader(bank_slot: u8, query_dim: u8, top_k: u8) -> Self {
Self {
target: bank_slot,
param_a: query_dim,
param_b: top_k,
flags: 0,
}
}
pub fn memory_matcher(bank_slot: u8, threshold: u8) -> Self {
Self {
target: bank_slot,
param_a: threshold,
param_b: 0,
flags: 0,
}
}
pub fn gate(chemical_id: u8, sensitivity: u8) -> Self {
Self {
target: chemical_id,
param_a: sensitivity,
param_b: 0,
flags: 0,
}
}
pub fn oscillator(period: u8, amplitude: u8, phase_offset: u8) -> Self {
Self {
target: period,
param_a: amplitude,
param_b: phase_offset,
flags: 0,
}
}
#[inline]
pub fn sensory_offset(&self) -> u16 {
self.param_a as u16 | ((self.param_b as u16) << 8)
}
}
pub struct BindingTable {
entries: Vec<BindingConfig>,
next_slot: u8,
}
impl BindingTable {
pub fn new() -> Self {
Self {
entries: vec![BindingConfig::default()], next_slot: 1,
}
}
pub fn add(&mut self, config: BindingConfig) -> Option<u8> {
if self.next_slot == 0 {
return None; }
let slot = self.next_slot;
if self.entries.len() <= slot as usize {
self.entries.resize(slot as usize + 1, BindingConfig::default());
}
self.entries[slot as usize] = config;
self.next_slot = self.next_slot.checked_add(1).unwrap_or(0);
Some(slot)
}
#[inline]
pub fn get(&self, slot: u8) -> Option<&BindingConfig> {
if slot == 0 || slot as usize >= self.entries.len() {
None
} else {
Some(&self.entries[slot as usize])
}
}
pub fn len(&self) -> usize {
if self.entries.len() <= 1 { 0 } else { self.entries.len() - 1 }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn entries(&self) -> &[BindingConfig] {
&self.entries
}
pub fn from_entries(entries: Vec<BindingConfig>) -> Self {
let next_slot = if entries.len() >= 256 { 0 } else { entries.len() as u8 };
Self { entries, next_slot }
}
}
impl Default for BindingTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binding_table_add_get() {
let mut table = BindingTable::new();
assert!(table.is_empty());
let cfg = BindingConfig::sensory(3, 128);
let slot = table.add(cfg).unwrap();
assert_eq!(slot, 1);
assert_eq!(table.len(), 1);
let retrieved = table.get(slot).unwrap();
assert_eq!(retrieved.target, 3);
assert_eq!(retrieved.sensory_offset(), 128);
assert!(table.get(0).is_none());
}
#[test]
fn binding_config_constructors() {
let s = BindingConfig::sensory(5, 300);
assert_eq!(s.target, 5);
assert_eq!(s.sensory_offset(), 300);
let m = BindingConfig::motor(2, 128);
assert_eq!(m.target, 2);
assert_eq!(m.param_a, 128);
let mr = BindingConfig::memory_reader(1, 8, 3);
assert_eq!(mr.target, 1);
assert_eq!(mr.param_a, 8);
assert_eq!(mr.param_b, 3);
let mm = BindingConfig::memory_matcher(0, 200);
assert_eq!(mm.target, 0);
assert_eq!(mm.param_a, 200);
let g = BindingConfig::gate(4, 100);
assert_eq!(g.target, 4);
assert_eq!(g.param_a, 100);
let o = BindingConfig::oscillator(20, 50, 10);
assert_eq!(o.target, 20);
assert_eq!(o.param_a, 50);
assert_eq!(o.param_b, 10);
}
}