2015-01-28 14:19:50 +01:00
|
|
|
|
/**
|
2016-02-23 12:58:39 +01:00
|
|
|
|
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
2015-01-28 14:19:50 +01:00
|
|
|
|
*/
|
|
|
|
|
|
package akka.stream.impl
|
|
|
|
|
|
|
2016-02-22 20:18:15 +01:00
|
|
|
|
import java.util.concurrent.atomic.{ AtomicReference }
|
2015-12-14 17:02:00 +01:00
|
|
|
|
import java.{ util ⇒ ju }
|
2016-01-20 10:00:37 +02:00
|
|
|
|
import akka.NotUsed
|
2015-06-22 18:02:42 +02:00
|
|
|
|
import akka.stream.impl.MaterializerSession.MaterializationPanic
|
2015-03-30 14:22:12 +02:00
|
|
|
|
import akka.stream.impl.StreamLayout.Module
|
2015-04-10 16:49:49 +02:00
|
|
|
|
import akka.stream.scaladsl.Keep
|
2015-01-28 14:19:50 +01:00
|
|
|
|
import akka.stream._
|
2015-06-06 14:36:49 +02:00
|
|
|
|
import org.reactivestreams.{ Processor, Subscription, Publisher, Subscriber }
|
2015-06-22 18:02:42 +02:00
|
|
|
|
import scala.util.control.{ NoStackTrace, NonFatal }
|
2015-04-23 13:32:07 +02:00
|
|
|
|
import akka.event.Logging.simpleName
|
2015-06-16 15:34:54 +02:00
|
|
|
|
import scala.annotation.tailrec
|
|
|
|
|
|
import java.util.concurrent.atomic.AtomicLong
|
2015-12-14 17:02:00 +01:00
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
|
import akka.stream.impl.fusing.GraphStageModule
|
|
|
|
|
|
import akka.stream.impl.fusing.GraphStages.MaterializedValueSource
|
|
|
|
|
|
import akka.stream.impl.fusing.GraphModule
|
2016-03-11 17:08:30 +01:00
|
|
|
|
import akka.event.Logging
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
|
*/
|
2015-12-15 16:44:48 +01:00
|
|
|
|
object StreamLayout {
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
// compile-time constant
|
2015-05-07 15:20:25 +02:00
|
|
|
|
final val Debug = false
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
final def validate(m: Module, level: Int = 0, doPrint: Boolean = false, idMap: ju.Map[AnyRef, Integer] = new ju.HashMap): Unit = {
|
2015-07-06 22:00:21 +02:00
|
|
|
|
val ids = Iterator from 1
|
|
|
|
|
|
def id(obj: AnyRef) = idMap get obj match {
|
2015-12-14 17:02:00 +01:00
|
|
|
|
case null ⇒
|
2015-07-06 22:00:21 +02:00
|
|
|
|
val x = ids.next()
|
2015-12-14 17:02:00 +01:00
|
|
|
|
idMap.put(obj, x)
|
2015-07-06 22:00:21 +02:00
|
|
|
|
x
|
2015-12-14 17:02:00 +01:00
|
|
|
|
case x ⇒ x
|
2015-07-06 22:00:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
def in(i: InPort) = s"${i.toString}@${id(i)}"
|
|
|
|
|
|
def out(o: OutPort) = s"${o.toString}@${id(o)}"
|
|
|
|
|
|
def ins(i: Iterable[InPort]) = i.map(in).mkString("In[", ",", "]")
|
|
|
|
|
|
def outs(o: Iterable[OutPort]) = o.map(out).mkString("Out[", ",", "]")
|
|
|
|
|
|
def pair(p: (OutPort, InPort)) = s"${in(p._2)}->${out(p._1)}"
|
|
|
|
|
|
def pairs(p: Iterable[(OutPort, InPort)]) = p.map(pair).mkString("[", ",", "]")
|
|
|
|
|
|
|
|
|
|
|
|
import m._
|
|
|
|
|
|
|
|
|
|
|
|
val inset: Set[InPort] = shape.inlets.toSet
|
|
|
|
|
|
val outset: Set[OutPort] = shape.outlets.toSet
|
|
|
|
|
|
var problems: List[String] = Nil
|
|
|
|
|
|
|
|
|
|
|
|
if (inset.size != shape.inlets.size) problems ::= "shape has duplicate inlets: " + ins(shape.inlets)
|
2016-03-18 12:28:07 +01:00
|
|
|
|
if (inset != inPorts) problems ::= s"shape has extra ${ins(inset diff inPorts)}, module has extra ${ins(inPorts diff inset)}"
|
2015-07-06 22:00:21 +02:00
|
|
|
|
if (inset.intersect(upstreams.keySet).nonEmpty) problems ::= s"found connected inlets ${inset.intersect(upstreams.keySet)}"
|
|
|
|
|
|
if (outset.size != shape.outlets.size) problems ::= "shape has duplicate outlets: " + outs(shape.outlets)
|
2016-03-18 12:28:07 +01:00
|
|
|
|
if (outset != outPorts) problems ::= s"shape has extra ${outs(outset diff outPorts)}, module has extra ${outs(outPorts diff outset)}"
|
2015-07-06 22:00:21 +02:00
|
|
|
|
if (outset.intersect(downstreams.keySet).nonEmpty) problems ::= s"found connected outlets ${outset.intersect(downstreams.keySet)}"
|
|
|
|
|
|
val ups = upstreams.toSet
|
|
|
|
|
|
val ups2 = ups.map(_.swap)
|
|
|
|
|
|
val downs = downstreams.toSet
|
|
|
|
|
|
val inter = ups2.intersect(downs)
|
2016-03-18 12:28:07 +01:00
|
|
|
|
if (downs != ups2) problems ::= s"inconsistent maps: ups ${pairs(ups2 diff inter)} downs ${pairs(downs diff inter)}"
|
2015-07-06 22:00:21 +02:00
|
|
|
|
val (allIn, dupIn, allOut, dupOut) =
|
|
|
|
|
|
subModules.foldLeft((Set.empty[InPort], Set.empty[InPort], Set.empty[OutPort], Set.empty[OutPort])) {
|
2015-08-01 00:13:14 +02:00
|
|
|
|
case ((ai, di, ao, doo), sm) ⇒
|
|
|
|
|
|
(ai ++ sm.inPorts, di ++ ai.intersect(sm.inPorts), ao ++ sm.outPorts, doo ++ ao.intersect(sm.outPorts))
|
2015-07-06 22:00:21 +02:00
|
|
|
|
}
|
|
|
|
|
|
if (dupIn.nonEmpty) problems ::= s"duplicate ports in submodules ${ins(dupIn)}"
|
|
|
|
|
|
if (dupOut.nonEmpty) problems ::= s"duplicate ports in submodules ${outs(dupOut)}"
|
2016-03-18 12:28:07 +01:00
|
|
|
|
if (!isSealed && (inset diff allIn).nonEmpty) problems ::= s"foreign inlets ${ins(inset diff allIn)}"
|
|
|
|
|
|
if (!isSealed && (outset diff allOut).nonEmpty) problems ::= s"foreign outlets ${outs(outset diff allOut)}"
|
|
|
|
|
|
val unIn = allIn diff inset diff upstreams.keySet
|
2015-07-06 22:00:21 +02:00
|
|
|
|
if (unIn.nonEmpty && !isCopied) problems ::= s"unconnected inlets ${ins(unIn)}"
|
2016-03-18 12:28:07 +01:00
|
|
|
|
val unOut = allOut diff outset diff downstreams.keySet
|
2015-07-06 22:00:21 +02:00
|
|
|
|
if (unOut.nonEmpty && !isCopied) problems ::= s"unconnected outlets ${outs(unOut)}"
|
|
|
|
|
|
|
|
|
|
|
|
def atomics(n: MaterializedValueNode): Set[Module] =
|
|
|
|
|
|
n match {
|
|
|
|
|
|
case Ignore ⇒ Set.empty
|
|
|
|
|
|
case Transform(f, dep) ⇒ atomics(dep)
|
2015-08-01 00:13:14 +02:00
|
|
|
|
case Atomic(module) ⇒ Set(module)
|
2015-07-06 22:00:21 +02:00
|
|
|
|
case Combine(f, left, right) ⇒ atomics(left) ++ atomics(right)
|
|
|
|
|
|
}
|
|
|
|
|
|
val atomic = atomics(materializedValueComputation)
|
2015-12-14 17:02:00 +01:00
|
|
|
|
val graphValues = subModules.flatMap {
|
|
|
|
|
|
case GraphModule(_, _, _, mvids) ⇒ mvids
|
|
|
|
|
|
case _ ⇒ Nil
|
|
|
|
|
|
}
|
2016-03-18 12:28:07 +01:00
|
|
|
|
if (((atomic diff subModules diff graphValues) - m).nonEmpty)
|
|
|
|
|
|
problems ::= s"computation refers to non-existent modules [${(atomic diff subModules diff graphValues) - m mkString ","}]"
|
2015-07-06 22:00:21 +02:00
|
|
|
|
|
|
|
|
|
|
val print = doPrint || problems.nonEmpty
|
|
|
|
|
|
|
|
|
|
|
|
if (print) {
|
|
|
|
|
|
val indent = " " * (level * 2)
|
|
|
|
|
|
println(s"$indent${simpleName(this)}($shape): ${ins(inPorts)} ${outs(outPorts)}")
|
|
|
|
|
|
downstreams foreach { case (o, i) ⇒ println(s"$indent ${out(o)} -> ${in(i)}") }
|
|
|
|
|
|
problems foreach (p ⇒ println(s"$indent -!- $p"))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
subModules foreach (sm ⇒ validate(sm, level + 1, print, idMap))
|
|
|
|
|
|
|
|
|
|
|
|
if (problems.nonEmpty && !doPrint) throw new IllegalStateException(s"module inconsistent, found ${problems.size} problems")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
object IgnorableMatValComp {
|
|
|
|
|
|
def apply(comp: MaterializedValueNode): Boolean =
|
|
|
|
|
|
comp match {
|
|
|
|
|
|
case Atomic(module) ⇒ IgnorableMatValComp(module)
|
|
|
|
|
|
case _: Combine | _: Transform ⇒ false
|
|
|
|
|
|
case Ignore ⇒ true
|
|
|
|
|
|
}
|
|
|
|
|
|
def apply(module: Module): Boolean =
|
|
|
|
|
|
module match {
|
|
|
|
|
|
case _: AtomicModule | EmptyModule ⇒ true
|
|
|
|
|
|
case CopiedModule(_, _, module) ⇒ IgnorableMatValComp(module)
|
|
|
|
|
|
case CompositeModule(_, _, _, _, comp, _) ⇒ IgnorableMatValComp(comp)
|
|
|
|
|
|
case FusedModule(_, _, _, _, comp, _, _) ⇒ IgnorableMatValComp(comp)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
sealed trait MaterializedValueNode {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* These nodes are used in hash maps and therefore must have efficient implementations
|
|
|
|
|
|
* of hashCode and equals. There is no value in allowing aliases to be equal, so using
|
|
|
|
|
|
* reference equality.
|
|
|
|
|
|
*/
|
|
|
|
|
|
override def hashCode: Int = super.hashCode
|
|
|
|
|
|
override def equals(other: Any): Boolean = super.equals(other)
|
|
|
|
|
|
}
|
|
|
|
|
|
case class Combine(f: (Any, Any) ⇒ Any, dep1: MaterializedValueNode, dep2: MaterializedValueNode) extends MaterializedValueNode {
|
|
|
|
|
|
override def toString: String = s"Combine($dep1,$dep2)"
|
|
|
|
|
|
}
|
|
|
|
|
|
case class Atomic(module: Module) extends MaterializedValueNode {
|
2016-03-11 17:08:30 +01:00
|
|
|
|
override def toString: String = f"Atomic(${module.attributes.nameOrDefault(module.getClass.getName)}[${System.identityHashCode(module)}%08x])"
|
2015-12-14 17:02:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
case class Transform(f: Any ⇒ Any, dep: MaterializedValueNode) extends MaterializedValueNode {
|
|
|
|
|
|
override def toString: String = s"Transform($dep)"
|
|
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
case object Ignore extends MaterializedValueNode
|
|
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
sealed trait Module {
|
2015-07-06 22:00:21 +02:00
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
|
def shape: Shape
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Verify that the given Shape has the same ports and return a new module with that shape.
|
|
|
|
|
|
* Concrete implementations may throw UnsupportedOperationException where applicable.
|
2016-02-12 08:28:16 +01:00
|
|
|
|
*
|
|
|
|
|
|
* Please note that this method MUST NOT be implemented using a CopiedModule since
|
|
|
|
|
|
* the purpose of replaceShape can also be to rearrange the ports (as in BidiFlow.reversed)
|
|
|
|
|
|
* and that purpose would be defeated.
|
2015-01-28 14:19:50 +01:00
|
|
|
|
*/
|
|
|
|
|
|
def replaceShape(s: Shape): Module
|
|
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
final lazy val inPorts: Set[InPort] = shape.inlets.toSet
|
|
|
|
|
|
final lazy val outPorts: Set[OutPort] = shape.outlets.toSet
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
def isRunnable: Boolean = inPorts.isEmpty && outPorts.isEmpty
|
2015-04-23 13:32:07 +02:00
|
|
|
|
final def isSink: Boolean = (inPorts.size == 1) && outPorts.isEmpty
|
|
|
|
|
|
final def isSource: Boolean = (outPorts.size == 1) && inPorts.isEmpty
|
|
|
|
|
|
final def isFlow: Boolean = (inPorts.size == 1) && (outPorts.size == 1)
|
|
|
|
|
|
final def isBidiFlow: Boolean = (inPorts.size == 2) && (outPorts.size == 2)
|
|
|
|
|
|
def isAtomic: Boolean = subModules.isEmpty
|
|
|
|
|
|
def isCopied: Boolean = false
|
2015-12-15 16:44:48 +01:00
|
|
|
|
def isFused: Boolean = false
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-07-06 22:00:21 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Fuses this Module to `that` Module by wiring together `from` and `to`,
|
|
|
|
|
|
* retaining the materialized value of `this` in the result
|
|
|
|
|
|
* @param that a Module to fuse with
|
|
|
|
|
|
* @param from the data source to wire
|
|
|
|
|
|
* @param to the data sink to wire
|
|
|
|
|
|
* @return a Module representing the fusion of `this` and `that`
|
|
|
|
|
|
*/
|
|
|
|
|
|
final def fuse(that: Module, from: OutPort, to: InPort): Module =
|
|
|
|
|
|
fuse(that, from, to, Keep.left)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-07-06 22:00:21 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Fuses this Module to `that` Module by wiring together `from` and `to`,
|
|
|
|
|
|
* transforming the materialized values of `this` and `that` using the
|
|
|
|
|
|
* provided function `f`
|
|
|
|
|
|
* @param that a Module to fuse with
|
|
|
|
|
|
* @param from the data source to wire
|
|
|
|
|
|
* @param to the data sink to wire
|
|
|
|
|
|
* @param f the function to apply to the materialized values
|
|
|
|
|
|
* @return a Module representing the fusion of `this` and `that`
|
|
|
|
|
|
*/
|
|
|
|
|
|
final def fuse[A, B, C](that: Module, from: OutPort, to: InPort, f: (A, B) ⇒ C): Module =
|
|
|
|
|
|
this.compose(that, f).wire(from, to)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-07-06 22:00:21 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new Module based on the current Module but with
|
|
|
|
|
|
* the given OutPort wired to the given InPort.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param from the OutPort to wire
|
|
|
|
|
|
* @param to the InPort to wire
|
|
|
|
|
|
* @return a new Module with the ports wired
|
|
|
|
|
|
*/
|
|
|
|
|
|
final def wire(from: OutPort, to: InPort): Module = {
|
|
|
|
|
|
if (Debug) validate(this)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
require(outPorts(from),
|
|
|
|
|
|
if (downstreams.contains(from)) s"The output port [$from] is already connected"
|
|
|
|
|
|
else s"The output port [$from] is not part of the underlying graph.")
|
|
|
|
|
|
require(inPorts(to),
|
|
|
|
|
|
if (upstreams.contains(to)) s"The input port [$to] is already connected"
|
|
|
|
|
|
else s"The input port [$to] is not part of the underlying graph.")
|
|
|
|
|
|
|
|
|
|
|
|
CompositeModule(
|
2015-12-15 16:44:48 +01:00
|
|
|
|
if (isSealed) Set(this) else subModules,
|
2015-01-28 14:19:50 +01:00
|
|
|
|
AmorphousShape(shape.inlets.filterNot(_ == to), shape.outlets.filterNot(_ == from)),
|
2015-04-23 13:32:07 +02:00
|
|
|
|
downstreams.updated(from, to),
|
|
|
|
|
|
upstreams.updated(to, from),
|
2015-01-28 14:19:50 +01:00
|
|
|
|
materializedValueComputation,
|
2016-02-12 08:28:16 +01:00
|
|
|
|
if (isSealed) Attributes.none else attributes)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
final def transformMaterializedValue(f: Any ⇒ Any): Module = {
|
2015-07-06 22:00:21 +02:00
|
|
|
|
if (Debug) validate(this)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
CompositeModule(
|
2015-12-14 17:02:00 +01:00
|
|
|
|
if (this.isSealed) Set(this) else this.subModules,
|
2015-01-28 14:19:50 +01:00
|
|
|
|
shape,
|
2015-04-23 13:32:07 +02:00
|
|
|
|
downstreams,
|
|
|
|
|
|
upstreams,
|
|
|
|
|
|
Transform(f, if (this.isSealed) Atomic(this) else this.materializedValueComputation),
|
2015-10-31 14:46:10 +01:00
|
|
|
|
if (this.isSealed) Attributes.none else attributes)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-06 22:00:21 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new Module which is `this` Module composed with `that` Module.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param that a Module to be composed with (cannot be itself)
|
|
|
|
|
|
* @return a Module that represents the composition of `this` and `that`
|
|
|
|
|
|
*/
|
|
|
|
|
|
def compose(that: Module): Module = compose(that, Keep.left)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-07-06 22:00:21 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new Module which is `this` Module composed with `that` Module,
|
|
|
|
|
|
* using the given function `f` to compose the materialized value of `this` with
|
|
|
|
|
|
* the materialized value of `that`.
|
|
|
|
|
|
* @param that a Module to be composed with (cannot be itself)
|
|
|
|
|
|
* @param f a function which combines the materialized values
|
|
|
|
|
|
* @tparam A the type of the materialized value of `this`
|
|
|
|
|
|
* @tparam B the type of the materialized value of `that`
|
|
|
|
|
|
* @tparam C the type of the materialized value of the returned Module
|
|
|
|
|
|
* @return a Module that represents the composition of `this` and `that`
|
|
|
|
|
|
*/
|
|
|
|
|
|
def compose[A, B, C](that: Module, f: (A, B) ⇒ C): Module = {
|
|
|
|
|
|
if (Debug) validate(this)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-07-29 23:01:26 +01:00
|
|
|
|
require(that ne this, "A module cannot be added to itself. You should pass a separate instance to compose().")
|
2015-01-28 14:19:50 +01:00
|
|
|
|
require(!subModules(that), "An existing submodule cannot be added again. All contained modules must be unique.")
|
|
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
val modulesLeft = if (this.isSealed) Set(this) else this.subModules
|
|
|
|
|
|
val modulesRight = if (that.isSealed) Set(that) else that.subModules
|
|
|
|
|
|
|
|
|
|
|
|
val matCompLeft = if (this.isSealed) Atomic(this) else this.materializedValueComputation
|
|
|
|
|
|
val matCompRight = if (that.isSealed) Atomic(that) else that.materializedValueComputation
|
|
|
|
|
|
|
|
|
|
|
|
val mat =
|
|
|
|
|
|
{
|
|
|
|
|
|
val comp =
|
|
|
|
|
|
if (f == scaladsl.Keep.left) {
|
|
|
|
|
|
if (IgnorableMatValComp(matCompRight)) matCompLeft else null
|
|
|
|
|
|
} else if (f == scaladsl.Keep.right) {
|
|
|
|
|
|
if (IgnorableMatValComp(matCompLeft)) matCompRight else null
|
|
|
|
|
|
} else null
|
|
|
|
|
|
if (comp == null) Combine(f.asInstanceOf[(Any, Any) ⇒ Any], matCompLeft, matCompRight)
|
|
|
|
|
|
else comp
|
|
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
CompositeModule(
|
2016-03-18 12:28:07 +01:00
|
|
|
|
modulesLeft union modulesRight,
|
2015-01-28 14:19:50 +01:00
|
|
|
|
AmorphousShape(shape.inlets ++ that.shape.inlets, shape.outlets ++ that.shape.outlets),
|
2015-04-23 13:32:07 +02:00
|
|
|
|
downstreams ++ that.downstreams,
|
|
|
|
|
|
upstreams ++ that.upstreams,
|
2016-03-11 17:08:30 +01:00
|
|
|
|
mat,
|
2015-10-31 14:46:10 +01:00
|
|
|
|
Attributes.none)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new Module which is `this` Module composed with `that` Module.
|
|
|
|
|
|
*
|
|
|
|
|
|
* The difference to compose(that) is that this version completely ignores the materialized value
|
|
|
|
|
|
* computation of `that` while the normal version executes the computation and discards its result.
|
|
|
|
|
|
* This means that this version must not be used for user-provided `that` modules because users may
|
|
|
|
|
|
* transform materialized values only to achieve some side-effect; it can only be
|
|
|
|
|
|
* used where we know that there is no meaningful computation to be done (like for
|
|
|
|
|
|
* MaterializedValueSource).
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param that a Module to be composed with (cannot be itself)
|
|
|
|
|
|
* @return a Module that represents the composition of `this` and `that`
|
|
|
|
|
|
*/
|
|
|
|
|
|
def composeNoMat(that: Module): Module = {
|
|
|
|
|
|
if (Debug) validate(this)
|
|
|
|
|
|
|
|
|
|
|
|
require(that ne this, "A module cannot be added to itself. You should pass a separate instance to compose().")
|
|
|
|
|
|
require(!subModules(that), "An existing submodule cannot be added again. All contained modules must be unique.")
|
|
|
|
|
|
|
|
|
|
|
|
val modules1 = if (this.isSealed) Set(this) else this.subModules
|
|
|
|
|
|
val modules2 = if (that.isSealed) Set(that) else that.subModules
|
|
|
|
|
|
|
|
|
|
|
|
val matComputation = if (this.isSealed) Atomic(this) else this.materializedValueComputation
|
|
|
|
|
|
|
|
|
|
|
|
CompositeModule(
|
|
|
|
|
|
modules1 ++ modules2,
|
|
|
|
|
|
AmorphousShape(shape.inlets ++ that.shape.inlets, shape.outlets ++ that.shape.outlets),
|
|
|
|
|
|
downstreams ++ that.downstreams,
|
|
|
|
|
|
upstreams ++ that.upstreams,
|
|
|
|
|
|
// would like to optimize away this allocation for Keep.{left,right} but that breaks side-effecting transformations
|
|
|
|
|
|
matComputation,
|
|
|
|
|
|
Attributes.none)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
|
def subModules: Set[Module]
|
2016-02-12 08:28:16 +01:00
|
|
|
|
final def isSealed: Boolean = isAtomic || isCopied || isFused || attributes.attributeList.nonEmpty
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
def downstreams: Map[OutPort, InPort] = Map.empty
|
|
|
|
|
|
def upstreams: Map[InPort, OutPort] = Map.empty
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
def materializedValueComputation: MaterializedValueNode = Atomic(this)
|
2016-02-12 08:28:16 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* The purpose of this method is to create a copy to be included in a larger
|
|
|
|
|
|
* graph such that port identity clashes are avoided. Where a full copy is not
|
|
|
|
|
|
* possible or desirable, use a CopiedModule. The shape of the resulting
|
|
|
|
|
|
* module MUST NOT contain the same ports as this module’s shape.
|
|
|
|
|
|
*/
|
2015-01-28 14:19:50 +01:00
|
|
|
|
def carbonCopy: Module
|
|
|
|
|
|
|
2015-06-23 17:32:55 +02:00
|
|
|
|
def attributes: Attributes
|
|
|
|
|
|
def withAttributes(attributes: Attributes): Module
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
final override def hashCode(): Int = super.hashCode()
|
|
|
|
|
|
final override def equals(obj: scala.Any): Boolean = super.equals(obj)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
case object EmptyModule extends Module {
|
2015-02-26 11:58:29 +01:00
|
|
|
|
override def shape = ClosedShape
|
2015-01-28 14:19:50 +01:00
|
|
|
|
override def replaceShape(s: Shape) =
|
2016-02-15 10:37:19 +01:00
|
|
|
|
if (s != shape) throw new UnsupportedOperationException("cannot replace the shape of the EmptyModule")
|
|
|
|
|
|
else this
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-07-06 22:00:21 +02:00
|
|
|
|
override def compose(that: Module): Module = that
|
2015-04-23 13:32:07 +02:00
|
|
|
|
|
2015-07-06 22:00:21 +02:00
|
|
|
|
override def compose[A, B, C](that: Module, f: (A, B) ⇒ C): Module =
|
2015-04-23 13:32:07 +02:00
|
|
|
|
throw new UnsupportedOperationException("It is invalid to combine materialized value with EmptyModule")
|
|
|
|
|
|
|
2015-06-23 17:32:55 +02:00
|
|
|
|
override def withAttributes(attributes: Attributes): Module =
|
2015-01-28 14:19:50 +01:00
|
|
|
|
throw new UnsupportedOperationException("EmptyModule cannot carry attributes")
|
|
|
|
|
|
|
2016-02-15 10:37:19 +01:00
|
|
|
|
override def subModules: Set[Module] = Set.empty
|
|
|
|
|
|
override def attributes = Attributes.none
|
2015-01-28 14:19:50 +01:00
|
|
|
|
override def carbonCopy: Module = this
|
|
|
|
|
|
override def isRunnable: Boolean = false
|
|
|
|
|
|
override def isAtomic: Boolean = false
|
|
|
|
|
|
override def materializedValueComputation: MaterializedValueNode = Ignore
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
final case class CopiedModule(override val shape: Shape,
|
|
|
|
|
|
override val attributes: Attributes,
|
|
|
|
|
|
copyOf: Module) extends Module {
|
2015-04-23 13:32:07 +02:00
|
|
|
|
override val subModules: Set[Module] = Set(copyOf)
|
|
|
|
|
|
|
2016-02-15 10:37:19 +01:00
|
|
|
|
override def withAttributes(attr: Attributes): Module =
|
|
|
|
|
|
if (attr ne attributes) this.copy(attributes = attr)
|
|
|
|
|
|
else this
|
2015-04-23 13:32:07 +02:00
|
|
|
|
|
|
|
|
|
|
override def carbonCopy: Module = this.copy(shape = shape.deepCopy())
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2016-02-15 10:37:19 +01:00
|
|
|
|
override def replaceShape(s: Shape): Module =
|
|
|
|
|
|
if (s != shape) {
|
|
|
|
|
|
shape.requireSamePortsAs(s)
|
|
|
|
|
|
CompositeModule(this, s)
|
|
|
|
|
|
} else this
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
override val materializedValueComputation: MaterializedValueNode = Atomic(copyOf)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
override def isCopied: Boolean = true
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
override def toString: String = f"[${System.identityHashCode(this)}%08x] copy of $copyOf"
|
2015-04-23 13:32:07 +02:00
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
final case class CompositeModule(
|
2016-02-24 11:55:28 +01:00
|
|
|
|
override val subModules: Set[Module],
|
|
|
|
|
|
override val shape: Shape,
|
|
|
|
|
|
override val downstreams: Map[OutPort, InPort],
|
|
|
|
|
|
override val upstreams: Map[InPort, OutPort],
|
|
|
|
|
|
override val materializedValueComputation: MaterializedValueNode,
|
|
|
|
|
|
override val attributes: Attributes) extends Module {
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2016-02-15 10:37:19 +01:00
|
|
|
|
override def replaceShape(s: Shape): Module =
|
|
|
|
|
|
if (s != shape) {
|
|
|
|
|
|
shape.requireSamePortsAs(s)
|
|
|
|
|
|
copy(shape = s)
|
|
|
|
|
|
} else this
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
override def carbonCopy: Module = CopiedModule(shape.deepCopy(), attributes, copyOf = this)
|
|
|
|
|
|
|
2015-06-23 17:32:55 +02:00
|
|
|
|
override def withAttributes(attributes: Attributes): Module = copy(attributes = attributes)
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
|
|
|
|
|
override def toString =
|
2016-03-11 17:08:30 +01:00
|
|
|
|
f"""CompositeModule [${System.identityHashCode(this)}%08x]
|
2016-02-15 10:37:19 +01:00
|
|
|
|
| Name: ${this.attributes.nameOrDefault("unnamed")}
|
|
|
|
|
|
| Modules:
|
2016-03-11 17:08:30 +01:00
|
|
|
|
| ${subModules.iterator.map(m ⇒ s"(${m.attributes.nameLifted.getOrElse("unnamed")}) ${m.toString.replaceAll("\n", "\n ")}").mkString("\n ")}
|
2016-02-15 10:37:19 +01:00
|
|
|
|
| Downstreams: ${downstreams.iterator.map { case (in, out) ⇒ s"\n $in -> $out" }.mkString("")}
|
|
|
|
|
|
| Upstreams: ${upstreams.iterator.map { case (out, in) ⇒ s"\n $out -> $in" }.mkString("")}
|
2016-03-11 17:08:30 +01:00
|
|
|
|
| MatValue: $materializedValueComputation""".stripMargin
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
2015-12-14 17:02:00 +01:00
|
|
|
|
|
2016-02-12 08:28:16 +01:00
|
|
|
|
object CompositeModule {
|
|
|
|
|
|
def apply(m: Module, s: Shape): CompositeModule = CompositeModule(Set(m), s, Map.empty, Map.empty, Atomic(m), Attributes.none)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
final case class FusedModule(
|
2016-02-24 11:55:28 +01:00
|
|
|
|
override val subModules: Set[Module],
|
|
|
|
|
|
override val shape: Shape,
|
|
|
|
|
|
override val downstreams: Map[OutPort, InPort],
|
|
|
|
|
|
override val upstreams: Map[InPort, OutPort],
|
|
|
|
|
|
override val materializedValueComputation: MaterializedValueNode,
|
|
|
|
|
|
override val attributes: Attributes,
|
|
|
|
|
|
info: Fusing.StructuralInfo) extends Module {
|
2015-12-14 17:02:00 +01:00
|
|
|
|
|
2015-12-15 16:44:48 +01:00
|
|
|
|
override def isFused: Boolean = true
|
|
|
|
|
|
|
2016-02-15 10:37:19 +01:00
|
|
|
|
override def replaceShape(s: Shape): Module =
|
|
|
|
|
|
if (s != shape) {
|
|
|
|
|
|
shape.requireSamePortsAs(s)
|
|
|
|
|
|
copy(shape = s)
|
|
|
|
|
|
} else this
|
2015-12-14 17:02:00 +01:00
|
|
|
|
|
|
|
|
|
|
override def carbonCopy: Module = CopiedModule(shape.deepCopy(), attributes, copyOf = this)
|
|
|
|
|
|
|
|
|
|
|
|
override def withAttributes(attributes: Attributes): FusedModule = copy(attributes = attributes)
|
|
|
|
|
|
|
|
|
|
|
|
override def toString =
|
2016-03-11 17:08:30 +01:00
|
|
|
|
f"""FusedModule [${System.identityHashCode(this)}%08x]
|
|
|
|
|
|
| Name: ${this.attributes.nameOrDefault("unnamed")}
|
|
|
|
|
|
| Modules:
|
|
|
|
|
|
| ${subModules.iterator.map(m ⇒ m.attributes.nameLifted.getOrElse(m.toString.replaceAll("\n", "\n "))).mkString("\n ")}
|
|
|
|
|
|
| Downstreams: ${downstreams.iterator.map { case (in, out) ⇒ s"\n $in -> $out" }.mkString("")}
|
|
|
|
|
|
| Upstreams: ${upstreams.iterator.map { case (out, in) ⇒ s"\n $out -> $in" }.mkString("")}
|
|
|
|
|
|
| MatValue: $materializedValueComputation""".stripMargin
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* This is the only extension point for the sealed type hierarchy: composition
|
|
|
|
|
|
* (i.e. the module tree) is managed strictly within this file, only leaf nodes
|
|
|
|
|
|
* may be declared elsewhere.
|
|
|
|
|
|
*/
|
|
|
|
|
|
abstract class AtomicModule extends Module {
|
|
|
|
|
|
final override def subModules: Set[Module] = Set.empty
|
|
|
|
|
|
final override def downstreams: Map[OutPort, InPort] = super.downstreams
|
|
|
|
|
|
final override def upstreams: Map[InPort, OutPort] = super.upstreams
|
2015-12-14 17:02:00 +01:00
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
|
*/
|
2015-06-16 15:34:54 +02:00
|
|
|
|
private[stream] object VirtualProcessor {
|
2016-02-24 11:55:28 +01:00
|
|
|
|
case object Inert {
|
|
|
|
|
|
val subscriber = new CancellingSubscriber[Any]
|
|
|
|
|
|
}
|
|
|
|
|
|
case class Both(subscriber: Subscriber[Any])
|
|
|
|
|
|
object Both {
|
|
|
|
|
|
def create(s: Subscriber[_]) = Both(s.asInstanceOf[Subscriber[Any]])
|
|
|
|
|
|
}
|
2015-06-06 14:36:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is a transparent processor that shall consume as little resources as
|
|
|
|
|
|
* possible. Due to the possibility of receiving uncoordinated inputs from both
|
|
|
|
|
|
* downstream and upstream, this needs an atomic state machine which looks a
|
|
|
|
|
|
* little like this:
|
|
|
|
|
|
*
|
|
|
|
|
|
* +--------------+ (2) +------------+
|
|
|
|
|
|
* | null | ----------> | Subscriber |
|
|
|
|
|
|
* +--------------+ +------------+
|
|
|
|
|
|
* | |
|
|
|
|
|
|
* (1) | | (1)
|
|
|
|
|
|
* \|/ \|/
|
|
|
|
|
|
* +--------------+ (2) +------------+ --\
|
|
|
|
|
|
* | Subscription | ----------> | Both | | (4)
|
|
|
|
|
|
* +--------------+ +------------+ <-/
|
|
|
|
|
|
* | |
|
|
|
|
|
|
* (3) | | (3)
|
|
|
|
|
|
* \|/ \|/
|
|
|
|
|
|
* +--------------+ (2) +------------+ --\
|
|
|
|
|
|
* | Publisher | ----------> | Inert | | (4, *)
|
|
|
|
|
|
* +--------------+ +------------+ <-/
|
|
|
|
|
|
*
|
|
|
|
|
|
* The idea is to keep the major state in only one atomic reference. The actions
|
|
|
|
|
|
* that can happen are:
|
|
|
|
|
|
*
|
|
|
|
|
|
* (1) onSubscribe
|
|
|
|
|
|
* (2) subscribe
|
|
|
|
|
|
* (3) onError / onComplete
|
|
|
|
|
|
* (4) onNext
|
|
|
|
|
|
* (*) Inert can be reached also by cancellation after which onNext is still fine
|
|
|
|
|
|
* so we just silently ignore possible spec violations here
|
|
|
|
|
|
*
|
|
|
|
|
|
* Any event that occurs in a state where no matching outgoing arrow can be found
|
|
|
|
|
|
* is a spec violation, leading to the shutdown of this processor (meaning that
|
|
|
|
|
|
* the state is updated such that all following actions match that of a failed
|
|
|
|
|
|
* Publisher or a cancelling Subscriber, and the non-guilty party is informed if
|
|
|
|
|
|
* already connected).
|
|
|
|
|
|
*
|
|
|
|
|
|
* request() can only be called after the Subscriber has received the Subscription
|
|
|
|
|
|
* and that also means that onNext() will only happen after having transitioned into
|
|
|
|
|
|
* the Both state as well. The Publisher state means that if the real
|
|
|
|
|
|
* Publisher terminates before we get the Subscriber, we can just forget about the
|
|
|
|
|
|
* real one and keep an already finished one around for the Subscriber.
|
|
|
|
|
|
*
|
|
|
|
|
|
* The Subscription that is offered to the Subscriber must cancel the original
|
|
|
|
|
|
* Publisher if things go wrong (like `request(0)` coming in from downstream) and
|
|
|
|
|
|
* it must ensure that we drop the Subscriber reference when `cancel` is invoked.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private[stream] final class VirtualProcessor[T] extends AtomicReference[AnyRef] with Processor[T, T] {
|
2015-06-16 15:34:54 +02:00
|
|
|
|
import VirtualProcessor._
|
|
|
|
|
|
import ReactiveStreamsCompliance._
|
|
|
|
|
|
|
2015-04-16 16:05:49 +02:00
|
|
|
|
override def subscribe(s: Subscriber[_ >: T]): Unit = {
|
2016-02-24 11:55:28 +01:00
|
|
|
|
@tailrec def rec(sub: Subscriber[Any]): Unit =
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case null => if (!compareAndSet(null, s)) rec(sub)
|
|
|
|
|
|
case subscription: Subscription =>
|
|
|
|
|
|
if (compareAndSet(subscription, Both(sub))) establishSubscription(sub, subscription)
|
|
|
|
|
|
else rec(sub)
|
|
|
|
|
|
case pub: Publisher[_] =>
|
|
|
|
|
|
if (compareAndSet(pub, Inert)) pub.subscribe(sub)
|
|
|
|
|
|
else rec(sub)
|
|
|
|
|
|
case _ =>
|
|
|
|
|
|
rejectAdditionalSubscriber(sub, "VirtualProcessor")
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
2016-02-24 11:55:28 +01:00
|
|
|
|
|
|
|
|
|
|
if (s == null) {
|
|
|
|
|
|
val ex = subscriberMustNotBeNullException
|
|
|
|
|
|
try rec(Inert.subscriber)
|
|
|
|
|
|
finally throw ex // must throw NPE, rule 2:13
|
|
|
|
|
|
} else rec(s.asInstanceOf[Subscriber[Any]])
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
override final def onSubscribe(s: Subscription): Unit = {
|
|
|
|
|
|
@tailrec def rec(obj: AnyRef): Unit =
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case null => if (!compareAndSet(null, obj)) rec(obj)
|
|
|
|
|
|
case subscriber: Subscriber[_] =>
|
|
|
|
|
|
obj match {
|
|
|
|
|
|
case subscription: Subscription =>
|
|
|
|
|
|
if (compareAndSet(subscriber, Both.create(subscriber))) establishSubscription(subscriber, subscription)
|
|
|
|
|
|
else rec(obj)
|
|
|
|
|
|
case pub: Publisher[_] =>
|
|
|
|
|
|
getAndSet(Inert) match {
|
|
|
|
|
|
case Inert => // nothing to be done
|
|
|
|
|
|
case _ => pub.subscribe(subscriber.asInstanceOf[Subscriber[Any]])
|
|
|
|
|
|
}
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
2016-02-24 11:55:28 +01:00
|
|
|
|
case _ =>
|
|
|
|
|
|
// spec violation
|
|
|
|
|
|
tryCancel(s)
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
2016-02-24 11:55:28 +01:00
|
|
|
|
|
|
|
|
|
|
if (s == null) {
|
|
|
|
|
|
val ex = subscriptionMustNotBeNullException
|
|
|
|
|
|
try rec(ErrorPublisher(ex, "failed-VirtualProcessor"))
|
|
|
|
|
|
finally throw ex // must throw NPE, rule 2:13
|
|
|
|
|
|
} else rec(s)
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
private def establishSubscription(subscriber: Subscriber[_], subscription: Subscription): Unit = {
|
|
|
|
|
|
val wrapped = new WrappedSubscription(subscription)
|
|
|
|
|
|
try subscriber.onSubscribe(wrapped)
|
|
|
|
|
|
catch {
|
|
|
|
|
|
case NonFatal(ex) =>
|
|
|
|
|
|
set(Inert)
|
|
|
|
|
|
tryCancel(subscription)
|
|
|
|
|
|
tryOnError(subscriber, ex)
|
|
|
|
|
|
}
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
override def onError(t: Throwable): Unit = {
|
|
|
|
|
|
/*
|
|
|
|
|
|
* `ex` is always a reasonable Throwable that we should communicate downstream,
|
|
|
|
|
|
* but if `t` was `null` then the spec requires us to throw an NPE (which `ex`
|
|
|
|
|
|
* will be in this case).
|
|
|
|
|
|
*/
|
|
|
|
|
|
@tailrec def rec(ex: Throwable): Unit =
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case null =>
|
|
|
|
|
|
if (!compareAndSet(null, ErrorPublisher(ex, "failed-VirtualProcessor"))) rec(ex)
|
|
|
|
|
|
else if (t == null) throw ex
|
|
|
|
|
|
case s: Subscription =>
|
|
|
|
|
|
if (!compareAndSet(s, ErrorPublisher(ex, "failed-VirtualProcessor"))) rec(ex)
|
|
|
|
|
|
else if (t == null) throw ex
|
|
|
|
|
|
case Both(s) =>
|
|
|
|
|
|
set(Inert)
|
|
|
|
|
|
try tryOnError(s, ex)
|
|
|
|
|
|
finally if (t == null) throw ex // must throw NPE, rule 2:13
|
|
|
|
|
|
case s: Subscriber[_] => // spec violation
|
|
|
|
|
|
getAndSet(Inert) match {
|
|
|
|
|
|
case Inert => // nothing to be done
|
|
|
|
|
|
case _ => ErrorPublisher(ex, "failed-VirtualProcessor").subscribe(s)
|
|
|
|
|
|
}
|
|
|
|
|
|
case _ => // spec violation or cancellation race, but nothing we can do
|
|
|
|
|
|
}
|
2015-06-16 15:34:54 +02:00
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
val ex = if (t == null) exceptionMustNotBeNullException else t
|
|
|
|
|
|
rec(ex)
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
@tailrec override final def onComplete(): Unit =
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case null => if (!compareAndSet(null, EmptyPublisher)) onComplete()
|
|
|
|
|
|
case s: Subscription => if (!compareAndSet(s, EmptyPublisher)) onComplete()
|
|
|
|
|
|
case Both(s) =>
|
|
|
|
|
|
set(Inert)
|
|
|
|
|
|
tryOnComplete(s)
|
|
|
|
|
|
case s: Subscriber[_] => // spec violation
|
|
|
|
|
|
set(Inert)
|
|
|
|
|
|
EmptyPublisher.subscribe(s)
|
|
|
|
|
|
case _ => // spec violation or cancellation race, but nothing we can do
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override def onNext(t: T): Unit =
|
|
|
|
|
|
if (t == null) {
|
|
|
|
|
|
val ex = elementMustNotBeNullException
|
|
|
|
|
|
@tailrec def rec(): Unit =
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case x @ (null | _: Subscription) => if (!compareAndSet(x, ErrorPublisher(ex, "failed-VirtualProcessor"))) rec()
|
|
|
|
|
|
case s: Subscriber[_] => try s.onError(ex) catch { case NonFatal(_) => } finally set(Inert)
|
|
|
|
|
|
case Both(s) => try s.onError(ex) catch { case NonFatal(_) => } finally set(Inert)
|
|
|
|
|
|
case _ => // spec violation or cancellation race, but nothing we can do
|
|
|
|
|
|
}
|
|
|
|
|
|
rec()
|
|
|
|
|
|
throw ex // must throw NPE, rule 2:13
|
|
|
|
|
|
} else {
|
|
|
|
|
|
@tailrec def rec(): Unit =
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case Both(s) =>
|
|
|
|
|
|
try s.onNext(t)
|
|
|
|
|
|
catch {
|
|
|
|
|
|
case NonFatal(e) =>
|
|
|
|
|
|
set(Inert)
|
|
|
|
|
|
throw new IllegalStateException("Subscriber threw exception, this is in violation of rule 2:13", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
case s: Subscriber[_] => // spec violation
|
|
|
|
|
|
val ex = new IllegalStateException(noDemand)
|
|
|
|
|
|
getAndSet(Inert) match {
|
|
|
|
|
|
case Inert => // nothing to be done
|
|
|
|
|
|
case _ => ErrorPublisher(ex, "failed-VirtualProcessor").subscribe(s)
|
|
|
|
|
|
}
|
|
|
|
|
|
throw ex
|
|
|
|
|
|
case Inert | _: Publisher[_] => // nothing to be done
|
|
|
|
|
|
case other =>
|
|
|
|
|
|
val pub = ErrorPublisher(new IllegalStateException(noDemand), "failed-VirtualPublisher")
|
|
|
|
|
|
if (!compareAndSet(other, pub)) rec()
|
|
|
|
|
|
else throw pub.t
|
|
|
|
|
|
}
|
|
|
|
|
|
rec()
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
2016-02-24 11:55:28 +01:00
|
|
|
|
|
|
|
|
|
|
private def noDemand = "spec violation: onNext was signaled from upstream without demand"
|
|
|
|
|
|
|
|
|
|
|
|
private class WrappedSubscription(real: Subscription) extends Subscription {
|
2015-06-16 15:34:54 +02:00
|
|
|
|
override def request(n: Long): Unit = {
|
2016-02-24 11:55:28 +01:00
|
|
|
|
if (n < 1) {
|
|
|
|
|
|
tryCancel(real)
|
|
|
|
|
|
getAndSet(Inert) match {
|
|
|
|
|
|
case Both(s) => rejectDueToNonPositiveDemand(s)
|
|
|
|
|
|
case Inert => // another failure has won the race
|
|
|
|
|
|
case _ => // this cannot possibly happen, but signaling errors is impossible at this point
|
|
|
|
|
|
}
|
|
|
|
|
|
} else real.request(n)
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
2016-02-24 11:55:28 +01:00
|
|
|
|
override def cancel(): Unit = {
|
|
|
|
|
|
set(Inert)
|
|
|
|
|
|
real.cancel()
|
2015-06-16 15:34:54 +02:00
|
|
|
|
}
|
2015-04-16 16:05:49 +02:00
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-22 18:02:42 +02:00
|
|
|
|
/**
|
2016-02-24 22:00:32 -08:00
|
|
|
|
* INTERNAL API
|
2016-02-24 11:55:28 +01:00
|
|
|
|
*
|
|
|
|
|
|
* The implementation of `Sink.asPublisher` needs to offer a `Publisher` that
|
|
|
|
|
|
* defers to the upstream that is connected during materialization. This would
|
|
|
|
|
|
* be trivial if it were not for materialized value computations that may even
|
|
|
|
|
|
* spawn the code that does `pub.subscribe(sub)` in a Future, running concurrently
|
|
|
|
|
|
* with the actual materialization. Therefore we implement a minimial shell here
|
|
|
|
|
|
* that plugs the downstream and the upstream together as soon as both are known.
|
|
|
|
|
|
* Using a VirtualProcessor would technically also work, but it would defeat the
|
|
|
|
|
|
* purpose of subscription timeouts—the subscription would always already be
|
|
|
|
|
|
* established from the Actor’s perspective, regardless of whether a downstream
|
|
|
|
|
|
* will ever be connected.
|
|
|
|
|
|
*
|
|
|
|
|
|
* One important consideration is that this `Publisher` must not retain a reference
|
|
|
|
|
|
* to the `Subscriber` after having hooked it up with the real `Publisher`, hence
|
|
|
|
|
|
* the use of `Inert.subscriber` as a tombstone.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private[impl] class VirtualPublisher[T] extends AtomicReference[AnyRef] with Publisher[T] {
|
|
|
|
|
|
import VirtualProcessor.Inert
|
|
|
|
|
|
import ReactiveStreamsCompliance._
|
|
|
|
|
|
|
|
|
|
|
|
override def subscribe(subscriber: Subscriber[_ >: T]): Unit = {
|
|
|
|
|
|
requireNonNullSubscriber(subscriber)
|
|
|
|
|
|
@tailrec def rec(): Unit = {
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case null => if (!compareAndSet(null, subscriber)) rec()
|
|
|
|
|
|
case pub: Publisher[_] =>
|
|
|
|
|
|
if (compareAndSet(pub, Inert.subscriber)) {
|
|
|
|
|
|
pub.asInstanceOf[Publisher[T]].subscribe(subscriber)
|
|
|
|
|
|
} else rec()
|
|
|
|
|
|
case _: Subscriber[_] => rejectAdditionalSubscriber(subscriber, "Sink.asPublisher(fanout = false)")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
rec() // return value is boolean only to make the expressions above compile
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@tailrec final def registerPublisher(pub: Publisher[_]): Unit =
|
|
|
|
|
|
get() match {
|
|
|
|
|
|
case null => if (!compareAndSet(null, pub)) registerPublisher(pub)
|
|
|
|
|
|
case sub: Subscriber[r] =>
|
|
|
|
|
|
set(Inert.subscriber)
|
|
|
|
|
|
pub.asInstanceOf[Publisher[r]].subscribe(sub)
|
|
|
|
|
|
case _ => throw new IllegalStateException("internal error")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* INERNAL API
|
2015-06-22 18:02:42 +02:00
|
|
|
|
*/
|
|
|
|
|
|
private[stream] object MaterializerSession {
|
|
|
|
|
|
class MaterializationPanic(cause: Throwable) extends RuntimeException("Materialization aborted.", cause) with NoStackTrace
|
2015-12-14 17:02:00 +01:00
|
|
|
|
|
|
|
|
|
|
final val Debug = false
|
2015-06-22 18:02:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
|
*/
|
2015-10-31 14:46:10 +01:00
|
|
|
|
private[stream] abstract class MaterializerSession(val topLevel: StreamLayout.Module, val initialAttributes: Attributes) {
|
2015-01-28 14:19:50 +01:00
|
|
|
|
import StreamLayout._
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
// the contained maps store either Subscriber[Any] or VirtualPublisher, but the type system cannot express that
|
|
|
|
|
|
private var subscribersStack: List[ju.Map[InPort, AnyRef]] =
|
|
|
|
|
|
new ju.HashMap[InPort, AnyRef] :: Nil
|
2015-12-14 17:02:00 +01:00
|
|
|
|
private var publishersStack: List[ju.Map[OutPort, Publisher[Any]]] =
|
|
|
|
|
|
new ju.HashMap[OutPort, Publisher[Any]] :: Nil
|
2016-03-11 17:08:30 +01:00
|
|
|
|
private var matValSrcStack: List[ju.Map[MaterializedValueNode, List[MaterializedValueSource[Any]]]] =
|
|
|
|
|
|
new ju.HashMap[MaterializedValueNode, List[MaterializedValueSource[Any]]] :: Nil
|
2015-04-23 13:32:07 +02:00
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* Please note that this stack keeps track of the scoped modules wrapped in CopiedModule but not the CopiedModule
|
|
|
|
|
|
* itself. The reason is that the CopiedModule itself is only needed for the enterScope and exitScope methods but
|
|
|
|
|
|
* not elsewhere. For this reason they are just simply passed as parameters to those methods.
|
|
|
|
|
|
*
|
|
|
|
|
|
* The reason why the encapsulated (copied) modules are stored as mutable state to save subclasses of this class
|
|
|
|
|
|
* from passing the current scope around or even knowing about it.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private var moduleStack: List[Module] = topLevel :: Nil
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
private def subscribers: ju.Map[InPort, AnyRef] = subscribersStack.head
|
2015-12-14 17:02:00 +01:00
|
|
|
|
private def publishers: ju.Map[OutPort, Publisher[Any]] = publishersStack.head
|
2015-04-23 13:32:07 +02:00
|
|
|
|
private def currentLayout: Module = moduleStack.head
|
2016-03-11 17:08:30 +01:00
|
|
|
|
private def matValSrc: ju.Map[MaterializedValueNode, List[MaterializedValueSource[Any]]] = matValSrcStack.head
|
2015-04-23 13:32:07 +02:00
|
|
|
|
|
|
|
|
|
|
// Enters a copied module and establishes a scope that prevents internals to leak out and interfere with copies
|
|
|
|
|
|
// of the same module.
|
|
|
|
|
|
// We don't store the enclosing CopiedModule itself as state since we don't use it anywhere else than exit and enter
|
|
|
|
|
|
private def enterScope(enclosing: CopiedModule): Unit = {
|
2016-03-11 17:08:30 +01:00
|
|
|
|
if (MaterializerSession.Debug) println(f"entering scope [${System.identityHashCode(enclosing)}%08x]")
|
2015-12-14 17:02:00 +01:00
|
|
|
|
subscribersStack ::= new ju.HashMap
|
|
|
|
|
|
publishersStack ::= new ju.HashMap
|
2016-03-11 17:08:30 +01:00
|
|
|
|
matValSrcStack ::= new ju.HashMap
|
2015-04-23 13:32:07 +02:00
|
|
|
|
moduleStack ::= enclosing.copyOf
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Exits the scope of the copied module and propagates Publishers/Subscribers to the enclosing scope assigning
|
|
|
|
|
|
// them to the copied ports instead of the original ones (since there might be multiple copies of the same module
|
|
|
|
|
|
// leading to port identity collisions)
|
|
|
|
|
|
// We don't store the enclosing CopiedModule itself as state since we don't use it anywhere else than exit and enter
|
|
|
|
|
|
private def exitScope(enclosing: CopiedModule): Unit = {
|
2016-03-11 17:08:30 +01:00
|
|
|
|
if (MaterializerSession.Debug) println(f"exiting scope [${System.identityHashCode(enclosing)}%08x]")
|
2015-04-23 13:32:07 +02:00
|
|
|
|
val scopeSubscribers = subscribers
|
|
|
|
|
|
val scopePublishers = publishers
|
|
|
|
|
|
subscribersStack = subscribersStack.tail
|
|
|
|
|
|
publishersStack = publishersStack.tail
|
2016-03-11 17:08:30 +01:00
|
|
|
|
matValSrcStack = matValSrcStack.tail
|
2015-04-23 13:32:07 +02:00
|
|
|
|
moduleStack = moduleStack.tail
|
|
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
if (MaterializerSession.Debug) println(s" subscribers = $scopeSubscribers\n publishers = $scopePublishers")
|
|
|
|
|
|
|
2015-04-23 13:32:07 +02:00
|
|
|
|
// When we exit the scope of a copied module, pick up the Subscribers/Publishers belonging to exposed ports of
|
|
|
|
|
|
// the original module and assign them to the copy ports in the outer scope that we will return to
|
|
|
|
|
|
enclosing.copyOf.shape.inlets.iterator.zip(enclosing.shape.inlets.iterator).foreach {
|
2015-12-14 17:02:00 +01:00
|
|
|
|
case (original, exposed) ⇒ assignPort(exposed, scopeSubscribers.get(original))
|
2015-04-23 13:32:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enclosing.copyOf.shape.outlets.iterator.zip(enclosing.shape.outlets.iterator).foreach {
|
2015-12-14 17:02:00 +01:00
|
|
|
|
case (original, exposed) ⇒ assignPort(exposed, scopePublishers.get(original))
|
2015-06-22 18:02:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
|
final def materialize(): Any = {
|
2016-03-11 17:08:30 +01:00
|
|
|
|
if (MaterializerSession.Debug) println(s"beginning materialization of $topLevel")
|
2015-01-28 14:19:50 +01:00
|
|
|
|
require(topLevel ne EmptyModule, "An empty module cannot be materialized (EmptyModule was given)")
|
|
|
|
|
|
require(
|
|
|
|
|
|
topLevel.isRunnable,
|
|
|
|
|
|
s"The top level module cannot be materialized because it has unconnected ports: ${(topLevel.inPorts ++ topLevel.outPorts).mkString(", ")}")
|
2015-10-31 14:46:10 +01:00
|
|
|
|
try materializeModule(topLevel, initialAttributes and topLevel.attributes)
|
2015-06-22 18:02:42 +02:00
|
|
|
|
catch {
|
2015-06-14 03:12:30 -04:00
|
|
|
|
case NonFatal(cause) ⇒
|
|
|
|
|
|
// PANIC!!! THE END OF THE MATERIALIZATION IS NEAR!
|
|
|
|
|
|
// Cancels all intermediate Publishers and fails all intermediate Subscribers.
|
|
|
|
|
|
// (This is an attempt to clean up after an exception during materialization)
|
|
|
|
|
|
val errorPublisher = new ErrorPublisher(new MaterializationPanic(cause), "")
|
2015-12-14 17:02:00 +01:00
|
|
|
|
for (subMap ← subscribersStack; sub ← subMap.asScala.valuesIterator)
|
2016-02-24 11:55:28 +01:00
|
|
|
|
doSubscribe(errorPublisher, sub)
|
2015-06-14 03:12:30 -04:00
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
for (pubMap ← publishersStack; pub ← pubMap.asScala.valuesIterator)
|
2015-06-14 03:12:30 -04:00
|
|
|
|
pub.subscribe(new CancellingSubscriber)
|
|
|
|
|
|
|
|
|
|
|
|
throw cause
|
2015-06-22 18:02:42 +02:00
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-23 17:32:55 +02:00
|
|
|
|
protected def mergeAttributes(parent: Attributes, current: Attributes): Attributes =
|
2015-01-28 14:19:50 +01:00
|
|
|
|
parent and current
|
|
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
def registerSrc(ms: MaterializedValueSource[Any]): Unit = {
|
|
|
|
|
|
if (MaterializerSession.Debug) println(s"registering source $ms")
|
|
|
|
|
|
matValSrc.get(ms.computation) match {
|
|
|
|
|
|
case null ⇒ matValSrc.put(ms.computation, ms :: Nil)
|
|
|
|
|
|
case xs ⇒ matValSrc.put(ms.computation, ms :: xs)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-23 17:32:55 +02:00
|
|
|
|
protected def materializeModule(module: Module, effectiveAttributes: Attributes): Any = {
|
2015-12-14 17:02:00 +01:00
|
|
|
|
val materializedValues: ju.Map[Module, Any] = new ju.HashMap
|
2015-03-30 14:22:12 +02:00
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
if (MaterializerSession.Debug) println(f"entering module [${System.identityHashCode(module)}%08x] (${Logging.simpleName(module)})")
|
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
|
for (submodule ← module.subModules) {
|
|
|
|
|
|
val subEffectiveAttributes = mergeAttributes(effectiveAttributes, submodule.attributes)
|
2015-03-30 14:22:12 +02:00
|
|
|
|
submodule match {
|
2016-03-11 17:08:30 +01:00
|
|
|
|
case atomic: AtomicModule ⇒
|
2015-12-14 17:02:00 +01:00
|
|
|
|
materializeAtomic(atomic, subEffectiveAttributes, materializedValues)
|
2015-04-23 13:32:07 +02:00
|
|
|
|
case copied: CopiedModule ⇒
|
|
|
|
|
|
enterScope(copied)
|
|
|
|
|
|
materializedValues.put(copied, materializeModule(copied, subEffectiveAttributes))
|
|
|
|
|
|
exitScope(copied)
|
2016-03-11 17:08:30 +01:00
|
|
|
|
case composite @ (_: CompositeModule | _: FusedModule) ⇒
|
2015-03-30 14:22:12 +02:00
|
|
|
|
materializedValues.put(composite, materializeComposite(composite, subEffectiveAttributes))
|
2016-03-11 17:08:30 +01:00
|
|
|
|
case EmptyModule => // nothing to do or say
|
2015-03-30 14:22:12 +02:00
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
2015-03-30 14:22:12 +02:00
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
|
if (MaterializerSession.Debug) {
|
2016-03-11 17:08:30 +01:00
|
|
|
|
println(f"resolving module [${System.identityHashCode(module)}%08x] computation ${module.materializedValueComputation}")
|
2015-12-14 17:02:00 +01:00
|
|
|
|
println(s" matValSrc = $matValSrc")
|
2016-03-11 17:08:30 +01:00
|
|
|
|
println(s" matVals =\n ${materializedValues.asScala.map(p ⇒ "%08x".format(System.identityHashCode(p._1)) -> p._2).mkString("\n ")}")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val ret = resolveMaterialized(module.materializedValueComputation, materializedValues, 2)
|
|
|
|
|
|
while (!matValSrc.isEmpty) {
|
|
|
|
|
|
val node = matValSrc.keySet.iterator.next()
|
|
|
|
|
|
if (MaterializerSession.Debug) println(s" delayed computation of $node")
|
|
|
|
|
|
resolveMaterialized(node, materializedValues, 4)
|
2015-12-14 17:02:00 +01:00
|
|
|
|
}
|
2016-03-11 17:08:30 +01:00
|
|
|
|
|
|
|
|
|
|
if (MaterializerSession.Debug) println(f"exiting module [${System.identityHashCode(module)}%08x]")
|
|
|
|
|
|
|
|
|
|
|
|
ret
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-06-23 17:32:55 +02:00
|
|
|
|
protected def materializeComposite(composite: Module, effectiveAttributes: Attributes): Any = {
|
2015-01-28 14:19:50 +01:00
|
|
|
|
materializeModule(composite, effectiveAttributes)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
protected def materializeAtomic(atomic: AtomicModule, effectiveAttributes: Attributes, matVal: ju.Map[Module, Any]): Unit
|
2015-01-28 14:19:50 +01:00
|
|
|
|
|
2016-03-11 17:08:30 +01:00
|
|
|
|
private def resolveMaterialized(matNode: MaterializedValueNode, matVal: ju.Map[Module, Any], spaces: Int): Any = {
|
|
|
|
|
|
if (MaterializerSession.Debug) println(" " * spaces + matNode)
|
2015-12-14 17:02:00 +01:00
|
|
|
|
val ret = matNode match {
|
|
|
|
|
|
case Atomic(m) ⇒ matVal.get(m)
|
2016-03-11 17:08:30 +01:00
|
|
|
|
case Combine(f, d1, d2) ⇒ f(resolveMaterialized(d1, matVal, spaces + 2), resolveMaterialized(d2, matVal, spaces + 2))
|
|
|
|
|
|
case Transform(f, d) ⇒ f(resolveMaterialized(d, matVal, spaces + 2))
|
2016-01-20 10:00:37 +02:00
|
|
|
|
case Ignore ⇒ NotUsed
|
2015-12-14 17:02:00 +01:00
|
|
|
|
}
|
2016-03-11 17:08:30 +01:00
|
|
|
|
if (MaterializerSession.Debug) println(" " * spaces + s"result = $ret")
|
2015-12-14 17:02:00 +01:00
|
|
|
|
matValSrc.remove(matNode) match {
|
|
|
|
|
|
case null ⇒ // nothing to do
|
|
|
|
|
|
case srcs ⇒
|
2016-03-11 17:08:30 +01:00
|
|
|
|
if (MaterializerSession.Debug) println(" " * spaces + s"triggering sources $srcs")
|
2015-12-14 17:02:00 +01:00
|
|
|
|
srcs.foreach(_.setValue(ret))
|
|
|
|
|
|
}
|
|
|
|
|
|
ret
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
final protected def assignPort(in: InPort, subscriberOrVirtual: AnyRef): Unit = {
|
|
|
|
|
|
subscribers.put(in, subscriberOrVirtual)
|
2016-04-05 13:11:54 +02:00
|
|
|
|
|
|
|
|
|
|
currentLayout.upstreams.get(in) match {
|
|
|
|
|
|
case Some(upstream) =>
|
|
|
|
|
|
val publisher = publishers.get(upstream)
|
|
|
|
|
|
if (publisher ne null) doSubscribe(publisher, subscriberOrVirtual)
|
|
|
|
|
|
// Interface (unconnected) ports of the current scope will be wired when exiting the scope (or some parent scope)
|
|
|
|
|
|
case None =>
|
2015-04-23 13:32:07 +02:00
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final protected def assignPort(out: OutPort, publisher: Publisher[Any]): Unit = {
|
2015-12-14 17:02:00 +01:00
|
|
|
|
publishers.put(out, publisher)
|
2016-04-05 13:11:54 +02:00
|
|
|
|
|
|
|
|
|
|
currentLayout.downstreams.get(out) match {
|
|
|
|
|
|
case Some(downstream) =>
|
|
|
|
|
|
val subscriber = subscribers.get(downstream)
|
|
|
|
|
|
if (subscriber ne null) doSubscribe(publisher, subscriber)
|
2015-04-23 13:32:07 +02:00
|
|
|
|
// Interface (unconnected) ports of the current scope will be wired when exiting the scope
|
2016-04-05 13:11:54 +02:00
|
|
|
|
case None =>
|
2015-04-23 13:32:07 +02:00
|
|
|
|
}
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-24 11:55:28 +01:00
|
|
|
|
private def doSubscribe(publisher: Publisher[_ <: Any], subscriberOrVirtual: AnyRef): Unit =
|
|
|
|
|
|
subscriberOrVirtual match {
|
|
|
|
|
|
case s: Subscriber[_] => publisher.subscribe(s.asInstanceOf[Subscriber[Any]])
|
|
|
|
|
|
case v: VirtualPublisher[_] => v.registerPublisher(publisher)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-01-28 14:19:50 +01:00
|
|
|
|
}
|