use std::collections::{HashMap, HashSet};
use crate::{
Error,
channel::ChannelPosition,
connection::Connection,
event::EventsFrame,
frame::Frame,
node::{Node, NodeBuilderTrait, NodeId, NodeInputs, NodeOutputs, NodeResetCtx},
nodes::NodeInfo,
port::{Port, PortId, PortType},
signal::SingalsFrame,
time::{SampleBaseType, SampleRateBaseType, TimeFrom, TimeRange, TimeUnit},
track::{Track, TrackId},
};
#[derive(Debug, PartialEq)]
enum ProcessorState {
Paused,
Playing,
Stopped,
}
#[derive(Debug, Clone, Copy)]
enum FadeMode {
In,
Out,
}
#[derive(Debug, Clone, Copy)]
struct Fader {
mode: FadeMode,
step: usize,
duration: usize,
}
impl IntoIterator for Fader {
type Item = f32;
type IntoIter = FaderIter;
fn into_iter(self) -> Self::IntoIter {
FaderIter {
mode: self.mode,
step: self.step,
duration: self.duration,
}
}
}
impl Fader {
pub const fn new(mode: FadeMode, duration: usize) -> Self {
Self {
mode,
step: duration,
duration,
}
}
const fn remaining(&self) -> usize {
self.duration - self.step
}
const fn done(&self) -> bool {
self.step >= self.duration
}
const fn fade_in(self) -> Self {
Self {
mode: FadeMode::In,
step: 0,
..self
}
}
const fn fade_out(self) -> Self {
Self {
mode: FadeMode::Out,
step: 0,
..self
}
}
pub const fn update(&mut self, step: usize) {
self.step += step;
if self.step > self.duration {
self.step = self.duration;
}
}
}
struct FaderIter {
mode: FadeMode,
step: usize,
duration: usize,
}
impl Iterator for FaderIter {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
let mul = match self.mode {
FadeMode::Out => {
if self.step >= self.duration {
0f32
} else {
let mul = (self.duration - self.step) as f32 / self.duration as f32;
self.step += 1;
mul
}
}
FadeMode::In => {
if self.step > self.duration {
1f32
} else {
let mul = self.step as f32 / self.duration as f32;
self.step += 1;
mul
}
}
};
Some(mul)
}
}
pub struct Processor {
state: ProcessorState,
sr: SampleRateBaseType,
n_channels: u16,
buffer_size: usize,
pub step: SampleBaseType,
id: usize,
pub(crate) nodes_map: HashMap<NodeId, Node>,
pub(crate) tracks_map: HashMap<TrackId, Track>,
pub(crate) connections_map: HashMap<(NodeId, PortId), Connection>,
pub(crate) frames_maps: HashMap<NodeId, HashMap<PortId, Frame>>,
compiled_connections_map: HashMap<(NodeId, PortId), Connection>,
nodes: Vec<NodeId>,
output_port: Option<Port>,
end_time: SampleBaseType,
invalidate: bool,
fader: Fader,
}
impl Processor {
const TRACKS_MAP_CAPACITY: usize = 32;
const NODES_MAP_CAPACITY: usize = 512;
const CONNECTIONS_MAP_CAPACITY: usize = 1024;
const FRAMES_MAPS_CAPACITY: usize = 1024;
const FADE_DURATION: f64 = 0.020f64;
#[must_use]
pub fn new(sr: SampleRateBaseType, n_channels: u16, buffer_size: usize) -> Self {
let mut processor = Self {
state: ProcessorState::Paused,
sr,
n_channels,
buffer_size,
step: 0 as SampleBaseType,
id: 0,
tracks_map: HashMap::with_capacity(Self::TRACKS_MAP_CAPACITY),
nodes_map: HashMap::with_capacity(Self::NODES_MAP_CAPACITY),
connections_map: HashMap::with_capacity(Self::CONNECTIONS_MAP_CAPACITY),
frames_maps: HashMap::with_capacity(Self::FRAMES_MAPS_CAPACITY),
compiled_connections_map: HashMap::with_capacity(Self::CONNECTIONS_MAP_CAPACITY),
nodes: vec![],
output_port: None,
end_time: 0 as SampleBaseType,
invalidate: true,
fader: Fader::new(
FadeMode::In,
TimeUnit::Seconds(Self::FADE_DURATION).to_samples(sr) as usize,
),
};
processor.reset();
processor
}
fn process_fader(&mut self, chunk: usize) {
self.fader.update(chunk);
debug_assert!(!matches!(self.state, ProcessorState::Stopped));
if self.fader.done() {
match self.fader.mode {
FadeMode::In => {
self.set_playing(true);
}
FadeMode::Out => {
self.set_playing(false);
}
}
}
}
#[inline]
fn reset(&mut self) {
self.set_playing(false);
self.step = 0;
self.tracks_map.clear();
self.connections_map.clear();
self.nodes_map.clear();
self.id = 0;
self.output_port = None;
}
#[inline]
pub(crate) fn new_id(&mut self) -> usize {
let id = self.id;
assert!(self.id != usize::MAX, "ID exhausted!");
self.id += 1;
id
}
fn remove_node_connections(&mut self, id: NodeId) {
self.connections_map.retain(|_, connection| {
connection.source.node_id != id && connection.target.node_id != id
});
}
pub(crate) fn remove_nodes(&mut self, root_id: NodeId, remove_connection: bool) {
if remove_connection
&& self
.output_port
.as_ref()
.is_some_and(|output_port| output_port.node_id == root_id)
{
self.output_port = None;
}
let mut stack = Vec::<NodeId>::with_capacity(self.nodes_map.capacity());
stack.push(root_id);
while let Some(id) = stack.pop() {
self.nodes_map.remove(&id);
self.frames_maps.remove(&id);
if remove_connection {
self.remove_node_connections(id);
}
for (child_id, _) in self
.nodes_map
.extract_if(|_, node| node.parent_id() == Some(id))
{
stack.push(child_id);
}
}
}
pub(crate) fn build_frames(&self, node: &Node) -> HashMap<PortId, Frame> {
let mut frames: HashMap<PortId, Frame> = HashMap::<PortId, Frame>::new();
for port in node.ports() {
match port.kind {
PortType::EventsIn | PortType::SignalIn | PortType::Proxy(_) => {}
PortType::EventsOut => {
let frame = Frame::Events(EventsFrame::new(self.min_events_per_frame()));
frames.insert(port.id, frame);
}
PortType::SignalOut(channel_mask) => {
let frame = Frame::Signals(SingalsFrame::new(channel_mask, self.buffer_size));
frames.insert(port.id, frame);
}
}
}
frames
}
fn reset_frames(&mut self, id: NodeId) {
if let Some(frames) = self.frames_maps.get_mut(&id) {
for frame in frames.values_mut() {
frame.reset();
}
}
}
pub(crate) fn is_child(&self, parent_id: NodeId, id: NodeId) -> bool {
let mut node = self.nodes_map.get(&id);
while let Some(id) = node.and_then(Node::parent_id) {
if id == parent_id {
return true;
}
node = self.nodes_map.get(&id);
}
false
}
pub(crate) fn reverse_port(&self, mut port: Port) -> Result<Port, Error> {
while let PortType::Proxy(port_proxy) = port.kind {
let node = self
.nodes_map
.get(&port_proxy.node_id)
.ok_or_else(|| Error::msg("Failed to reverse port".into()))?;
port = node
.get_port(port_proxy.port_id)
.ok_or_else(|| Error::msg("Failed to reverse port".into()))?;
}
Ok(port)
}
#[allow(clippy::too_many_lines)]
pub fn process(&mut self, buffer: &mut [f32]) -> Result<usize, Error> {
let n_channels = usize::from(self.n_channels);
if !buffer.len().is_multiple_of(n_channels) {
return Err(Error::msg(
"Buffer must be mulutple of number of channels".to_string(),
));
}
let buffer_size = buffer.len() / n_channels;
if buffer_size > self.buffer_size {
return Err(Error::msg(format!(
"Buffer size must be <= number of channels * {}",
self.buffer_size
)));
}
if !self.is_playing() {
buffer.fill(0f32);
return Ok(buffer.len());
}
let fader_rem = self.fader.remaining();
let chunk = if matches!(self.fader.mode, FadeMode::Out) && fader_rem > 0 {
buffer_size.min(fader_rem)
} else {
buffer_size
};
if self.invalidate {
self.recompile_graph();
self.invalidate = false;
}
let start_sample = self.step;
let end_sample = start_sample + chunk as SampleBaseType;
for i in 0..self.nodes.len() {
let node_id = self.nodes[i];
let mut node = self.nodes_map.remove(&node_id).unwrap();
let (track_start_sample, track_end_sample) = {
let time_range = self.tracks_map.get(&node.track_id()).unwrap().time_range();
(
time_range.start().to_samples(self.sr),
time_range
.end()
.map_or_else(|| end_sample, |t| t.to_samples(self.sr)),
)
};
let track_end_sample = node.inner.duration_extension().map_or_else(
|| end_sample, |time| {
track_end_sample + time.to_samples(self.sr).max(0)
},
);
let new_start_sample = start_sample.max(track_start_sample);
let new_end_sample = end_sample.min(track_end_sample);
if new_start_sample >= new_end_sample {
if !node.frames_invalidated {
node.frames_invalidated = true;
self.reset_frames(node_id);
}
self.nodes_map.insert(node_id, node);
continue;
}
node.frames_invalidated = false;
let frame_range = {
debug_assert!(new_start_sample - start_sample >= 0);
debug_assert!(new_end_sample - start_sample >= 0);
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let frame_range_start = (new_start_sample - start_sample) as usize;
let frame_range_end = (new_end_sample - start_sample) as usize;
frame_range_start..frame_range_end
};
let mut frames = self.frames_maps.remove(&node_id).unwrap();
let node_inputs = NodeInputs::new(
self.sr,
frame_range.clone(),
&self.frames_maps,
node_id,
&self.compiled_connections_map,
);
let mut node_outputs = NodeOutputs::new(self.sr, frame_range, &mut frames);
node_outputs.clear();
let step_range = {
debug_assert!(new_start_sample - track_start_sample >= 0);
debug_assert!(new_end_sample - track_start_sample >= 0);
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let step_range_start = (new_start_sample - track_start_sample) as usize;
let step_range_end = (new_end_sample - track_start_sample) as usize;
step_range_start..step_range_end
};
node.inner
.process(step_range, &node_inputs, &mut node_outputs);
self.frames_maps.insert(node_id, frames);
self.nodes_map.insert(node_id, node);
}
if let Some(output_port) = self.output_port {
let signals = self
.frames_maps
.get(&output_port.node_id)
.unwrap()
.get(&output_port.id)
.unwrap()
.get_signals(0..((end_sample - start_sample) as usize))
.unwrap();
if let Some(signal) = signals.get(ChannelPosition::FrontLeft) {
let mut fader = self.fader.into_iter();
for i in 0..chunk {
let mul = fader.next().unwrap();
buffer[n_channels * i] = signal[i] * mul;
}
}
if let Some(signal) = signals.get(ChannelPosition::FrontRight) {
let mut fader = self.fader.into_iter();
for i in 0..chunk {
let mul = fader.next().unwrap();
buffer[(n_channels * i) + 1] = signal[i] * mul;
}
}
}
self.process_fader(chunk);
self.step = end_sample;
Ok(n_channels * chunk)
}
fn recompile_connections(&mut self) {
let mut compiled_connections_map = self.connections_map.clone();
for (&(node_id, port_id), connection) in &self.connections_map {
let source = self.reverse_port(connection.source).unwrap();
if source == connection.source {
continue;
}
let connection = Connection::new(source, connection.target).unwrap();
compiled_connections_map.insert((node_id, port_id), connection);
}
self.compiled_connections_map = compiled_connections_map;
}
fn recompile_graph_iter(
&mut self,
target_node_id: NodeId,
node_status: &mut HashSet<NodeId>,
connections_map: &mut HashMap<NodeId, Vec<PortId>>,
) {
if let Some(connections) = connections_map.remove(&target_node_id) {
for &target_port_id in &connections {
let &connection = self
.compiled_connections_map
.get(&(target_node_id, target_port_id))
.unwrap();
debug_assert!(
connection.target.id == target_port_id,
"Target ID not valid"
);
if node_status.insert(connection.source.node_id) {
self.recompile_graph_iter(
connection.source.node_id,
node_status,
connections_map,
);
self.nodes.push(connection.source.node_id);
}
}
connections_map.insert(target_node_id, connections);
}
}
fn recompile_graph(&mut self) {
self.nodes = Vec::<NodeId>::with_capacity(self.nodes_map.len());
let mut node_status = HashSet::<NodeId>::with_capacity(self.nodes_map.len());
self.end_time = 0;
for track in self.tracks_map.values() {
if let Some(end) = track.time_range().end() {
let end_time = end.to_samples(self.sr);
self.end_time = end_time.max(self.end_time);
}
}
self.recompile_connections();
if let Some(output_port) = self.output_port {
let mut connections_map = HashMap::<NodeId, Vec<PortId>>::new();
for connection in self.compiled_connections_map.values() {
connections_map
.entry(connection.target.node_id)
.or_default()
.push(connection.target.id);
}
self.recompile_graph_iter(output_port.node_id, &mut node_status, &mut connections_map);
self.nodes.push(output_port.node_id);
}
}
pub fn add_track(&mut self, time_range: TimeRange) -> Result<TrackId, Error> {
let id: TrackId = self.new_id().into();
self.tracks_map.insert(id, Track::new(id, time_range));
self.invalidate = true;
Ok(id)
}
pub fn add_node(
&mut self,
track_id: TrackId,
builder: &dyn NodeBuilderTrait,
) -> Result<NodeId, Error> {
self.add_node_with_parent(track_id, None, builder)
}
pub(crate) fn add_node_with_parent(
&mut self,
track_id: TrackId,
parent_id: Option<NodeId>,
builder: &dyn NodeBuilderTrait,
) -> Result<NodeId, Error> {
let id = Node::add(self, track_id, parent_id, builder);
self.invalidate = true;
id
}
pub fn replace_node(
&mut self,
id: NodeId,
builder: &dyn NodeBuilderTrait,
) -> Result<bool, Error> {
let invalidate_connections = Node::replace(self, id, builder)?;
if invalidate_connections
&& self
.output_port
.is_some_and(|output_port| output_port.node_id == id)
{
self.output_port = None;
}
self.invalidate = true;
Ok(invalidate_connections)
}
pub fn remove_node(&mut self, id: NodeId) -> Result<(), Error> {
let node = self
.nodes_map
.get(&id)
.ok_or_else(|| Error::msg("Node not found".into()))?;
if node.parent_id().is_some() {
return Err(Error::msg("Child nodes can't be removed".into()));
}
self.remove_nodes(id, true);
self.invalidate = true;
Ok(())
}
pub fn remove_track(&mut self, id: TrackId) -> Result<Track, Error> {
let track = self
.tracks_map
.remove(&id)
.ok_or_else(|| Error::msg("Track not found".into()))?;
let ids: Vec<NodeId> = self
.nodes_map
.iter()
.filter(|(_, node)| node.track_id() == id)
.map(|(&id, _)| id)
.collect();
for id in ids {
self.remove_nodes(id, true);
}
self.invalidate = true;
Ok(track)
}
pub fn set_track_time_range(
&mut self,
id: TrackId,
time_range: TimeRange,
) -> Result<(), Error> {
let track = self
.tracks_map
.get_mut(&id)
.ok_or_else(|| Error::msg("Track not found".into()))?;
track.set_time_range(time_range);
self.invalidate = true;
Ok(())
}
pub fn set_output_port(&mut self, port: Option<(NodeId, PortId)>) -> Result<(), Error> {
if let Some(port) = port {
let node = self
.nodes_map
.get(&port.0)
.ok_or_else(|| Error::msg("Node not found".into()))?;
if node.parent_id().is_some() {
return Err(Error::msg(
"Port of child nodes can't be set as output".into(),
));
}
self.output_port = Some(
node.get_port(port.1)
.ok_or_else(|| Error::msg("Port not found".into()))?,
);
} else {
self.output_port = None;
}
self.invalidate = true;
Ok(())
}
#[must_use]
pub const fn get_output_port(&self) -> Option<Port> {
self.output_port
}
pub fn connect_ports(
&mut self,
(source_node_id, source_port_id): (NodeId, PortId),
(target_node_id, target_port_id): (NodeId, PortId),
) -> Result<(), Error> {
let source_node = self
.nodes_map
.get(&source_node_id)
.ok_or_else(|| Error::msg("Source node not found".into()))?;
let source = source_node
.get_port(source_port_id)
.ok_or_else(|| Error::msg("Source port not found".into()))?;
let target_node = self
.nodes_map
.get(&target_node_id)
.ok_or_else(|| Error::msg("Target node not found".into()))?;
let target = target_node
.get_port(target_port_id)
.ok_or_else(|| Error::msg("Target port not found".into()))?;
let connection = Connection::new(source, target)?;
self.connections_map
.insert((target.node_id, target.id), connection);
self.invalidate = true;
Ok(())
}
pub fn connect_nodes(&mut self, source_id: NodeId, target_id: NodeId) -> Result<(), Error> {
let source_node = self
.nodes_map
.get(&source_id)
.ok_or_else(|| Error::msg("Invalid source node".into()))?;
let ports = source_node.ports();
let mut source: Option<Port> = None;
for port in ports {
if port.auto_connect && port.kind.is_output() {
source = Some(*port);
break;
}
}
let source =
source.ok_or_else(|| Error::msg("No auto connect port in source node".into()))?;
let target_node = self
.nodes_map
.get(&target_id)
.ok_or_else(|| Error::msg("Invalid target node".into()))?;
let ports = target_node.ports();
let mut target: Option<Port> = None;
for port in ports {
if port.auto_connect
&& port.kind.is_input()
&& !self.connections_map.contains_key(&(port.node_id, port.id))
{
target = Some(*port);
break;
}
}
let target =
target.ok_or_else(|| Error::msg("No auto connect port in target node".into()))?;
let connection = Connection::new(source, target)?;
self.connections_map
.insert((target.node_id, target.id), connection);
self.invalidate = true;
Ok(())
}
pub fn unlink_port(
&mut self,
(target_node_id, target_port_id): (NodeId, PortId),
) -> Result<(), Error> {
self.connections_map
.remove(&(target_node_id, target_port_id))
.ok_or_else(|| Error::msg("Invalid port".into()))?;
self.invalidate = true;
Ok(())
}
#[must_use]
pub fn get_node(&self, id: NodeId) -> Option<&Node> {
self.nodes_map.get(&id)
}
#[must_use]
pub fn min_events_per_frame(&self) -> usize {
(self.buffer_size() / 2).max(64)
}
pub fn clear(&mut self) {
self.reset();
self.invalidate = true;
}
#[must_use]
pub fn get_track_count(&self) -> usize {
self.tracks_map.len()
}
pub fn get_tracks(&self, tracks: &mut Vec<Track>) {
for track in self.tracks_map.values() {
tracks.push(track.clone());
}
}
#[must_use]
pub fn get_node_count(&self, track_id: TrackId) -> usize {
self.nodes_map.iter().fold(0usize, |acc, (_, node)| {
if node.track_id() == track_id && node.parent_id().is_none() {
acc + 1
} else {
acc
}
})
}
pub fn get_node_infos(&self, track_id: TrackId, nodes: &mut Vec<NodeInfo>) {
for node in self.nodes_map.values() {
if node.track_id() == track_id && node.parent_id().is_none() {
nodes.push(NodeInfo {
id: node.id(),
track_id: node.track_id(),
});
}
}
}
pub const fn stop(&mut self) {
self.state = ProcessorState::Stopped;
}
pub const fn set_playing(&mut self, enable: bool) {
self.state = if enable {
ProcessorState::Playing
} else {
ProcessorState::Paused
}
}
pub fn set_playing_with_fader(&mut self, enable: bool) {
if enable && self.is_playing() {
self.set_playing(true);
} else if !enable && !self.is_playing() {
self.set_playing(false);
} else {
#[allow(clippy::collapsible_else_if)]
if enable {
self.set_playing(true);
self.fader = self.fader.fade_in();
} else {
self.fader = self.fader.fade_out();
}
}
}
#[must_use]
pub const fn is_fader_done(&self) -> bool {
self.fader.done()
}
#[must_use]
pub fn seek(&mut self, time: TimeFrom) -> f64 {
let last_step = self.step;
match time {
TimeFrom::Start(time) => {
self.step = time.to_samples(self.sr);
}
TimeFrom::Current(time) => {
self.step += time.to_samples(self.sr);
}
TimeFrom::End(time) => {
self.step = self.end_time + time.to_samples(self.sr);
}
}
if self.step != last_step {
let mut ctx = NodeResetCtx {
sample_rate: self.sr,
step: self.step,
};
for node in self.nodes_map.values_mut() {
ctx.step = self.step
- self
.tracks_map
.get(&node.track_id())
.unwrap()
.time_range()
.start()
.to_samples(self.sr);
node.inner.reset(&ctx);
}
for frames in self.frames_maps.values_mut() {
for frame in frames.values_mut() {
frame.reset();
}
}
}
TimeUnit::Samples(self.step).to_seconds(self.sr)
}
#[must_use]
pub const fn sample_rate(&self) -> SampleRateBaseType {
self.sr
}
#[must_use]
pub const fn buffer_size(&self) -> usize {
self.buffer_size
}
#[must_use]
pub const fn n_channels(&self) -> u16 {
self.n_channels
}
#[must_use]
pub fn is_running(&self) -> bool {
self.state != ProcessorState::Stopped
}
#[must_use]
pub fn is_playing(&self) -> bool {
self.state == ProcessorState::Playing
}
#[cfg(feature = "debug")]
pub fn debug_render_graph(&self, path: String) {
const NODE_DISTANCE: f64 = 3f64;
const NODE_SIZE: f64 = 32f64;
const PORT_DISTANCE: f64 = 1f64;
const PORT_SIZE: f64 = 16f64;
use charming::{
Chart, HtmlRenderer,
component::Legend,
element::{Label, LabelPosition, LineStyle, ScaleLimit, Tooltip},
series::{Graph, GraphCategory, GraphData, GraphLink, GraphNode},
};
let mut nodes = Vec::<GraphNode>::new();
let mut links = Vec::<GraphLink>::new();
let categories = vec![
GraphCategory {
name: "Nodes".into(),
},
GraphCategory {
name: "Input Events".into(),
},
GraphCategory {
name: "Input Signal".into(),
},
GraphCategory {
name: "Output Events".into(),
},
GraphCategory {
name: "Output Signal".into(),
},
GraphCategory {
name: "Proxy".into(),
},
];
let mut flattened = Vec::<NodeId>::with_capacity(self.nodes_map.capacity());
flattened.extend_from_slice(&self.nodes);
for id in self.nodes_map.keys() {
if !flattened.contains(id) {
flattened.push(*id);
}
}
let mut state = (0usize, 0usize, 1usize);
const DISTANCE: f64 = NODE_DISTANCE * std::f64::consts::TAU / 5f64;
for id in &flattened {
let node = self.nodes_map.get(id).unwrap();
let r = NODE_DISTANCE * state.0 as f64;
let t = state.1 as f64 / state.2 as f64;
let x = r * (t * std::f64::consts::TAU).cos();
let y = r * (t * std::f64::consts::TAU).sin();
state.1 += 1;
if state.1 >= state.2 {
state.0 += 1;
let p = NODE_DISTANCE * state.0 as f64 * std::f64::consts::TAU;
state.1 = 0;
state.2 = (p / DISTANCE).round() as usize;
}
let graph_node = GraphNode {
id: format!("N{}", node.id().0),
name: format!("N{} {}", node.id().0, node.inner.name()),
x,
y,
value: node.id().0 as f64,
category: 0,
symbol_size: NODE_SIZE,
label: None,
};
nodes.push(graph_node);
if let Some(parent_node_id) = node.parent_id() {
let link = GraphLink {
source: format!("N{}", parent_node_id.0),
target: format!("N{}", node.id().0),
value: None,
};
links.push(link);
}
let n_ports = node.ports().len();
for (j, port) in node.ports().iter().enumerate() {
let category = match port.kind {
PortType::EventsIn => 1,
PortType::SignalIn => 2,
PortType::EventsOut => 3,
PortType::SignalOut(_) => 4,
PortType::Proxy(_) => 5,
};
let t = j as f64 / n_ports as f64;
let x = x + (PORT_DISTANCE * (t * std::f64::consts::TAU).cos());
let y = y + (PORT_DISTANCE * (t * std::f64::consts::TAU).sin());
let postfix = match port.kind {
PortType::SignalOut(channel_mask) => format!("[{:?}]", channel_mask),
_ => "".to_string(),
};
let graph_node = GraphNode {
id: format!("N{}P{}", node.id().0, port.id.0),
name: format!("N{}P{} {}{}", node.id().0, port.id.0, port.name, postfix),
x,
y,
value: node.id().0 as f64,
category,
symbol_size: PORT_SIZE,
label: None,
};
nodes.push(graph_node);
let link = if port.kind.is_input() {
GraphLink {
source: format!("N{}P{}", port.node_id.0, port.id.0),
target: format!("N{}", node.id().0),
value: None,
}
} else {
GraphLink {
source: format!("N{}", node.id().0),
target: format!("N{}P{}", port.node_id.0, port.id.0),
value: None,
}
};
links.push(link);
}
}
for connection in self.connections_map.values() {
let link = GraphLink {
source: format!(
"N{}P{}",
connection.source.node_id.0, connection.source.id.0
),
target: format!(
"N{}P{}",
connection.target.node_id.0, connection.target.id.0
),
value: None,
};
links.push(link);
}
for connection in self.connections_map.values() {
let mut source = connection.source;
let mut last_source = source;
while let PortType::Proxy(port_proxy) = source.kind {
let link = GraphLink {
source: format!("N{}P{}", port_proxy.node_id.0, port_proxy.port_id.0),
target: format!("N{}P{}", source.node_id.0, source.id.0),
value: None,
};
links.push(link);
last_source = source;
source = self
.nodes_map
.get(&port_proxy.node_id)
.unwrap()
.get_port(port_proxy.port_id)
.unwrap();
}
if source != connection.source {
let link = GraphLink {
source: format!("N{}P{}", source.node_id.0, source.id.0),
target: format!("N{}P{}", last_source.node_id.0, last_source.id.0),
value: None,
};
links.push(link);
}
}
let data = GraphData {
nodes,
links,
categories,
};
let chart = Chart::new()
.tooltip(Tooltip::new())
.legend(Legend::new().data(data.categories.iter().map(|c| c.name.clone()).collect()))
.series(
Graph::new()
.name("Dhwani")
.roam(true)
.label(
Label::new()
.show(true)
.position(LabelPosition::Bottom)
.formatter("{b}"),
)
.edge_symbol(Some(("none".to_string(), "arrow".to_string())))
.scale_limit(ScaleLimit::new().min(0.01).max(16))
.line_style(LineStyle::new().width(1.0).color("source").curveness(0.25))
.data(data),
);
let mut renderer = HtmlRenderer::new("Dhwani - Graph", 1920, 1080);
renderer.save(&chart, path).unwrap();
}
}