Engine in web worker
parent
d3a59fffb5
commit
54bcc94227
|
@ -46,6 +46,7 @@ module.exports = {
|
|||
files: ['**/*.{ts,tsx}'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
tsconfigRootDir,
|
||||
project: ['*/tsconfig.json'],
|
||||
|
@ -72,7 +73,10 @@ module.exports = {
|
|||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': [
|
||||
'warn',
|
||||
{ additionalHooks: 'useAsyncData' },
|
||||
{
|
||||
additionalHooks:
|
||||
'usePromise|useLazyPromise|usePromiseOnSituationChange',
|
||||
},
|
||||
],
|
||||
|
||||
'@typescript-eslint/no-unsafe-call': 'warn',
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
import rawRules, { DottedName } from 'modele-social'
|
||||
import Engine, { Rule } from 'publicodes'
|
||||
|
||||
import type { ProviderProps } from '@/components/Provider'
|
||||
|
||||
import i18n from './locales/i18n'
|
||||
import ruleTranslations from './locales/rules-en.yaml'
|
||||
import translateRules from './locales/translateRules'
|
||||
|
||||
type Rules = Record<DottedName, Rule>
|
||||
|
||||
const unitsTranslations = Object.entries(
|
||||
i18n.getResourceBundle('fr', 'units') as Record<string, string>
|
||||
)
|
||||
const engineOptions = {
|
||||
getUnitKey(unit: string): string {
|
||||
const key = unitsTranslations
|
||||
.find(([, trans]) => trans === unit)?.[0]
|
||||
.replace(/_plural$/, '')
|
||||
|
||||
return key || unit
|
||||
},
|
||||
}
|
||||
|
||||
let warnCount = 0
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
const logger = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
warn: (message: string) => {
|
||||
// console.warn(message)
|
||||
|
||||
warnCount++
|
||||
timeout !== null && clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('⚠️', warnCount, 'warnings in the engine')
|
||||
warnCount = 0
|
||||
}, 1000)
|
||||
},
|
||||
error: (message: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message)
|
||||
},
|
||||
log: (message: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message)
|
||||
},
|
||||
}
|
||||
|
||||
export function engineFactory(rules: Rules, options = {}) {
|
||||
return new Engine(rules, { ...engineOptions, ...options, logger })
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type Actions =
|
||||
| {
|
||||
action: 'init'
|
||||
params: [{ basename: ProviderProps['basename'] }]
|
||||
result: void
|
||||
}
|
||||
| {
|
||||
action: 'setSituation'
|
||||
params: Parameters<Engine<DottedName>['setSituation']>
|
||||
result: void
|
||||
}
|
||||
| {
|
||||
action: 'evaluate'
|
||||
params: Parameters<Engine<DottedName>['evaluate']>
|
||||
result: ReturnType<Engine<DottedName>['evaluate']>
|
||||
}
|
||||
| {
|
||||
action: 'getRule'
|
||||
params: Parameters<Engine<DottedName>['getRule']>
|
||||
result: ReturnType<Engine<DottedName>['getRule']>
|
||||
}
|
||||
| {
|
||||
action: 'getParsedRules'
|
||||
params: []
|
||||
result: ReturnType<Engine<DottedName>['getParsedRules']>
|
||||
}
|
||||
| {
|
||||
action: 'shallowCopy'
|
||||
params: []
|
||||
result: void
|
||||
}
|
||||
| {
|
||||
action: 'deleteShallowCopy'
|
||||
params: [{ engineId: number }]
|
||||
result: void
|
||||
}
|
||||
|
||||
type GenericParams = {
|
||||
/**
|
||||
* The id of the engine to use, the default engine is 0
|
||||
*/
|
||||
engineId?: number
|
||||
|
||||
/**
|
||||
* The id of the message, used to identify the response
|
||||
*/
|
||||
id: number
|
||||
}
|
||||
|
||||
export type Action<T extends Actions['action']> = Extract<
|
||||
Actions,
|
||||
{ action: T }
|
||||
>
|
||||
|
||||
let engines: (Engine<DottedName> | undefined)[] = []
|
||||
|
||||
let setDefaultEngineReady: (() => void) | null = null
|
||||
const isDefaultEngineReady = new Promise(
|
||||
(resolve) => (setDefaultEngineReady = resolve as () => void)
|
||||
)
|
||||
|
||||
onmessage = async (e) => {
|
||||
console.log('[onmessage]', e.data)
|
||||
|
||||
const { engineId = 0, id, action, params } = e.data as Actions & GenericParams
|
||||
|
||||
try {
|
||||
if (action === 'init') {
|
||||
const [{ basename }] = params
|
||||
try {
|
||||
let rules = rawRules
|
||||
if (basename === 'infrance') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
rules = translateRules('en', ruleTranslations, rules)
|
||||
}
|
||||
|
||||
const engineId = engines.length
|
||||
engines.push(engineFactory(rules))
|
||||
console.log('[engine ready]', engines[engineId])
|
||||
postMessage({ engineId, id })
|
||||
setDefaultEngineReady?.()
|
||||
} catch (e) {
|
||||
console.error('[error]', e)
|
||||
// postMessage('error')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await isDefaultEngineReady
|
||||
|
||||
const engine = engines[engineId]
|
||||
if (!engine) {
|
||||
throw new Error('Engine does not exist')
|
||||
}
|
||||
|
||||
if (action === 'setSituation') {
|
||||
// safeSetSituation(
|
||||
// engine,
|
||||
// ({ situation, <faultyDottedName> }) => {
|
||||
// console.error('setSituation', { situation, faultyDottedName })
|
||||
// },
|
||||
// ...params
|
||||
// )
|
||||
engine.setSituation(...params)
|
||||
|
||||
return postMessage({ engineId, id })
|
||||
} else if (action === 'evaluate') {
|
||||
const result = engine.evaluate(...params)
|
||||
console.log('[result]', result)
|
||||
|
||||
return postMessage({ engineId, id, result })
|
||||
} else if (action === 'getRule') {
|
||||
const result = engine.getRule(...params)
|
||||
|
||||
return postMessage({ engineId, id, result })
|
||||
} else if (action === 'getParsedRules') {
|
||||
const result = engine.getParsedRules()
|
||||
|
||||
return postMessage({ engineId, id, result })
|
||||
} else if (action === 'shallowCopy') {
|
||||
const result = engine.shallowCopy()
|
||||
engines.push(result)
|
||||
|
||||
return postMessage({ engineId: engines.length - 1, id })
|
||||
} else if (action === 'deleteShallowCopy') {
|
||||
if (engineId === 0) {
|
||||
throw new Error('Cannot delete the default engine')
|
||||
}
|
||||
delete engines[engineId]
|
||||
|
||||
// false positive warning from eslint
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
const lastIndex: number = engines.findLastIndex(
|
||||
(el) => el instanceof Engine
|
||||
)
|
||||
engines = lastIndex >= 0 ? engines.splice(0, lastIndex) : engines
|
||||
|
||||
console.log('[engines]', engines)
|
||||
|
||||
return postMessage({ engineId, id })
|
||||
} else {
|
||||
console.log('[Message inconu]', e.data)
|
||||
}
|
||||
} catch (error) {
|
||||
return postMessage({ engineId, id, error })
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import { DependencyList, useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Executes a function that returns a promise (when dependencies change)
|
||||
* and returns the result of the promise when completed
|
||||
*/
|
||||
export const useAsyncData = <T, U = null>(
|
||||
getAsyncData: () => Promise<T>,
|
||||
defaultValue: U | null = null,
|
||||
deps: DependencyList = []
|
||||
): T | U | null => {
|
||||
const [state, setState] = useState<T | U | null>(defaultValue)
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
setState(await getAsyncData())
|
||||
})()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, deps)
|
||||
|
||||
return state
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { DependencyList, useCallback, useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Execute an asynchronous function and return its result (Return default value if the promise is not finished).
|
||||
* The function is executed each time the dependencies change.
|
||||
*/
|
||||
export const usePromise = <T, Default = undefined>(
|
||||
promise: () => Promise<T>,
|
||||
deps: DependencyList = [],
|
||||
defaultValue?: Default
|
||||
) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const [state, lazyPromise] = useLazyPromise(promise, deps, defaultValue)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => void lazyPromise(), deps)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an asynchronous function and return its result (Return default value if the promise is not finished).
|
||||
* Use this hook if you want to fire the promise manually.
|
||||
*/
|
||||
export const useLazyPromise = <
|
||||
T,
|
||||
Params extends unknown[],
|
||||
Default = undefined
|
||||
>(
|
||||
promise: (...params: Params) => Promise<T>,
|
||||
deps: DependencyList = [],
|
||||
defaultValue?: Default
|
||||
) => {
|
||||
// console.log('===>', defaultValue)
|
||||
const [state, setState] = useState<T | Default>(defaultValue as Default)
|
||||
|
||||
const lazyPromise = useCallback(
|
||||
async (...params: Params) => {
|
||||
// console.log('====', defaultValue)
|
||||
|
||||
const result = await promise(...params)
|
||||
setState(result)
|
||||
|
||||
return result as T
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
deps
|
||||
)
|
||||
|
||||
return [state, lazyPromise] as const
|
||||
}
|
|
@ -5,7 +5,7 @@ import { Strong } from '@/design-system/typography'
|
|||
import { H5 } from '@/design-system/typography/heading'
|
||||
import { Li, Ul } from '@/design-system/typography/list'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import { useAsyncData } from '@/hooks/useAsyncData'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
import { capitalise0 } from '@/utils'
|
||||
|
||||
const lazyApeToGuichet = () => import('@/public/data/ape-to-guichet.json')
|
||||
|
@ -16,8 +16,8 @@ type Guichet = typeof import('@/public/data/guichet.json')
|
|||
export type GuichetEntry = Guichet[keyof Guichet]
|
||||
|
||||
export function useGuichetInfo(codeApe?: string): GuichetEntry[] | null {
|
||||
const guichet = useAsyncData(lazyGuichet)?.default
|
||||
const apeToGuichet = useAsyncData(lazyApeToGuichet)?.default
|
||||
const guichet = usePromise(lazyGuichet)?.default
|
||||
const apeToGuichet = usePromise(lazyApeToGuichet)?.default
|
||||
|
||||
return useMemo(() => {
|
||||
if (!codeApe || !guichet || !apeToGuichet || !(codeApe in apeToGuichet)) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { VisibleRadio } from '@/design-system/field/Radio/Radio'
|
|||
import { RadioCardSkeleton } from '@/design-system/field/Radio/RadioCard'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import { SmallBody } from '@/design-system/typography/paragraphs'
|
||||
import { useAsyncData } from '@/hooks/useAsyncData'
|
||||
import { usePromise } from '@/hooks/usePromise'
|
||||
|
||||
import { Result } from './Result'
|
||||
|
||||
|
@ -102,12 +102,15 @@ export default function SearchCodeAPE({
|
|||
[]
|
||||
)
|
||||
|
||||
const lazyData = useAsyncData(() => import('@/public/data/ape-search.json'))
|
||||
const lazyData = usePromise(() => import('@/public/data/ape-search.json'))
|
||||
|
||||
const lastIdxs = useRef<Record<string, UFuzzy.HaystackIdxs>>({})
|
||||
const prevValue = useRef<string>(searchQuery)
|
||||
|
||||
const buildedResearch = useMemo(() => buildResearch(lazyData), [lazyData])
|
||||
const buildedResearch = useMemo(
|
||||
() => lazyData && buildResearch(lazyData),
|
||||
[lazyData]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!lazyData || !buildedResearch) {
|
||||
|
|
|
@ -283,8 +283,25 @@ export const generateUuid = () => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if x is not null, useful for filtering out nulls from arrays
|
||||
* Returns true if value is not null, useful for filtering out nulls from arrays
|
||||
* @example [1, null, 2].filter(isNotNull) // [1, 2]
|
||||
* @param x
|
||||
* @param value
|
||||
*/
|
||||
export const isNotNull = <T>(x: T | null): x is T => x !== null
|
||||
export const isNotNull = <T>(value: T | null): value is T => value !== null
|
||||
|
||||
/**
|
||||
* Returns true if value is not undefined, useful for filtering out undefined from arrays
|
||||
* @example [1, undefined, 2].filter(isDefined) // [1, 2]
|
||||
* @param value
|
||||
*/
|
||||
export const isDefined = <T>(value: T | undefined): value is T =>
|
||||
value !== undefined
|
||||
|
||||
/**
|
||||
* Returns true if value is not null or undefined, useful for filtering out nulls and undefined from arrays
|
||||
* @example [1, null, undefined, 2].filter(isNotNullOrUndefined) // [1, 2]
|
||||
* @param value
|
||||
*/
|
||||
export const isNotNullOrUndefined = <T>(
|
||||
value: T | null | undefined
|
||||
): value is T => isNotNull(value) && isDefined(value)
|
||||
|
|
|
@ -0,0 +1,350 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import React, { DependencyList, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { ProviderProps } from './components/Provider'
|
||||
import type { Action, Actions } from './engine.worker'
|
||||
import EngineWorker from './engine.worker?worker'
|
||||
import { usePromise } from './hooks/usePromise'
|
||||
|
||||
if (!window.Worker) {
|
||||
throw new Error('Worker is not supported in this browser')
|
||||
}
|
||||
|
||||
interface WorkerEngine {
|
||||
worker: Worker
|
||||
postMessage: <T extends Actions['action'], U extends Action<T>>(
|
||||
engineId: number,
|
||||
action: T,
|
||||
...params: U['params']
|
||||
) => Promise<U['result']>
|
||||
isWorkerReady: Promise<void>
|
||||
promises: {
|
||||
resolve: (value: unknown) => void
|
||||
reject: (value: unknown) => void
|
||||
}[]
|
||||
}
|
||||
|
||||
const initWorkerEngine = (
|
||||
basename: ProviderProps['basename'],
|
||||
setSituationVersion: (updater: (situationVersion: number) => number) => void
|
||||
) => {
|
||||
const newWorker: Worker = new EngineWorker()
|
||||
|
||||
const promises: WorkerEngine['promises'] = []
|
||||
|
||||
const postMessage = async <T extends Actions['action'], U extends Action<T>>(
|
||||
engineId: number,
|
||||
action: T,
|
||||
...params: U['params']
|
||||
) => {
|
||||
if (action === 'setSituation') {
|
||||
setSituationVersion((situationVersion) => situationVersion + 1)
|
||||
}
|
||||
|
||||
const warning = setTimeout(() => {
|
||||
console.log('promise waiting for too long, aborting!', action, params)
|
||||
promises[id].reject?.(new Error('timeout'))
|
||||
}, 5000)
|
||||
|
||||
const id = promises.length
|
||||
const promise = new Promise<U['result']>((resolve, reject) => {
|
||||
promises[id] = {
|
||||
resolve(...params: unknown[]) {
|
||||
clearTimeout(warning)
|
||||
|
||||
return resolve(...(params as Parameters<typeof resolve>))
|
||||
},
|
||||
reject(err) {
|
||||
clearTimeout(warning)
|
||||
|
||||
return reject(err)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
newWorker.postMessage({ engineId, action, params, id })
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
newWorker.onmessage = function (e) {
|
||||
console.log('msg:', e.data)
|
||||
if ('error' in e.data) {
|
||||
return promises[e.data.id].reject?.(e.data.error)
|
||||
}
|
||||
promises[e.data.id].resolve?.(e.data.result)
|
||||
}
|
||||
|
||||
const isWorkerReady = postMessage(0, 'init', { basename })
|
||||
|
||||
const workerEngine = {
|
||||
worker: newWorker,
|
||||
postMessage,
|
||||
isWorkerReady,
|
||||
promises,
|
||||
}
|
||||
|
||||
return workerEngine
|
||||
}
|
||||
|
||||
const sleepMs = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
let worker: ReturnType<typeof initWorkerEngine> | null = null
|
||||
|
||||
/**
|
||||
* This hook is used to create a worker engine.
|
||||
* @param basename
|
||||
*/
|
||||
const useCreateWorkerEngine = (basename: ProviderProps['basename']) => {
|
||||
const [situationVersion, setSituationVersion] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
worker = initWorkerEngine(basename, setSituationVersion)
|
||||
|
||||
console.time('init')
|
||||
void worker.isWorkerReady.then(() => console.timeEnd('init'))
|
||||
|
||||
// example of usage
|
||||
void Promise.all([
|
||||
asyncEvaluate('SMIC').then((result) => console.log('result', result)),
|
||||
asyncEvaluate('date').then((result) => console.log('result', result)),
|
||||
])
|
||||
|
||||
return () => {
|
||||
console.log('worker terminated!')
|
||||
|
||||
worker?.worker.terminate()
|
||||
worker?.promises.forEach((promise) =>
|
||||
promise.reject?.('worker terminated')
|
||||
)
|
||||
worker = null
|
||||
}
|
||||
}, [basename])
|
||||
|
||||
return situationVersion
|
||||
}
|
||||
|
||||
// asynchronous setSituation function
|
||||
|
||||
/**
|
||||
* This function is used to set the situation in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncSetSituationWithEngineId = async (
|
||||
engineId: number,
|
||||
...params: Action<'setSituation'>['params']
|
||||
): Promise<Action<'setSituation'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncSetSituationWithEngineId(engineId, ...params)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'setSituation', ...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* * This function is used to set the situation in the worker.
|
||||
*/
|
||||
export const asyncSetSituation = async (
|
||||
...params: Action<'setSituation'>['params']
|
||||
) => asyncSetSituationWithEngineId(0, ...params)
|
||||
|
||||
// asynchronous evaluate function
|
||||
|
||||
/**
|
||||
* This function is used to evaluate a publicodes expression in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncEvaluateWithEngineId = async (
|
||||
engineId: number,
|
||||
...params: Action<'evaluate'>['params']
|
||||
): Promise<Action<'evaluate'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncEvaluateWithEngineId(engineId, ...params)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'evaluate', ...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to evaluate a publicodes expression in the worker.
|
||||
*/
|
||||
export const asyncEvaluate = async (...params: Action<'evaluate'>['params']) =>
|
||||
asyncEvaluateWithEngineId(0, ...params)
|
||||
|
||||
/**
|
||||
* This hook is used to evaluate a publicodes expression in the worker.
|
||||
* @param defaultValue
|
||||
*/
|
||||
// export const useAsyncEvaluate = <T extends unknown = undefined>(
|
||||
// defaultValue?: T
|
||||
// ) => {
|
||||
// const [response, setResponse] = useState<Action<'evaluate'>['result'] | T>(
|
||||
// defaultValue as T
|
||||
// )
|
||||
|
||||
// const evaluate = useCallback(async (value: PublicodesExpression) => {
|
||||
// const result = await asyncEvaluate(value)
|
||||
// setResponse(result)
|
||||
|
||||
// return result
|
||||
// }, [])
|
||||
|
||||
// return [response, evaluate] as const
|
||||
// }
|
||||
|
||||
// asynchronous getRule function:
|
||||
|
||||
/**
|
||||
* This function is used to get a publicodes rule that is in the worker with a specific EngineId.
|
||||
*/
|
||||
export const asyncGetRuleWithEngineId = async (
|
||||
engineId: number,
|
||||
...params: Action<'getRule'>['params']
|
||||
): Promise<Action<'getRule'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncGetRuleWithEngineId(engineId, ...params)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'getRule', ...params)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to get a rule in the worker.
|
||||
*/
|
||||
export const asyncGetRule = async (...params: Action<'getRule'>['params']) =>
|
||||
asyncGetRuleWithEngineId(0, ...params)
|
||||
|
||||
/**
|
||||
* This hook is used to get a rule in the worker.
|
||||
* @param defaultValue
|
||||
*/
|
||||
export const useAsyncGetRule = <
|
||||
Names extends DottedName,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
|
||||
T extends unknown = undefined
|
||||
>(
|
||||
dottedName: Names,
|
||||
defaultValue?: T
|
||||
) =>
|
||||
usePromiseOnSituationChange(
|
||||
() => asyncGetRule(dottedName),
|
||||
[dottedName],
|
||||
defaultValue
|
||||
)
|
||||
|
||||
// asynchronous getParsedRules function
|
||||
|
||||
/**
|
||||
* This function is used to get all the parsed rules in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncGetParsedRulesWithEngineId = async (
|
||||
engineId: number
|
||||
): Promise<Action<'getParsedRules'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncGetParsedRulesWithEngineId(engineId)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'getParsedRules')
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to get all the parsed rules in the worker.
|
||||
*/
|
||||
export const asyncGetParsedRules = async () =>
|
||||
asyncGetParsedRulesWithEngineId(0)
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const useParsedRules = (engineId = 0) =>
|
||||
usePromiseOnSituationChange(
|
||||
async () => await asyncGetParsedRulesWithEngineId(engineId),
|
||||
[engineId]
|
||||
)
|
||||
|
||||
// asynchronous shallowCopy function
|
||||
|
||||
/**
|
||||
* This function is used to shallow copy an engine in the worker with a specific engineId.
|
||||
*/
|
||||
export const asyncShallowCopyWithEngineId = async (
|
||||
engineId: number
|
||||
): Promise<Action<'shallowCopy'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncShallowCopyWithEngineId(engineId)
|
||||
}
|
||||
|
||||
return await worker.postMessage(engineId, 'shallowCopy')
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to shallow copy an engine in the worker.
|
||||
*/
|
||||
export const asyncShallowCopy = async () => asyncShallowCopyWithEngineId(0)
|
||||
|
||||
// asynchronous deleteShallowCopy function
|
||||
|
||||
/**
|
||||
* * This function is used to delete a shallow copy of an engine in the worker.
|
||||
*/
|
||||
export const asyncDeleteShallowCopy = async (
|
||||
engineId: number
|
||||
): Promise<Action<'deleteShallowCopy'>['result']> => {
|
||||
if (!worker) {
|
||||
await sleepMs(10)
|
||||
|
||||
return asyncDeleteShallowCopy(engineId)
|
||||
}
|
||||
|
||||
return await worker.postMessage(0, 'deleteShallowCopy', { engineId })
|
||||
}
|
||||
|
||||
const SituationUpdated = React.createContext<number>(0)
|
||||
|
||||
export const SituationUpdatedProvider = ({
|
||||
children,
|
||||
basename,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
basename: ProviderProps['basename']
|
||||
}) => {
|
||||
const situationVersion = useCreateWorkerEngine(basename)
|
||||
|
||||
return (
|
||||
<SituationUpdated.Provider value={situationVersion}>
|
||||
{children}
|
||||
</SituationUpdated.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSituationUpdated = () => {
|
||||
const situationVersion = useContext(SituationUpdated)
|
||||
|
||||
return situationVersion
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around usePromise that adds the situation version to the dependencies
|
||||
* @example const date = usePromiseOnSituationChange(() => asyncEvaluate('date'), []) // date will be updated when the situation changes
|
||||
*/
|
||||
export const usePromiseOnSituationChange = <T, Default = undefined>(
|
||||
promise: () => Promise<T>,
|
||||
deps: DependencyList,
|
||||
defaultValue?: Default
|
||||
): T | Default => {
|
||||
const situationVersion = useSituationUpdated()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const state = usePromise(promise, [...deps, situationVersion], defaultValue)
|
||||
|
||||
return state
|
||||
}
|
Loading…
Reference in New Issue