1use core::marker::PhantomData;
9
10use crate::authoring::{
11 BuildError, InputPort, KnotHandle, OutputPort, Pattern, PatternInstance, ValidationError,
12 Weave, WeaveBuilder,
13};
14use crate::foundation::{
15 CalcOp, CompareOp, FlagPriority, KnotKind, Signal, SignalDomain, TimerMode,
16};
17
18#[derive(Clone, Debug, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum ComposeError {
22 Build(BuildError),
24 Validation(ValidationError),
26}
27
28impl From<BuildError> for ComposeError {
29 fn from(value: BuildError) -> Self {
30 Self::Build(value)
31 }
32}
33
34impl From<ValidationError> for ComposeError {
35 fn from(value: ValidationError) -> Self {
36 Self::Validation(value)
37 }
38}
39
40impl core::fmt::Display for ComposeError {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 match self {
43 Self::Build(error) => error.fmt(f),
44 Self::Validation(error) => error.fmt(f),
45 }
46 }
47}
48
49#[cfg(feature = "std")]
50impl std::error::Error for ComposeError {}
51
52pub trait WireDomain {
54 const DOMAIN: SignalDomain;
56}
57
58#[derive(Clone, Copy, Debug)]
60pub struct Bool;
61
62#[derive(Clone, Copy, Debug)]
64pub struct Level;
65
66#[derive(Clone, Copy, Debug)]
68pub struct Count;
69
70impl WireDomain for Bool {
71 const DOMAIN: SignalDomain = SignalDomain::Bool;
72}
73
74impl WireDomain for Level {
75 const DOMAIN: SignalDomain = SignalDomain::Level;
76}
77
78impl WireDomain for Count {
79 const DOMAIN: SignalDomain = SignalDomain::Count;
80}
81
82pub trait NumericWireDomain: WireDomain {}
84
85impl NumericWireDomain for Level {}
86impl NumericWireDomain for Count {}
87
88#[derive(Clone, Debug)]
92pub struct Wire<D: WireDomain> {
93 port: OutputPort,
94 marker: PhantomData<D>,
95}
96
97impl<D: WireDomain> Wire<D> {
98 pub fn port(&self) -> &OutputPort {
100 &self.port
101 }
102}
103
104pub type BoolWire = Wire<Bool>;
106pub type LevelWire = Wire<Level>;
108pub type CountWire = Wire<Count>;
110
111pub struct Composer {
118 builder: WeaveBuilder,
119}
120
121impl Composer {
122 pub(crate) fn new(id: impl Into<std::string::String>) -> Result<Self, BuildError> {
123 Ok(Self {
124 builder: WeaveBuilder::new(id)?,
125 })
126 }
127
128 pub(crate) fn build(self) -> Result<Weave, ValidationError> {
129 self.builder.build()
130 }
131
132 pub fn knot(
134 &mut self,
135 id: impl Into<std::string::String>,
136 kind: KnotKind,
137 ) -> Result<KnotHandle, BuildError> {
138 self.builder.knot(id, kind)
139 }
140
141 pub fn input(&self, knot: &KnotHandle, name: &str) -> Result<InputPort, BuildError> {
143 self.builder.input(knot, name)
144 }
145
146 pub fn output(&self, knot: &KnotHandle, name: &str) -> Result<OutputPort, BuildError> {
148 self.builder.output(knot, name)
149 }
150
151 pub fn thread(&mut self, from: &OutputPort, to: &InputPort) -> Result<(), BuildError> {
154 self.builder.connect(from.clone(), to.clone())?;
155 Ok(())
156 }
157
158 pub fn include(
160 &mut self,
161 instance_id: impl Into<std::string::String>,
162 pattern: &Pattern,
163 ) -> Result<PatternInstance, BuildError> {
164 self.builder.include(instance_id, pattern)
165 }
166
167 pub fn bool_input(
169 &mut self,
170 id: impl Into<std::string::String>,
171 ) -> Result<BoolWire, BuildError> {
172 self.source(id, KnotKind::signal_in(SignalDomain::Bool))
173 }
174
175 pub fn level_input(
177 &mut self,
178 id: impl Into<std::string::String>,
179 ) -> Result<LevelWire, BuildError> {
180 self.source(id, KnotKind::signal_in(SignalDomain::Level))
181 }
182
183 pub fn count_input(
185 &mut self,
186 id: impl Into<std::string::String>,
187 ) -> Result<CountWire, BuildError> {
188 self.source(id, KnotKind::signal_in(SignalDomain::Count))
189 }
190
191 pub fn bool_constant(
193 &mut self,
194 id: impl Into<std::string::String>,
195 value: bool,
196 ) -> Result<BoolWire, BuildError> {
197 self.source(id, KnotKind::constant_bool(value))
198 }
199
200 pub fn on_start(&mut self, id: impl Into<std::string::String>) -> Result<BoolWire, BuildError> {
202 self.source(id, KnotKind::OnStart)
203 }
204
205 pub fn level_constant(
207 &mut self,
208 id: impl Into<std::string::String>,
209 value: f32,
210 ) -> Result<LevelWire, BuildError> {
211 self.source(id, KnotKind::constant_level(value))
212 }
213
214 pub fn count_constant(
216 &mut self,
217 id: impl Into<std::string::String>,
218 value: i32,
219 ) -> Result<CountWire, BuildError> {
220 self.source(id, KnotKind::constant_count(value))
221 }
222
223 pub fn signal_out<D: WireDomain>(
225 &mut self,
226 id: impl Into<std::string::String>,
227 path: impl Into<std::string::String>,
228 wire: &Wire<D>,
229 ) -> Result<(), BuildError> {
230 let knot = self.knot(id, KnotKind::signal_out(path, D::DOMAIN))?;
231 let input = self.input(&knot, "in")?;
232 self.thread(wire.port(), &input)
233 }
234
235 pub fn not(
237 &mut self,
238 id: impl Into<std::string::String>,
239 input: &BoolWire,
240 ) -> Result<BoolWire, BuildError> {
241 self.unary(id, KnotKind::not(), input)
242 }
243
244 pub fn and(
246 &mut self,
247 id: impl Into<std::string::String>,
248 a: &BoolWire,
249 b: &BoolWire,
250 ) -> Result<BoolWire, BuildError> {
251 self.binary(id, KnotKind::and2(), a, b, "in_0", "in_1")
252 }
253
254 pub fn or(
256 &mut self,
257 id: impl Into<std::string::String>,
258 a: &BoolWire,
259 b: &BoolWire,
260 ) -> Result<BoolWire, BuildError> {
261 self.binary(id, KnotKind::or2(), a, b, "in_0", "in_1")
262 }
263
264 pub fn xor(
266 &mut self,
267 id: impl Into<std::string::String>,
268 a: &BoolWire,
269 b: &BoolWire,
270 ) -> Result<BoolWire, BuildError> {
271 self.binary(id, KnotKind::xor(), a, b, "a", "b")
272 }
273
274 pub fn rising<D: WireDomain>(
276 &mut self,
277 id: impl Into<std::string::String>,
278 input: &Wire<D>,
279 ) -> Result<BoolWire, BuildError> {
280 self.unary(id, KnotKind::rising_from_zero(), input)
281 }
282
283 pub fn falling<D: WireDomain>(
285 &mut self,
286 id: impl Into<std::string::String>,
287 input: &Wire<D>,
288 ) -> Result<BoolWire, BuildError> {
289 self.unary(id, KnotKind::falling_to_zero(), input)
290 }
291
292 pub fn change<D: WireDomain>(
294 &mut self,
295 id: impl Into<std::string::String>,
296 input: &Wire<D>,
297 ) -> Result<BoolWire, BuildError> {
298 self.unary(id, KnotKind::change(), input)
299 }
300
301 pub fn flag(
303 &mut self,
304 id: impl Into<std::string::String>,
305 priority: FlagPriority,
306 set: Option<&BoolWire>,
307 reset: Option<&BoolWire>,
308 toggle: Option<&BoolWire>,
309 ) -> Result<BoolWire, BuildError> {
310 let knot = self.knot(id, KnotKind::flag(priority, toggle.is_some()))?;
311 self.optional_thread(set, &knot, "set")?;
312 self.optional_thread(reset, &knot, "reset")?;
313 self.optional_thread(toggle, &knot, "toggle")?;
314 self.typed_output(&knot, "out")
315 }
316
317 pub fn counter(
319 &mut self,
320 id: impl Into<std::string::String>,
321 increment: Option<&BoolWire>,
322 decrement: Option<&BoolWire>,
323 reset: Option<&BoolWire>,
324 ) -> Result<CountWire, BuildError> {
325 let knot = self.knot(id, KnotKind::counter())?;
326 self.optional_thread(increment, &knot, "inc")?;
327 self.optional_thread(decrement, &knot, "dec")?;
328 self.optional_thread(reset, &knot, "reset")?;
329 self.typed_output(&knot, "count")
330 }
331
332 pub fn pulse_hold(
334 &mut self,
335 id: impl Into<std::string::String>,
336 ticks: u16,
337 start: &BoolWire,
338 ) -> Result<BoolWire, BuildError> {
339 let knot = self.knot(id, KnotKind::timer(TimerMode::PulseHold, ticks))?;
340 let input = self.input(&knot, "start")?;
341 self.thread(start.port(), &input)?;
342 self.typed_output(&knot, "active")
343 }
344
345 pub fn fed_countdown(
347 &mut self,
348 id: impl Into<std::string::String>,
349 ticks: u16,
350 feed: &BoolWire,
351 ) -> Result<BoolWire, BuildError> {
352 let knot = self.knot(id, KnotKind::timer(TimerMode::FedCountdown, ticks))?;
353 let input = self.input(&knot, "feed")?;
354 self.thread(feed.port(), &input)?;
355 self.typed_output(&knot, "active")
356 }
357
358 pub fn compare<D: WireDomain>(
360 &mut self,
361 id: impl Into<std::string::String>,
362 op: CompareOp,
363 lhs: &Wire<D>,
364 rhs: &Wire<D>,
365 ) -> Result<BoolWire, BuildError> {
366 self.binary(
367 id,
368 KnotKind::compare(op, None, D::DOMAIN),
369 lhs,
370 rhs,
371 "lhs",
372 "rhs",
373 )
374 }
375
376 pub fn compare_constant<D: WireDomain>(
378 &mut self,
379 id: impl Into<std::string::String>,
380 op: CompareOp,
381 lhs: &Wire<D>,
382 rhs: Signal,
383 ) -> Result<BoolWire, BuildError> {
384 let knot = self.knot(id, KnotKind::compare(op, Some(rhs), D::DOMAIN))?;
385 let input = self.input(&knot, "lhs")?;
386 self.thread(lhs.port(), &input)?;
387 self.typed_output(&knot, "out")
388 }
389
390 pub fn calc<D: NumericWireDomain>(
392 &mut self,
393 id: impl Into<std::string::String>,
394 op: CalcOp,
395 a: &Wire<D>,
396 b: &Wire<D>,
397 ) -> Result<Wire<D>, BuildError> {
398 self.binary(id, KnotKind::calc(op, D::DOMAIN), a, b, "a", "b")
399 }
400
401 pub fn map<D: NumericWireDomain>(
403 &mut self,
404 id: impl Into<std::string::String>,
405 input: &Wire<D>,
406 in_min: Signal,
407 in_max: Signal,
408 out_min: Signal,
409 out_max: Signal,
410 ) -> Result<Wire<D>, BuildError> {
411 self.unary(
412 id,
413 KnotKind::map(in_min, in_max, out_min, out_max, D::DOMAIN),
414 input,
415 )
416 }
417
418 pub fn threshold<D: NumericWireDomain>(
420 &mut self,
421 id: impl Into<std::string::String>,
422 input: &Wire<D>,
423 high: Signal,
424 low: Signal,
425 use_hysteresis: bool,
426 ) -> Result<ThresholdWires, BuildError> {
427 let knot = self.knot(
428 id,
429 KnotKind::Threshold {
430 domain: D::DOMAIN,
431 high,
432 low,
433 use_hysteresis,
434 },
435 )?;
436 let target = self.input(&knot, "in")?;
437 self.thread(input.port(), &target)?;
438 Ok(ThresholdWires {
439 out: self.typed_output(&knot, "out")?,
440 crossed_up: self.typed_output(&knot, "crossed_up")?,
441 crossed_down: self.typed_output(&knot, "crossed_down")?,
442 })
443 }
444
445 pub fn delay<D: WireDomain>(
447 &mut self,
448 id: impl Into<std::string::String>,
449 ticks: u16,
450 input: &Wire<D>,
451 ) -> Result<Wire<D>, BuildError> {
452 self.unary(id, KnotKind::Delay { ticks }, input)
453 }
454
455 pub fn abs<D: NumericWireDomain>(
457 &mut self,
458 id: impl Into<std::string::String>,
459 input: &Wire<D>,
460 ) -> Result<Wire<D>, BuildError> {
461 self.unary(id, KnotKind::abs(D::DOMAIN), input)
462 }
463
464 pub fn neg<D: NumericWireDomain>(
466 &mut self,
467 id: impl Into<std::string::String>,
468 input: &Wire<D>,
469 ) -> Result<Wire<D>, BuildError> {
470 self.unary(id, KnotKind::neg(D::DOMAIN), input)
471 }
472
473 pub fn sqrt<D: NumericWireDomain>(
475 &mut self,
476 id: impl Into<std::string::String>,
477 input: &Wire<D>,
478 ) -> Result<Wire<D>, BuildError> {
479 self.unary(id, KnotKind::sqrt(D::DOMAIN), input)
480 }
481
482 pub fn clamp<D: NumericWireDomain>(
484 &mut self,
485 id: impl Into<std::string::String>,
486 input: &Wire<D>,
487 min: Signal,
488 max: Signal,
489 ) -> Result<Wire<D>, BuildError> {
490 self.unary(id, KnotKind::clamp(min, max, D::DOMAIN), input)
491 }
492
493 pub fn digitize<D: NumericWireDomain>(
495 &mut self,
496 id: impl Into<std::string::String>,
497 input: &Wire<D>,
498 steps: u16,
499 ) -> Result<Wire<D>, BuildError> {
500 self.unary(id, KnotKind::digitize(steps, D::DOMAIN), input)
501 }
502
503 pub fn random<D: NumericWireDomain>(
505 &mut self,
506 id: impl Into<std::string::String>,
507 require_gate: bool,
508 min: Option<&Wire<D>>,
509 max: Option<&Wire<D>>,
510 gate: Option<&BoolWire>,
511 ) -> Result<Wire<D>, BuildError> {
512 let knot = self.knot(id, KnotKind::random(require_gate, D::DOMAIN))?;
513 self.optional_thread(min, &knot, "min")?;
514 self.optional_thread(max, &knot, "max")?;
515 self.optional_thread(gate, &knot, "gate")?;
516 self.typed_output(&knot, "out")
517 }
518
519 pub fn select<D: WireDomain>(
521 &mut self,
522 id: impl Into<std::string::String>,
523 select: &BoolWire,
524 a: &Wire<D>,
525 b: &Wire<D>,
526 ) -> Result<Wire<D>, BuildError> {
527 let knot = self.knot(id, KnotKind::select())?;
528 for (source, name) in [(select.port(), "sel"), (a.port(), "a"), (b.port(), "b")] {
529 let input = self.input(&knot, name)?;
530 self.thread(source, &input)?;
531 }
532 self.typed_output(&knot, "out")
533 }
534
535 pub fn convert<F: WireDomain, T: WireDomain>(
537 &mut self,
538 id: impl Into<std::string::String>,
539 input: &Wire<F>,
540 ) -> Result<Wire<T>, BuildError> {
541 self.unary(id, KnotKind::convert(F::DOMAIN, T::DOMAIN), input)
542 }
543
544 pub fn emit(
546 &mut self,
547 id: impl Into<std::string::String>,
548 name: impl Into<std::string::String>,
549 trigger: &BoolWire,
550 ) -> Result<(), BuildError> {
551 let knot = self.knot(id, KnotKind::emit_command(name))?;
552 let input = self.input(&knot, "trigger")?;
553 self.thread(trigger.port(), &input)
554 }
555
556 fn source<D: WireDomain>(
557 &mut self,
558 id: impl Into<std::string::String>,
559 kind: KnotKind,
560 ) -> Result<Wire<D>, BuildError> {
561 let knot = self.knot(id, kind)?;
562 self.typed_output(&knot, "out")
563 }
564
565 fn unary<D: WireDomain, R: WireDomain>(
566 &mut self,
567 id: impl Into<std::string::String>,
568 kind: KnotKind,
569 input: &Wire<D>,
570 ) -> Result<Wire<R>, BuildError> {
571 let knot = self.knot(id, kind)?;
572 let target = self.input(&knot, "in")?;
573 self.thread(input.port(), &target)?;
574 self.typed_output(&knot, "out")
575 }
576
577 fn binary<D: WireDomain, E: WireDomain, R: WireDomain>(
578 &mut self,
579 id: impl Into<std::string::String>,
580 kind: KnotKind,
581 a: &Wire<D>,
582 b: &Wire<E>,
583 a_port: &str,
584 b_port: &str,
585 ) -> Result<Wire<R>, BuildError> {
586 let knot = self.knot(id, kind)?;
587 let a_target = self.input(&knot, a_port)?;
588 let b_target = self.input(&knot, b_port)?;
589 self.thread(a.port(), &a_target)?;
590 self.thread(b.port(), &b_target)?;
591 self.typed_output(&knot, "out")
592 }
593
594 fn optional_thread<D: WireDomain>(
595 &mut self,
596 wire: Option<&Wire<D>>,
597 knot: &KnotHandle,
598 port: &str,
599 ) -> Result<(), BuildError> {
600 if let Some(wire) = wire {
601 let input = self.input(knot, port)?;
602 self.thread(wire.port(), &input)?;
603 }
604 Ok(())
605 }
606
607 fn typed_output<D: WireDomain>(
608 &self,
609 knot: &KnotHandle,
610 port: &str,
611 ) -> Result<Wire<D>, BuildError> {
612 Ok(Wire {
613 port: self.output(knot, port)?,
614 marker: PhantomData,
615 })
616 }
617}
618
619#[derive(Clone, Debug)]
621pub struct ThresholdWires {
622 pub out: BoolWire,
624 pub crossed_up: BoolWire,
626 pub crossed_down: BoolWire,
628}