Nodes

SimPort aims to provide a simple set of basic nodes that can be easily combined to produce arbitrary behaviour. Nodes are not ‘a road’ or ‘a crane’ but instead, a delay or a fork, which should be familiar to queue theorists.

This is useful because there can be multiple kinds of objects flowing through ports, and it’s simple to give your ports and objects names and make the intended real-world-relation clear.

Channels

Nodes in SimPort are connected by Channels, which represent graph edges. Channels can be push or pull channels.

For your intuition, in a push channel, the incoming node pushes an object into the channel and the outgoing node must receive it immediately or crash. In a pull channel, the incoming node registers an object as ready to be emitted, and the outgoing node pulls it in whenever they want.

some nodes need certain connections to be specifically a push channel or a pull channel. This is enforced through type safety and usually shouldn’t be a problem. However, you may sometimes need to insert a pump node inbetween two nodes to connect them.

Node types

A node is:

abstract class Node(
    label: String,
    final override val incoming: List<InputChannel<*, *>>,
    final override val outgoing: List<OutputChannel<*, *>>,
) : NodeGroup(label)

Source nodes are a special kind of node with no incoming channels:

abstract class SourceNode(
    label: String, 
    outgoing: List<PushOutputChannel<*>>
) : Node(label, emptyList(), outgoing)

Arrival node

class ArrivalNode<OutputT>(
    label: String,
    destination: PushOutputChannel<OutputT>,
    generator: Generator<OutputT>,
) : SourceNode(label, listOf(destination)), Source<OutputT>

A source node where objects arrive according to a generator.

Container node

abstract class ContainerNode<T>(
    label: String,
    incoming: List<InputChannel<*, *>>,
    outgoing: List<OutputChannel<*, *>>,
) : Node(label, incoming, outgoing), Container<T>

An abstraction for any node that can hold things.

Dead end node

class DeadEndNode<InputT>(
    label: String, 
    private val inputChannel: InputChannel<InputT, *>
) : Node(label, listOf(inputChannel), emptyList())

Dead end node closes its input channel immediately, representing a queue node that is ‘closed for maintenance’ or equivalent, and will crash the simulator if a vehicle is dispatched to this node.

Delay node

class DelayNode<T>(
    label: String,
    source: PushInputChannel<T>,
    destination: PushOutputChannel<T>,
    delayProvider: DelayProvider,
) : ContainerNode<T>(label, listOf(source), listOf(destination)), Delay<T>

Takes in a object, and sends it out through the designated destination output channel after some delay specified by the delayProvider. Can hold any number of objects and delays happen simultaneously.

Fork node

class ForkNode<T>(
    label: String,
    source: PushInputChannel<T>,
    destinations: List<PushOutputChannel<T>>,
    policy: ForkPolicy<T> = RandomForkPolicy(),
) : Node(label, listOf(source), destinations)

Takes in a object, and emits it to any one of its destinations, as long as the output channel is open. Use policy to define policy for where the node should emit.

Equivalent of OrForks in queue theory.

Join node

class JoinNode<T>(
    label: String, 
    sources: List<PushInputChannel<T>>, 
    destination: PushOutputChannel<T>
) : Node(label, sources, listOf(destination))

Push joins join multiple streams together.

Match node

class MatchNode<MainInputT, SideInputT, OutputT, ChannelT : ChannelType<ChannelT>>(
    label: String,
    mainSource: InputChannel<MainInputT, ChannelT>,
    sideSource: PullInputChannel<SideInputT>,
    destination: OutputChannel<OutputT, ChannelT>,
    combiner: (MainInputT, SideInputT) -> OutputT,
) :
    PassthroughNode<MainInputT, OutputT, ChannelT>(
        label,
        mainSource,
        destination,
        listOf(mainSource, sideSource),
        listOf(destination),
    ),
    Match<MainInputT, SideInputT, OutputT>

Match nodes wait till the combine an object from the main and side input to produce one object using a combiner. Crucially used with a split node in bounded subnetworks.

Pass-through node

abstract class PassthroughNode<InputT, OutputT, ChannelT : ChannelType<ChannelT>>(
    label: String,
    source: InputChannel<InputT, ChannelT>,
    destination: OutputChannel<OutputT, ChannelT>,
    sources: List<InputChannel<*, *>>,
    destinations: List<OutputChannel<*, *>>,
) : Node(label, sources, destinations)

An abstraction for nodes that have a main and side channel like match and split nodes.

Pump node

class PumpNode<T>(
    label: String,
    source: PullInputChannel<T>,
    destination: PushOutputChannel<T>,
) : Node(label, listOf(source), listOf(destination))

Pumps convert a pull channel into a push channel by pulling when available and pushing immediately.

Queue node

class QueueNode<T>(
    label: String,
    source: PushInputChannel<T>,
    destination: PullOutputChannel<T>,
    policy: QueuePolicy<T> = FIFOQueuePolicy(),
) : ContainerNode<T>(label, listOf(source), listOf(destination)), Queue<T>

Keeps a (by default, First-in first-out) queue of objects. Queue nodes are always unbounded.

Service node

class ServiceNode<T>(
    label: String,
    source: PushInputChannel<T>,
    destination: PushOutputChannel<T>,
    delayProvider: DelayProvider,
) : ContainerNode<T>(label, listOf(source), listOf(destination)), Service<T>

Similar to a delay node, but can only serve one object at a time.

Sink node

class SinkNode<InputT>(
    label: String, 
    source: PushInputChannel<InputT>
) : ContainerNode<InputT>(label, listOf(source), emptyList()), Sink<InputT>

A sink or destination for objects. A node with no outputs.

Split node

class SplitNode<InputT, MainOutputT, SideOutputT, ChannelT : ChannelType<ChannelT>>(
    label: String,
    source: InputChannel<InputT, ChannelT>,
    mainDestination: OutputChannel<MainOutputT, ChannelT>,
    sideDestination: PushOutputChannel<SideOutputT>,
    splitter: (InputT) -> Pair<MainOutputT, SideOutputT>,
) :
    PassthroughNode<InputT, MainOutputT, ChannelT>(
        label,
        source,
        mainDestination,
        listOf(source),
        listOf(mainDestination, sideDestination),
    ),
    Split<InputT, MainOutputT, SideOutputT>

Splits an object into two using a splitter and sends them down the main and side channel. Crucially used with a match node in bounded subnetworks.


This site uses Just the Docs, a documentation theme for Jekyll.