⚙️ 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
parent
a3fdb4640b
commit
0d285e82fe
|
@ -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([
|
||||
|
|
|
@ -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} />
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue