/**
   * 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];
  }
  /**
   * Handles event that the value of a submodule's out-port is no longer needed.
   *
   * <p>If the submodule of the given out-port no longer has any (other) out-port whose value is
   * required, this method starts (asynchronously) cleaning all intermediate results for this
   * module.
   *
   * <p>This method also finishes the asynchronous action (waiting for submodule out-port) started
   * previously in {@link #getChildExecutor}.
   *
   * @param outPort out-port whose value is no longer needed
   */
  private void submoduleOutPortNoLongerNeeded(RuntimeOutPort outPort) {
    assert state.compareTo(State.RUNNING) >= 0;

    RuntimeModule submodule = outPort.getModule();
    BitSet neededOutPorts = computeResumeState.getSubmodulesNeededOutPorts()[submodule.getIndex()];
    neededOutPorts.set(outPort.getOutIndex(), false);
    if (neededOutPorts.isEmpty() && getInterpreterProperties().isCleaningRequested()) {
      ExecutionTrace executionTrace =
          ExecutionTrace.empty().resolveContent().resolveModule(submodule.getSimpleName());
      CompletableFuture<Void> future = stagingArea.delete(executionTrace);
      awaitAsynchronousAction(
          future, "cleaning up intermediate output of submodule '%s'", submodule.getSimpleName());
    }

    endAsynchronousAction(outPort);
  }
  /**
   * 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();
  }
  /**
   * Performs the initialization steps of algorithm <em>ComputeResumeState</em>.
   *
   * <p>This method sets the state of this actor to {@link State#STARTING} and starts the
   * asynchronous initialization action, which is completed in {@link #startRunningIfQueueEmpty()}.
   *
   * <p>This method starts one asynchronous action for every in-port in {@link #recomputedInPorts}.
   * These asynchronous actions are finished in {@link #inPortHasSignal(int)}.
   */
  @Override
  public void preStart() {
    assert state == State.STARTING;
    assert !requestedOutPorts.isEmpty();
    // Another precondition: getPortState() is IRRELEVANT for all nodes in the dependency graph.

    publishStartModule();

    // Start an asynchronous action for each in-port that is promised to receive a value
    recomputedInPorts
        .stream()
        .mapToObj(module.getInPorts()::get)
        .forEach(
            inPort -> startAsynchronousAction(inPort, "waiting for %s to receive value", inPort));

    // Run algorithm ComputeResumeState
    computeResumeState.run();
    startRunningIfQueueEmpty();
  }
 /**
  * Handles event that a future returned by {@link StagingArea#exists(RuntimeExecutionTrace)}
  * (called by {@link #asynchronousHasValueCheck(ValueNode)}) was completed successfully.
  *
  * @param node node in the dependency graph
  * @param hasValue whether the node in the dependency graph has a value
  */
 private void finishedStagingAreaOperation(ValueNode node, boolean hasValue) {
   computeResumeState.updateHasValue(node, hasValue);
   startRunningIfQueueEmpty();
 }
  /**
   * Returns whether the given submodule's out-port is needed.
   *
   * @param outPort submodule's out-port
   */
  private boolean isOutPortNeeded(RuntimeOutPort outPort) {
    assert outPort.getModule().getParent() == module;

    return computeResumeState.getSubmodulesNeededOutPorts()[outPort.getModule().getIndex()].get(
        outPort.getOutIndex());
  }