Clean and update worker

engine-in-web-worker
Jérémy Rialland 2023-09-05 14:56:59 +02:00
parent 85e1d194cd
commit 7b2c44575f
6 changed files with 22 additions and 1071 deletions

9
site/source/types/engine-worker.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { DottedName } from 'modele-social'
import Engine from 'publicodes'
declare module '@publicodes/worker' {
interface UserConfig {
engine: Engine<DottedName>
// additionalActions: ActionType<'test', number[], number>
}
}

View File

@ -1,361 +0,0 @@
import { DottedName } from 'modele-social'
import {
createContext,
DependencyList,
useContext,
useEffect,
useMemo,
useState,
useTransition,
} from 'react'
import { ProviderProps } from '@/components/Provider'
import { useSetupSafeSituation } from '@/components/utils/EngineContext'
import { useLazyPromise, usePromise } from '@/hooks/usePromise'
import { Actions } from './socialWorkerEngine.worker'
import {
createWorkerEngineClient,
WorkerEngineClient,
} from './workerEngineClient'
export type WorkerEngine = NonNullable<ReturnType<typeof useCreateWorkerEngine>>
// @ts-expect-error
const WorkerEngineContext = createContext<WorkerEngine>()
// export const useWorkerEngineContext = () => {
// const context = useContext(WorkerEngineContext)
// if (!context) {
// throw new Error(
// 'You are trying to use the worker engine outside of its provider'
// )
// }
// return context
// }
export const useWorkerEngine = () => {
const context = useContext(WorkerEngineContext)
if (!context && !import.meta.env.SSR) {
throw new Error(
'You are trying to use the worker engine outside of its provider'
)
}
// if (!context) {
// throw new Error(
// 'You are trying to use the worker engine before it is ready'
// )
// }
return context
}
export const WorkerEngineProvider = ({
children,
basename,
workerClient,
}: {
children: React.ReactNode
basename: ProviderProps['basename']
workerClient: WorkerEngineClient<Actions>
}) => {
const workerEngine = useCreateWorkerEngine(basename, workerClient)
useSetupSafeSituation(workerEngine)
if (workerEngine === undefined) {
return children
}
return (
<WorkerEngineContext.Provider value={workerEngine}>
{children}
</WorkerEngineContext.Provider>
)
}
// export type WorkerEngine = WorkerEngineClient<Actions>
// let workerClient: | null = null
// setTimeout(() => {
// const preparedWorker = new SocialeWorkerEngine()
// const workerClient: WorkerEngineClient<Actions> =
// createWorkerEngineClient<Actions>(
// new SocialeWorkerEngine(),
// () => {},
// // (engineId) =>
// // setSituationVersion((situationVersion) => {
// // // console.log('??? setSituationVersion original')
// // // situationVersion[engineId] =
// // // typeof situationVersion[engineId] !== 'number'
// // // ? 0
// // // : situationVersion[engineId]++
// // // return situationVersion
// // return situationVersion + 1
// // }),
// { basename: 'mon-entreprise' }
// )
// workerClient.test.onSituationChange = function (engineId) {
// console.log('original onSituationChange')
// }
// // }, 50)
// console.time('loading')
/**
* This hook is used to create a worker engine.
* @param basename
*/
export const useCreateWorkerEngine = (
basename: ProviderProps['basename'],
workerClient: WorkerEngineClient<Actions>
) => {
const [situationVersion, setSituationVersion] = useState(0)
const [workerEngine, setWorkerEngine] =
useState<WorkerEngineClient<Actions>>(workerClient)
// console.log('llllllpppppppppppppppppppppppppp', workerClient)
const [transition, startTransition] = useTransition()
useEffect(() => {
console.timeEnd('time')
// const workerClient = createWorkerEngineClient<Actions>(
// new SocialeWorkerEngine(),
// // () => {},
// // () =>
// // startTransition(() => {
// // setSituationVersion((situationVersion) => {
// // // console.log('??? setSituationVersion original')
// // // situationVersion[engineId] =
// // // typeof situationVersion[engineId] !== 'number'
// // // ? 0
// // // : situationVersion[engineId]++
// // // return situationVersion
// // return situationVersion + 1
// // })
// // }),
// //
// {
// initParams: [{ basename: 'mon-entreprise' }],
// onSituationChange: function () {
// console.log('update *****************')
// startTransition(() => {
// setSituationVersion((situationVersion) => {
// return situationVersion + 1
// })
// })
// },
// }
// )
workerClient.onSituationChange = function () {
console.log('update *****************')
startTransition(() => {
setSituationVersion((situationVersion) => {
return situationVersion + 1
})
})
}
// workerClient.context.onSituationChange = function () {
// console.log('update !!!!!!!!!!!!!!!!!!')
// console.log('transition...')
// startTransition(() => {
// setSituationVersion((situationVersion) => {
// // console.log('??? setSituationVersion original')
// // situationVersion[engineId] =
// // typeof situationVersion[engineId] !== 'number'
// // ? 0
// // : situationVersion[engineId]++
// // return situationVersion
// return situationVersion + 1
// })
// })
// }
console.log('{init worker}', workerClient)
setWorkerEngine(workerClient)
// void workerClient.context.asyncSetSituation({})
console.time('{init}')
let init = false
void workerClient.isWorkerReady.finally(() => {
init = true
console.timeEnd('{init}')
})
// example of usage
// void Promise.all([
// workerClient
// .asyncEvaluate('SMIC')
// .then((result) => console.log('{result}', result)),
// workerClient
// .asyncEvaluate('date')
// .then((result) => console.log('{result}', result)),
// ])
return () => {
!init && console.timeEnd('{init}')
console.log('{terminate worker}', workerClient)
// workerClient.terminate()
}
}, [basename])
// return workerEngine ? { ...workerEngine, situationVersion } : null
const memo = useMemo(() => {
// console.log('update:', { situationVersion, workerEngine })
return workerEngine ? { ...workerEngine, situationVersion } : undefined
}, [situationVersion, workerEngine])
return memo
}
/**
*
*/
// const useSituationVersion = (workerEngineCtx: WorkerEngineCtx) =>
// workerEngineCtx.situationVersion
// [engineId]
interface Options<Default> {
defaultValue?: Default
workerEngine?: WorkerEngine
}
/**
* 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
* @deprecated
*/
export const usePromiseOnSituationChange = <T, Default = undefined>(
promise: () => Promise<T>,
deps: DependencyList,
{ defaultValue, workerEngine: workerEngineOption }: Options<Default> = {}
): T | Default => {
const defaultWorkerEngineCtx = useWorkerEngine()
const { situationVersion } = workerEngineOption ?? defaultWorkerEngineCtx
// eslint-disable-next-line react-hooks/exhaustive-deps
const state = usePromise(promise, [...deps, situationVersion], defaultValue)
return state
}
/**
* @deprecated
*/
export const useLazyPromiseOnSituationChange = <T, Default = undefined>(
promise: () => Promise<T>,
deps: DependencyList,
{ defaultValue, workerEngine: workerEngineOption }: Options<Default> = {}
): [T | Default, () => Promise<T>] => {
const defaultWorkerEngineCtx = useWorkerEngine()
const { situationVersion } = workerEngineOption ?? defaultWorkerEngineCtx
// eslint-disable-next-line react-hooks/exhaustive-deps
const tuple = useLazyPromise(
promise,
[...deps, situationVersion],
defaultValue
)
return tuple
}
/**
* This hook is used to get a rule in the worker.
* @param dottedName
* @param options
*/
export const useAsyncGetRule = <
// T extends unknown = undefined,
Default = undefined,
>(
dottedName: DottedName,
{ defaultValue, workerEngine: workerEngineOption }: Options<Default> = {}
) => {
const defaultWorkerEngine = useWorkerEngine()
const workerEngine = workerEngineOption ?? defaultWorkerEngine
return usePromiseOnSituationChange(
async () => workerEngine.asyncGetRule(dottedName),
[dottedName, workerEngine],
{ defaultValue, workerEngine }
)
}
/**
* This hook is used to get parsed rules in the worker.
* @param engineId
*/
export const useAsyncParsedRules = <
Default = undefined, //
>({
defaultValue,
workerEngine: workerEngineOption,
}: Options<Default> = {}) => {
const defaultWorkerEngine = useWorkerEngine()
const workerEngine = workerEngineOption ?? defaultWorkerEngine
return usePromiseOnSituationChange(
async () => workerEngine.asyncGetParsedRules(),
[workerEngine],
{ defaultValue, workerEngine }
)
}
export const useShallowCopy = (
workerEngine: WorkerEngine
): WorkerEngine | undefined => {
const [situationVersion, setSituationVersion] = useState(0)
// const defaultWorkerEngine = useWorkerEngine()
// const workerEngine = workerEngineParam
// ?? defaultWorkerEngine
// console.log('??? situ version', situationVersion)
const workerEngineCopy = usePromiseOnSituationChange(
async () => {
const copy = await workerEngine.asyncShallowCopy(() => {
// console.log('??? onSituationChange', copy)
setSituationVersion((x) => x + 1)
})
// copy.onSituationChange = (x) => {
// console.log('??? onSituationChange', copy)
// setSituationVersion(x)
// }
// console.log('??? xxxxxxxxxxxxxxxxxxxxxxxxxxx', copy)
return copy
},
[workerEngine],
{ defaultValue: undefined, workerEngine }
)
const memo = useMemo(
() =>
workerEngineCopy ? { ...workerEngineCopy, situationVersion } : undefined,
[situationVersion, workerEngineCopy]
)
return memo
}

View File

@ -1,12 +1,17 @@
import rawRules, { DottedName } from 'modele-social'
import { createWorkerEngine, WorkerEngineActions } from '@publicodes/worker'
import rawRules from 'modele-social'
import Engine from 'publicodes'
import {
publicodesReactActions,
PublicodesReactActions,
} from 'publicodes-react'
import type { ProviderProps } from '@/components/Provider'
import i18n from '@/locales/i18n'
import ruleTranslations from '@/locales/rules-en.yaml'
import translateRules from '@/locales/translateRules'
import { createWorkerEngine, WorkerEngineActions } from './workerEngine'
export type Actions = WorkerEngineActions | PublicodesReactActions
function getUnitKey(unit: string): string {
const units = i18n.getResourceBundle('fr', 'units') as Record<string, string>
@ -52,11 +57,13 @@ const init = ({ basename }: Pick<ProviderProps, 'basename'>) => {
const engine = new Engine(rules, { getUnitKey, logger })
console.timeEnd('[createWorkerEngine] init')
return engine
}
export type Actions = WorkerEngineActions<Parameters<typeof init>, DottedName>
console.time('[createWorkerEngine] init')
console.time('[createWorkerEngine]')
createWorkerEngine(init)
console.timeEnd('[createWorkerEngine]')
createWorkerEngine(init, {
...publicodesReactActions(),
})

View File

@ -1,237 +0,0 @@
import Engine from 'publicodes'
/**
* This file run any publicodes engine in a web worker.
*/
export type WorkerEngineActions<
InitParams extends unknown[] = unknown[],
Name extends string = string,
> =
| {
action: 'init'
params: InitParams
result: number
}
| {
action: 'setSituation'
params: Parameters<Engine<Name>['setSituation']>
result: void
}
| {
action: 'evaluate'
params: Parameters<Engine<Name>['evaluate']>
result: ReturnType<Engine<Name>['evaluate']>
}
| {
action: 'getRule'
params: Parameters<Engine<Name>['getRule']>
result: ReturnType<Engine<Name>['getRule']>
}
| {
action: 'getParsedRules'
params: []
result: ReturnType<Engine<Name>['getParsedRules']>
}
| {
action: 'shallowCopy'
params: [] // no params cause we use engineId
result: number
}
| {
action: 'deleteShallowCopy'
params: [] // no params cause we use engineId
result: void
}
export type WorkerEngineAction<
Actions extends WorkerEngineActions,
Action extends Actions['action'],
> = Extract<Actions, { action: Action }>
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
}
type DistributiveOmit<T, K extends keyof T> = T extends unknown
? Omit<T, K>
: never
/**
*/
export const createWorkerEngine = <
Name extends string = string,
InitParams extends unknown[] = unknown[],
>(
init: (...params: InitParams) => Engine<Name>
) => {
type Params = DistributiveOmit<
WorkerEngineActions<InitParams, Name> & GenericParams,
'result'
>
let engines: (Engine<Name> | undefined)[] = []
let queue: (Params & { engineId: number })[] = []
let setDefaultEngineReady: (() => void) | null = null
const isDefaultEngineReady = new Promise(
(resolve) => (setDefaultEngineReady = resolve as () => void)
)
const actions = (
data: Params
// & { engines: EngineType[] }
) => {
const { engineId = 0, id, action, params } = data
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 { id }
} else if (action === 'evaluate') {
const result = engine.evaluate(...params)
console.log('[result]', result)
return { id, result }
} else if (action === 'getRule') {
const result = engine.getRule(...params)
return { id, result }
} else if (action === 'getParsedRules') {
const result = engine.getParsedRules()
return { id, result }
} else if (action === 'shallowCopy') {
engines.push(engine.shallowCopy())
return { id, result: engines.length - 1 }
} else if (action === 'deleteShallowCopy') {
if (engineId === 0) {
throw new Error('Cannot delete the default engine')
}
delete engines[engineId]
engines = engines.splice(engineId, 1)
console.log('[engines]', engines)
return { id }
} else {
console.log('[unknow message]', data)
return { id }
}
}
let timeout: NodeJS.Timeout | null = null
onmessage = async (e) => {
console.log('[onmessage]', e.data)
const { engineId = 0, id, action, params } = e.data as Params
try {
if (action === 'init') {
// console.log('[init engine]')
// const [{ basename }] = params
try {
// let rules = rawRules
// if (basename === 'infrance') {
// // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
// rules = translateRules('en', ruleTranslations, rules)
// }
// engineFactory(rules)
const engine = init(...params)
const engineId = engines.length
engines.push(engine)
postMessage({ id, result: engineId })
setDefaultEngineReady?.()
console.log('[engine ready]', engines[engineId])
} catch (e) {
console.error('[error]', e)
// postMessage('error')
}
return
}
await isDefaultEngineReady
queue.push({ engineId, id, action, params } as Params & {
engineId: number
})
if (timeout !== null) {
return
}
// timeout !== null && clearTimeout(timeout)
timeout = setTimeout(() => {
const aborts: number[] = []
const setSituationEncountered: boolean[] = []
const filteredQueue = [...queue]
.reverse()
.filter(({ action, engineId, id }) => {
if (action === 'setSituation')
setSituationEncountered[engineId] = true
const keep =
!setSituationEncountered[engineId] ||
(setSituationEncountered[engineId] && action !== 'evaluate')
if (!keep) aborts.push(id)
return keep
})
.reverse()
console.log('[start queue]', queue, filteredQueue)
console.time('bench')
postMessage({
batch: filteredQueue.map((params) => {
try {
const res = actions(params)
return res
} catch (error) {
return { id: params.id, error }
}
}),
})
console.timeEnd('bench')
const error = new Error(
'aborts the action because the situation has changed'
)
postMessage({ batch: aborts.map((id) => ({ id, error })) })
queue = []
timeout = null
}, 50)
} catch (error) {
return postMessage({ id, error })
}
}
}

View File

@ -1,286 +0,0 @@
import type { WorkerEngineAction, WorkerEngineActions } from './workerEngine'
// if (typeof Worker === 'undefined') {
// throw new Error('Worker is not supported.')
// }
/**
* This file is a client to communicate with workerEngine.
*/
const isObject = (val: unknown): val is object =>
typeof val === 'object' && val !== null
const isId = (val: object): val is { id: number } =>
'id' in val && typeof val.id === 'number'
const isBatch = (val: object): val is { batch: unknown[] } =>
'batch' in val && Array.isArray(val.batch)
interface WorkerEnginePromise<
Actions extends WorkerEngineActions = WorkerEngineActions,
ActionNames extends Actions['action'] = Actions['action'],
// InitParams extends unknown[] = unknown[],
// Name extends string = string,
// T extends Actions['action'] = Actions['action'],
> {
engineId: number
action: ActionNames
resolve: (value: unknown) => void
reject: (value: unknown) => void
}
interface Ctx<
Actions extends WorkerEngineActions = WorkerEngineActions,
// Promises extends WorkerEnginePromise = WorkerEnginePromise,
// >
// Actions extends WorkerEngineActions<InitParams, Name>,
// InitParams extends unknown[] = unknown[],
// Name extends string = string,
> {
engineId: number
promises: WorkerEnginePromise<Actions>[]
lastCleanup: null | NodeJS.Timeout
worker: Worker
isWorkerReady: Promise<number>
}
export type WorkerEngineClient<Actions extends WorkerEngineActions> =
ReturnType<typeof createWorkerEngineClient<Actions>>
export const createWorkerEngineClient = <Actions extends WorkerEngineActions>(
worker: Worker,
options: {
initParams: WorkerEngineAction<Actions, 'init'>['params']
onSituationChange?: (engineId: number) => void
}
) => {
console.log('{createWorker}')
const ctx: Ctx<Actions> = {
engineId: 0,
promises: [],
lastCleanup: null,
worker,
isWorkerReady: null as unknown as Promise<number>, // will be set later in the function
}
worker.onmessageerror = function (e) {
console.log('{onmessageerror}', e)
}
worker.onerror = function (e) {
console.log('{onerror}', e)
}
const ppp = (data: { id: number; result?: unknown; error?: string }) => {
console.timeEnd(`execute-${data.id}`)
if (data.id === 0) {
console.timeEnd('loading')
}
if ('error' in data) {
return ctx.promises[data.id].reject?.(data.error)
}
ctx.promises[data.id].resolve?.(data.result)
}
worker.onmessage = function (e: MessageEvent<unknown>) {
const data = e.data
console.log('{msg}', data)
if (isObject(data)) {
if (isId(data)) {
ppp(data)
return
} else if (isBatch(data)) {
data.batch.forEach((d) => isObject(d) && isId(d) && ppp(d))
return
}
}
console.error('{unknown message}', data)
throw new Error('unknown message')
}
const { initParams, onSituationChange } = options
ctx.isWorkerReady = postMessage(ctx, 'init', ...initParams)
const workerEngine = workerEngineConstruct(ctx, { onSituationChange })
return workerEngine
}
/**
* Post message to worker engine and return a promise to get the result,
* if the promise is not resolved in 10 seconds, it will be rejected.
* @param ctx
* @param action
* @param params
*/
const postMessage = async <
Actions extends WorkerEngineActions,
ActionNames extends Actions['action'],
Action extends WorkerEngineAction<Actions, ActionNames>,
>(
ctx: Ctx,
action: ActionNames,
...params: Action['params']
) => {
const { engineId, worker } = ctx
console.log('{postMessage}', action, params)
const promiseTimeout = 10000
const warning = setTimeout(() => {
console.log('{promise waiting for too long, aborting!}', action, params)
ctx.promises[id].reject?.(new Error('timeout'))
}, promiseTimeout)
ctx.lastCleanup !== null && clearInterval(ctx.lastCleanup)
ctx.lastCleanup = setTimeout(() => {
if (ctx.promises.length) {
console.log('{cleanup}', ctx.promises.length)
ctx.promises = []
ctx.lastCleanup = null
}
}, promiseTimeout * 2)
const id = ctx.promises.length
console.time(`execute-${id}`)
const stack = new Error().stack
const promise = new Promise<Action['result']>((resolve, reject) => {
ctx.promises[id] = {
engineId,
action,
resolve: (...params: unknown[]) => {
clearTimeout(warning)
return resolve(...(params as Parameters<typeof resolve>))
},
reject: (err: unknown) => {
clearTimeout(warning)
console.error(err)
console.error(stack)
console.error(new Error((err as Error).message, { cause: stack }))
return reject(err)
},
}
})
worker.postMessage({ engineId: ctx.engineId, action, params, id })
return promise
}
/**
*/
const wrappedPostMessage =
(ctx: Ctx) =>
<
Actions extends WorkerEngineActions,
ActionNames extends Actions['action'],
Action extends WorkerEngineAction<Actions, ActionNames>,
>(
action: ActionNames,
...params: Action['params']
) =>
postMessage<Actions, ActionNames, Action>(ctx, action, ...params)
/**
*/
const workerEngineConstruct = <Actions extends WorkerEngineActions>(
ctx: Ctx<Actions>,
options: {
onSituationChange?: (engineId: number) => void
}
) => {
type Action<T extends Actions['action']> = WorkerEngineAction<Actions, T>
const context = {
engineId: ctx.engineId,
worker: ctx.worker,
isWorkerReady: ctx.isWorkerReady,
onSituationChange: options.onSituationChange,
postMessage: wrappedPostMessage(ctx),
terminate: () => {
context.worker.terminate()
ctx.promises.forEach((promise) => promise.reject?.('worker terminated'))
ctx.promises = []
},
/**
* This function is used to set the situation in the worker with a specific engineId.
*/
asyncSetSituation: async (
...params: Action<'setSituation'>['params']
): Promise<Action<'setSituation'>['result']> => {
const ret = await context.postMessage('setSituation', ...params)
context.onSituationChange?.(ctx.engineId)
return ret
},
/**
* This function is used to evaluate a publicodes expression in the worker with a specific engineId.
*/
asyncEvaluate: async (
...params: Action<'evaluate'>['params']
): Promise<Action<'evaluate'>['result']> => {
const promise = await context.postMessage('evaluate', ...params)
// console.trace('{asyncEvaluate}')
return promise
},
/**
* This function is used to get a publicodes rule that is in the worker with a specific EngineId.
*/
asyncGetRule: async (
...params: Action<'getRule'>['params']
): Promise<Action<'getRule'>['result']> => {
return await context.postMessage('getRule', ...params)
},
/**
* This function is used to get all the parsed rules in the worker with a specific engineId.
*/
asyncGetParsedRules: async (): Promise<
Action<'getParsedRules'>['result']
> => {
return await context.postMessage('getParsedRules')
},
/**
* This function is used to shallow copy an engine in the worker with a specific engineId.
*/
asyncShallowCopy: async (onSituationChange: () => void = () => {}) => {
const engineId = await context.postMessage('shallowCopy')
return workerEngineConstruct({ ...ctx, engineId }, { onSituationChange })
},
/**
* This function is used to delete a shallow copy of an engine in the worker.
*/
asyncDeleteShallowCopy: async (): Promise<
Action<'deleteShallowCopy'>['result']
> => {
return context.postMessage('deleteShallowCopy')
},
}
return context
}

View File

@ -1,181 +0,0 @@
import { DottedName } from 'modele-social'
import {
createContext,
useContext,
useEffect,
useMemo,
useState,
useTransition,
} from 'react'
import { useSetupSafeSituation } from '@/components/utils/EngineContext'
import { usePromise } from '@/hooks/usePromise'
import { Actions } from './socialWorkerEngine.worker'
import { WorkerEngineClient } from './workerEngineClient'
/**
*/
export const useSynchronizedWorkerEngine = (
workerClient: WorkerEngineClient<Actions>
) => {
const [transition, startTransition] = useTransition()
const [situationVersion, setSituationVersion] = useState(0)
const [workerEngine, setWorkerEngine] = useState<WorkerEngineClient<Actions>>(
() => {
workerClient.onSituationChange = function () {
console.log('onSituationChange', workerClient.engineId)
startTransition(() => {
setSituationVersion((situationVersion) => {
return situationVersion + 1
})
})
}
return workerClient
}
)
const memo = useMemo(() => {
return { ...workerEngine, situationVersion }
}, [situationVersion, workerEngine])
return memo
}
export type WorkerEngine = NonNullable<
ReturnType<typeof useSynchronizedWorkerEngine>
>
const WorkerEngineContext = createContext<WorkerEngine>(
undefined as unknown as WorkerEngine
)
/**
*/
export const useWorkerEngine = () => {
const context = useContext(WorkerEngineContext)
if (!context && !import.meta.env.SSR) {
throw new Error(
'You are trying to use the worker engine outside of its provider'
)
}
return context
}
/**
*/
export const WorkerEngineProvider = ({
workerClient,
children,
}: {
workerClient: WorkerEngineClient<Actions>
children: React.ReactNode
}) => {
const workerEngine = useSynchronizedWorkerEngine(workerClient)
useSetupSafeSituation(workerEngine)
if (workerEngine === undefined) {
return children
}
return (
<WorkerEngineContext.Provider value={workerEngine}>
{children}
</WorkerEngineContext.Provider>
)
}
interface Options<DefaultValue> {
workerEngine?: WorkerEngine
defaultValue?: DefaultValue
}
/**
* This hook is used to get a rule in the worker engine.
*/
export const useAsyncGetRule = <
DefaultValue = undefined, //
>(
dottedName: DottedName,
{ defaultValue, workerEngine: workerEngineOption }: Options<DefaultValue> = {}
) => {
const defaultWorkerEngine = useWorkerEngine()
const workerEngine = workerEngineOption ?? defaultWorkerEngine
return usePromise(
async () => workerEngine.asyncGetRule(dottedName),
[dottedName, workerEngine],
defaultValue
)
}
/**
* This hook is used to get parsed rules in the worker engine.
*/
export const useAsyncParsedRules = <
DefaultValue = undefined, //
>({
workerEngine: workerEngineOption,
defaultValue,
}: Options<DefaultValue> = {}) => {
const defaultWorkerEngine = useWorkerEngine()
const workerEngine = workerEngineOption ?? defaultWorkerEngine
return usePromise(
async () => workerEngine.asyncGetParsedRules(),
[workerEngine],
defaultValue
)
}
/**
* This hook is used to make a shallow copy of the worker engine.
*/
export const useShallowCopy = (
workerEngine: WorkerEngine
): WorkerEngine | undefined => {
const [transition, startTransition] = useTransition()
const [situationVersion, setSituationVersion] = useState(0)
const workerEngineShallowCopy = usePromise(async () => {
const copy = await workerEngine.asyncShallowCopy(() => {
console.log('onSituationChange in shallow copy', copy.engineId)
startTransition(() => {
setSituationVersion((x) => x + 1)
})
})
return copy
}, [workerEngine])
useEffect(
() => () => {
console.log('deleteShallowCopy', workerEngineShallowCopy?.engineId)
void workerEngineShallowCopy?.asyncDeleteShallowCopy()
},
[workerEngineShallowCopy]
)
const memo = useMemo(
() =>
workerEngineShallowCopy
? { ...workerEngineShallowCopy, situationVersion }
: undefined,
[situationVersion, workerEngineShallowCopy]
)
return memo
}
export function useInversionFail() {
// return useContext(EngineContext).inversionFail()
}