use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
use super::latency::LatencyEstimate;
use super::neuron::{Gate, Neuron, NeuronKind, Quiet};
use super::receptor::{Afferent, Receptor, Stop};
use super::signal::{NeuronId, PathwayId};
pub(crate) const MAX_HOPS: usize = 32;
mod builder;
mod pathway;
mod route;
mod summing;
#[cfg(test)]
mod tests;
pub use builder::{BuildError, FabricBuilder, Link};
use pathway::Pathway;
pub use pathway::{PathwayKind, Summation};
pub(crate) use route::Announced;
#[derive(Default)]
pub(crate) struct Counters {
pub received: AtomicU64,
pub fired: AtomicU64,
pub computed: AtomicU64,
pub quiet_threshold: AtomicU64,
pub quiet_habituation: AtomicU64,
pub quiet_refractory: AtomicU64,
pub quiet_not_interested: AtomicU64,
}
impl Counters {
fn quiet(&self, reason: Quiet) {
let cell = match reason {
Quiet::Threshold => &self.quiet_threshold,
Quiet::Habituation => &self.quiet_habituation,
Quiet::Refractory => &self.quiet_refractory,
Quiet::NotInterested => &self.quiet_not_interested,
};
cell.fetch_add(1, Ordering::Relaxed);
}
}
pub(crate) struct Cell {
pub neuron: Arc<dyn Neuron>,
pub kind: NeuronKind,
pub gate: Gate,
pub counters: Counters,
pub out: Vec<PathwayId>,
last_prepared: AtomicU64,
pub arcs: Vec<OnceLock<LatencyEstimate>>,
}
pub(crate) struct Inner {
pub epoch: Instant,
pub cells: Vec<Cell>,
pub pathways: Vec<Pathway>,
pub by_name: HashMap<String, NeuronId>,
receptors: Vec<(NeuronId, Arc<dyn Receptor>)>,
started: AtomicBool,
pub received: AtomicU64,
pub reached_brain: AtomicU64,
pub unrouted: AtomicU64,
pub no_eligible: AtomicU64,
pub loops: AtomicU64,
pub commands_direct: AtomicU64,
stop: Arc<Stop>,
efferent: Mutex<Option<tokio::runtime::Runtime>>,
efferent_handle: tokio::runtime::Handle,
afferent: Mutex<Option<tokio::runtime::Runtime>>,
afferent_handle: tokio::runtime::Handle,
}
impl Drop for Inner {
fn drop(&mut self) {
self.stop.raise();
let take = |runtime: &mut Mutex<Option<tokio::runtime::Runtime>>| {
runtime.get_mut().ok().and_then(Option::take)
};
let runtimes = (take(&mut self.efferent), take(&mut self.afferent));
if tokio::runtime::Handle::try_current().is_ok() {
std::thread::spawn(move || drop(runtimes));
} else {
drop(runtimes);
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Outcome {
pub quiet: Option<(NeuronId, Quiet)>,
pub hops: usize,
pub queued: usize,
pub acted: usize,
pub unrouted: usize,
pub no_pathway: usize,
pub dropped: usize,
}
impl Outcome {
pub fn is_quiet(&self) -> bool {
self.hops == 0
}
fn merge(&mut self, other: Outcome) {
if self.quiet.is_none() {
self.quiet = other.quiet;
}
self.hops = self.hops.max(other.hops);
self.queued += other.queued;
self.acted += other.acted;
self.unrouted += other.unrouted;
self.no_pathway += other.no_pathway;
self.dropped += other.dropped;
}
}
#[derive(Clone)]
pub struct Fabric(pub(crate) Arc<Inner>);
impl Fabric {
pub fn builder() -> FabricBuilder {
FabricBuilder::new()
}
pub fn start(&self) {
if self.0.started.swap(true, Ordering::AcqRel) {
return;
}
for (id, receptor) in &self.0.receptors {
receptor.start(Afferent::new(
Arc::downgrade(&self.0),
*id,
self.0.stop.clone(),
));
}
}
pub(crate) fn receptors(&self) -> &[(NeuronId, Arc<dyn Receptor>)] {
&self.0.receptors
}
pub fn neuron(&self, name: &str) -> Option<NeuronId> {
self.0.by_name.get(name).copied()
}
pub(crate) fn name(&self, id: NeuronId) -> &str {
self.0.cells[id.index()].neuron.name()
}
pub(crate) fn effector_names(&self) -> Vec<String> {
self.0
.cells
.iter()
.filter(|c| c.kind == NeuronKind::Effector)
.map(|c| c.neuron.name().to_string())
.collect()
}
pub(crate) fn events(
&self,
id: NeuronId,
) -> Option<tokio::sync::broadcast::Receiver<bytes::Bytes>> {
self.0.cells.get(id.index())?.neuron.events()
}
pub(crate) fn efferent(&self) -> &tokio::runtime::Handle {
&self.0.efferent_handle
}
pub(crate) fn afferent(&self) -> &tokio::runtime::Handle {
&self.0.afferent_handle
}
pub(crate) fn nanos(&self, now: Instant) -> u64 {
super::nanos_since(self.0.epoch, now)
}
pub fn brain_fraction(&self) -> f64 {
let received = self.0.received.load(Ordering::Relaxed);
if received == 0 {
0.0
} else {
self.0.reached_brain.load(Ordering::Relaxed) as f64 / received as f64
}
}
pub fn waiting(&self) -> u64 {
self.0
.pathways
.iter()
.filter_map(|p| p.queue.as_ref())
.map(|q| q.waiting() as u64)
.sum()
}
}