Refactor controller: extract component for managing dispatching front,
drop Store, introduce ProcessingState and StateFrame. ProcessingState class to represent current state, including the dispatching front and all the logical observers. StateFrame is a persistent class that reacts to activated constraint and launches processing via Controller. Breaking change: the frames correspond to an activated constraint occurrence, and not to a triggered rule match as it has been the case before.
This commit is contained in:
parent
11a9505ee9
commit
658963903d
|
|
@ -17,7 +17,6 @@
|
|||
package jetbrains.mps.logic.reactor.core
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus
|
||||
import jetbrains.mps.logic.reactor.core.internal.RuleMatchImpl
|
||||
import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation
|
||||
import jetbrains.mps.logic.reactor.evaluation.StoreView
|
||||
|
||||
|
|
@ -34,7 +33,7 @@ interface Controller {
|
|||
|
||||
fun reactivate(occ: Occurrence)
|
||||
|
||||
fun currentFrame(): FrameObservable
|
||||
fun state(): ProcessingState
|
||||
|
||||
/** For tests only */
|
||||
fun evaluate(occ: Occurrence): StoreView
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ data class Occurrence (val controller: Controller,
|
|||
init {
|
||||
for (a in arguments) {
|
||||
if (a is Logical<*>) {
|
||||
controller.currentFrame().addObserver(a) { this }
|
||||
controller.state().addForwardingObserver(a, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ data class Occurrence (val controller: Controller,
|
|||
fun terminate() {
|
||||
for (a in arguments) {
|
||||
if (a is Logical<*>) {
|
||||
controller.currentFrame().removeObserver(a) { this }
|
||||
controller.state().removeForwardingObserver(a, this)
|
||||
}
|
||||
}
|
||||
alive = false
|
||||
|
|
|
|||
|
|
@ -19,15 +19,13 @@ package jetbrains.mps.logic.reactor.core
|
|||
import jetbrains.mps.logic.reactor.logical.Logical
|
||||
|
||||
/**
|
||||
* Serves to add/remove observers of [Logical] that track the frame stack.
|
||||
* @author Fedor Isakov
|
||||
*/
|
||||
interface FrameObservable {
|
||||
|
||||
/** Returns the store associated with this frame */
|
||||
fun storeObserver(): LogicalObserver
|
||||
interface ProcessingState {
|
||||
|
||||
fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver)
|
||||
|
||||
fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver)
|
||||
fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver)
|
||||
|
||||
fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver)
|
||||
|
||||
}
|
||||
|
|
@ -30,24 +30,20 @@ import com.github.andrewoma.dexx.collection.Map as PersMap
|
|||
|
||||
internal class ControllerImpl (
|
||||
val supervisor: Supervisor,
|
||||
val ruleIndex: RuleIndex,
|
||||
val state: ProcessingStateImpl,
|
||||
val trace: EvaluationTrace = EvaluationTrace.NULL,
|
||||
val profiler: Profiler? = null) : Controller
|
||||
{
|
||||
|
||||
private var dispatchFront = Dispatcher(ruleIndex).front()
|
||||
|
||||
// FIXME move to context
|
||||
private val frameStack = FrameStack()
|
||||
|
||||
/** For tests only */
|
||||
override fun storeView(): StoreView = frameStack.current.store.view()
|
||||
override fun storeView(): StoreView = state.storeView()
|
||||
|
||||
/** For tests only */
|
||||
override fun evaluate(occ: Occurrence): StoreView {
|
||||
// create the internal occurrence
|
||||
val active = occ.constraint().occurrence(this, occ.arguments())
|
||||
val status = process(active, NORMAL())
|
||||
|
||||
val status = state.processActivated(active, NORMAL())
|
||||
if (status is FAILED) {
|
||||
throw status.failure.failureCause()
|
||||
}
|
||||
|
|
@ -63,7 +59,8 @@ internal class ControllerImpl (
|
|||
return context.currentStatus()
|
||||
}
|
||||
|
||||
override fun currentFrame(): FrameObservable = frameStack.current
|
||||
override fun state(): ProcessingStateImpl =
|
||||
state
|
||||
|
||||
override fun ask(invocation: PredicateInvocation): Boolean {
|
||||
val solver = invocation.predicate().symbol().solver()
|
||||
|
|
@ -80,35 +77,13 @@ internal class ControllerImpl (
|
|||
|
||||
override fun reactivate(occ: Occurrence) {
|
||||
// FIXME propagate the status further up the call stack
|
||||
// TODO: introduce status to solver API?
|
||||
// TODO update the stack
|
||||
|
||||
val status = process(occ, NORMAL())
|
||||
|
||||
val status = state.processActivated(occ, NORMAL())
|
||||
if (status is FAILED) {
|
||||
throw status.failure.failureCause()
|
||||
}
|
||||
}
|
||||
|
||||
private fun processMatch(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus =
|
||||
offerMatch(match, inStatus)
|
||||
.let { when (it) {
|
||||
is ABORTED -> { // guard is not satisfied
|
||||
trace.reject(match)
|
||||
return it.recover() // return from the enclosing method
|
||||
|
||||
} is FAILED -> { // guard failed
|
||||
trace.feedback(it.failure)
|
||||
return it.recover() // return from the enclosing method
|
||||
|
||||
} else -> it
|
||||
} }
|
||||
.also { consumeMatch(match) }
|
||||
.also { trace.trigger(match) }
|
||||
.also { processDiscarded(match) }
|
||||
.then { processBody(match, it) }
|
||||
.also { trace.finish(match) }
|
||||
|
||||
override fun offerMatch(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus =
|
||||
inStatus.then { checkMatchPreconditions(match, it) }
|
||||
.also { trace.trying(match) }
|
||||
|
|
@ -154,8 +129,7 @@ internal class ControllerImpl (
|
|||
}
|
||||
}
|
||||
|
||||
val savedFrame = frameStack.current
|
||||
frameStack.push()
|
||||
val savedFrame = state.currentFrame()
|
||||
|
||||
for (item in body) {
|
||||
val itemOk = when (item) {
|
||||
|
|
@ -201,7 +175,7 @@ internal class ControllerImpl (
|
|||
|
||||
if (!altOk) {
|
||||
// all constraints activated up to a failure are lost
|
||||
frameStack.reset(savedFrame)
|
||||
state.reset(savedFrame)
|
||||
|
||||
} else {
|
||||
// body finished normally
|
||||
|
|
@ -211,61 +185,12 @@ internal class ControllerImpl (
|
|||
|
||||
return context.currentStatus()
|
||||
}
|
||||
|
||||
private fun process(active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus {
|
||||
assert(active.alive)
|
||||
|
||||
return profiler.profile<FeedbackStatus>("process_${active.constraint().symbol()}") {
|
||||
|
||||
if (!active.stored) {
|
||||
frameStack.current.store.store(active)
|
||||
trace.activate(active)
|
||||
} else {
|
||||
trace.reactivate(active)
|
||||
}
|
||||
|
||||
val activatedFront = dispatchFront.expand(active)
|
||||
this.dispatchFront = activatedFront
|
||||
|
||||
val outStatus = activatedFront.matches().toList().fold(inStatus) { status, match ->
|
||||
// TODO: paranoid check. should be isAlive() instead
|
||||
// FIXME: move this check elsewhere
|
||||
if (status.operational && active.stored && match.allStored())
|
||||
processMatch(match as RuleMatchImpl, status)
|
||||
else
|
||||
status
|
||||
}
|
||||
|
||||
// TODO: should be isAlive()
|
||||
if (active.stored) {
|
||||
trace.suspend(active)
|
||||
}
|
||||
|
||||
outStatus
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeMatch(match: RuleMatchEx) {
|
||||
this.dispatchFront = dispatchFront.consume(match)
|
||||
}
|
||||
|
||||
private fun processDiscarded (match: RuleMatchEx) {
|
||||
match.forEachReplaced { occ ->
|
||||
this.dispatchFront = dispatchFront.contract(occ)
|
||||
|
||||
frameStack.current.store.discard(occ)
|
||||
|
||||
trace.discard(occ)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun activateConstraint(constraint: Constraint, context: Context) : Boolean {
|
||||
val args = supervisor.instantiateArguments(constraint.arguments(), context.logicalContext, context)
|
||||
return context.eval { status ->
|
||||
|
||||
// TODO update the state stack
|
||||
val active = constraint.occurrence(this, args, context.logicalContext)
|
||||
process(active, status)
|
||||
state.processActivated(constraint.occurrence(this, args, context.logicalContext), status)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -364,6 +289,6 @@ fun createController(
|
|||
supervisor: Supervisor,
|
||||
ruleIndex: RuleIndex,
|
||||
trace: EvaluationTrace = EvaluationTrace.NULL,
|
||||
profiler: Profiler? = null,
|
||||
storeView: StoreView? = null) : Controller =
|
||||
ControllerImpl(supervisor, ruleIndex, trace, profiler)
|
||||
profiler: Profiler? = null) : Controller =
|
||||
|
||||
ControllerImpl(supervisor, ProcessingStateImpl(Dispatcher(ruleIndex)), trace, profiler)
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@
|
|||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.*
|
||||
import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus.FAILED
|
||||
import jetbrains.mps.logic.reactor.core.EvaluationSessionEx
|
||||
import jetbrains.mps.logic.reactor.core.Feedback
|
||||
import jetbrains.mps.logic.reactor.core.RuleIndex
|
||||
import jetbrains.mps.logic.reactor.evaluation.*
|
||||
import jetbrains.mps.logic.reactor.program.Constraint
|
||||
import jetbrains.mps.logic.reactor.program.Program
|
||||
|
|
@ -44,7 +42,9 @@ internal class EvaluationSessionImpl private constructor (
|
|||
override fun controller() = controller
|
||||
|
||||
private fun launch(main: Constraint, profiler: Profiler?) : FeedbackStatus {
|
||||
this.controller = ControllerImpl(supervisor, RuleIndex(program().handlers()), trace, profiler)
|
||||
val dispatcher = Dispatcher(RuleIndex(program().handlers()))
|
||||
val state = ProcessingStateImpl(dispatcher, trace, profiler)
|
||||
this.controller = ControllerImpl(supervisor, state, trace, profiler)
|
||||
return controller.activate(main)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import com.github.andrewoma.dexx.collection.ConsList
|
||||
import com.github.andrewoma.dexx.collection.Map
|
||||
import com.github.andrewoma.dexx.collection.Maps
|
||||
import jetbrains.mps.logic.reactor.core.FrameObservable
|
||||
import jetbrains.mps.logic.reactor.core.LogicalObserver
|
||||
import jetbrains.mps.logic.reactor.core.addObserver
|
||||
import jetbrains.mps.logic.reactor.evaluation.StoreView
|
||||
import jetbrains.mps.logic.reactor.logical.Logical
|
||||
import jetbrains.mps.logic.reactor.util.Id
|
||||
import jetbrains.mps.logic.reactor.util.cons
|
||||
import jetbrains.mps.logic.reactor.util.remove
|
||||
import java.util.*
|
||||
|
||||
internal class Frame: LogicalObserver, FrameObservable {
|
||||
|
||||
val stack: FrameStack
|
||||
|
||||
val store: Store
|
||||
|
||||
private var observers: Map<Id<Logical<*>>, ConsList<(FrameObservable) -> LogicalObserver>>
|
||||
|
||||
constructor(stack: FrameStack) {
|
||||
this.stack = stack
|
||||
this.store = Store() { stack.current }
|
||||
this.observers = Maps.of()
|
||||
}
|
||||
|
||||
constructor(stack: FrameStack, prev: Frame) {
|
||||
this.stack = stack
|
||||
this.store = Store(prev.store) { stack.current }
|
||||
this.observers = prev.observers
|
||||
}
|
||||
|
||||
override fun storeObserver() = store
|
||||
|
||||
override fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) {
|
||||
val logicalId = Id(logical)
|
||||
if (!observers.containsKey(logicalId)) {
|
||||
stack.addObserver(logical)
|
||||
}
|
||||
this.observers = observers.put(logicalId,
|
||||
observers[logicalId]?.prepend(obs) ?: cons(obs))
|
||||
}
|
||||
|
||||
override fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) {
|
||||
val logicalId = Id(logical)
|
||||
observers[logicalId].remove(obs)?.let { newList ->
|
||||
this.observers = observers.put(logicalId, newList)
|
||||
if (newList.isEmpty) {
|
||||
stack.removeObserver(logical)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun valueUpdated(logical: Logical<*>) {
|
||||
observers[Id(logical)]?.let { list ->
|
||||
for (obs in list) {
|
||||
obs(this).valueUpdated(logical)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun parentUpdated(logical: Logical<*>) {
|
||||
observers[Id(logical)]?.let { list ->
|
||||
for (obs in list) {
|
||||
obs(this).parentUpdated(logical)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class FrameStack : LogicalObserver {
|
||||
|
||||
var current: Frame
|
||||
|
||||
val observing = HashSet<Id<Logical<*>>>()
|
||||
|
||||
init {
|
||||
this.current = Frame(this)
|
||||
}
|
||||
|
||||
fun push(): Frame {
|
||||
val frame = Frame(this, current)
|
||||
this.current = frame
|
||||
return frame
|
||||
}
|
||||
|
||||
fun reset(frame: Frame): Unit {
|
||||
this.current = frame
|
||||
}
|
||||
|
||||
fun addObserver(logical: Logical<*>) {
|
||||
val token = Id(logical)
|
||||
if (!observing.contains(token)) {
|
||||
logical.addObserver(this)
|
||||
observing.add(token)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeObserver(logical: Logical<*>) {
|
||||
// NOP
|
||||
// yes, keep listening, the updates are still filtered down the stream
|
||||
}
|
||||
|
||||
override fun valueUpdated(logical: Logical<*>) {
|
||||
current.valueUpdated(logical)
|
||||
}
|
||||
|
||||
override fun parentUpdated(logical: Logical<*>) {
|
||||
current.parentUpdated(logical)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import jetbrains.mps.logic.reactor.core.*
|
||||
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
|
||||
import jetbrains.mps.logic.reactor.evaluation.EvaluationTrace
|
||||
import jetbrains.mps.logic.reactor.evaluation.StoreView
|
||||
import jetbrains.mps.logic.reactor.logical.Logical
|
||||
import jetbrains.mps.logic.reactor.program.ConstraintSymbol
|
||||
import jetbrains.mps.logic.reactor.util.Id
|
||||
import jetbrains.mps.logic.reactor.util.Profiler
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Processing of constraints comprises several major activities:
|
||||
*
|
||||
* - finding match(-es) corresponding to an active occurrence
|
||||
* - checking the guard condition
|
||||
* - deactivating discarded occurrences from the match
|
||||
* - processing the body of a constraints rule.
|
||||
*
|
||||
* A ProcessingState maintains a stack of [StateFrame] instances.
|
||||
* It also serves as a proxy for observers of [Logical] that are added during program
|
||||
* evaluation.
|
||||
*
|
||||
* @author Fedor Isakov
|
||||
*/
|
||||
|
||||
internal class ProcessingStateImpl private constructor(val trace: EvaluationTrace = EvaluationTrace.NULL,
|
||||
val profiler: Profiler? = null) :
|
||||
ProcessingState, LogicalObserver
|
||||
{
|
||||
|
||||
// invariant: never empty
|
||||
private val stateFrames = LinkedList<StateFrame>()
|
||||
|
||||
constructor(dispatcher: Dispatcher,
|
||||
trace: EvaluationTrace = EvaluationTrace.NULL,
|
||||
profiler: Profiler? = null) :
|
||||
this(trace, profiler)
|
||||
{
|
||||
stateFrames.push(StateFrame(this, dispatcher))
|
||||
}
|
||||
|
||||
fun processActivated(active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus =
|
||||
push().processActivated(active, inStatus)
|
||||
|
||||
fun currentFrame() : StateFrame =
|
||||
stateFrames.first
|
||||
|
||||
fun push() : StateFrame {
|
||||
val newFrame = StateFrame(stateFrames.first)
|
||||
stateFrames.push(newFrame)
|
||||
return newFrame
|
||||
}
|
||||
|
||||
fun reset(frame: StateFrame) {
|
||||
val it = stateFrames.iterator()
|
||||
while (it.hasNext()) {
|
||||
if (frame === it.next()) { // referential equality
|
||||
return
|
||||
}
|
||||
it.remove()
|
||||
}
|
||||
throw IllegalStateException("invalid frame")
|
||||
}
|
||||
|
||||
override fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
|
||||
if (!currentFrame().isObserving(logical)) {
|
||||
logical.addObserver(this)
|
||||
}
|
||||
currentFrame().addForwardingObserver(logical, observer)
|
||||
}
|
||||
|
||||
override fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
|
||||
currentFrame().removeForwardingObserver(logical, observer)
|
||||
if (!currentFrame().isObserving(logical)) {
|
||||
logical.removeObserver(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun valueUpdated(logical: Logical<*>) {
|
||||
// forward to the top frame
|
||||
currentFrame().valueUpdated(logical)
|
||||
}
|
||||
|
||||
override fun parentUpdated(logical: Logical<*>) {
|
||||
// forward to the top frame
|
||||
currentFrame().parentUpdated(logical)
|
||||
}
|
||||
|
||||
fun storeView(): StoreView =
|
||||
StoreViewImpl(allOccurrences())
|
||||
|
||||
private fun allOccurrences(): Sequence<Occurrence> {
|
||||
val dit = stateFrames.descendingIterator()
|
||||
val set = HashSet<Id<Occurrence>>()
|
||||
while (dit.hasNext()) {
|
||||
val stateFrame = dit.next()
|
||||
stateFrame.allActivated().forEach { set.add(Id(it)) }
|
||||
stateFrame.allDeactivated().forEach { set.remove(Id(it)) }
|
||||
}
|
||||
return set.map { it.wrapped }.asSequence()
|
||||
}
|
||||
|
||||
private class StoreViewImpl(occurrences: Sequence<Occurrence>) : StoreView {
|
||||
|
||||
val allOccurrences = occurrences.toSet()
|
||||
|
||||
val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet()
|
||||
|
||||
override fun constraintSymbols(): Iterable<ConstraintSymbol> = allSymbols
|
||||
|
||||
override fun allOccurrences(): Iterable<ConstraintOccurrence> = allOccurrences
|
||||
|
||||
override fun occurrences(symbol: ConstraintSymbol): Iterable<ConstraintOccurrence> =
|
||||
allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet()
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import com.github.andrewoma.dexx.collection.ConsList
|
||||
import com.github.andrewoma.dexx.collection.Map as PersMap
|
||||
import com.github.andrewoma.dexx.collection.Maps
|
||||
import jetbrains.mps.logic.reactor.core.*
|
||||
import jetbrains.mps.logic.reactor.core.Dispatcher.DispatchingFront
|
||||
import jetbrains.mps.logic.reactor.evaluation.RuleMatch
|
||||
import jetbrains.mps.logic.reactor.logical.Logical
|
||||
import jetbrains.mps.logic.reactor.util.*
|
||||
|
||||
/**
|
||||
* A StateFrame captures a processing state corresponding to an event that consists
|
||||
* of an activated constraint occurrence followed by a series of triggered rule
|
||||
* matches.
|
||||
*
|
||||
* Each triggered match may result in one or more constraint occurrences
|
||||
* to be deactivated/terminated.
|
||||
*
|
||||
* An activated or deactivated constraint occurrence updates the dispatching front --
|
||||
* an object that maintains a search tree for possible rule matches.
|
||||
*
|
||||
* @author Fedor Isakov
|
||||
*/
|
||||
|
||||
internal class StateFrame private constructor(val state: ProcessingStateImpl) : LogicalObserver
|
||||
{
|
||||
private lateinit var dispatchingFront: DispatchingFront
|
||||
|
||||
private var observers: PersMap<Id<Logical<*>>, ConsList<LogicalObserver>> = Maps.of()
|
||||
|
||||
private val activatedOccurrences = ArrayList<Occurrence>()
|
||||
|
||||
private val deactivatedOccurrences = ArrayList<Occurrence>()
|
||||
|
||||
constructor(state: ProcessingStateImpl, dispatcher: Dispatcher) : this(state) {
|
||||
this.dispatchingFront = dispatcher.front()
|
||||
}
|
||||
|
||||
constructor(prototype: StateFrame) : this(prototype.state) {
|
||||
this.dispatchingFront = prototype.dispatchingFront
|
||||
this.observers = prototype.observers
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
// TODO remove observers
|
||||
|
||||
}
|
||||
|
||||
fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
|
||||
val logicalId = Id(logical)
|
||||
this.observers = observers.put(logicalId,
|
||||
observers[logicalId]?.prepend(observer) ?: cons(observer))
|
||||
}
|
||||
|
||||
fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
|
||||
val logicalId = Id(logical)
|
||||
observers[logicalId]?.let {
|
||||
this.observers = observers.put(logicalId, it.remove(observer))
|
||||
}
|
||||
}
|
||||
|
||||
fun isObserving(logical: Logical<*>) =
|
||||
observers[Id(logical)] != null
|
||||
|
||||
override fun valueUpdated(logical: Logical<*>) {
|
||||
observers[Id(logical)]?.let { list ->
|
||||
for (observer in list) {
|
||||
observer.valueUpdated(logical)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun parentUpdated(logical: Logical<*>) {
|
||||
observers[Id(logical)]?.let { list ->
|
||||
for (observer in list) {
|
||||
observer.parentUpdated(logical)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun allActivated() : Sequence<Occurrence> =
|
||||
activatedOccurrences.asSequence()
|
||||
|
||||
fun allDeactivated() : Sequence<Occurrence> =
|
||||
deactivatedOccurrences.asSequence()
|
||||
|
||||
/**
|
||||
* Called to update the state with the currently active constraint occurrence.
|
||||
* Calls the controller to process matches (if any) that were triggered.
|
||||
* This method may be called at most once for a fresh state frame.
|
||||
*/
|
||||
fun processActivated(active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus {
|
||||
assert(active.alive)
|
||||
|
||||
return state.profiler.profile<FeedbackStatus>("activated_${active.constraint().symbol()}") {
|
||||
|
||||
if (!active.stored) {
|
||||
active.stored = true
|
||||
state.trace.activate(active)
|
||||
|
||||
} else {
|
||||
state.trace.reactivate(active)
|
||||
}
|
||||
|
||||
activatedOccurrences += active
|
||||
this.dispatchingFront = dispatchingFront.expand(active)
|
||||
|
||||
val outStatus = dispatchingFront.matches().toList().fold(inStatus) { status, match ->
|
||||
// TODO: paranoid check. should be isAlive() instead
|
||||
// FIXME: move this check elsewhere
|
||||
if (status.operational && active.stored && match.allStored())
|
||||
processMatch(active.controller, match, status)
|
||||
else
|
||||
status
|
||||
}
|
||||
|
||||
// TODO: should be isAlive()
|
||||
if (active.stored) {
|
||||
state.trace.suspend(active)
|
||||
}
|
||||
|
||||
outStatus
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun FeedbackStatus.then(action: (FeedbackStatus) -> FeedbackStatus) : FeedbackStatus =
|
||||
if (operational) action(this) else this
|
||||
|
||||
private fun processMatch(controller: Controller, match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus =
|
||||
controller.offerMatch(match, inStatus)
|
||||
.let { when (it) {
|
||||
is FeedbackStatus.ABORTED -> { // guard is not satisfied
|
||||
state.trace.reject(match)
|
||||
return it.recover() // return from the enclosing method
|
||||
|
||||
} is FeedbackStatus.FAILED -> { // guard failed
|
||||
state.trace.feedback(it.failure)
|
||||
return it.recover() // return from the enclosing method
|
||||
|
||||
} else -> it
|
||||
} }
|
||||
.also { state.trace.trigger(match) }
|
||||
.also { accept(match) }
|
||||
.then { controller.processBody(match, it) }
|
||||
.also { state.trace.finish(match) }
|
||||
|
||||
|
||||
private fun accept (match: RuleMatchEx) {
|
||||
this.dispatchingFront = dispatchingFront.consume(match)
|
||||
|
||||
match.forEachReplaced { occ ->
|
||||
this.dispatchingFront = dispatchingFront.contract(occ)
|
||||
deactivatedOccurrences += occ
|
||||
|
||||
occ.stored = false
|
||||
occ.terminate()
|
||||
|
||||
state.trace.discard(occ)
|
||||
}
|
||||
}
|
||||
|
||||
private fun RuleMatch.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored }
|
||||
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jetbrains.mps.logic.reactor.core.internal
|
||||
|
||||
import com.github.andrewoma.dexx.collection.Maps
|
||||
import jetbrains.mps.logic.reactor.core.FrameObservable
|
||||
import jetbrains.mps.logic.reactor.core.LogicalObserver
|
||||
import jetbrains.mps.logic.reactor.core.Occurrence
|
||||
import jetbrains.mps.logic.reactor.core.occurrence
|
||||
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
|
||||
import jetbrains.mps.logic.reactor.evaluation.StoreView
|
||||
import jetbrains.mps.logic.reactor.logical.Logical
|
||||
import jetbrains.mps.logic.reactor.program.ConstraintSymbol
|
||||
import jetbrains.mps.logic.reactor.util.*
|
||||
import java.util.*
|
||||
import com.github.andrewoma.dexx.collection.Map as PersMap
|
||||
import com.github.andrewoma.dexx.collection.Set as PersSet
|
||||
import com.github.andrewoma.dexx.collection.Vector as PersVector
|
||||
|
||||
/**
|
||||
* @author Fedor Isakov
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constrants storeObserver.
|
||||
*
|
||||
* TODO: make this class persistent.
|
||||
*/
|
||||
internal class Store : LogicalObserver {
|
||||
|
||||
val currentFrame: () -> FrameObservable
|
||||
|
||||
var symbol2occurrences: PersMap<ConstraintSymbol, IdHashSet<Occurrence>>
|
||||
|
||||
var logical2occurrences: PersMap<Id<Logical<*>>, IdHashSet<Occurrence>>
|
||||
|
||||
constructor(copyFrom: Store, currentFrame: () -> FrameObservable) {
|
||||
this.currentFrame = currentFrame
|
||||
this.symbol2occurrences = copyFrom.symbol2occurrences
|
||||
this.logical2occurrences = copyFrom.logical2occurrences
|
||||
}
|
||||
|
||||
constructor(currentFrame: () -> FrameObservable) {
|
||||
this.currentFrame = currentFrame
|
||||
this.symbol2occurrences = Maps.of()
|
||||
this.logical2occurrences = Maps.of()
|
||||
}
|
||||
|
||||
override fun valueUpdated(logical: Logical<*>) {
|
||||
}
|
||||
|
||||
override fun parentUpdated(logical: Logical<*>) {
|
||||
// TODO: should we care about the order in which occurrences are stored?
|
||||
val logicalId = Id(logical)
|
||||
logical2occurrences[logicalId]?.let { toMerge ->
|
||||
val rootId = Id(logical.findRoot())
|
||||
var newSet = logical2occurrences[rootId] ?: emptyIdSet()
|
||||
for (log in toMerge) {
|
||||
newSet = newSet.add(log)
|
||||
}
|
||||
this.logical2occurrences = logical2occurrences.remove(logicalId).put(rootId, newSet)
|
||||
assert(logical2occurrences.containsKey(rootId))
|
||||
assert(!logical2occurrences.containsKey(logicalId))
|
||||
}
|
||||
}
|
||||
|
||||
fun store(occ: Occurrence) {
|
||||
val symbol = occ.constraint().symbol()
|
||||
|
||||
this.symbol2occurrences = symbol2occurrences.put(symbol,
|
||||
symbol2occurrences[symbol]?.add(occ) ?: singletonIdSet(occ))
|
||||
|
||||
for (arg in occ.arguments()) {
|
||||
// FIXME extracting the value is unnecessary here
|
||||
val value = if (arg is Logical<*> && arg.isBound) arg.findRoot().value() else arg
|
||||
when (value) {
|
||||
is Logical<*> -> {
|
||||
// free logical
|
||||
val argId = Id(value.findRoot())
|
||||
this.logical2occurrences = logical2occurrences.put(argId,
|
||||
logical2occurrences[argId]?.add(occ) ?: singletonIdSet(occ))
|
||||
currentFrame().addObserver(value) { frame -> frame.storeObserver() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
occ.stored = true
|
||||
}
|
||||
|
||||
fun discard(occ: Occurrence, profiler: Profiler? = null, tag: String? = null): Unit {
|
||||
val symbol = occ.constraint().symbol()
|
||||
|
||||
symbol2occurrences[symbol]?.remove(occ)?.let { newList ->
|
||||
this.symbol2occurrences = symbol2occurrences.put(symbol, newList)
|
||||
}
|
||||
|
||||
for (arg in occ.arguments()) {
|
||||
when (arg) {
|
||||
is Logical<*> -> {
|
||||
val argId = Id(arg.findRoot())
|
||||
logical2occurrences[argId]?.remove(occ)?.let { newList ->
|
||||
this.logical2occurrences = if (newList.isEmpty) {
|
||||
logical2occurrences.remove(argId)
|
||||
|
||||
} else {
|
||||
logical2occurrences.put(argId, newList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
occ.stored = false
|
||||
occ.terminate()
|
||||
}
|
||||
|
||||
fun allOccurrences(): Sequence<ConstraintOccurrence> {
|
||||
return symbol2occurrences.values().flatten().filter { co -> co.stored }.asSequence()
|
||||
}
|
||||
|
||||
fun view(): StoreView = StoreViewImpl(allOccurrences())
|
||||
|
||||
}
|
||||
|
||||
private class StoreViewImpl(occurrences: Sequence<ConstraintOccurrence>) : StoreView {
|
||||
|
||||
val allOccurrences = occurrences.toSet()
|
||||
|
||||
val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet()
|
||||
|
||||
override fun constraintSymbols(): Iterable<ConstraintSymbol> = allSymbols
|
||||
|
||||
override fun allOccurrences(): Iterable<ConstraintOccurrence> = allOccurrences
|
||||
|
||||
override fun occurrences(symbol: ConstraintSymbol): Iterable<ConstraintOccurrence> =
|
||||
allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet()
|
||||
|
||||
}
|
||||
|
|
@ -127,20 +127,7 @@ fun sym2(id: String): ConstraintSymbol =
|
|||
ConstraintSymbol(id, 2)
|
||||
|
||||
class MockController : Controller {
|
||||
|
||||
override fun currentFrame(): FrameObservable = object : FrameObservable {
|
||||
override fun storeObserver(): LogicalObserver {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun addObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) {
|
||||
}
|
||||
|
||||
override fun removeObserver(logical: Logical<*>, obs: (FrameObservable) -> LogicalObserver) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun ask(invocation: PredicateInvocation): Boolean {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
|
@ -157,6 +144,14 @@ class MockController : Controller {
|
|||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun state(): ProcessingState = object : ProcessingState {
|
||||
override fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
|
||||
}
|
||||
|
||||
override fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeView(): StoreView {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -342,31 +342,34 @@ class TestController {
|
|||
val X = metaLogical<Int>("X")
|
||||
programWithRules(
|
||||
rule("zeroth",
|
||||
headKept(constraint("foo")), body(statement({ x -> x.set(999) }, X),
|
||||
constraint("bar", X))
|
||||
headKept(constraint("foo")),
|
||||
body(statement({ x -> x.set(999) }, X),
|
||||
constraint("bar", X))
|
||||
),
|
||||
rule("first",
|
||||
headKept(constraint("foo")),
|
||||
body(constraint("bar", X),
|
||||
constraint("qux", X))
|
||||
body(constraint("bar", X),
|
||||
constraint("qux", X))
|
||||
),
|
||||
rule("second",
|
||||
headReplaced(constraint("qux", X)),
|
||||
body(constraint("expected1"),
|
||||
statement({ x -> x.set(123) }, X))
|
||||
body(constraint("expected1"),
|
||||
statement({ x -> x.set(123) }, X))
|
||||
),
|
||||
rule("third",
|
||||
headReplaced(constraint("foo")),
|
||||
body(constraint("unexpected"))
|
||||
body(constraint("unexpected"))
|
||||
),
|
||||
rule("fourth",
|
||||
headReplaced(constraint("foo")),
|
||||
headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 123 }, X)),
|
||||
body(constraint("expected2"))
|
||||
headReplaced(constraint("bar", X)),
|
||||
guard(expression({ x -> x.getNullable() == 123 }, X)),
|
||||
body(constraint("expected2"))
|
||||
),
|
||||
rule("fifth",
|
||||
headReplaced(constraint("bar", X)), guard(expression({ x -> x.getNullable() == 999 }, X)),
|
||||
body(constraint("expected3", X))
|
||||
headReplaced(constraint("bar", X)),
|
||||
guard(expression({ x -> x.getNullable() == 999 }, X)),
|
||||
body(constraint("expected3", X))
|
||||
)
|
||||
).controller().evaluate(occurrence("foo")).run {
|
||||
assertEquals(3, allOccurrences().count())
|
||||
|
|
|
|||
Loading…
Reference in New Issue