diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt index 10a2b9c1..f466d3fb 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt @@ -17,7 +17,6 @@ package jetbrains.mps.logic.reactor.core import com.github.andrewoma.dexx.collection.Maps -import javaslang.collection.Map import jetbrains.mps.logic.reactor.core.internal.RuleMatchImpl import com.github.andrewoma.dexx.collection.Map as PersMap @@ -77,10 +76,13 @@ class Dispatcher (val ruleIndex: RuleIndex) { } } - private constructor(pred: DispatchingFront, consumedMatch: RuleMatchEx) { + private constructor(pred: DispatchingFront, consumedMatch: RuleMatchEx, isForgetting: Boolean) { this.ruletag2probe = pred.ruletag2probe pred.ruletag2probe[consumedMatch.rule().uniqueTag()]?.let { - this.ruletag2probe = ruletag2probe.put(consumedMatch.rule().uniqueTag(), it.consume(consumedMatch)) + this.ruletag2probe = ruletag2probe.put( + consumedMatch.rule().uniqueTag(), + if (!isForgetting) it.consume(consumedMatch) else it.forget(consumedMatch) + ) } } @@ -112,11 +114,29 @@ class Dispatcher (val ruleIndex: RuleIndex) { probe.contract(discarded) }) + /** + * Returns a new [DispatchingFront] instance that completely "forgot" occurrence: + * forgot all consumed matches involving it & is contracted on it accordingly. + */ + fun forget(dropped: Occurrence) = DispatchingFront( + // note contraction, it's needed to avoid adding dropped matches back again to allMatches + this.contract(dropped), + ruleIndex.forOccurrence(dropped).mapNotNull { rule -> + ruletag2probe[rule.uniqueTag()] + }.map { probe -> + probe.forget(dropped) + }) + /** * Serves to indicate that the specified [RuleMatchEx] has been processed (consumed) and has to * be excluded from any further "match" set returned by [matches]. */ - internal fun consume(ruleMatch: RuleMatchEx) = DispatchingFront(this, ruleMatch) + internal fun consume(ruleMatch: RuleMatchEx) = DispatchingFront(this, ruleMatch, false) + + /** + * Forgets that the specified [RuleMatchEx] has been consumed. + */ + internal fun forget(ruleMatch: RuleMatchEx) = DispatchingFront(this, ruleMatch, true) } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt index 7567d108..500008bd 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchingProbe.kt @@ -33,10 +33,14 @@ interface RuleMatchingProbe { fun consume(ruleMatch: RuleMatchEx): RuleMatchingProbe + fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe + fun expand(occ: Occurrence): RuleMatchingProbe fun expand(occ: Occurrence, mask: BitSet): RuleMatchingProbe fun contract(occ: Occurrence): RuleMatchingProbe + fun forget(occ: Occurrence): RuleMatchingProbe + } \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt index 1633ec50..71d6fe61 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt @@ -53,7 +53,7 @@ internal class ControllerImpl ( fun incrLaunch(constraint: Constraint, rulesDiff: RulesDiff): FeedbackStatus { // todo: use profiler here? state.invalidateByRules(rulesDiff.removed) - state.addRuleApplications(rulesDiff.added) + state.addRuleMatches(rulesDiff.added) val status = state.launchQueue(this) return status } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt index 5b97d00d..cddb81b6 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt @@ -27,6 +27,7 @@ import jetbrains.mps.logic.reactor.program.* import jetbrains.mps.logic.reactor.util.Id import java.util.* import kotlin.collections.ArrayList +import kotlin.collections.HashMap interface MatchJournal : MutableIterable { @@ -34,6 +35,8 @@ interface MatchJournal : MutableIterable { fun logMatch(match: RuleMatch) fun logActivation(occ: Occurrence) + fun activatingChunkOf(occ: Occurrence): Chunk? + fun currentPos(): Pos fun resetPos() fun reset(pastPos: Pos) @@ -51,17 +54,23 @@ interface MatchJournal : MutableIterable { override fun toString() = (if (isDiscarded) '-' else '+') + occ.toString() } - abstract fun entries(): List - fun activated(): List = entries().filter { !it.isDiscarded }.map { it.occ } - fun discarded(): List = entries().filter { it.isDiscarded }.map { it.occ } + abstract fun entriesLog(): List + fun activatedLog(): List = entriesLog().filter { !it.isDiscarded }.map { it.occ } + fun discardedLog(): List = entriesLog().filter { it.isDiscarded }.map { it.occ } + // Get the resulting set of activated occurrences + fun activated(): List = HashSet>().apply { + entriesLog().forEach { + if (it.isDiscarded) remove(Id(it.occ)) else add(Id(it.occ)) + } + }.map { it.wrapped } abstract fun findOccurrence(ctr: Constraint): Occurrence? abstract fun principalConstraint(): Constraint? - override fun toString() = "(id=$id, $justifications, ${match.rule().uniqueTag()}, ${entries()})" + override fun toString() = "(id=$id, $justifications, ${match.rule().uniqueTag()}, ${entriesLog()})" override fun chunk(): Chunk = this - override fun entriesInChunk(): Int = entries().size + override fun entriesInChunk(): Int = entriesLog().size } abstract class Pos { @@ -77,7 +86,7 @@ interface MatchJournal : MutableIterable { internal open class MatchJournalImpl( - private val ispec: IncrementalProgramSpec, + protected val ispec: IncrementalProgramSpec, view: MatchJournal.View? = null ) : MatchJournal { @@ -103,6 +112,8 @@ internal open class MatchJournalImpl( private var pos: MutableListIterator = hist.listIterator() private var current: ChunkImpl = pos.next() // take the initial chunk, move pos + private val parentChunksIndex = HashMap() + override fun iterator(): MutableIterator = hist.iterator() @@ -133,6 +144,18 @@ internal open class MatchJournalImpl( } + override fun activatingChunkOf(occ: Occurrence): MatchJournal.Chunk? { + if (ispec.isPrincipal(occ.constraint)) { + //todo: maintain index and find from index + // maintain, build from view, anything else? + return parentChunksIndex[occ] + + } + // todo: for non-principal find manually... need it? + return null + } + + override fun currentPos(): MatchJournal.Pos = current // fixme: unclear, whether this makes sense here, in "pure" journal without store? along with reset and replay? @@ -178,7 +201,7 @@ internal open class MatchJournalImpl( val set = HashSet>() for (chunk in hist) { // initial chunk is counted too - chunk.entries().forEach { + chunk.entriesLog().forEach { if (it.isDiscarded) set.remove(Id(it.occ)) else set.add(Id(it.occ)) } if (chunk === current) { @@ -193,7 +216,7 @@ internal open class MatchJournalImpl( { var occurrences: MutableList = mutableListOf() - override fun entries(): List = occurrences + override fun entriesLog(): List = occurrences override fun findOccurrence(ctr: Constraint): Occurrence? = occurrences.find { !it.isDiscarded && it.occ.constraint.symbol() == ctr.symbol() }?.occ diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt index c057ada7..2134f56a 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ProcessingStateImpl.kt @@ -103,7 +103,7 @@ internal open class StateFrameStack() : ProcessingState, LogicalObserver internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.DispatchingFront, - journal: MatchJournal, + journal: MatchJournalImpl, ruleIndex: RuleIndex, val trace: EvaluationTrace = EvaluationTrace.NULL, val profiler: Profiler? = null) @@ -128,14 +128,68 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp } fun invalidateByRules(ruleIds: Set) { -// for (ruleTag in rulesDiff.removed) { -// -// } + val justificationRoots = mutableListOf() + + val it = this.iterator() + var prevChunk = it.next() // skip initial chunk + + while (it.hasNext()) { + val chunk = it.next() + + val toRemove = ruleIds.contains(chunk.match.rule().uniqueTag()) + if (toRemove) { + justificationRoots.add(chunk.id) + // Remove the chunk from the journal + it.remove() + + // We removed the match, so need to reactivate all still valid occurrences from the head + // by definition of Chunk and principal rule, all occurrences from the head are principal + // todo: check/assert it + + val matchedOccs = chunk.match.allHeads() as Iterable + val (invalidatedOccs, validOccs) = matchedOccs.partition { occ -> + occ.justifications().intersects(justificationRoots) + } + assert(invalidatedOccs.isEmpty()) + + // todo: do need to reactivate only the main, matching~activating match? + // (i.e. don't reactivate additional, inactive heads that only completed the match?) + execQueue.addAll(validOccs.map { occ -> + val chunkPos = activatingChunkOf(occ) + if (chunkPos != null) ExecPos(chunkPos, occ) else null + }.filterNotNull()) + + continue + } + + val toInvalidate = chunk.justifications.intersects(justificationRoots) + if (toInvalidate) { + // Remove the chunk from the journal + it.remove() + + // Seems, it's not strictly necessary, because some of its head occurrences are anyway invalidated forever + // and storing this invalid consumed match can make no harm, except some memory overhead. + // fixme: move Chunk's interface to RuleMatchEx instead of RuleMatch + dispatchingFront = dispatchingFront.forget(chunk.match as RuleMatchEx) + + // 'Undo' all activated in this chunk occurrences and related to them matches. + chunk.activated().forEach { + dispatchingFront = dispatchingFront.forget(it) + } + } + + prevChunk = chunk + } } + private fun Justs.intersects(other: Iterable): Boolean = other.any { this.contains(it) } + private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int) - fun addRuleApplications(rules: Iterable) { + // todo: what if non-principal rules will be passed as input there? + // only rules that can match on principal occurrences will pass through this method to the queue + // so, no non-principal rule should pass it. + fun addRuleMatches(rules: Iterable) { val activationCandidates = mutableListOf() val it = this.iterator() @@ -298,5 +352,6 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp } - private fun RuleMatch.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored } + private fun RuleMatch.allHeads() = matchHeadKept() + matchHeadReplaced() + private fun RuleMatch.allStored() = allHeads().all { co -> (co as Occurrence).stored } } \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt index ba31e3e4..7b3dd19f 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ReteRuleMatcherImpl.kt @@ -347,12 +347,20 @@ internal class ReteRuleMatcherImpl(val rule: Rule) : RuleMatcher { return this } + override fun forget(occ: Occurrence): ReteNetwork { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + override fun matches(): Collection = generations.last().matches() override fun consume(ruleMatch: RuleMatchEx): RuleMatchingProbe { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } + override fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + fun indexOf(occurrence: ConstraintOccurrence): Int = seenOcc2Idx[occurrence] ?: (nextOccIdx++).also { idx -> seenOcc2Idx[occurrence] = idx } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt index c820bcaa..0efe0d9f 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/RuleMatcherImpl.kt @@ -66,6 +66,12 @@ internal class RuleMatcherImpl(private val ruleLookup: RuleLookup, consumedSignatures.add(ruleMatch.signature()), genId) + override fun forget(ruleMatch: RuleMatchEx): RuleMatchingProbe = + RuleMatchFront(nodes, + seenOccurrences, + consumedSignatures.remove(ruleMatch.signature()), + genId) + override fun expand(occ: Occurrence): RuleMatchingProbe = expand(occ, bitSetOfOnes(head.size)) @@ -94,6 +100,11 @@ internal class RuleMatcherImpl(private val ruleLookup: RuleLookup, return RuleMatchFront(newNodes, seenOccurrences, consumedSignatures, genId + 1) } + override fun forget(occ: Occurrence): RuleMatchFront { + val newConsumed = Sets.copyOf(consumedSignatures.filter{ !it.contains(Id(occ)) }) + return RuleMatchFront(nodes, seenOccurrences, newConsumed, genId) + } + } open inner class MatchNode(val subst: Subst, val vacant: BitSet = bitSetOfOnes(head.size)) {