use core::marker::PhantomData;
use crate::authoring::{
BuildError, InputPort, KnotHandle, OutputPort, Pattern, PatternInstance, ValidationError,
Weave, WeaveBuilder,
};
use crate::foundation::{
CalcOp, CompareOp, FlagPriority, KnotKind, Signal, SignalDomain, TimerMode,
};
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ComposeError {
Build(BuildError),
Validation(ValidationError),
}
impl From<BuildError> for ComposeError {
fn from(value: BuildError) -> Self {
Self::Build(value)
}
}
impl From<ValidationError> for ComposeError {
fn from(value: ValidationError) -> Self {
Self::Validation(value)
}
}
impl core::fmt::Display for ComposeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Build(error) => error.fmt(f),
Self::Validation(error) => error.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ComposeError {}
pub trait WireDomain {
const DOMAIN: SignalDomain;
}
#[derive(Clone, Copy, Debug)]
pub struct Bool;
#[derive(Clone, Copy, Debug)]
pub struct Level;
#[derive(Clone, Copy, Debug)]
pub struct Count;
impl WireDomain for Bool {
const DOMAIN: SignalDomain = SignalDomain::Bool;
}
impl WireDomain for Level {
const DOMAIN: SignalDomain = SignalDomain::Level;
}
impl WireDomain for Count {
const DOMAIN: SignalDomain = SignalDomain::Count;
}
pub trait NumericWireDomain: WireDomain {}
impl NumericWireDomain for Level {}
impl NumericWireDomain for Count {}
#[derive(Clone, Debug)]
pub struct Wire<D: WireDomain> {
port: OutputPort,
marker: PhantomData<D>,
}
impl<D: WireDomain> Wire<D> {
pub fn port(&self) -> &OutputPort {
&self.port
}
}
pub type BoolWire = Wire<Bool>;
pub type LevelWire = Wire<Level>;
pub type CountWire = Wire<Count>;
pub struct Composer {
builder: WeaveBuilder,
}
impl Composer {
pub(crate) fn new(id: impl Into<std::string::String>) -> Result<Self, BuildError> {
Ok(Self {
builder: WeaveBuilder::new(id)?,
})
}
pub(crate) fn build(self) -> Result<Weave, ValidationError> {
self.builder.build()
}
pub fn knot(
&mut self,
id: impl Into<std::string::String>,
kind: KnotKind,
) -> Result<KnotHandle, BuildError> {
self.builder.knot(id, kind)
}
pub fn input(&self, knot: &KnotHandle, name: &str) -> Result<InputPort, BuildError> {
self.builder.input(knot, name)
}
pub fn output(&self, knot: &KnotHandle, name: &str) -> Result<OutputPort, BuildError> {
self.builder.output(knot, name)
}
pub fn thread(&mut self, from: &OutputPort, to: &InputPort) -> Result<(), BuildError> {
self.builder.connect(from.clone(), to.clone())?;
Ok(())
}
pub fn include(
&mut self,
instance_id: impl Into<std::string::String>,
pattern: &Pattern,
) -> Result<PatternInstance, BuildError> {
self.builder.include(instance_id, pattern)
}
pub fn bool_input(
&mut self,
id: impl Into<std::string::String>,
) -> Result<BoolWire, BuildError> {
self.source(id, KnotKind::signal_in(SignalDomain::Bool))
}
pub fn level_input(
&mut self,
id: impl Into<std::string::String>,
) -> Result<LevelWire, BuildError> {
self.source(id, KnotKind::signal_in(SignalDomain::Level))
}
pub fn count_input(
&mut self,
id: impl Into<std::string::String>,
) -> Result<CountWire, BuildError> {
self.source(id, KnotKind::signal_in(SignalDomain::Count))
}
pub fn bool_constant(
&mut self,
id: impl Into<std::string::String>,
value: bool,
) -> Result<BoolWire, BuildError> {
self.source(id, KnotKind::constant_bool(value))
}
pub fn on_start(&mut self, id: impl Into<std::string::String>) -> Result<BoolWire, BuildError> {
self.source(id, KnotKind::OnStart)
}
pub fn level_constant(
&mut self,
id: impl Into<std::string::String>,
value: f32,
) -> Result<LevelWire, BuildError> {
self.source(id, KnotKind::constant_level(value))
}
pub fn count_constant(
&mut self,
id: impl Into<std::string::String>,
value: i32,
) -> Result<CountWire, BuildError> {
self.source(id, KnotKind::constant_count(value))
}
pub fn signal_out<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
path: impl Into<std::string::String>,
wire: &Wire<D>,
) -> Result<(), BuildError> {
let knot = self.knot(id, KnotKind::signal_out(path, D::DOMAIN))?;
let input = self.input(&knot, "in")?;
self.thread(wire.port(), &input)
}
pub fn not(
&mut self,
id: impl Into<std::string::String>,
input: &BoolWire,
) -> Result<BoolWire, BuildError> {
self.unary(id, KnotKind::not(), input)
}
pub fn and(
&mut self,
id: impl Into<std::string::String>,
a: &BoolWire,
b: &BoolWire,
) -> Result<BoolWire, BuildError> {
self.binary(id, KnotKind::and2(), a, b, "in_0", "in_1")
}
pub fn or(
&mut self,
id: impl Into<std::string::String>,
a: &BoolWire,
b: &BoolWire,
) -> Result<BoolWire, BuildError> {
self.binary(id, KnotKind::or2(), a, b, "in_0", "in_1")
}
pub fn xor(
&mut self,
id: impl Into<std::string::String>,
a: &BoolWire,
b: &BoolWire,
) -> Result<BoolWire, BuildError> {
self.binary(id, KnotKind::xor(), a, b, "a", "b")
}
pub fn rising<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
) -> Result<BoolWire, BuildError> {
self.unary(id, KnotKind::rising_from_zero(), input)
}
pub fn falling<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
) -> Result<BoolWire, BuildError> {
self.unary(id, KnotKind::falling_to_zero(), input)
}
pub fn change<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
) -> Result<BoolWire, BuildError> {
self.unary(id, KnotKind::change(), input)
}
pub fn flag(
&mut self,
id: impl Into<std::string::String>,
priority: FlagPriority,
set: Option<&BoolWire>,
reset: Option<&BoolWire>,
toggle: Option<&BoolWire>,
) -> Result<BoolWire, BuildError> {
let knot = self.knot(id, KnotKind::flag(priority, toggle.is_some()))?;
self.optional_thread(set, &knot, "set")?;
self.optional_thread(reset, &knot, "reset")?;
self.optional_thread(toggle, &knot, "toggle")?;
self.typed_output(&knot, "out")
}
pub fn counter(
&mut self,
id: impl Into<std::string::String>,
increment: Option<&BoolWire>,
decrement: Option<&BoolWire>,
reset: Option<&BoolWire>,
) -> Result<CountWire, BuildError> {
let knot = self.knot(id, KnotKind::counter())?;
self.optional_thread(increment, &knot, "inc")?;
self.optional_thread(decrement, &knot, "dec")?;
self.optional_thread(reset, &knot, "reset")?;
self.typed_output(&knot, "count")
}
pub fn pulse_hold(
&mut self,
id: impl Into<std::string::String>,
ticks: u16,
start: &BoolWire,
) -> Result<BoolWire, BuildError> {
let knot = self.knot(id, KnotKind::timer(TimerMode::PulseHold, ticks))?;
let input = self.input(&knot, "start")?;
self.thread(start.port(), &input)?;
self.typed_output(&knot, "active")
}
pub fn fed_countdown(
&mut self,
id: impl Into<std::string::String>,
ticks: u16,
feed: &BoolWire,
) -> Result<BoolWire, BuildError> {
let knot = self.knot(id, KnotKind::timer(TimerMode::FedCountdown, ticks))?;
let input = self.input(&knot, "feed")?;
self.thread(feed.port(), &input)?;
self.typed_output(&knot, "active")
}
pub fn compare<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
op: CompareOp,
lhs: &Wire<D>,
rhs: &Wire<D>,
) -> Result<BoolWire, BuildError> {
self.binary(
id,
KnotKind::compare(op, None, D::DOMAIN),
lhs,
rhs,
"lhs",
"rhs",
)
}
pub fn compare_constant<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
op: CompareOp,
lhs: &Wire<D>,
rhs: Signal,
) -> Result<BoolWire, BuildError> {
let knot = self.knot(id, KnotKind::compare(op, Some(rhs), D::DOMAIN))?;
let input = self.input(&knot, "lhs")?;
self.thread(lhs.port(), &input)?;
self.typed_output(&knot, "out")
}
pub fn calc<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
op: CalcOp,
a: &Wire<D>,
b: &Wire<D>,
) -> Result<Wire<D>, BuildError> {
self.binary(id, KnotKind::calc(op, D::DOMAIN), a, b, "a", "b")
}
pub fn map<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
in_min: Signal,
in_max: Signal,
out_min: Signal,
out_max: Signal,
) -> Result<Wire<D>, BuildError> {
self.unary(
id,
KnotKind::map(in_min, in_max, out_min, out_max, D::DOMAIN),
input,
)
}
pub fn threshold<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
high: Signal,
low: Signal,
use_hysteresis: bool,
) -> Result<ThresholdWires, BuildError> {
let knot = self.knot(
id,
KnotKind::Threshold {
domain: D::DOMAIN,
high,
low,
use_hysteresis,
},
)?;
let target = self.input(&knot, "in")?;
self.thread(input.port(), &target)?;
Ok(ThresholdWires {
out: self.typed_output(&knot, "out")?,
crossed_up: self.typed_output(&knot, "crossed_up")?,
crossed_down: self.typed_output(&knot, "crossed_down")?,
})
}
pub fn delay<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
ticks: u16,
input: &Wire<D>,
) -> Result<Wire<D>, BuildError> {
self.unary(id, KnotKind::Delay { ticks }, input)
}
pub fn abs<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
) -> Result<Wire<D>, BuildError> {
self.unary(id, KnotKind::abs(D::DOMAIN), input)
}
pub fn neg<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
) -> Result<Wire<D>, BuildError> {
self.unary(id, KnotKind::neg(D::DOMAIN), input)
}
pub fn sqrt<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
) -> Result<Wire<D>, BuildError> {
self.unary(id, KnotKind::sqrt(D::DOMAIN), input)
}
pub fn clamp<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
min: Signal,
max: Signal,
) -> Result<Wire<D>, BuildError> {
self.unary(id, KnotKind::clamp(min, max, D::DOMAIN), input)
}
pub fn digitize<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<D>,
steps: u16,
) -> Result<Wire<D>, BuildError> {
self.unary(id, KnotKind::digitize(steps, D::DOMAIN), input)
}
pub fn random<D: NumericWireDomain>(
&mut self,
id: impl Into<std::string::String>,
require_gate: bool,
min: Option<&Wire<D>>,
max: Option<&Wire<D>>,
gate: Option<&BoolWire>,
) -> Result<Wire<D>, BuildError> {
let knot = self.knot(id, KnotKind::random(require_gate, D::DOMAIN))?;
self.optional_thread(min, &knot, "min")?;
self.optional_thread(max, &knot, "max")?;
self.optional_thread(gate, &knot, "gate")?;
self.typed_output(&knot, "out")
}
pub fn select<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
select: &BoolWire,
a: &Wire<D>,
b: &Wire<D>,
) -> Result<Wire<D>, BuildError> {
let knot = self.knot(id, KnotKind::select())?;
for (source, name) in [(select.port(), "sel"), (a.port(), "a"), (b.port(), "b")] {
let input = self.input(&knot, name)?;
self.thread(source, &input)?;
}
self.typed_output(&knot, "out")
}
pub fn convert<F: WireDomain, T: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
input: &Wire<F>,
) -> Result<Wire<T>, BuildError> {
self.unary(id, KnotKind::convert(F::DOMAIN, T::DOMAIN), input)
}
pub fn emit(
&mut self,
id: impl Into<std::string::String>,
name: impl Into<std::string::String>,
trigger: &BoolWire,
) -> Result<(), BuildError> {
let knot = self.knot(id, KnotKind::emit_command(name))?;
let input = self.input(&knot, "trigger")?;
self.thread(trigger.port(), &input)
}
fn source<D: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
kind: KnotKind,
) -> Result<Wire<D>, BuildError> {
let knot = self.knot(id, kind)?;
self.typed_output(&knot, "out")
}
fn unary<D: WireDomain, R: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
kind: KnotKind,
input: &Wire<D>,
) -> Result<Wire<R>, BuildError> {
let knot = self.knot(id, kind)?;
let target = self.input(&knot, "in")?;
self.thread(input.port(), &target)?;
self.typed_output(&knot, "out")
}
fn binary<D: WireDomain, E: WireDomain, R: WireDomain>(
&mut self,
id: impl Into<std::string::String>,
kind: KnotKind,
a: &Wire<D>,
b: &Wire<E>,
a_port: &str,
b_port: &str,
) -> Result<Wire<R>, BuildError> {
let knot = self.knot(id, kind)?;
let a_target = self.input(&knot, a_port)?;
let b_target = self.input(&knot, b_port)?;
self.thread(a.port(), &a_target)?;
self.thread(b.port(), &b_target)?;
self.typed_output(&knot, "out")
}
fn optional_thread<D: WireDomain>(
&mut self,
wire: Option<&Wire<D>>,
knot: &KnotHandle,
port: &str,
) -> Result<(), BuildError> {
if let Some(wire) = wire {
let input = self.input(knot, port)?;
self.thread(wire.port(), &input)?;
}
Ok(())
}
fn typed_output<D: WireDomain>(
&self,
knot: &KnotHandle,
port: &str,
) -> Result<Wire<D>, BuildError> {
Ok(Wire {
port: self.output(knot, port)?,
marker: PhantomData,
})
}
}
#[derive(Clone, Debug)]
pub struct ThresholdWires {
pub out: BoolWire,
pub crossed_up: BoolWire,
pub crossed_down: BoolWire,
}