/**
   * Returns, and if necessary creates, the submodule interpreter actor for the given submodule.
   *
   * <p>A submodule interpreter actor is created at most once during the lifetime of the current
   * actor. If the submodule actor is created, it will be supervised by the current actor.
   *
   * <p>This method starts one asynchronous action for each out-port of the submodule that satisfies
   * {@link #isOutPortNeeded(RuntimeOutPort)}; these will be finished in {@link
   * #submoduleOutPortNoLongerNeeded}. This prevents this actor from terminating even though a child
   * interpreter may still send values for out-ports.
   *
   * <p>One additional asynchronous action is started for the child actor, which will be finished in
   * {@link #childActorTerminated(ActorRef)}.
   *
   * @param submodule submodule
   * @return reference to the submodule actor
   */
  private ActorRef getChildExecutor(RuntimeModule submodule) {
    assert state == State.REPLAY || state == State.RUNNING || state == State.ALL_OUTPUTS;

    int submoduleId = submodule.getIndex();
    if (childExecutors[submoduleId] == null) {
      List<SubmoduleInPortNode> submoduleInPortNodes =
          dependencyGraph.submodulesInPortNodes().get(submoduleId);

      ImmutableList<HasValue> submoduleInPortHasValueList =
          submoduleInPortNodes
              .stream()
              .map(SubmoduleInPortNode::getHasValue)
              .collect(ImmutableList.collector());

      BitSet submoduleRecomputedInPorts = new BitSet(submodule.getInPorts().size());
      submoduleInPortNodes
          .stream()
          .filter(node -> node.getPortState() == PortState.RECOMPUTE)
          .forEach(node -> submoduleRecomputedInPorts.set(node.getElement().getInIndex()));

      BitSet submoduleRequestedOutPorts =
          computeResumeState.getSubmodulesNeededOutPorts()[submoduleId];
      submoduleRequestedOutPorts
          .stream()
          .mapToObj(id -> submodule.getOutPorts().get(id))
          .forEach(
              outPort ->
                  startAsynchronousAction(
                      outPort,
                      "waiting for value to pass through submodule out-port %s#%s",
                      submodule.getSimpleName(),
                      outPort.getSimpleName()));

      childExecutors[submoduleId] =
          getContext()
              .actorOf(
                  interpreterPropsProvider.provideInterpreterProps(
                      getInterpreterProperties(),
                      stagingArea.resolveDescendant(
                          ExecutionTrace.empty()
                              .resolveContent()
                              .resolveModule(submodule.getSimpleName())),
                      submoduleId,
                      submoduleInPortHasValueList,
                      submoduleRecomputedInPorts,
                      submoduleRequestedOutPorts),
                  submodule.getSimpleName().toString());
      getContext().watch(childExecutors[submoduleId]);
      childActorMap.put(childExecutors[submoduleId], submodule);
      startAsynchronousAction(
          childExecutors[submoduleId],
          "supervising interpreter for submodule %s",
          submodule.getSimpleName());
    }
    return childExecutors[submoduleId];
  }
  /**
   * Starts the interpretation of the current parent module if algorithm ComputeResumeState has
   * finished.
   *
   * <p>This method sets the state of this actor to {@link State#RUNNING} and thus also finished the
   * asynchronous action that was started in {@link #preStart()}.
   */
  private void startRunningIfQueueEmpty() {
    assert state == State.STARTING;

    if (!computeResumeState.isFinished()) {
      return;
    }

    // Trigger in-ports
    initialInPortsTrigger();
    state = State.RUNNING;

    // Start child interpreters for all submodules that have at least one in-port whose state is
    // READY
    submodules:
    for (List<SubmoduleInPortNode> submoduleInPortNodes : dependencyGraph.submodulesInPortNodes()) {
      for (SubmoduleInPortNode submoduleInPortNode : submoduleInPortNodes) {
        if (submoduleInPortNode.getPortState() == PortState.READY) {
          getChildExecutor(submoduleInPortNode.getElement().getModule());
          continue submodules;
        }
      }
    }

    // Start child interpreters for all submodules that have no in-ports and whose state is READY
    for (SubmoduleNode submoduleNode : dependencyGraph.submoduleNodes()) {
      if (submoduleNode.getElement().getInPorts().isEmpty()
          && submoduleNode.getPortState() == PortState.READY) {
        getChildExecutor(submoduleNode.getElement());
      }
    }

    // Trigger all out-ports whose state is READY
    dependencyGraph
        .outPortNodes()
        .stream()
        .filter(node -> node.getPortState() == PortState.READY)
        .forEach(node -> outportCarriesSignal(node.getElement().getOutIndex()));

    if (outPortsRequiringValue.isEmpty()) {
      state = State.ALL_OUTPUTS;
    }

    // Finish the asynchronous action started in #preStart().
    checkIfNoAsynchronousActions();
  }