Remove unnecessary feature: StateFrameStack is no longer used.

StateFrameStack adds no functionality but requires too much processing.
Rename StateFrameStack -> LogicalState.
Rename ProcessingStateImpl -> ConstraintsProcessing.
Simplify occurrence's logical observer.
Introduce transient dependency on controller to LogicalState.
Keep logical state as part of session token, avoid re-initializing
all logical observers.
Make replaying of match journal independent of controller.
This commit is contained in:
Fedor Isakov 2020-01-10 13:46:47 +01:00
parent fa7532f34f
commit d208e802b9
17 changed files with 335 additions and 299 deletions

View File

@ -33,7 +33,9 @@ interface Controller {
fun reactivate(occ: Occurrence): FeedbackStatus
fun state(): ProcessingState
fun logicalState(): LogicalStateObservable
fun clearState()
/** For tests only */
fun evaluate(occ: Occurrence): StoreView

View File

@ -17,7 +17,7 @@
package jetbrains.mps.logic.reactor.core
import jetbrains.mps.logic.reactor.core.internal.LogicalImpl
import jetbrains.mps.logic.reactor.core.internal.StateFrameStack
import jetbrains.mps.logic.reactor.core.internal.LogicalState
import jetbrains.mps.logic.reactor.logical.Logical
/**
@ -35,8 +35,8 @@ interface LogicalObserver {
// Helps ensuring absence of memory leak of StateFrameStack through variable's internal arrays of observers
internal fun checkForwardingObserverUniqueInstance(logical: Logical<*>): Boolean = with(logical as LogicalImpl<*>) {
valueObservers.map { it.second }.filterIsInstance<StateFrameStack>().toHashSet().size <= 1 &&
parentObservers.map { it.second }.filterIsInstance<StateFrameStack>().toHashSet().size <= 1
valueObservers.map { it.second }.filterIsInstance<LogicalState>().toHashSet().size <= 1 &&
parentObservers.map { it.second }.filterIsInstance<LogicalState>().toHashSet().size <= 1
}

View File

@ -22,10 +22,26 @@ import jetbrains.mps.logic.reactor.logical.Logical
* @author Fedor Isakov
*/
interface ProcessingState {
interface LogicalStateObservable {
fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver)
fun addForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver)
fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver)
}
fun removeForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver)
fun init(controller: Controller): InitToken
interface InitToken {
fun clear()
}
}
interface ForwardingLogicalObserver {
fun valueUpdated(logical: Logical<*>, controller: Controller)
fun parentUpdated(logical: Logical<*>, controller: Controller)
}

View File

@ -23,7 +23,6 @@ import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.logical.MetaLogical
import jetbrains.mps.logic.reactor.program.Constraint
@ -34,40 +33,18 @@ fun justsOf(vararg elements: Int) = TIntHashSet(elements)
fun justsFromCollection(collection: Collection<Int>) = TIntHashSet(collection)
fun justsCopy(other: Justs) = TIntHashSet(other)
data class OccurrenceObserver(val occurrence: Occurrence, val controller: Controller) : LogicalObserver {
override fun valueUpdated(logical: Logical<*>) = doReactivate()
override fun parentUpdated(logical: Logical<*>) = doReactivate()
private fun doReactivate() {
if (occurrence.alive) {
val status = controller.reactivate(occurrence)
// FIXME propagate the status further up the call stack
handleFeedbackStatus(status)
}
}
private fun handleFeedbackStatus(status: FeedbackStatus) {
if (status is FeedbackStatus.FAILED) {
throw status.failure.failureCause()
}
}
}
/**
* Class representing a single constraint occurrence.
*
* @author Fedor Isakov
*/
class Occurrence (controller: Controller,
class Occurrence (observable: LogicalStateObservable,
val constraint: Constraint,
val logicalContext: LogicalContext,
val arguments: List<*>,
val justifications: Justs,
val ruleUniqueTag: Any? = null):
ConstraintOccurrence
ConstraintOccurrence, ForwardingLogicalObserver
{
var alive = true
@ -77,7 +54,7 @@ class Occurrence (controller: Controller,
val identity = System.identityHashCode(this)
init {
revive(controller)
revive(observable)
}
override fun constraint(): Constraint = constraint
@ -90,34 +67,49 @@ class Occurrence (controller: Controller,
override fun justifications(): Justs = justifications
fun terminate(controller: Controller) {
val obs = OccurrenceObserver(this, controller)
override fun valueUpdated(logical: Logical<*>, controller: Controller) = doReactivate(controller)
override fun parentUpdated(logical: Logical<*>, controller: Controller) = doReactivate(controller)
fun terminate(observable: LogicalStateObservable) {
for (a in arguments) {
if (a is Logical<*>) {
controller.state().removeForwardingObserver(a, obs)
observable.removeForwardingObserver(a, this)
}
}
alive = false
}
fun revive(controller: Controller) {
val obs = OccurrenceObserver(this, controller)
fun revive(observable: LogicalStateObservable) {
for (a in arguments) {
if (a is Logical<*>) {
controller.state().addForwardingObserver(a, obs)
observable.addForwardingObserver(a, this)
}
}
alive = true
}
override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})"
private fun doReactivate(controller: Controller) {
if (alive) {
val status = controller.reactivate(this)
// FIXME propagate the status further up the call stack
handleFeedbackStatus(status)
}
}
private fun handleFeedbackStatus(status: FeedbackStatus) {
if (status is FeedbackStatus.FAILED) {
throw status.failure.failureCause()
}
}
override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})"
}
fun Constraint.occurrence(controller: Controller,
fun Constraint.occurrence(observable: LogicalStateObservable,
arguments: List<*>,
justifications: Justs,
logicalContext: LogicalContext,
ruleUniqueTag: Any? = null): Occurrence =
Occurrence(controller, this, logicalContext, arguments, justifications, ruleUniqueTag)
Occurrence(observable, this, logicalContext, arguments, justifications, ruleUniqueTag)

View File

@ -27,26 +27,24 @@ import jetbrains.mps.logic.reactor.util.profile
/**
* Processing of constraints comprises several major activities:
* Represents the processing of constraints, which 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 through [StateFrameStack].
* It also serves as a proxy for observers of [Logical] that are added during program evaluation.
*
* @author Fedor Isakov
*/
internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.DispatchingFront,
journal: MatchJournalImpl,
private val ruleIndex: RuleIndex,
private val ispec: IncrementalProgramSpec = IncrementalProgramSpec.DefaultSpec,
val trace: EvaluationTrace = EvaluationTrace.NULL,
val profiler: Profiler? = null)
: StoreAwareJournalImpl(journal)
internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.DispatchingFront,
journal: MatchJournalImpl,
private val ruleIndex: RuleIndex,
val logicalState: LogicalState = LogicalState(),
private val ispec: IncrementalProgramSpec = IncrementalProgramSpec.DefaultSpec,
val trace: EvaluationTrace = EvaluationTrace.NULL,
val profiler: Profiler? = null)
: StoreAwareJournalImpl(journal, logicalState)
{
private val ruleOrdering: RuleOrdering = RuleOrdering(ruleIndex)
@ -202,7 +200,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
val histView = view()
resetStore() // clear observers
val rules = ArrayList<Rule>().apply { ruleIndex.forEach { add(it) } }
return SessionTokenImpl(histView, rules, dispatchingFront.state())
return SessionTokenImpl(histView, rules, dispatchingFront.state(), logicalState)
}
/**
@ -217,7 +215,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
if (!active.stored) {
active.stored = true
logActivation(active)
active.revive(controller)
active.revive(logicalState)
}
profiler.profile("dispatch_${active.constraint().symbol()}") {
@ -308,7 +306,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp
profiler.profile("terminateOccurrence") {
occ.terminate(controller)
occ.terminate(logicalState)
}

View File

@ -30,23 +30,26 @@ import jetbrains.mps.logic.reactor.util.profile
internal class ControllerImpl (
val supervisor: Supervisor,
val state: ProcessingStateImpl,
val processing: ConstraintsProcessing,
val ispec: IncrementalProgramSpec = IncrementalProgramSpec.DefaultSpec,
val trace: EvaluationTrace = EvaluationTrace.NULL,
val profiler: Profiler? = null) : Controller
{
private val initToken: LogicalStateObservable.InitToken = processing.init(this)
/** For tests only */
override fun storeView(): StoreView = state.storeView()
override fun storeView(): StoreView = processing.storeView()
/** For tests only */
override fun evaluate(occ: Occurrence): StoreView {
// create the internal occurrence
val active = occ.constraint().occurrence(this, occ.arguments(), occ.justifications(), object: LogicalContext {
val active = occ.constraint().occurrence(logicalState(), occ.arguments(), occ.justifications(), object: LogicalContext {
override fun <V : Any> variable(metaLogical: MetaLogical<V>): Logical<V>? = null
})
trace.activate(occ)
val status = state.processActivated(this, active, NORMAL())
val status = processing.processActivated(this, active, NORMAL())
if (status is FAILED) {
throw status.failure.failureCause()
}
@ -55,13 +58,13 @@ internal class ControllerImpl (
fun incrLaunch(constraint: Constraint, rulesDiff: RulesDiff): FeedbackStatus {
profiler.profile("invalidation") {
state.invalidateRuleMatches(rulesDiff.removed)
processing.invalidateRuleMatches(rulesDiff.removed)
}
profiler.profile("adding_matches") {
state.addRuleMatches(rulesDiff.added)
processing.addRuleMatches(rulesDiff.added)
}
return profiler.profile<FeedbackStatus>("reexecution") {
state.launchQueue(this)
processing.launchQueue(this)
}
}
@ -71,13 +74,17 @@ internal class ControllerImpl (
// fixme: is it valid to always provide current justifications?
// while this method is used only in one place at program kick-off, yes, it's initial justs provided.
activateConstraint(constraint, state.justs(), context)
activateConstraint(constraint, processing.justs(), context)
return context.currentStatus()
}
override fun state(): ProcessingStateImpl =
state
override fun logicalState(): LogicalStateObservable =
processing
override fun clearState() {
initToken.clear()
}
override fun ask(invocation: PredicateInvocation): Boolean {
val solver = invocation.predicate().symbol().solver()
@ -96,7 +103,7 @@ internal class ControllerImpl (
profiler.profile<FeedbackStatus>("reactivate_${occ.constraint.symbol()}") {
trace.reactivate(occ)
state.processActivated(this, occ, NORMAL())
processing.processActivated(this, occ, NORMAL())
}
@ -146,9 +153,9 @@ internal class ControllerImpl (
}
}
val savedPos = state.currentPos()
val savedPos = processing.currentPos()
val currentJusts = state.justs()
val currentJusts = processing.justs()
for (item in body) {
val itemOk = when (item) {
@ -199,7 +206,7 @@ internal class ControllerImpl (
if (!altOk) {
// all constraints activated up to a failure are lost
state.reset(savedPos)
processing.reset(savedPos)
} else {
// body finished normally
@ -216,9 +223,9 @@ internal class ControllerImpl (
profiler.profile<FeedbackStatus>("activate_${constraint.symbol()}") {
constraint.occurrence(this, args, justsCopy(justs), context.logicalContext, context.ruleUniqueTag).let { occ ->
constraint.occurrence(logicalState(), args, justsCopy(justs), context.logicalContext, context.ruleUniqueTag).let { occ ->
trace.activate(occ)
state.processActivated(this, occ, status)
processing.processActivated(this, occ, status)
}
}
@ -333,10 +340,11 @@ fun createController(
ControllerImpl(
supervisor,
ProcessingStateImpl(
ConstraintsProcessing(
Dispatcher(ruleIndex).front(),
MatchJournalImpl(),
ruleIndex,
LogicalState(),
IncrementalProgramSpec.DefaultSpec,
trace,
profiler

View File

@ -49,24 +49,25 @@ internal class EvaluationSessionImpl private constructor (
val ruleIndex = RuleIndex(program.rulesLists())
if (ispec is IncrementalProgramSpec.NonIncrSpec || token == null) {
val state = ProcessingStateImpl(
val processing
= ConstraintsProcessing(
Dispatcher(ruleIndex).front(),
MatchJournalImpl(ispec),
ruleIndex, ispec, trace, profiler
ruleIndex, LogicalState(), ispec, trace, profiler
)
this.controller = ControllerImpl(supervisor, state, ispec, trace, profiler)
this.controller = ControllerImpl(supervisor, processing, ispec, trace, profiler)
return controller.activate(main)
} else {
val tkn = token as SessionTokenImpl
val state = ProcessingStateImpl(
val processing = ConstraintsProcessing(
Dispatcher(ruleIndex, tkn.getFrontState()).front(),
MatchJournalImpl(ispec, tkn.journalView),
ruleIndex, ispec, trace, profiler
ruleIndex, tkn.logicalState, ispec, trace, profiler
)
this.controller = ControllerImpl(supervisor, state, ispec, trace, profiler)
this.controller = ControllerImpl(supervisor, processing, ispec, trace, profiler)
return controller.incrLaunch(main, rulesDiff)
}
}
@ -108,7 +109,7 @@ internal class EvaluationSessionImpl private constructor (
override fun start(supervisor: Supervisor): EvaluationResult {
var session = Backend.ourBackend.ourSession.get()
if (session != null) throw IllegalStateException("session already active")
@Suppress("UNCHECKED_CAST")
val durations = parameters[ParameterKey.of("profiling.data", MutableMap::class.java)]
as MutableMap<String, String>?
@ -125,6 +126,7 @@ internal class EvaluationSessionImpl private constructor (
}
}
finally {
session.controller.clearState()
try {
profiler?.run {
formattedData().entries.forEach { e -> durations.put(e.key, e.value) }
@ -138,7 +140,7 @@ internal class EvaluationSessionImpl private constructor (
}
return object : EvaluationResult {
private val token = session.controller.state.endSession()
private val token = session.controller.processing.endSession()
override fun token(): SessionToken = token

View File

@ -43,10 +43,10 @@ internal class ExecutionQueue(
private val seen: MutableSet<ExecPos> = HashSet()
fun run(controller: Controller, state: ProcessingStateImpl): FeedbackStatus {
fun run(controller: Controller, processing: ConstraintsProcessing): FeedbackStatus {
var status: FeedbackStatus = FeedbackStatus.NORMAL()
if (execQueue.isNotEmpty()) {
state.resetStore()
processing.resetStore()
var prevPos: MatchJournal.Pos? = null
do {
@ -55,21 +55,21 @@ internal class ExecutionQueue(
// Handles the case when several matches are added to the same position.
// Then shouldn't replay, because currentPos is valid and more recent (!) than execPos.
if (execPos.pos != prevPos) {
state.replay(controller, execPos.pos)
processing.replay(processing.logicalState, execPos.pos)
lastIncrementalRootPos = execPos.pos
}
prevPos = execPos.pos
// If the occurrence is still in the store after replay (i.e. if it's valid to activate it)
if (execPos.activeOcc.stored) {
status = state.reactivate(controller, execPos.activeOcc)
// Leave journal state as it was at the point of failure
status = processing.reactivate(controller, execPos.activeOcc)
// Leave journal processing as it was at the point of failure
if (!status.operational) return status
}
} while (execQueue.isNotEmpty())
}
// Also replay to the end after queue is fully executed
state.replay(controller, state.last().toPos())
processing.replay(processing.logicalState, processing.last().toPos())
return status
}

View File

@ -0,0 +1,186 @@
/*
* 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.logical.Logical
import java.util.*
/**
* Captures the state of logical variables in respect to observers.
* Serves as a middle layer between logical variables and observers, the latter being notified of changes in the
* state in context of the currenly active controller.
* Keeps a reference to [Controller] instance, which is used to notify the observers.
*
* NOTE: the following is no longer relevant, as the "stack frame" functionality has been removed.
*
* Internally [LogicalState] maintains a stack of [LogicalStateFrame]s capturing processing
* state related to logical variable observers. The top [LogicalStateFrame] contains
* the current relevant set of [LogicalObserver]s. Events corresponding to
* adding and removing observers are forwarded to the top frame.
*
* Frames are added on events which could require reverting processing state to
* the point before them, which can be achieved by simply dropping a frame and
* thus dropping all added observers.
*/
class LogicalState : LogicalStateObservable, LogicalObserver
{
// invariant: never empty
private val stateFrames = LinkedList<LogicalStateFrame>()
private var controller: Controller? = null
init {
stateFrames.push(LogicalStateFrame())
}
override fun init(controller: Controller): LogicalStateObservable.InitToken {
assert(this@LogicalState.controller === null)
this.controller = controller
return object : LogicalStateObservable.InitToken {
override fun clear() {
assert(this@LogicalState.controller !== null)
this@LogicalState.controller = null
}
}
}
fun currentFrame() : LogicalStateFrame =
stateFrames.first
fun push() : LogicalStateFrame {
return currentFrame()
// val newFrame = StateFrame(stateFrames.first)
// stateFrames.push(newFrame)
// return newFrame
}
fun reset() {
// Clear logicals from this forwarding observer on full reset
currentFrame().observed().forEach {
it.removeObserver(this)
}
reset(stateFrames.last)
}
fun reset(frame: LogicalStateFrame) {
val it = stateFrames.iterator()
while (it.hasNext()) {
val next = it.next()
if (frame === next) { // referential equality
return
}
it.remove()
}
throw IllegalStateException("invalid frame")
}
override fun addForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver) {
if (!currentFrame().isObserving(logical)) {
logical.addObserver(this)
}
currentFrame().addForwardingObserver(logical, observer)
// assert(checkForwardingObserverUniqueInstance(logical))
}
override fun removeForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver) {
currentFrame().removeForwardingObserver(logical, observer)
if (!currentFrame().isObserving(logical)) {
logical.removeObserver(this)
}
}
override fun valueUpdated(logical: Logical<*>) {
// forward to the top frame
currentFrame().valueUpdated(logical, controller!!)
}
override fun parentUpdated(logical: Logical<*>) {
// forward to the top frame
currentFrame().parentUpdated(logical, controller!!)
}
}
/**
* NOTE: this class is deprecated and the "stack of frames" functionality is no longer functioning.
* To be removed soon.
*
* A [LogicalStateFrame] captures a logical state corresponding to currently relevant
* observers of changes of logical variables. It roughly corresponds to a bunch of event
* that update a set of such observers. By dropping a frame the processing state
* can be reverted.
*
* The main (and only) example of such event is an occurrence activation and the
* corresponding observer reactivates occurrence on changes of logical variables
* used as its arguments.
*
* @author Fedor Isakov
*/
@Deprecated("to be removed")
class LogicalStateFrame : ForwardingLogicalObserver
{
// FIXME use Vector instead of ConsList
// private var observers: PersMap<Id<Logical<*>>, PersList<LogicalObserver>> = Maps.of()
private val observers = IdentityHashMap<Logical<*>, ArrayList<ForwardingLogicalObserver>>()
// constructor(prototype: StateFrame) : this() {
// this.observers = prototype.observers
// }
fun addForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver) {
// val logicalId = Id(logical)
// this.observers = observers.assoc(logicalId,
// observers[logicalId]?.prepend(observer) ?: Lists.of(observer))
observers.getOrPut(logical) { arrayListOf() }.add(observer)
}
fun removeForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver) {
// val logicalId = Id(logical)
// observers[logicalId]?.let {
// this.observers = observers.assoc(logicalId, it.remove(observer)!!)
// }
observers[logical]?.remove(observer)
}
fun isObserving(logical: Logical<*>) = observers.containsKey(logical)
// observers[Id(logical)] != null
fun observed(): Sequence<Logical<*>> = observers.keys.asSequence()
// observers.keys().asSequence().map { it.wrapped }
override fun valueUpdated(logical: Logical<*>, controller: Controller) {
observers[logical]?.let { list ->
for (observer in ArrayList(list)) {
observer.valueUpdated(logical, controller)
}
}
}
override fun parentUpdated(logical: Logical<*>, controller: Controller) {
observers[logical]?.let { list ->
for (observer in ArrayList(list)) {
observer.parentUpdated(logical, controller)
}
}
}
}

View File

@ -67,7 +67,7 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk> {
* Advances journal position to specified position.
* @throws IllegalStateException when position is not from the future (relative to current pos).
*/
fun replay(controller: Controller, futurePos: Pos)
fun replay(observable: LogicalStateObservable, futurePos: Pos)
/**

View File

@ -16,10 +16,7 @@
package jetbrains.mps.logic.reactor.core.internal
import jetbrains.mps.logic.reactor.core.Controller
import jetbrains.mps.logic.reactor.core.Justs
import jetbrains.mps.logic.reactor.core.Occurrence
import jetbrains.mps.logic.reactor.core.justsOf
import jetbrains.mps.logic.reactor.core.*
import jetbrains.mps.logic.reactor.core.internal.MatchJournal.*
import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence
import jetbrains.mps.logic.reactor.evaluation.MatchJournalView
@ -110,25 +107,27 @@ internal open class MatchJournalImpl(
if (currentPos() != pastPos) throw IllegalStateException()
}
override fun replay(controller: Controller, futurePos: MatchJournal.Pos) {
override fun replay(observable: LogicalStateObservable, futurePos: MatchJournal.Pos) {
while (posPtr.hasNext()) {
current = posPtr.next()
if (futurePos.chunk === current) {
replayOccurrences(controller, current.entries.take(futurePos.entriesCount))
replayOccurrences(observable, current.entries.take(futurePos.entriesCount))
return
}
replayOccurrences(controller, current.entries)
replayOccurrences(observable, current.entries)
}
if (currentPos() != futurePos) throw IllegalStateException()
}
private fun replayOccurrences(controller: Controller, occSpecs: Iterable<MatchJournal.Chunk.Entry>) =
private fun replayOccurrences(observable: LogicalStateObservable, occSpecs: Iterable<MatchJournal.Chunk.Entry>) =
occSpecs.forEach {
if (it.discarded) {
it.occ.terminate(controller)
// it.occ.terminate(observable)
it.occ.alive = false
it.occ.stored = false
} else {
it.occ.revive(controller)
// it.occ.revive(observable)
it.occ.alive = true
it.occ.stored = true
}
}

View File

@ -24,8 +24,10 @@ import jetbrains.mps.logic.reactor.program.Rule
data class SessionTokenImpl(
private val journalView: MatchJournal.View,
private val rules: Iterable<Rule>,
private val frontState: DispatchingFrontState
) : SessionToken {
private val frontState: DispatchingFrontState,
val logicalState: LogicalState
) : SessionToken
{
override fun getJournalView(): MatchJournalView = journalView
override fun getRules(): Iterable<Rule> = rules
fun getFrontState(): DispatchingFrontState = frontState

View File

@ -1,80 +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 jetbrains.mps.logic.reactor.core.LogicalObserver
import jetbrains.mps.logic.reactor.logical.Logical
import jetbrains.mps.logic.reactor.util.*
/**
* A [StateFrame] captures a processing state corresponding to current relevant
* observers of changes of logical variables. It roughly corresponds to an event
* that updates a set of such observers. By dropping a frame the processing state
* can be reverted.
*
* The main (and only) example of such event is an occurrence activation and the
* corresponding observer reactivates occurrence on changes of logical variables
* used as its arguments.
*
* @author Fedor Isakov
*/
internal class StateFrame constructor() : LogicalObserver
{
// FIXME use Vector instead of ConsList
private var observers: PersMap<Id<Logical<*>>, PersList<LogicalObserver>> = Maps.of()
constructor(prototype: StateFrame) : this() {
this.observers = prototype.observers
}
fun addForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
val logicalId = Id(logical)
this.observers = observers.assoc(logicalId,
observers[logicalId]?.prepend(observer) ?: Lists.of(observer))
}
fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
val logicalId = Id(logical)
observers[logicalId]?.let {
this.observers = observers.assoc(logicalId, it.remove(observer)!!)
}
}
fun isObserving(logical: Logical<*>) =
observers[Id(logical)] != null
fun observed(): Sequence<Logical<*>> =
observers.keys().asSequence().map { it.wrapped }
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)
}
}
}
}

View File

@ -1,98 +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 jetbrains.mps.logic.reactor.core.*
import jetbrains.mps.logic.reactor.logical.Logical
import java.util.*
/**
* A [StateFrameStack] maintains a stack of [StateFrame]s capturing processing
* state related to logical variable observers. The top [StateFrame] contains
* the current relevant set of [LogicalObserver]s. Events corresponding to
* adding and removing observers are forwarded to the top frame.
*
* Frames are added on events which could require reverting processing state to
* the point before them, which can be achieved by simply dropping a frame and
* thus dropping all added observers.
*/
internal open class StateFrameStack() : ProcessingState, LogicalObserver
{
// invariant: never empty
private val stateFrames = LinkedList<StateFrame>()
init {
stateFrames.push(StateFrame())
}
fun currentFrame() : StateFrame =
stateFrames.first
fun push() : StateFrame {
val newFrame = StateFrame(stateFrames.first)
stateFrames.push(newFrame)
return newFrame
}
fun reset() {
// Clear logicals from this forwarding observer on full reset
currentFrame().observed().forEach {
it.removeObserver(this)
}
reset(stateFrames.last)
}
fun reset(frame: StateFrame) {
val it = stateFrames.iterator()
while (it.hasNext()) {
val next = it.next()
if (frame === 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)
// assert(checkForwardingObserverUniqueInstance(logical))
}
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)
}
}

View File

@ -16,15 +16,15 @@
package jetbrains.mps.logic.reactor.core.internal
import jetbrains.mps.logic.reactor.core.ProcessingState
import jetbrains.mps.logic.reactor.core.LogicalStateObservable
import jetbrains.mps.logic.reactor.program.IncrementalProgramSpec
import java.lang.IllegalArgumentException
/**
* [MatchJournal] which also maintains observers in [ProcessingState] in sync with its current position.
* [MatchJournal] which also maintains observers in [LogicalStateObservable] in sync with its current position.
*/
interface StoreAwareJournal : MatchJournal, ProcessingState {
interface StoreAwareJournal : MatchJournal, LogicalStateObservable {
// Only for testing push() in Impl
fun testPush()
@ -41,12 +41,13 @@ interface StoreAwareJournal : MatchJournal, ProcessingState {
}
internal open class StoreAwareJournalImpl(private val journal: MatchJournal, private val state: StateFrameStack = StateFrameStack())
: MatchJournal by journal, StoreAwareJournal, ProcessingState by state
internal open class StoreAwareJournalImpl(private val journal: MatchJournal,
private val state: LogicalState = LogicalState())
: MatchJournal by journal, StoreAwareJournal, LogicalStateObservable by state
{
private class FramePos(
val frame: StateFrame,
val frame: LogicalStateFrame,
chunk: MatchJournal.Chunk,
entriesCount: Int = 0
) : MatchJournal.Pos(chunk, entriesCount)

View File

@ -174,15 +174,15 @@ fun equals(left: Any, right: Any): ConjBuilder.() -> Unit = {
fun occurrence(id: String, vararg args: Any): Occurrence =
MockConstraint(ConstraintSymbol.symbol(id, args.size))
.occurrence(MockController(), listOf(* args), justsOf(), noLogicalContext)
.occurrence(MockController().logicalState(), listOf(* args), justsOf(), noLogicalContext)
fun taggedOccurrence(ruleUniqueTag: Any, id: String, vararg args: Any): Occurrence =
MockConstraint(ConstraintSymbol.symbol(id, args.size))
.occurrence(MockController(), listOf(* args), justsOf(), noLogicalContext, ruleUniqueTag)
.occurrence(MockController().logicalState(), listOf(* args), justsOf(), noLogicalContext, ruleUniqueTag)
fun justifiedOccurrence(id: String, justs: Justs, vararg args: Any): Occurrence =
MockConstraint(ConstraintSymbol.symbol(id, args.size), true)
.occurrence(MockController(), listOf(* args), justs, noLogicalContext)
.occurrence(MockController().logicalState(), listOf(* args), justs, noLogicalContext)
fun justifiedOccurrence(id: String, justs: Collection<Int>, vararg args: Any): Occurrence =
justifiedOccurrence(id, justsFromCollection(justs), * args)
@ -201,7 +201,11 @@ private val noLogicalContext = object : LogicalContext {
}
class MockController : Controller {
override fun clearState() {
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.
}
@ -218,11 +222,15 @@ 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 logicalState(): LogicalStateObservable = object : LogicalStateObservable {
override fun addForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver) {
}
override fun removeForwardingObserver(logical: Logical<*>, observer: LogicalObserver) {
override fun removeForwardingObserver(logical: Logical<*>, observer: ForwardingLogicalObserver) {
}
override fun init(controller: Controller): LogicalStateObservable.InitToken {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -162,7 +162,7 @@ class TestStoreAwareJournal {
nchunks shouldBe 4 * 2 // 4 rules, each activates 1 principal occurrence
// try replay the last chunk
replay(mockController, lastPos)
replay(mockController.logicalState(), lastPos)
// should change nothing
storeView().constraintSymbols() shouldBe setOf(sym0("qux"), sym0("lax"))
@ -175,11 +175,11 @@ class TestStoreAwareJournal {
storeView().allOccurrences().count() shouldBe 0
view().chunks.size shouldBe nchunks
replay(mockController, initPos)
replay(mockController.logicalState(), initPos)
storeView().constraintSymbols() shouldBe setOf<ConstraintSymbol>()
replay(mockController, fooPos)
replay(mockController.logicalState(), fooPos)
storeView().constraintSymbols() shouldBe setOf(sym0("foo"))
// although storeView() results change, journal remains the same
@ -231,7 +231,7 @@ class TestStoreAwareJournal {
storeView().allOccurrences().count() shouldBe 0
replay(mockController, savedPos)
replay(mockController.logicalState(), savedPos)
storeView().allOccurrences() shouldBe oldStore
currentPos() shouldBe savedPos
@ -473,7 +473,7 @@ class TestStoreAwareJournal {
// store is not longer valid after removing chunks from history, so reset it
resetStore()
// move to the point where we want to insert new rule
replay(mockController, continueFrom)
replay(mockController.logicalState(), continueFrom)
// according to the history 'qux' wasn't activated at this point & 'bar1' wasn't discarded
storeView().constraintSymbols() shouldBe setOf<ConstraintSymbol>()
@ -498,7 +498,7 @@ class TestStoreAwareJournal {
assertNotEquals(lastPos, currentPos())
// finally, purely go the the end, applying the rest of the history to the store
replay(mockController, lastPos)
replay(mockController.logicalState(), lastPos)
currentPos().chunk shouldBeSame lastPos.chunk // we inserted in the middle -- the last chunk should remain the same
storeView().constraintSymbols() shouldBe setOf(sym0("bar1"), sym0("qux"), sym0("marker"))