⚙️ Utilise des uuid pour cacher l'evaluation dans une structure à part

Plutôt que directement sur le noeud, ce qui aboutissait à des references cycliques (non serialisables)
pull/1278/head
Johan Girod 2020-12-14 11:50:48 +01:00 committed by Johan Girod
parent a3fdb4640b
commit 0d285e82fe
6 changed files with 39 additions and 48 deletions

View File

@ -7,14 +7,13 @@ export default (tracker: Tracker) => {
next(action)
const newState = getState()
if (action.type == 'STEP_ACTION' && action.name == 'fold') {
tracker.debouncedPush([
'trackEvent',
'Simulator::answer',
action.source,
action.step,
situationSelector(newState)[action.step as DottedName],
])
// tracker.debouncedPush([
// 'trackEvent',
// 'Simulator::answer',
// action.source,
// action.step,
// situationSelector(newState)[action.step as DottedName],
// ])
// TODO : add tracking in UI instead ?
// if (!currentQuestionSelector(newState)) {
// tracker.push([

View File

@ -28,6 +28,9 @@ import UneDeCesConditions from './mecanisms/UneDeCesConditions'
import UnePossibilité from './mecanisms/UnePossibilité'
import Unité from './mecanisms/Unité'
import Variations from './mecanisms/Variations'
import { useContext } from 'react'
import { EngineContext } from './contexts'
import { InternalError } from '../error'
const UIComponents = {
constant: ConstantNode,
@ -66,9 +69,19 @@ const UIComponents = {
export default function Explanation({ node }) {
const visualisationKind = node.visualisationKind || node.nodeKind
const engine = useContext(EngineContext)
if (!engine) {
throw new InternalError(node)
}
// On ne veut pas (pour l'instant) déclencher une évaluation juste pour
// l'affichage.
// A voir si cela doit évoluer...
const displayedNode = node.evaluationId ? engine.evaluateNode(node) : node
const Component = UIComponents[visualisationKind]
if (!Component) {
throw new Error(`Unknown visualisation: ${visualisationKind}`)
}
return <Component {...node} />
return <Component {...displayedNode} />
}

View File

@ -289,19 +289,7 @@ export function Leaf(
!node.name.includes(' . ') &&
rule.virtualRule
) {
return (
<Explanation
node={
// Cette logique est un peu hacky car on accède à cache, qui
// est une structure interne de Engine. Seulement, on ne veut
// pas (pour l'instant) déclencher une évaluation juste pour
// l'affichage.
// A voir si cela doit évoluer...
engine?.cache[dottedName] ?? rule
}
/>
)
return <Explanation node={rule} />
}
return (
<div

View File

@ -16,6 +16,7 @@ import { reduceAST, transformAST } from './AST'
const emptyCache = () => ({
_meta: { contextRule: [] },
nodes: new Map(),
})
type Cache = {
@ -31,6 +32,7 @@ type Cache = {
inRecalcul?: boolean
filter?: string
}
nodes: Map<ASTNode, EvaluatedNode>
}
export type EvaluationOptions = Partial<{
@ -60,16 +62,10 @@ export default class Engine<Name extends string = string> {
parsedRules: ParsedRules<Name>
parsedSituation: Record<string, ASTNode> = {}
replacements: Record<string, Array<ReplacementRule>> = {}
cache: Cache
// A number that is incremented every time the situation changes, and is used
// for inline cache invalidation.
situationVersion = 0
cache: Cache = emptyCache()
private warnings: Array<string> = []
constructor(rules: string | Record<string, Rule> | ParsedRules<Name>) {
this.cache = emptyCache()
this.resetCache()
if (typeof rules === 'string') {
this.parsedRules = parsePublicodes(rules) as ParsedRules<Name>
}
@ -88,7 +84,7 @@ export default class Engine<Name extends string = string> {
this.replacements = getReplacements(this.parsedRules)
}
private resetCache() {
resetCache() {
this.cache = emptyCache()
}
@ -96,7 +92,6 @@ export default class Engine<Name extends string = string> {
situation: Partial<Record<Name, string | number | object | ASTNode>> = {}
) {
this.resetCache()
this.situationVersion++
this.parsedSituation = mapObjIndexed((value, key) => {
if (value && typeof value === 'object' && 'nodeKind' in value) {
return value as ASTNode
@ -153,20 +148,19 @@ export default class Engine<Name extends string = string> {
}
evaluateNode<N extends ASTNode = ASTNode>(
node: N & { lastEvaluation?: number; res?: EvaluatedNode }
node: N & { evaluationId?: string }
): N & EvaluatedNode {
if (!node.nodeKind) {
throw Error('The provided node must have a "nodeKind" attribute')
} else if (!evaluationFunctions[node.nodeKind]) {
throw Error(`Unknown "nodeKind": ${node.nodeKind}`)
}
if (!node.res || node.lastEvaluation !== this.situationVersion) {
node.res = evaluationFunctions[node.nodeKind].call(this, node)
node.lastEvaluation = this.situationVersion
let result = this.cache.nodes.get(node)
if (result === undefined) {
result = evaluationFunctions[node.nodeKind].call(this, node)
}
return node.res!
this.cache.nodes.set(node, result!)
return result as N & EvaluatedNode
}
}

View File

@ -55,16 +55,15 @@ export const evaluateInversion: EvaluationFunction<'inversion'> = function (
const evaluatedInversionGoal = this.evaluateNode(inversionGoal)
const unit = 'unit' in node ? node.unit : evaluatedInversionGoal.unit
const originalCache = { ...this.cache }
const originalCache = this.cache
const originalSituation = { ...this.parsedSituation }
let inversionNumberOfIterations = 0
delete this.parsedSituation[inversionGoal.dottedName as string]
const evaluateWithValue = (n: number) => {
inversionNumberOfIterations++
this.cache = {
_meta: { ...originalCache._meta },
}
this.situationVersion++
this.resetCache()
this.cache._meta = { ...originalCache._meta }
this.parsedSituation[node.explanation.ruleToInverse] = {
unit: unit,
nodeKind: 'unité',
@ -141,7 +140,6 @@ export const evaluateInversion: EvaluationFunction<'inversion'> = function (
// // Uncomment to display the two attempts and their result
// console.table([{ x: x1, y: y1 }, { x: x2, y: y2 }])
// console.log('iteration:', inversionNumberOfIterations)
this.situationVersion++
this.cache = originalCache
this.parsedSituation = originalSituation
return {

View File

@ -32,13 +32,13 @@ const evaluateRecalcul: EvaluationFunction<'recalcul'> = function (node) {
serializeUnit(originRule.unit) !== serializeUnit(replacement.unit)
) as Array<[ReferenceNode & EvaluatedNode, EvaluatedNode]>
const originalCache = { ...this.cache }
const originalCache = this.cache
const originalSituation = { ...this.parsedSituation }
// Optimisation : no need for recalcul if situation is the same
const invalidateCache = Object.keys(amendedSituation).length > 0
if (invalidateCache) {
this.cache = { _meta: { ...this.cache._meta, inRecalcul: true } }
this.situationVersion++
this.resetCache()
this.cache._meta = { ...this.cache._meta, inRecalcul: true }
}
this.parsedSituation = {
@ -59,7 +59,6 @@ const evaluateRecalcul: EvaluationFunction<'recalcul'> = function (node) {
this.parsedSituation = originalSituation
if (invalidateCache) {
this.cache = originalCache
this.situationVersion++
}
return {
...node,