1use std::collections::HashMap;
4use std::sync::atomic::{AtomicBool, AtomicU64};
5use std::sync::{Arc, Mutex, OnceLock, Weak};
6use std::time::Instant;
7
8use super::pathway::{Pathway, PathwayKind, Summation};
9use super::summing::Summing;
10use super::{Cell, Counters, Fabric, Inner};
11use crate::nerve::effector::Effector;
12use crate::nerve::latency::LatencyEstimate;
13use crate::nerve::neuron::{Gate, Mechanisms, Neuron, NeuronKind};
14use crate::nerve::receptor::{Receptor, Stop};
15use crate::nerve::signal::{NeuronId, PathwayId};
16use amate::Engine;
17
18#[derive(Debug)]
20#[non_exhaustive]
21pub enum BuildError {
22 DuplicateName(String),
24 UnknownNeuron {
26 from: NeuronId,
28 to: NeuronId,
30 },
31 Url {
34 name: String,
36 reason: String,
38 },
39 Runtime(&'static str, std::io::Error),
41}
42
43impl std::fmt::Display for BuildError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 Self::DuplicateName(name) => write!(f, "two neurons are named '{name}'"),
47 Self::UnknownNeuron { from, to } => {
48 write!(
49 f,
50 "pathway {from} -> {to} names a neuron that was not added"
51 )
52 }
53 Self::Url { name, reason } => write!(f, "{name}: {reason}"),
54 Self::Runtime(which, e) => write!(f, "the {which} runtime: {e}"),
55 }
56 }
57}
58
59impl std::error::Error for BuildError {
60 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
61 match self {
62 Self::Runtime(_, e) => Some(e),
63 _ => None,
64 }
65 }
66}
67
68pub struct FabricBuilder {
70 neurons: Vec<(Arc<dyn Neuron>, Mechanisms)>,
71 receptors: Vec<(NeuronId, Arc<dyn Receptor>)>,
72 pathways: Vec<(NeuronId, NeuronId, PathwayKind, f32, Option<Summation>)>,
73 efferent_threads: usize,
74 efferent: Option<tokio::runtime::Handle>,
75}
76
77impl Default for FabricBuilder {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl FabricBuilder {
84 pub fn new() -> Self {
86 Self {
87 neurons: Vec::new(),
88 receptors: Vec::new(),
89 pathways: Vec::new(),
90 efferent_threads: 2,
91 efferent: None,
92 }
93 }
94
95 pub fn efferent_threads(mut self, threads: usize) -> Self {
97 self.efferent_threads = threads.max(1);
98 self
99 }
100
101 pub fn efferent(mut self, runtime: tokio::runtime::Handle) -> Self {
105 self.efferent = Some(runtime);
106 self
107 }
108
109 pub fn neuron(&mut self, neuron: impl Neuron) -> NeuronId {
113 self.add(Arc::new(neuron))
114 }
115
116 pub(crate) fn add(&mut self, neuron: Arc<dyn Neuron>) -> NeuronId {
118 let id = NeuronId(u32::try_from(self.neurons.len()).expect("fewer than 4G neurons"));
119 self.neurons.push((neuron, Mechanisms::NONE));
120 id
121 }
122
123 pub fn receptor<R: Receptor>(&mut self, receptor: R) -> NeuronId {
126 let receptor: Arc<R> = Arc::new(receptor);
127 let neuron: Arc<dyn Neuron> = receptor.clone();
128 let id = self.add(neuron);
129 self.receptors.push((id, receptor));
130 id
131 }
132
133 pub fn effector(&mut self, effector: impl Into<Effector>) -> NeuronId {
137 self.neuron(effector.into())
138 }
139
140 pub fn gate(&mut self, id: NeuronId, mechanisms: Mechanisms) -> &mut Self {
146 self.neurons[id.index()].1 = mechanisms;
147 self
148 }
149
150 pub(crate) fn next_pathway(&self) -> PathwayId {
152 PathwayId(u32::try_from(self.pathways.len()).expect("fewer than 4G pathways"))
153 }
154
155 pub fn reflex(&mut self, from: NeuronId, to: NeuronId) -> Link<'_> {
158 self.link(from, to, PathwayKind::Reflex, REFLEX_WEIGHT, None)
159 }
160
161 pub fn deliberative(&mut self, from: NeuronId, to: NeuronId) -> Link<'_> {
165 self.link(
166 from,
167 to,
168 PathwayKind::Deliberative,
169 DELIBERATIVE_WEIGHT,
170 Some(Summation::default()),
171 )
172 }
173
174 fn link(
175 &mut self,
176 from: NeuronId,
177 to: NeuronId,
178 kind: PathwayKind,
179 weight: f32,
180 summation: Option<Summation>,
181 ) -> Link<'_> {
182 self.pathways.push((from, to, kind, weight, summation));
183 Link {
184 pathway: self.pathways.last_mut().expect("just pushed"),
185 }
186 }
187
188 pub fn build(self) -> Result<Fabric, BuildError> {
192 let count = self.neurons.len();
193 let mut names = std::collections::HashSet::new();
194 for (neuron, _) in &self.neurons {
195 if !names.insert(neuron.name()) {
196 return Err(BuildError::DuplicateName(neuron.name().to_string()));
197 }
198 }
199 drop(names);
200 for &(from, to, ..) in &self.pathways {
201 if from.index() >= count || to.index() >= count {
202 return Err(BuildError::UnknownNeuron { from, to });
203 }
204 }
205 let (efferent, efferent_handle) = match self.efferent {
206 Some(handle) => (None, handle),
207 None => {
208 let runtime = tokio::runtime::Builder::new_multi_thread()
209 .worker_threads(self.efferent_threads)
210 .thread_name("furnace-efferent")
211 .enable_all()
212 .build()
213 .map_err(|e| BuildError::Runtime("efferent", e))?;
214 let handle = runtime.handle().clone();
215 (Some(runtime), handle)
216 }
217 };
218 let afferent = tokio::runtime::Builder::new_multi_thread()
219 .worker_threads(1)
220 .thread_name("furnace-afferent")
221 .enable_all()
222 .build()
223 .map_err(|e| BuildError::Runtime("afferent", e))?;
224 let mut by_name = HashMap::new();
225 let mut cells: Vec<Cell> = self
226 .neurons
227 .into_iter()
228 .enumerate()
229 .map(|(i, (neuron, mechanisms))| {
230 by_name.insert(neuron.name().to_string(), NeuronId(i as u32));
231 let kind = neuron.kind();
232 Cell {
233 arcs: if kind == NeuronKind::Effector {
234 (0..count).map(|_| OnceLock::new()).collect()
235 } else {
236 Vec::new()
237 },
238 neuron,
239 kind,
240 gate: Gate::new(mechanisms),
241 counters: Counters::default(),
242 out: Vec::new(),
243 last_prepared: AtomicU64::new(0),
244 }
245 })
246 .collect();
247 let inner = Arc::new_cyclic(|weak: &Weak<Inner>| {
248 let pathways: Vec<Pathway> = self
249 .pathways
250 .into_iter()
251 .enumerate()
252 .map(|(i, (from, to, kind, weight, summation))| {
253 let id = PathwayId(i as u32);
254 cells[from.index()].out.push(id);
255 let queue = summation.map(|s| {
256 Engine::start(
257 Summing {
258 fabric: weak.clone(),
259 pathway: id,
260 },
261 amate::Config {
262 max_batch: s.max_batch,
263 workers: s.workers,
264 capacity: Some(s.capacity),
265 waits_unannounced: !s.announced_only,
266 },
267 move |_| format!("furnace-nerve-{i}"),
268 )
269 });
270 Pathway {
271 id,
272 from,
273 to,
274 kind,
275 weight,
276 latency: LatencyEstimate::new(),
277 signals: AtomicU64::new(0),
278 ineligible: AtomicU64::new(0),
279 overflow: AtomicU64::new(0),
280 summation,
281 queue,
282 }
283 })
284 .collect();
285 Inner {
286 epoch: Instant::now(),
287 cells,
288 pathways,
289 by_name,
290 receptors: self.receptors,
291 started: AtomicBool::new(false),
292 received: AtomicU64::new(0),
293 reached_brain: AtomicU64::new(0),
294 unrouted: AtomicU64::new(0),
295 no_eligible: AtomicU64::new(0),
296 loops: AtomicU64::new(0),
297 commands_direct: AtomicU64::new(0),
298 stop: Arc::new(Stop::default()),
299 efferent_handle,
300 efferent: Mutex::new(efferent),
301 afferent_handle: afferent.handle().clone(),
302 afferent: Mutex::new(Some(afferent)),
303 }
304 });
305 Ok(Fabric(inner))
306 }
307}
308
309const REFLEX_WEIGHT: f32 = 1.0;
311const DELIBERATIVE_WEIGHT: f32 = 2.0;
314
315pub struct Link<'a> {
317 pathway: &'a mut (NeuronId, NeuronId, PathwayKind, f32, Option<Summation>),
318}
319
320impl Link<'_> {
321 pub fn weight(self, weight: f32) -> Self {
323 self.pathway.3 = weight;
324 self
325 }
326
327 pub fn summation(self, summation: Summation) -> Self {
330 self.pathway.2 = PathwayKind::Deliberative;
331 self.pathway.4 = Some(summation);
332 self
333 }
334}