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

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

There is a newer version: 0.35.1
Show newest version
/** This implementation tries to mirror the formalization
  * of the corresponding paper as closely as possible
  */
package rescala.scheduler

import rescala.core.{
  AccessHandler, AdmissionTicket, Derived, Initializer, Observation, ReSource, ReadAs, ReevTicket, SchedulerImpl,
  Transaction
}

trait CalculusLike {

  type State[V] = StoreValue[V]

  /** The formalization uses a per device store mapping reactives to their
    * inputs, values, and operator.
    * The operator is already handled by the common implementation, so we keep the value and inputs.
    * The store mapping does not exist as a single object, but instead each reactive has this state.
    */
  class StoreValue[V](var value: V) {
    var inputs: Set[ReSource.of[State]] = Set.empty
    override def toString: String       = s""
  }

  /** The main task of the initializer is to handle creation of reactives,
    * especially during an ongoing propagation.
    * The formalization does not support this, to keep the complexity of the proofs in check.
    */
  final class SimpleCreation() extends Initializer {

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

    override protected[this] def makeDerivedStructState[V](initialValue: V): StoreValue[V] =
      new StoreValue[V](initialValue)

    override protected[this] def register(reactive: ReSource.of[State], inputs: Set[ReSource.of[State]]): Unit = {
      super.register(reactive, inputs)
      FScheduler.allReactives += reactive
    }

    override protected[this] def initialize(
        reactive: Derived.of[State],
        incoming: Set[ReSource.of[State]],
        needsReevaluation: Boolean
    ): Unit = {
      println(s"creating $reactive $needsReevaluation")
      println(incoming)

      reactive.state.inputs = incoming

      if (needsReevaluation || requiresReev(reactive)) {
        println(s"creation evaluation $reactive")
        // evaluate immediately to support some higher order + creation nonsense
        Reevaluate.evaluate(reactive, _ => true, FTransaction(this))
        ()
      }
    }

    def requiresReev(reSource: ReSource.of[State]): Boolean = {
      if (FScheduler.currentPropagation == null) false
      else
        FScheduler.currentPropagation.isReady(reSource) &&
        FScheduler.currentPropagation.isOutdated(reSource)
    }

  }

  case class FTransaction(override val initializer: Initializer.of[CalculusLike.this.State]) extends Transaction {
    override type State[V] = CalculusLike.this.State[V]
    override private[rescala] def access(reactive: ReSource.of[State]): reactive.Value = reactive.state.value
    override def observe(obs: Observation): Unit                                       = obs.execute()
  }

  object FScheduler
      extends SchedulerImpl[State, FTransaction] {

    override def schedulerName: String = "FormalizationLike"

    var allReactives                    = Set.empty[ReSource.of[State]]
    var currentPropagation: Propagation = null

    var idle = true

    /** this corresponds very roughly to the fire rule.
      * The initial writes contains the reactives which change (only one for fire),
      * and teh admission phase updates their values to the fired values (μ(r).val ← v)
      */
    override def forceNewTransaction[R](
        initialWrites: Set[ReSource.of[State]],
        admissionPhase: AdmissionTicket[State] => R
    ): R =
      synchronized {
        // some broken user code may start a new transaction during an ongoing one
        // this is not supported by this propagation algorithm,
        // and is detected here because it leads to weird behaviour otherwise
        if (!idle) throw new IllegalStateException("Scheduler is not reentrant")
        idle = false
        try {
          println(s"\nexecuting turn from $initialWrites")
          val transaction = FTransaction(new SimpleCreation())
          withDynamicInitializer(transaction) {
            // admission
            val admissionTicket = new AdmissionTicket[State](transaction, initialWrites)

            // collect the fired values
            val admissionResult = admissionPhase(admissionTicket)
            // write all fired values to the store (r.writeValue)
            // we collect those where the write is propagated
            // (non propagation is not supported by the formalization, but is used for filter events,
            // and signals that do not change their values
            val sources = admissionTicket.initialChanges.values.collect {
              case r if r.writeValue(r.source.state.value, r.source.state.value = _) => r.source
            }.toSet

            println(s"sources: $sources")

            // start a new propagation changes in REScala always have to happen as a side effect
            val propagation = Propagation(active = sources, processed = sources, allReactives, transaction)
            println(s"starting propagation $propagation")
            val result = propagation.run()
            println(s"done activate")

            // wrapup, this is for a rarely used rescala features, where transactions can
            // do some cleanup when they complete. Not supported in the formalization
            if (admissionTicket.wrapUp != null) admissionTicket.wrapUp(transaction)

            // commit cleans up some internal state
            result.commit()

            // transactions return whatever the user code did
            admissionResult
          }
        } finally {
          currentPropagation = null
          idle = true
        }
      }
    override private[rescala] def singleReadValueOnce[A](reactive: ReadAs.of[State, A]): A =
      reactive.read(reactive.state.value)
  }

  case class Propagation(
      active: Set[ReSource.of[State]],
      processed: Set[ReSource.of[State]],
      knownReactives: Set[ReSource.of[State]],
      transaction: FTransaction
  ) {

    // resets the state of all reactives back to whatever it should be after propagation
    // this is used primarily to reset all events back to no value
    def commit(): Unit =
      active.foreach { a =>
        a.state.value = a.commit(a.state.value)
      }

    def run(): Propagation = {
      // make this available, as we may need it to create new reactives
      FScheduler.currentPropagation = this
      // println("propagating:")
      // println(s"active: $active\nprocessed: $processed\n" +
      //        s"ready: $ready\noutdated: $outdated\nall: $allReactives\n" +
      //        s"unprocessed: $unprocessed")

      // we may process more than just the known initial reactives,
      // because REScala allows the creation of new reactives during propagation.
      // their correctness is ensured by the creation method
      if (knownReactives == processed) this // snapshots are handled elsewhere
      else {                                // reevaluate or skip

        // if there is anything to skip, we just skip all of them at once
        val toSkip = ready -- outdated
        if (toSkip.nonEmpty) {
          // println(s"skipping: $toSkip")
          Propagation(active, processed ++ toSkip, knownReactives, transaction).run()
        }
        // if there is nothing to skip, we continue with reevaluation
        else {

          // rule does not specify which reactive to select
          val candidates = ready.intersect(outdated)
          val r          = candidates.head
          // Reevaluate r. Sources are handled differently from derived reactives,
          // as they have no attached operation in REScala
          // in the calculus they just execute the empty operation
          println(s"reevaluating $r")
          val oldRstring = r.state.inputs
          val (evaluated, propagate) = r match {
            case r: Derived =>
              Reevaluate.evaluate(r, isReady, transaction)
            case _ => (true, false)
          }

          val newReactives = FScheduler.allReactives -- knownReactives
          if (newReactives.nonEmpty) {
            println(s"created reactives $newReactives")
          }

          if (evaluated) {
            val nextActive = if (propagate) active + r else active
            Propagation(nextActive, processed + r, knownReactives, transaction).run()
          } else {
            println(s"redoing \n$oldRstring\nto\n${r.state.inputs}")
            Propagation(active, processed, knownReactives, transaction).run()
          }
        }
      }
    }

    /** helper for better inspection */
    lazy val unprocessed: Set[ReSource.of[State]] = knownReactives -- processed

    /** Compute the set of all ready reactives. Logic is identical to the paper. */
    lazy val ready: Set[ReSource.of[State]] = {
      unprocessed.filter { r =>
        // intersect with all known reactives
        // as there may be new inputs that were created during this propagation
        // which we always consider as processed
        isReady(r)
      }
    }

    def isReady(r: ReSource.of[State]): Boolean = {
      r.state.inputs.intersect(knownReactives).subsetOf(processed + r)
    }

    /** Compute outdated reactives. Logic is identical to the paper. */
    lazy val outdated: Set[ReSource.of[State]] = {
      knownReactives.filter(isOutdated)
    }
    def isOutdated(r: ReSource.of[State]): Boolean = {
      r.state.inputs.exists(active.contains)
    }
  }

  object Reevaluate {
    def evaluate(
        reactive: Derived.of[State],
        dynamicOk: ReSource.of[State] => Boolean,
        transaction: FTransaction
    ): (Boolean, Boolean) = {
      val dt = new ReevTicket[State, reactive.Value](
        transaction,
        reactive.state.value,
        new AccessHandler[State] {
          override def dynamicAccess(input: ReSource.of[State]): input.Value = input.state.value
          override def staticAccess(input: ReSource.of[State]): input.Value  = input.state.value
        }
      )

      val reev = reactive.reevaluate(dt)

      def finishReevaluation() = {
        // good, store value (write rule)
        reev.forValue(reactive.state.value = _)
        // and execute observers (not covered in the formalization)
        // real schedulers may want to postpone these to the the commit
        reev.forEffect(_.execute())
        (true, reev.activate)
      }

      reev.inputs() match {
        case None => // static reactive
          finishReevaluation()
        case Some(inputs) => // dynamic reactive
          reactive.state.inputs = inputs
          if (dynamicOk(reactive)) finishReevaluation()
          else (false, reev.activate)

      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy