All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rescala.scheduler.Levelbased.scala Maven / Gradle / Ivy

There is a newer version: 0.35.1
Show newest version
package rescala.scheduler

import rescala.core.{InitialChange, Initializer, ReSource, ReevTicket}

import java.util.PriorityQueue
import scala.collection.mutable.ListBuffer

trait Levelbased extends Twoversion {

  type State[V] <: LevelState[V]

  class LevelState[V](initialValue: V) extends TwoVersionState[V](initialValue) {

    private var _level: Int = 0

    def level(): Int = _level

    def updateLevel(i: Int): Int = {
      val max = math.max(i, _level)
      _level = max
      max
    }
  }

  /** Further implementation of level-based propagation based on the common propagation implementation. */
  trait LevelBasedTransaction extends TwoVersionTransactionImpl with LevelQueue.Evaluator with Initializer {

    override type State[V] = Levelbased.this.State[V]

    /** Stores all active reactives in case we create more later and need to reevaluate them. */
    private val _propagating: ListBuffer[ReSource] = ListBuffer[ReSource]()

    lazy val levelQueue = new LevelQueue(this)

    /** Store a single resettable ticket for the whole evaluation.
      * This optimization drastically reduces garbage generation of a relatively expensive object
      */
    private val reevaluationTicket: ReevTicket[State, _] = makeDynamicReevaluationTicket(null)

    /** Overrides the evaluator, this is essentially an inlined callback */
    override def evaluate(r: Derived): Unit = evaluateIn(r)(reevaluationTicket.reset(r.state.base(token)))
    def evaluateIn(head: Derived)(dt: ReevTicket[head.State, head.Value]): Unit = {
      val reevRes = head.reevaluate(dt)

      val dependencies: Option[Set[ReSource]] = reevRes.inputs()
      val minimalLevel                        = dependencies.fold(0)(nextLevel)
      val redo                                = head.state.level() < minimalLevel
      if (redo) {
        levelQueue.enqueue(minimalLevel)(head)
      } else {
        dependencies.foreach(commitDependencyDiff(head, head.state.incoming))
        reevRes.forValue(writeState(head))
        reevRes.forEffect(observe)
        if (reevRes.activate) enqueueOutgoing(head, minimalLevel)
      }
    }

    private def enqueueOutgoing(head: ReSource, minLevel: Int): Unit = {
      head.state.outgoing.foreach(levelQueue.enqueue(minLevel))
      _propagating += head
      ()
    }

    private def nextLevel(dependencies: Set[ReSource]): Int =
      if (dependencies.isEmpty) 0 else dependencies.map(_.state.level()).max + 1

    override def initializer: Initializer.of[State] = this

    override protected def initialize(
        reactive: Derived,
        incoming: Set[ReSource],
        needsReevaluation: Boolean
    ): Unit = {
      val level = nextLevel(incoming)
      reactive.state.updateLevel(level)

      incoming.foreach { dep =>
        beforeDynamicDependencyInteraction(dep)
        discover(dep, reactive)
      }
      reactive.state.updateIncoming(incoming)

      if (needsReevaluation || incoming.exists(_propagating.contains)) {
        if (level <= levelQueue.currentLevel()) {
          evaluateIn(reactive)(makeDynamicReevaluationTicket(reactive.state.base(token)))
        } else {
          levelQueue.enqueue(level)(reactive)
        }
      }
    }

    final override def initializationPhase(initialChanges: Map[ReSource, InitialChange[State]]): Unit =
      initialChanges.values.foreach(prepareInitialChange)

    final def prepareInitialChange(ic: InitialChange[State]): Unit = {
      val n = ic.writeValue(ic.source.state.base(token), writeState(ic.source))
      if (n) enqueueOutgoing(ic.source, LevelQueue.noLevelIncrease)
    }

    def propagationPhase(): Unit = levelQueue.evaluateQueue()
  }

  /** Level-based queue used the determine an evaluation order for reactive elements
    *
    * @tparam S Struct type that defines the spore type used to manage the reactive evaluation
    */
  final private[Levelbased] class LevelQueue(evaluator: LevelQueue.Evaluator) {

    import LevelQueue._

    private val elements = new PriorityQueue[QueueElement]()

    /** Gets the level of the current head element of the queue (if existing).
      * Used to determine if newly created reactives have to be evaluated.
      *
      * @return Level of the current queue head
      */
    def currentLevel(): Int =
      if (elements.peek() == null) Int.MaxValue
      else elements.peek().level

    /** Adds a new reactive element to the queue
      *
      * @param minLevel      Minimum level to assign the the element (overrides the elements original level if larger)
      * @param needsEvaluate Indicates if the element needs re-evaluation itself, otherwise it is just a level change
      * @param dep           Element to add to the queue
      */
    def enqueue(minLevel: Int, needsEvaluate: Boolean = true)(dep: Derived): Unit = {
      elements.offer(QueueElement(dep.state.level(), dep, minLevel, needsEvaluate))
      ()
    }

    /** Handles a queue element by applying the given evaluator to it and scheduling the next elements for evaluation
      *
      * @param queueElement Element to evaluate
      */
    private def handleElement(queueElement: QueueElement): Unit = {
      val QueueElement(headLevel, head, headMinLevel, reevaluate) = queueElement
      // handle level increases
      if (headLevel < headMinLevel) {
        head.state.updateLevel(headMinLevel)
        enqueue(headMinLevel, reevaluate)(head)
        head.state.outgoing.foreach { r =>
          if (r.state.level() <= headMinLevel)
            enqueue(headMinLevel + 1, needsEvaluate = false)(r)
        }
      } else if (reevaluate) {
        evaluator.evaluate(head)
      }
    }

    /** Evaluates all currently queued elements by applying the given evaluator to them. */
    def evaluateQueue(): Unit = {
      var current = elements.poll()
      var next    = elements.peek()
      while (current != null) {
        // if the current and next reactive are equal, merge the queue entries
        if (next != null && current.reactive == next.reactive) {
          next.minLevel = next.minLevel max current.minLevel
          next.needsEvaluate ||= current.needsEvaluate
        } else {
          handleElement(current)
        }
        current = elements.poll()
        next = elements.peek()
      }

    }

    /** Removes a reactive element from the queue
      *
      * @param reactive Element to remove from the queue
      */
    def remove(reactive: Derived): Unit = {
      val it = elements.iterator()
      while (it.hasNext) if (it.next().reactive eq reactive) it.remove()
    }
  }

  private[Levelbased] object LevelQueue {

    trait Evaluator {
      def evaluate(r: Derived): Unit
    }

    /** The value to not increase the level of an enqueued [[QueueElement]]. */
    def noLevelIncrease: Int = Int.MinValue

    private case class QueueElement(
        level: Int,
        reactive: Derived,
        var minLevel: Int,
        var needsEvaluate: Boolean
    ) extends Comparable[QueueElement] {
      // order by level, then by reactive
      val order: Long = (level.toLong << 32) | (reactive.hashCode.toLong & 0x00000000ffffffffL)
      override def compareTo(o: QueueElement): Int = java.lang.Long.compare(order, o.order)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy