Alternative solution for invalidating feedback

Introduce feedback basis to track dependencies on rules.
Index journal chunks by evidence for quick lookup.

The feedback basis is the collection of principal rules tags
from the justifications of the current match.
This commit is contained in:
Fedor Isakov 2021-03-31 18:38:28 +02:00
parent 7c89dfdffd
commit c51a984648
9 changed files with 51 additions and 24 deletions

View File

@ -33,12 +33,12 @@ class CompositeFeedback private constructor(private val elements: List<Feedback>
this.severity = maxSeverity(elements)
}
override fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, supervisor: Supervisor): Boolean {
override fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: List<Any>, supervisor: Supervisor): Boolean {
var unhandled = 0
for (feedback in elements) {
if (!feedback.alreadyHandled()) {
unhandled += 1
if (supervisor.handleFeedback(currentRuleMatch, feedbackKey, feedback)) {
if (supervisor.handleFeedback(currentRuleMatch, feedbackKey, feedbackBasis, feedback)) {
feedback.setHandled()
unhandled -= 1
}

View File

@ -40,8 +40,8 @@ abstract class Feedback : EvaluationFeedback() {
/**
* Returns true if the feedback has been handled.
*/
open fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, supervisor: Supervisor): Boolean {
if (!alreadyHandled() && supervisor.handleFeedback(currentRuleMatch, feedbackKey, this)) {
open fun handle(currentRuleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: List<Any>, supervisor: Supervisor): Boolean {
if (!alreadyHandled() && supervisor.handleFeedback(currentRuleMatch, feedbackKey, feedbackBasis, this)) {
setHandled()
}
return alreadyHandled()

View File

@ -30,7 +30,6 @@ typealias Evidence = Int
*/
typealias Justifications = TIntSet
fun emptyJustifications(): Justifications = TIntHashSet(1)
fun justsOf(vararg elements: Evidence): Justifications = TIntHashSet(elements)
fun justsFromCollection(collection: Collection<Evidence>): Justifications = TIntHashSet(collection)

View File

@ -167,7 +167,10 @@ internal class ControllerImpl (
if (itemOk) {
context.withStatus { status ->
if (status.feedback?.alreadyHandled() == false) {
status.feedback.handle(match, newParent.match.feedbackKey, supervisor)
status.feedback.handle(match,
newParent.match.feedbackKey,
processing.principalRuleTags(newParent),
supervisor)
}
}
@ -188,7 +191,10 @@ internal class ControllerImpl (
// if failure can be handled here then recover
} else if (status.feedback?.alreadyHandled() == false
&& status.failure.handle(match, newParent.match.feedbackKey, supervisor)) {
&& status.failure.handle(match,
newParent.match.feedbackKey,
processing.principalRuleTags(newParent),
supervisor)) {
status.recover()

View File

@ -16,6 +16,7 @@
package jetbrains.mps.logic.reactor.core.internal
import gnu.trove.TIntObjectHashMap
import jetbrains.mps.logic.reactor.core.*
import jetbrains.mps.logic.reactor.evaluation.*
import jetbrains.mps.logic.reactor.program.*
@ -23,8 +24,10 @@ import jetbrains.mps.logic.reactor.util.Id
import java.util.*
import kotlin.Comparator
typealias ChunkIndex = TIntObjectHashMap<MatchJournal.Chunk>
interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
interface MatchJournal : EvidenceSource {
/**
* Add new [Chunk] for matches of principal rules.
@ -68,7 +71,7 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
/**
* Returns read-only [JournalIterator] that allows to remove future [Chunk]s.
*/
override fun iterator(): JournalIterator
fun iterator(): JournalIterator
/**
* Returns internal [JournalIterator] that allows to remove future [Chunk]s.
@ -118,6 +121,7 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
*/
fun index(): Index
fun principalRuleTags(chunk: Chunk): List<Any>
/**
* Simplifies some search operations on [MatchJournal].
@ -165,9 +169,9 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
* Length of the indexed [MatchJournal]
*/
val size: Int
}
/**
* Immutable snapshot of [MatchJournal].
*/
@ -178,7 +182,6 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
override fun getStoreView(): StoreView = StoreViewImpl(
chunks.flatMap { it.entries() }.allOccurrences().asSequence()
)
override fun getPreamble(info: PreambleInfo): View {
val cornerChunk = chunks.first() // corner chunk at beginning
val initialChunk = chunks[1]
@ -209,6 +212,7 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
* the next [Chunk] was logged.
*/
interface Chunk : Justified {
/**
* Corresponds to event of activated or discarded [Occurrence]
*/
@ -235,7 +239,6 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
* Same as [entries], but returns only discarded [Occurrence]s.
*/
fun discardedLog(): List<Occurrence> = entries().filter { it.discarded }.map { it.occ }
fun toPos(): Pos = Pos(this, entries().size)
}
@ -243,6 +246,7 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
* [Chunk] corresponding to a [RuleMatch] of a principal [Rule].
*/
interface MatchChunk : Chunk {
/**
* Returns true if this [Chunk] depends on changes to specified rule.
* Relevant for rules with origin. Doesn't include rule from [match].
@ -253,7 +257,6 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
* [RuleMatch] which defines this [Chunk]
*/
val match: RuleMatch
val ruleUniqueTag: Any get() = match.rule().uniqueTag()
}
@ -265,16 +268,15 @@ interface MatchJournal : Iterable<MatchJournal.Chunk>, EvidenceSource {
}
open class Pos(val chunk: Chunk, val entriesCount: Int) {
override fun equals(other: Any?) =
other is Pos
&& other.chunk === chunk
&& other.entriesCount == entriesCount
override fun hashCode(): Int = 31 * chunk.hashCode() + entriesCount
override fun toString(): String = "($chunk, $entriesCount)"
}
}

View File

@ -64,6 +64,8 @@ internal open class MatchJournalImpl(
// invariant: never empty
private val hist: IteratorMutableList<ChunkImpl>
private val chunkIndex = ChunkIndex()
// pointer to current position where logging (chunk additions) and log erasing (chunk removals) happen
// initially points at initial chunk
private val __cursor: Cursor
@ -133,6 +135,13 @@ internal open class MatchJournalImpl(
return added
}
private fun indexChunk(chunk: Chunk) {
chunkIndex.put(chunk.evidence, chunk)
}
private fun lookupChunkByEvidence(evidence: Evidence) =
chunkIndex.get(evidence)
private fun trackAncestor(logEvent: Justified) {
while (!logEvent.justifiedBy(parentChunk())) {
ancestorChunksStack.pop()
@ -234,6 +243,20 @@ internal open class MatchJournalImpl(
override fun index(): MatchJournal.Index = IndexImpl(hist)
override fun principalRuleTags(chunk: Chunk): List<Any> {
val ptags = mutableListOf<Any>()
chunk.justifications().forEach { jn ->
// hist is sequential, random access can be expensive
(lookupChunkByEvidence(jn) as? MatchChunk)?.let {
if (it.match.isPrincipal) {
ptags.add(it.ruleUniqueTag)
}
}
true
}
return ptags
}
private fun allOccurrences(): Sequence<Occurrence> {
// the following loop doesn't handle this case of starting posPtr, when 'current' isn't valid (e.g. just right after resetPos())
if (__cursor.atStart()) return emptySequence()
@ -320,6 +343,7 @@ internal open class MatchJournalImpl(
override fun add(chunk: Chunk) {
it.add(chunk as ChunkImpl)
__current = chunk
indexChunk(chunk)
}
override fun removeNext() {
@ -328,7 +352,6 @@ internal open class MatchJournalImpl(
updateNext()
}
/**
* Walk in journal in direct order ([from] -> [current])
* while applying [action] to each visited [Chunk]
@ -380,7 +403,6 @@ internal open class MatchJournalImpl(
}
}
/**
* Immutable view for MutableList that provides mutability only through its iterators
*/
@ -453,7 +475,6 @@ internal open class MatchJournalImpl(
}
}
/**
* Mock RuleMatch for use only in initial Chunk
*/

View File

@ -34,8 +34,5 @@ public interface Supervisor {
* Override this method in order to "handle" the feedback.
* Returns true if the method has handled (consumed) the feedback.
*/
default boolean handleFeedback(RuleMatch ruleMatch, Object feedbackKey, EvaluationFeedback feedback) {
return false;
}
boolean handleFeedback(RuleMatch ruleMatch, Object feedbackKey, List<Object> feedbackBasis, EvaluationFeedback feedback);
}

View File

@ -3,7 +3,9 @@
*/
import jetbrains.mps.logic.reactor.core.RulesDiff
import jetbrains.mps.logic.reactor.evaluation.EvaluationFeedback
import jetbrains.mps.logic.reactor.evaluation.InvocationContext
import jetbrains.mps.logic.reactor.evaluation.RuleMatch
import jetbrains.mps.logic.reactor.evaluation.Supervisor
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.logical.MetaLogical
@ -128,7 +130,7 @@ open class MockSupervisor : Supervisor {
else a
}
// override fun handleFeedback(rule: Rule?, feedback: EvaluationFeedback?): Boolean = false
override fun handleFeedback(ruleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: MutableList<Any>, feedback: EvaluationFeedback): Boolean = false
}
class MockConstraintRegistry() {

View File

@ -68,7 +68,7 @@ class TestController {
vararg occurrences: ConstraintOccurrence): Controller {
val program = MockProgram("test", rulesLists, registry = MockConstraintRegistry())
val supervisor = object : MockSupervisor() {
override fun handleFeedback(ruleMatch: RuleMatch, feedbackKey: Any, feedback: EvaluationFeedback): Boolean =
override fun handleFeedback(ruleMatch: RuleMatch, feedbackKey: Any, feedbackBasis: MutableList<Any>, feedback: EvaluationFeedback): Boolean =
feedbackHandler(ruleMatch, feedback)
}
MockSession.init(program, supervisor)