rescala.extra.incremental.IncrementalBundle.scala Maven / Gradle / Ivy
package rescala.extra.incremental
import rescala.compat.SignalCompatBundle
import rescala.core._
import rescala.interface.RescalaInterface
import rescala.operator.{EventBundle, SignalBundle, cutOutOfUserComputation}
import scala.collection.mutable
import scala.util.control.Breaks.{break, breakable}
trait IncrementalBundle extends Core {
self: EventBundle with SignalBundle with SignalCompatBundle with RescalaInterface =>
/** @tparam T Type of values inside Deltas
* @tparam S Structure of Reactive Sequence source
*/
trait ReactiveDeltaSeq[T] extends Derived with DisconnectableImpl {
override protected[rescala] def commit(base: Delta[T]): Delta[T] = Delta.noChange
/** the value of deltas send through the set */
override type Value = Delta[T]
/** Returns current ReactiveDeltaSeq as an Event
*
* @param ticket a creation ticket as a new event will be created which has the ReactiveDeltaSeq as dependency
* @return
*/
@cutOutOfUserComputation
def asEvent(implicit ticket: CreationTicket): Event[Delta[T]] = {
Events.static(this) { staticTicket =>
// each time a change occurs it is represented by a Delta
// the staticTicket gets this Delta and the Event representing the ReactiveDeltaSeq will fire the Delta
val delta = staticTicket.collectStatic(this)
// It can be that when the event is fired no changes have occurred
// That is why Some(delta) is used
Some(delta)
}
}
/** Based on the concept of reversible Folds
* Used to fold the deltas basing on fold for Addition-Delta and unfold for Removal-Delta
*
* @param initial is the initial value the foldUndo folds to
* @param fold the function used when an Addition occurs
* @param unfold the function used when a Removal occurs
* @param ticket as we will create
* @tparam A the value returned by applying fold or unfold on the value T of Deltas
* @return
*/
@cutOutOfUserComputation
def foldUndo[A](initial: A)(fold: (A, Delta[T]) => A)(unfold: (A, Delta[T]) => A)(implicit
ticket: CreationTicket
): Signal[A] = {
// first we create an event that fires each time a Delta occurs
val event = asEvent
// Than we fold the event by applying the fold- or unfold-function respectively
// In case Nothing Changed we return the old value of fold
// This will automatically create a Signal whose value is updated each time the event fires
Events.foldOne(event, initial)((x: A, y: Delta[T]) => {
y match {
case Addition(_) => fold(x, y)
case Removal(_) => unfold(x, y)
case NoChange() => x
}
})
}
// /**
// * Based on the concept of reversible Folds
// * Used to fold the deltas basing on fold for Addition-Delta and unfold for Removal-Delta
// *
// * @param initial is the initial value the foldUndo folds to
// * @param fold the function used when an Addition occurs
// * @param unfold the function used when a Removal occurs
// * @param ticket as we will create
// * @tparam A the value returned by applying fold or unfold on the value T of Deltas
// * @return
// */
// @cutOutOfUserComputation
// def flatMap[A: ReSerializable] (f: T => ReactiveDeltaSeq[A]) (implicit ticket: CreationTicket): ReactiveDeltaSeq[A] = {
// // first we create an event that fires each time a Delta occurs
// val event = asEvent
//
// // Than we fold the event by applying the fold- or unfold-function respectively
// // In case Nothing Changed we return the old value of fold
// // This will automatically create a Signal whose value is updated each time the event fires
// Events.foldOne(event, initial)(
// (x: A, y: Delta[T]) => {
// y match {
// case Addition(_) => fold(x, y)
// case Removal(_) => unfold(x, y)
// case NoChange() => x
// }
// }
// )
// }
/** Filters the sequence , basing on filterExpression and returns the new filtered sequence
*
* @param filterOperation the operation used for filtering
* @param ticket for creating the new source
* @return the filtered ReactiveDeltaSeq
*/
@cutOutOfUserComputation
def filter(filterOperation: T => Boolean)(implicit ticket: CreationTicket): ReactiveDeltaSeq[T] = {
// as a new reactive sequence will be returned after filtering we use the creation ticket to create the new source
// The new created Source will be a FilterDeltaSeq which is basically a ReactiveDeltaSeq, which reevaluates differently when changes are propagated
// FilterDeltaSeq depends on this (ReactiveDeltaSeq). It is initialized as an empty sequence.
// Each time a change on ReactiveDeltaSeq occurs, if it passes the filterOperation, it is automatically added to FilterDeltaSeq
ticket.create[Delta[T], FilterDeltaSeq[T]](Set(this), Delta.noChange, needsReevaluation = false) {
state => new FilterDeltaSeq[T](this, filterOperation)(state, ticket.rename) with DisconnectableImpl
}
}
/** Maps the elements of ReactiveDeltaSeq and returns a new ReactiveDeltaSeq with the mapped deltas with the old ReactiveDeltaSeq as dependency
*
* @param mapOperation the operation used for mapping the values of ReactiveDeltaSeq to MapDeltaSeq
* @param ticket Ticket for creating the new ReactiveDeltaSeq
* @tparam A new Value type for deltas in the mapped ReactiveDeltaSeq
* @return the mapped ReactiveDeltaSeq
*/
@cutOutOfUserComputation
def map[A](mapOperation: T => A)(implicit ticket: CreationTicket): ReactiveDeltaSeq[A] = {
// as a new reactive sequence will be returned after mapping we use the creation ticket to create the new source
// The new created Source will be a MapDeltaSeq which is basically a ReactiveDeltaSeq, which reevaluates differently when changes are propagated
// MapDeltaSeq depends on this (ReactiveDeltaSeq). It is initialized as an empty sequence.
// Each time a change on ReactiveDeltaSeq occurs, it is mapped and automatically added to MapDeltaSeq
ticket.create[Delta[A], MapDeltaSeq[T, A]](Set(this), Delta.noChange, needsReevaluation = false) {
state => new MapDeltaSeq[T, A](this, mapOperation)(state, ticket.rename) with DisconnectableImpl
}
}
/** Concatenates the ReactiveDeltaSeq with another (that) ReactiveDeltaSeq by returning a new ReactiveDeltaSeq (ConcatenateDeltaSeq)
*
* @param that the ReactiveDeltaSeq which will be concatenated with this
* @param ticket used for the creation of the concatenated ReactiveDeltaSeq
* @return ConcatenateDeltaSeq
*/
@cutOutOfUserComputation
def ++(that: ReactiveDeltaSeq[T])(implicit ticket: CreationTicket): ReactiveDeltaSeq[T] = {
// as a new reactive sequence will be returned after concatenating we use the creation ticket to create the new source
// The new created Source will be a ConcatenateDeltaSeq which is basically a ReactiveDeltaSeq, which reevaluates differently when changes are propagated
// ConcatenateDeltaSeq depends on this (ReactiveDeltaSeq) and the one being concatenated (that). It is initialized as an empty sequence.
// Each time a change on this or that occurs, it is automatically added to ConcatenateDeltaSeq
ticket.create[Delta[T], ConcatenateDeltaSeq[T]](
Set(this, that),
Delta.noChange,
needsReevaluation = false
) {
state => new ConcatenateDeltaSeq[T](this, that)(state, ticket.rename)
}
}
/** Returns the sizeOfSeq of the ReactiveDeltaSeq
*
* @param ticket for creating the Signal holding the value of sizeOfSeq
* @param resInt needed by REScala API for Signal/Event holding Ints //TODO check
* @return
*/
@cutOutOfUserComputation
def size(implicit ticket: CreationTicket): Signal[Int] =
foldUndo(0)((counted: Int, _) => counted + 1)((counted: Int, _) => counted - 1)
/** Counts number of elements fulfilling the condition provided
*
* @param fulfillsCondition the condition values of deltas have to fulfill to be taken in consideration
* @param ticket for creating the Signal holding the value of counted elements
* @param resInt needed by REScala API for Signal/Event holding Ints
* @return
*/
@cutOutOfUserComputation
def count(fulfillsCondition: T => Boolean)(implicit
ticket: CreationTicket
): Signal[Int] =
foldUndo(0) { (counted, x) => if (fulfillsCondition(x.value)) counted + 1 else counted } { (counted, x) =>
if (fulfillsCondition(x.value)) counted - 1 else counted
}
/** To check if an element is in the sequence
*
* @param element element to search for
* @param ticket for creating the Signal holding the boolean value
* @param resInt needed by REScala API for Signal/Event holding Ints
* @return
*/
@cutOutOfUserComputation
def contains(element: T)(implicit
ticket: CreationTicket,
ord: Ordering[T]
): Signal[Boolean] = { exists { (seqElement: T) => ord.equiv(element, seqElement) } }
/** To check if elements fulfilling the condition exists
*
* @param fulfillsCondition the condition values of deltas have to fulfill to be taken in consideration
* @param ticket for creating the Signal holding the boolean value
* @param resInt needed by REScala API for Signal/Event holding Ints
* @return
*/
@cutOutOfUserComputation
def exists(fulfillsCondition: T => Boolean)(implicit ticket: CreationTicket): Signal[Boolean] = {
// count all elements fulfilling the condition of existence
val instancesNumber = count(fulfillsCondition)
// if more than one found
Signals.static(instancesNumber)(st => st.dependStatic(instancesNumber) > 0)(ticket)
}
/** @param ticket used for creation of new sources
* @param ord the ordering needed to compare values of deltas for finding the minimum
* @param res ...
* @return Signal holding the optional minimum (as it could be None if the seqeunce is empty)
*/
@cutOutOfUserComputation
def min(implicit ticket: CreationTicket, ord: Ordering[T]): Signal[Option[T]] = {
val minimum = foldUndo(mutable.IndexedSeq.empty[(T, T)])(
// fold operation
(trackingSequence: mutable.IndexedSeq[(T, T)], delta: Delta[T]) => {
if (trackingSequence.isEmpty) {
(delta.value, delta.value) +: trackingSequence
} else {
var min = trackingSequence.head._2 // current minimum
if (ord.compare(delta.value, min) < 0) // update if added element is smaller
min = delta.value
(delta.value, min) +: trackingSequence // prepend to the tracking-sequence
}
}
)(
// unfold operation
(trackingSequence: mutable.IndexedSeq[(T, T)], delta: Delta[T]) => {
// index of element, being removed
val deletionIndex = trackingSequence.indexWhere(element => ord.compare(element._1, delta.value) == 0)
if (deletionIndex < 0)
throw new Exception("min: Element not found in the sequence")
if (deletionIndex > 0) { // must be more than two elements to make sense to change minimum
var min = trackingSequence(deletionIndex)._2
if (deletionIndex == trackingSequence.size - 1) // last element
min = trackingSequence(deletionIndex - 1)._1 // new min will be same as the element on the left
else
min =
trackingSequence(
deletionIndex + 1
)._2 // new min will be same as the min stored in the tuple on the right
breakable {
for (i <- (deletionIndex - 1) to 0 by -1) {
val element = trackingSequence(i)
if (ord.compare(element._1, min) < 0) // if no more update needed, stop
break()
trackingSequence.update(i, (element._1, min)) // otherwise update the minimum
}
}
}
trackingSequence.take(deletionIndex) ++ trackingSequence.drop(deletionIndex + 1)
}
)
Signals.static(minimum)(_.dependStatic(minimum).headOption.map(_._2))
}
/** @param ticket used for creation of new sources
* @param ord the ordering needed to compare values of deltas for finding the minimum
* @param res ...
* @return Signal holding the optional minimum (as it could be None if the seqeunce is empty)
*/
@cutOutOfUserComputation
def max(implicit ticket: CreationTicket, ord: Ordering[T]): Signal[Option[T]] = {
val seqMaximum = foldUndo(mutable.IndexedSeq.empty[(T, T)])(
(seq: mutable.IndexedSeq[(T, T)], delta: Delta[T]) => {
if (seq.isEmpty) {
(delta.value, delta.value) +: seq
} else {
var max = seq.head._2
if (ord.gt(delta.value, max))
max = delta.value
(delta.value, max) +: seq
}
}
)((trackingSequence: mutable.IndexedSeq[(T, T)], delta: Delta[T]) => {
val deletionIndex = trackingSequence.indexWhere(element => ord.equiv(element._1, delta.value))
if (deletionIndex < 0)
throw new Exception("max: Element not found in the sequence")
if (deletionIndex > 0) { // must be more than two elements to make sense to change maxValue
var max = trackingSequence(deletionIndex)._2
if (deletionIndex == trackingSequence.size - 1) // last element
max = trackingSequence(deletionIndex - 1)._1
else
max = trackingSequence(deletionIndex + 1)._2
// after setting the new min, update the minimum of the elements on the left till minimum has different value
breakable {
for (i <- (0 until deletionIndex).reverse) {
val element = trackingSequence(i)
if (ord.gteq(element._1, max))
break()
trackingSequence.update(i, (element._1, max))
}
}
}
trackingSequence.take(deletionIndex) ++ trackingSequence.drop(deletionIndex + 1)
})
Signals.static(seqMaximum)(_.dependStatic(seqMaximum).headOption.map(tuple => tuple._2))
}
}
/** @param left
* @param right
* @param initialState
* @param name
* @tparam T Type of values in Deltas
* @tparam S Structure of Source
*/
class ConcatenateDeltaSeq[T](left: ReactiveDeltaSeq[T], right: ReactiveDeltaSeq[T])(
initialState: IncSeq.SeqState[T],
name: ReName
) extends Base[Delta[T]](initialState, name)
with ReactiveDeltaSeq[T] with DisconnectableImpl {
/** @param input
* @return
*/
override protected[rescala] def guardedReevaluate(input: ReIn): Rout = {
val leftDelta = input.collectStatic(left)
leftDelta match {
case NoChange() =>
val rightDelta = input.collectStatic(right)
input.withValue(rightDelta)
case _ =>
input.withValue(leftDelta)
}
input
}
}
/** Class used for filtering ReactiveDeltaSeq
*
* @param in the ReactiveDeltaSeq to filter
* @param expression filterExpression with return type boolean used for filtering elements inside the sequence
* @param initialState ...
* @param name Name of the new filtered sequence
* @tparam T Value inside Delta
* @tparam S Structure of Delta
*/
class FilterDeltaSeq[T](in: ReactiveDeltaSeq[T], expression: T => Boolean)(
initialState: IncSeq.SeqState[T],
name: ReName
) extends Base[Delta[T]](initialState, name) with Derived
with ReactiveDeltaSeq[T] {
/** @param input Basing ReIn Ticket filters the ReactiveDeltaSeq using the filterExpression define above. That it uses withValue to write the new Sequence
* @return Returns the new Sequence
*/
override protected[rescala] def guardedReevaluate(input: ReIn): Rout = {
val filteredDeltas = input.collectStatic(in).filter(expression)
input.withValue(filteredDeltas)
input
}
}
/** Class used for filtering ReactiveDeltaSeq
*
* @param in the ReactiveDeltaSeq to filter
* @param op mapOperation to map sequence
* @param initialState ...
* @param name Name of the new filtered sequence
* @tparam T Value inside Delta
* @tparam S Structure of Delta
*/
class MapDeltaSeq[T, A](in: ReactiveDeltaSeq[T], op: T => A)(
initialState: IncSeq.SeqState[A],
name: ReName
) extends Base[Delta[A]](initialState, name)
with ReactiveDeltaSeq[A] {
/** @param input Basing ReIn Ticket maps the ReactiveDeltaSeq using the fold defined above. That it uses withValue to write the new Sequence
* @return Returns the new Sequence
*/
override protected[rescala] def guardedReevaluate(input: ReIn): Rout = {
val mappedDeltas = input.collectStatic(in).map(op)
input.withValue(mappedDeltas)
input
}
}
///**
// * Class used for filtering ReactiveDeltaSeq
// *
// * @param in the ReactiveDeltaSeq to filter
// * @param op mapOperation to map sequence
// * @param initialState ...
// * @param name Name of the new filtered sequence
// * @tparam T Value inside Delta
// * @tparam S Structure of Delta
// */
//class FlatMapDeltaSeq[T, A]
//(in: ReactiveDeltaSeq[T], f: T => ReactiveDeltaSeq[A,S])
//(initialState: IncSeq.SeqState[A], name: ReName)
// extends Base[Delta[A]](initialState, name)
// with ReactiveDeltaSeq[A] {
//
// /**
// *
// * @param input Basing ReIn Ticket maps the ReactiveDeltaSeq using the fold defined above. That it uses withValue to write the new Sequence
// * @return Returns the new Sequence
// */
// override protected[rescala] def reevaluate(input: ReIn): Rout = {
// val delta = input.collectStatic(in).map(op)
// input.withValue(mappedDeltas)
// input
// }
//}
/** Source events with imperative occurrences
*
* @param initialState of by the event
* @tparam T Type returned when the event fires
* @tparam S Struct type used for the propagation of the event
*/
class IncSeq[T] private[rescala] (initialState: IncSeq.SeqState[T], name: ReName)
extends Base[Delta[T]](initialState, name)
with ReactiveDeltaSeq[T] {
private val elements: mutable.Map[T, Int] = mutable.HashMap()
override protected[rescala] def guardedReevaluate(input: ReIn): Rout = ??? // TODO what comes here...
def add(value: T)(implicit fac: Scheduler): Unit =
fac.forceNewTransaction(this) {
addInTx(Addition(value))(_)
}
def remove(value: T)(implicit fac: Scheduler): Unit =
fac.forceNewTransaction(this) {
addInTx(Removal(value))(_)
}
def addInTx(delta: Delta[T])(implicit ticket: AdmissionTicket): Unit = {
(delta: @unchecked) match {
case Addition(value) => {
val counter = elements.getOrElse(value, 0)
if (counter == 0)
elements.put(value, 1)
else
elements.put(value, counter + 1)
}
case Removal(value) => {
val counter = elements.getOrElse(value, 0)
if (counter > 1)
elements.put(value, counter - 1)
else if (counter == 1)
elements.remove(value)
else
throw new Exception(s"Cannot remove element as it cannot be found")
}
}
ticket.recordChange(new InitialChange {
override val source: IncSeq.this.type = IncSeq.this
override def writeValue(b: Delta[T], v: Delta[T] => Unit): Boolean = {
v(delta)
true
}
})
}
def printMap(): Unit = {
elements.foreach(t => print(s"${t._1}, "))
}
}
object IncSeq {
type SeqState[T] = State[Delta[T]]
def apply[T](implicit ticket: CreationTicket): IncSeq[T] = empty[T]
def empty[T](implicit ticket: CreationTicket): IncSeq[T] = fromDelta(Delta.noChange[T])
private[this] def fromDelta[T](init: Delta[T])(implicit ticket: CreationTicket): IncSeq[T] =
ticket.createSource[Delta[T], IncSeq[T]](init)(new IncSeq[T](
_,
ticket.rename
))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy