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 7c1719e8..ed5bb609 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Dispatcher.kt @@ -112,14 +112,19 @@ class Dispatcher (val ruleIndex: RuleIndex) { } /** - * Returns a new [DispatchingFront] instance which "forgot" that it seen occurrence. - * Need it for incremental reactivations to discern them from reactivations due to logicals. + * Returns a new [DispatchingFront] instance which "forgot" that it has seen occurrence. + * Needed for incremental reactivations to discern them from reactivations due to logicals. */ internal fun forgetSeen(dropped: Occurrence) = forRelatedProbe(dropped) { probe -> probe.forgetSeen(dropped) } + /** + * Returns a new [DispatchingFront] instance which contracts state with this occurrence + * and "forgets" that has seen it or that consumed any matches involving it. + * Needed for pruning outdated unrelevant state on incremental reactivations. + */ internal fun forget(dropped: Occurrence) = forRelatedProbe(dropped) { probe -> probe.contract(dropped).forgetSeen(dropped).forgetConsumed(dropped) 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 846369cb..0d0024da 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 @@ -91,26 +91,11 @@ interface MatchJournal : MutableIterable { * Also serves as a comparator for positions: later in journal means greater. */ interface Index : Comparator { - /** - * Returns [Chunk] where provided occurrence was activated. - * Works not for all occurrences: may return null even if there exists activating chunk. - * Must work for principal occurrences. + * Returns [Chunk] where provided principal occurrence was activated. + * Returns null for non-principal occurrences. */ - fun activatingChunkOf(occId: Id): Chunk? - - /** - * Returns position of the first principal occurrence activated in provided chunk. - * Returns null if the chunk activates no principal occurrences. - */ - fun principalOccurrenceOf(chunk: Chunk): Pos? - - /** - * Find [Pos] by occurrence. - */ - fun principalPos(occ: Id): Pos? = - // just a composition of two operations - activatingChunkOf(occ)?.let { principalOccurrenceOf(it) } + fun activatingChunkOf(occId: Id): OccChunk? /** * Find [Pos] at which provided match was triggered. @@ -121,7 +106,7 @@ interface MatchJournal : MutableIterable { // The latest matched occurrence from match's head is(by definition) // the occurrence which activated this match. match.signature().mapNotNull { occSig -> - occSig?.let { principalPos(it) } + occSig?.let { activatingChunkOf(it)?.toPos() } }.maxWith(this) // compare positions: find latest } @@ -136,11 +121,13 @@ interface MatchJournal : MutableIterable { ) } - abstract class Chunk(val match: RuleMatch, val id: Int, val justifications: Justs) : MatchJournalChunk - { - override fun match(): RuleMatch = match - override fun id(): Int = id - override fun justifications(): TIntSet = justifications + interface Chunk : MatchJournalChunk { + val id: Int + val justifications: Justs + + // fixme: hide rm-mutability + var entries: MutableList + override fun entriesLog(): List = entries data class Entry(val occ: Occurrence, val discarded: Boolean = false) : MatchJournalChunk.Entry { override fun occ(): Occurrence = occ @@ -148,10 +135,9 @@ interface MatchJournal : MutableIterable { override fun toString() = (if (discarded) '-' else '+') + occ.toString() } - fun isDescendantOf(chunkId: Int): Boolean = justifications().contains(chunkId) - fun isTopLevel(): Boolean = justifications().size() <= 1 // this condition implies that there're no ancestor chunks + fun isDescendantOf(chunkId: Int): Boolean = justifications.contains(chunkId) + fun isTopLevel(): Boolean = justifications.size() <= 1 // this condition implies that there're no ancestor chunks - abstract override fun entriesLog(): List fun activatedLog(): List = entriesLog().filter { !it.discarded() }.map { it.occ() as Occurrence } fun discardedLog(): List = entriesLog().filter { it.discarded() }.map { it.occ() as Occurrence } @@ -161,11 +147,31 @@ interface MatchJournal : MutableIterable { */ fun activated(): List = entriesLog().allOccurrences() - override fun toString() = "(id=${id()}, ${justifications()}, ${match().rule().uniqueTag()}, ${entriesLog()})" - fun toPos(): Pos = Pos(this, entriesLog().size) } + class MatchChunk(override val id: Int, val match: RuleMatch) : Chunk { + override val justifications: Justs = match.headJustifications().apply { add(id) } + + override var entries: MutableList = mutableListOf() + + override fun entriesLog(): List = entries + + override fun toString() = "(id=$id, $justifications, ${match.rule().tag()}, $entries)" + } + + class OccChunk(override val id: Int, val occ: Occurrence) : Chunk { + + init { occ.justifications.add(id) } + + override val justifications: Justs + get() = occ.justifications + + override var entries: MutableList = mutableListOf() + + override fun toString() = "(id=$id, $justifications, activation of $occ)" + } + open class Pos(val chunk: Chunk, val entriesCount: Int) { val occ: Occurrence get() = chunk.entriesLog()[entriesCount - 1].occ() @@ -176,6 +182,8 @@ interface MatchJournal : MutableIterable { && other.entriesCount == entriesCount override fun hashCode(): Int = 31 * chunk.hashCode() + entriesCount + + override fun toString(): String = "($chunk, $entriesCount)" } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt index a06fd667..3cb34ed6 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt @@ -20,6 +20,7 @@ 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.internal.MatchJournal.* import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence import jetbrains.mps.logic.reactor.evaluation.MatchJournalView import jetbrains.mps.logic.reactor.evaluation.RuleMatch @@ -31,25 +32,28 @@ import jetbrains.mps.logic.reactor.program.* import jetbrains.mps.logic.reactor.util.Id import java.util.* +//typealias Chunk = MatchJournal.Chunk + internal open class MatchJournalImpl( private val ispec: IncrementalProgramSpec, view: MatchJournalView? = null ) : MatchJournal { // invariant: never empty - private val hist: IteratorMutableList + private val hist: IteratorMutableList private var nextChunkId: Int init { if (view == null) { nextChunkId = 0 - val initChunk = ChunkImpl(InitRuleMatch, nextChunkId, justsOf(nextChunkId)) - nextChunkId++ - hist = IteratorMutableList(LinkedList().apply { add(initChunk) }) + val initChunk = MatchChunk(nextChunkId++, InitRuleMatch) + hist = IteratorMutableList(LinkedList().apply { add(initChunk) }) } else { // assert that initial chunk is present - assert (view.chunks.first().match() is InitRuleMatch) - hist = IteratorMutableList(LinkedList(view.chunks as List)) + with (view.chunks.first()) { + assert(this is MatchChunk && match is InitRuleMatch) + } + hist = IteratorMutableList(LinkedList(view.chunks as List)) nextChunkId = view.nextChunkId } } @@ -57,37 +61,31 @@ internal open class MatchJournalImpl( constructor(view: MatchJournal.View? = null) : this(IncrementalProgramSpec.DefaultSpec, view) // pointer to current position in history where logging (chunk additions) and log erasing (chunk removals) happens - private var posPtr: MutableListIterator = hist.listIterator() + private var posPtr: MutableListIterator = hist.listIterator() // invariant: always contains valid chunk - private var current: ChunkImpl = posPtr.next() // take the initial chunk, move posPtr + private var current: Chunk = posPtr.next() // take the initial chunk, move posPtr - override fun iterator(): MutableIterator = hist.iterator() + override fun iterator(): MutableIterator = hist.iterator() override fun logMatch(match: RuleMatch) { - // Two cases when a new chunk is created: - // either the set of justifications isn't empty - // or we directly know that we deal with a principal rule. - val justs = match.headJustifications() - if (ispec.isPrincipal(match.rule()) || !justs.isEmpty) { - - justs.add(nextChunkId) - val newChunk = ChunkImpl(match, nextChunkId, justs) - posPtr.add(newChunk) - - ++nextChunkId - current = newChunk + if (ispec.isPrincipal(match.rule())) { + current = MatchChunk(nextChunkId++, match) + posPtr.add(current) } - - // Log discards + // Log discarded occurrences (match as RuleMatchImpl).forEachReplaced { occ -> - current.entries.add(MatchJournal.Chunk.Entry(occ, true)) + current.entries.add(Chunk.Entry(occ, true)) } } override fun logActivation(occ: Occurrence) { - current.entries.add(MatchJournal.Chunk.Entry(occ)) + if (ispec.isPrincipal(occ.constraint)) { + current = OccChunk(nextChunkId++, occ) + posPtr.add(current) + } + current.entries.add(Chunk.Entry(occ)) } @@ -138,7 +136,7 @@ internal open class MatchJournalImpl( override fun storeView(): StoreView = StoreViewImpl(allOccurrences()) - override fun index(): MatchJournal.Index = IndexImpl(ispec, hist) + override fun index(): MatchJournal.Index = IndexImpl(hist) private fun allOccurrences(): Sequence { // the following loop doesn't handle this case of starting posPtr, when 'current' isn't valid (e.g. just right after resetPos()) @@ -168,44 +166,22 @@ internal open class MatchJournalImpl( override fun listIterator(index: Int): MutableListIterator = l.listIterator(index) } - private class ChunkImpl(match: RuleMatch, id: Int, justifications: Justs) : MatchJournal.Chunk(match, id, justifications) { - - var entries: MutableList = mutableListOf() - - override fun entriesLog(): List = entries - } - - private class IndexImpl(ispec: IncrementalProgramSpec, chunks: Iterable): MatchJournal.Index + private class IndexImpl(chunks: Iterable): MatchJournal.Index { - private val chunkOrder: Map - // only for principal constraints - private val parentChunks: Map, MatchJournal.Chunk> - - private val principalOccurrences: Map + private val chunkOrder = HashMap() + private val occChunks = HashMap, OccChunk>() init { - chunkOrder = HashMap().apply { - chunks.forEachIndexed { index, chunk -> put(chunk.id(), index) } - } + chunks.forEachIndexed { index, chunk -> + chunkOrder[chunk.id] = index - val m = HashMap, MatchJournal.Chunk>() - val m2 = HashMap() - chunks.forEach { chunk -> - // actually there should be only a single principal occurrence, 'find' is enough - chunk.entriesLog().forEachIndexed { index, e -> - if (ispec.isPrincipal(e.occ.constraint()) && !e.discarded()) { - m[Id(e.occ)] = chunk - m2[chunk.id] = MatchJournal.Pos(chunk, index + 1) - } + if (chunk is OccChunk) { + occChunks[Id(chunk.occ)] = chunk } } - parentChunks = m - principalOccurrences = m2 } - override fun activatingChunkOf(occId: Id) = parentChunks[occId] - - override fun principalOccurrenceOf(chunk: MatchJournal.Chunk) = principalOccurrences[chunk.id] + override fun activatingChunkOf(occId: Id) = occChunks[occId] // todo: throw for invalid positions? override fun compare(lhs: MatchJournal.Pos, rhs: MatchJournal.Pos): Int { @@ -231,19 +207,19 @@ internal open class MatchJournalImpl( object EmptyRule : Rule() { override fun kind(): Kind = Kind.PROPAGATION override fun uniqueTag(): Any = tag().hashCode() - override fun tag(): String = "initialrule${"initial_rule".hashCode()}" + override fun tag(): String = "initialrule${"initialrule".hashCode()}" override fun headKept(): Iterable = emptyList() override fun headReplaced(): Iterable = emptyList() override fun guard(): Iterable = emptyList() - override fun bodyAlternation(): Iterable> = emptyList() // fixme: maybe provide 'main' constraint prod? + override fun bodyAlternation(): Iterable> = emptyList() override fun all(): Iterable = emptyList() } } } -fun MatchJournal.justs() = this.currentPos().chunk.justifications() +fun MatchJournal.justs() = this.currentPos().chunk.justifications -private fun RuleMatch.headJustifications(): Justs { +fun RuleMatch.headJustifications(): Justs { val res: Justs = justsOf() this.matchHeadKept().forEach { it.justifications().let { res.addAll(it) } } this.matchHeadReplaced().forEach { it.justifications().let { res.addAll(it) } } 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 88eb8fae..64f25408 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 @@ -68,7 +68,7 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp private data class ExecPos(val pos: MatchJournal.Pos, val activeOcc: Occurrence) - private data class MatchCandidate(val rule: Rule, val occ: Occurrence, val occParentId: Int) + private data class MatchCandidate(val rule: Rule, val occChunk: MatchJournal.OccChunk) /** @@ -87,13 +87,12 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp while (it.hasNext()) { val chunk = it.next() - val toRemove = ruleIds.contains(chunk.match().rule().uniqueTag()) - if (toRemove) { + if (chunk is MatchJournal.MatchChunk && ruleIds.contains(chunk.match.rule().uniqueTag())) { justificationRoots.add(chunk.id) // 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 - val matchedOccs = chunk.match().allHeads() as Iterable + val matchedOccs = chunk.match.allHeads() as Iterable val (invalidatedOccs, validOccs) = matchedOccs.partition { occ -> occ.justifications().intersects(justificationRoots) } @@ -102,33 +101,36 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp // 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.mapNotNull { occ -> - journalIndex.activatingChunkOf(Id(occ))?.let { chunkPos -> - ExecPos(chunkPos.toPos(), occ) + journalIndex.activatingChunkOf(Id(occ))?.let { chunk -> + ExecPos(chunk.toPos(), occ) } }) } // Invalidating dependent chunks - val toInvalidate = toRemove || chunk.justifications.intersects(justificationRoots) - if (toInvalidate) { - // Remove the chunk from the journal + if (chunk.justifications.intersects(justificationRoots)) { + + // Remove chunk from the journal it.remove() - trace.invalidate(chunk.match()) - - // 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) - - // Need to 'cancel' discarding. - // These nodes may become valid and will be processed due to reactivation of needed occurrences. - chunk.match().matchHeadReplaced().forEach { - dispatchingFront = dispatchingFront.forget(it as Occurrence) - } // 'Undo' all activated in this chunk occurrences chunk.activated().forEach { dispatchingFront = dispatchingFront.forget(it) } + + if (chunk is MatchJournal.MatchChunk) { + trace.invalidate(chunk.match) + + // 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) + + // Need to 'cancel' discarding. + // These nodes may become valid and will be processed due to reactivation of needed occurrences. + chunk.match.matchHeadReplaced().forEach { + dispatchingFront = dispatchingFront.forget(it as Occurrence) + } + } } prevChunk = chunk @@ -146,21 +148,19 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp var prevChunk = chunk while (true) { - // Does this chunk have principal occurrence and can activate anything at all? - journalIndex.principalOccurrenceOf(chunk)?.occ?.let { pOcc -> - + if (chunk is MatchJournal.OccChunk) { // filters out rules using occurrence's arguments - val allRuleCandidates = ruleIndex.forOccurrence(pOcc).map { it.uniqueTag() }.toHashSet() + val allRuleCandidates = ruleIndex.forOccurrence(chunk.occ).map { it.uniqueTag() }.toHashSet() for (rule in rules) { - if (allRuleCandidates.contains(rule.uniqueTag()) && canMatch(rule, pOcc)) { + if (allRuleCandidates.contains(rule.uniqueTag()) && canMatch(rule, chunk.occ)) { // Can this rule be matched by principal occurrence? // Then we will need to find the place among existing child chunks // (i.e. among some number of following ones) // to activate this occurrence, to (possibly) match this rule. // Also remember the parent justification of this rule candidate // to drop it from monitoring when child chunks end. - activationCandidates.add(MatchCandidate(rule, pOcc, chunk.id)) + activationCandidates.add(MatchCandidate(rule, chunk)) // todo: also use the rule to help Dispatcher in future? // i.e. try matching only on the candidate rule } @@ -169,13 +169,14 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp val aIt = activationCandidates.listIterator() while (aIt.hasNext()) { - val (candRule, candOcc, parentId) = aIt.next() + val (candRule, occChunk) = aIt.next() // Place to activate candidate: - // either as the last one, after all existing activations - // or according to the ordering between rules. - val placeToInsertFound = ruleOrdering.compare(chunk.match().rule(), candRule) > 0 - val childChunksEnded = !chunk.isDescendantOf(parentId) + // either according to the ordering between rules. + // or as the last one, after all existing activations + val placeToInsertFound = + chunk is MatchJournal.MatchChunk && ruleOrdering.compare(chunk.match.rule(), candRule) > 0 + val childChunksEnded = !chunk.isDescendantOf(occChunk.id) val pos = // We need the previous chunk as pos here (i.e. adding after it). @@ -184,8 +185,8 @@ internal class ProcessingStateImpl(private var dispatchingFront: Dispatcher.Disp else if (!it.hasNext()) chunk.toPos() else continue - execQueue.offer(ExecPos(pos, candOcc)) - trace.potentialMatch(candOcc, candRule) + execQueue.offer(ExecPos(pos, occChunk.occ)) + trace.potentialMatch(occChunk.occ, candRule) // Drop the candidate if appropriate activation place is found. aIt.remove() } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/MatchJournalChunk.java b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/MatchJournalChunk.java index 65ca9548..758f2f28 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/MatchJournalChunk.java +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/evaluation/MatchJournalChunk.java @@ -16,7 +16,7 @@ package jetbrains.mps.logic.reactor.evaluation; -import gnu.trove.set.TIntSet; +//import gnu.trove.set.TIntSet; import java.util.List; @@ -26,8 +26,7 @@ public interface MatchJournalChunk { boolean discarded(); } - RuleMatch match(); - int id(); - TIntSet justifications(); +// int id(); +// TIntSet justifications(); List entriesLog(); }