From 7b2c44575f55c17b4affde0deaed09e25cd49037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Tue, 5 Sep 2023 14:56:59 +0200 Subject: [PATCH] Clean and update worker --- site/source/types/engine-worker.d.ts | 9 + .../worker/__socialWorkerEngineClient.tsx | 361 ------------------ .../worker/socialWorkerEngine.worker.ts | 19 +- site/source/worker/workerEngine.ts | 237 ------------ site/source/worker/workerEngineClient.ts | 286 -------------- .../source/worker/workerEngineClientReact.tsx | 181 --------- 6 files changed, 22 insertions(+), 1071 deletions(-) create mode 100644 site/source/types/engine-worker.d.ts delete mode 100644 site/source/worker/__socialWorkerEngineClient.tsx delete mode 100644 site/source/worker/workerEngine.ts delete mode 100644 site/source/worker/workerEngineClient.ts delete mode 100644 site/source/worker/workerEngineClientReact.tsx diff --git a/site/source/types/engine-worker.d.ts b/site/source/types/engine-worker.d.ts new file mode 100644 index 000000000..2da46c0ab --- /dev/null +++ b/site/source/types/engine-worker.d.ts @@ -0,0 +1,9 @@ +import { DottedName } from 'modele-social' +import Engine from 'publicodes' + +declare module '@publicodes/worker' { + interface UserConfig { + engine: Engine + // additionalActions: ActionType<'test', number[], number> + } +} diff --git a/site/source/worker/__socialWorkerEngineClient.tsx b/site/source/worker/__socialWorkerEngineClient.tsx deleted file mode 100644 index 9c73f1c38..000000000 --- a/site/source/worker/__socialWorkerEngineClient.tsx +++ /dev/null @@ -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> - -// @ts-expect-error -const WorkerEngineContext = createContext() - -// 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 -}) => { - const workerEngine = useCreateWorkerEngine(basename, workerClient) - - useSetupSafeSituation(workerEngine) - - if (workerEngine === undefined) { - return children - } - - return ( - - {children} - - ) -} - -// export type WorkerEngine = WorkerEngineClient -// let workerClient: | null = null -// setTimeout(() => { -// const preparedWorker = new SocialeWorkerEngine() -// const workerClient: WorkerEngineClient = -// createWorkerEngineClient( -// 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 -) => { - const [situationVersion, setSituationVersion] = useState(0) - const [workerEngine, setWorkerEngine] = - useState>(workerClient) - // console.log('llllllpppppppppppppppppppppppppp', workerClient) - - const [transition, startTransition] = useTransition() - - useEffect(() => { - console.timeEnd('time') - // const workerClient = createWorkerEngineClient( - // 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 { - 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 = ( - promise: () => Promise, - deps: DependencyList, - { defaultValue, workerEngine: workerEngineOption }: Options = {} -): 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 = ( - promise: () => Promise, - deps: DependencyList, - { defaultValue, workerEngine: workerEngineOption }: Options = {} -): [T | Default, () => Promise] => { - 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 = {} -) => { - 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 = {}) => { - 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 -} diff --git a/site/source/worker/socialWorkerEngine.worker.ts b/site/source/worker/socialWorkerEngine.worker.ts index 5868da5fc..9e2a3f0d1 100644 --- a/site/source/worker/socialWorkerEngine.worker.ts +++ b/site/source/worker/socialWorkerEngine.worker.ts @@ -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 @@ -52,11 +57,13 @@ const init = ({ basename }: Pick) => { const engine = new Engine(rules, { getUnitKey, logger }) + console.timeEnd('[createWorkerEngine] init') + return engine } -export type Actions = WorkerEngineActions, DottedName> +console.time('[createWorkerEngine] init') -console.time('[createWorkerEngine]') -createWorkerEngine(init) -console.timeEnd('[createWorkerEngine]') +createWorkerEngine(init, { + ...publicodesReactActions(), +}) diff --git a/site/source/worker/workerEngine.ts b/site/source/worker/workerEngine.ts deleted file mode 100644 index fbdb8a70b..000000000 --- a/site/source/worker/workerEngine.ts +++ /dev/null @@ -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['setSituation']> - result: void - } - | { - action: 'evaluate' - params: Parameters['evaluate']> - result: ReturnType['evaluate']> - } - | { - action: 'getRule' - params: Parameters['getRule']> - result: ReturnType['getRule']> - } - | { - action: 'getParsedRules' - params: [] - result: ReturnType['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 - -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 extends unknown - ? Omit - : never - -/** - */ -export const createWorkerEngine = < - Name extends string = string, - InitParams extends unknown[] = unknown[], ->( - init: (...params: InitParams) => Engine -) => { - type Params = DistributiveOmit< - WorkerEngineActions & GenericParams, - 'result' - > - - let engines: (Engine | 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, }) => { - // 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 }) - } - } -} diff --git a/site/source/worker/workerEngineClient.ts b/site/source/worker/workerEngineClient.ts deleted file mode 100644 index b0aa619cf..000000000 --- a/site/source/worker/workerEngineClient.ts +++ /dev/null @@ -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 extends unknown[] = unknown[], - // Name extends string = string, -> { - engineId: number - promises: WorkerEnginePromise[] - lastCleanup: null | NodeJS.Timeout - worker: Worker - isWorkerReady: Promise -} - -export type WorkerEngineClient = - ReturnType> - -export const createWorkerEngineClient = ( - worker: Worker, - options: { - initParams: WorkerEngineAction['params'] - onSituationChange?: (engineId: number) => void - } -) => { - console.log('{createWorker}') - - const ctx: Ctx = { - engineId: 0, - promises: [], - lastCleanup: null, - worker, - isWorkerReady: null as unknown as Promise, // 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) { - 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, ->( - 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((resolve, reject) => { - ctx.promises[id] = { - engineId, - action, - resolve: (...params: unknown[]) => { - clearTimeout(warning) - - return resolve(...(params as Parameters)) - }, - 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, - >( - action: ActionNames, - ...params: Action['params'] - ) => - postMessage(ctx, action, ...params) - -/** - */ -const workerEngineConstruct = ( - ctx: Ctx, - options: { - onSituationChange?: (engineId: number) => void - } -) => { - type Action = WorkerEngineAction - - 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['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['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['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 -} diff --git a/site/source/worker/workerEngineClientReact.tsx b/site/source/worker/workerEngineClientReact.tsx deleted file mode 100644 index 968b2ffe8..000000000 --- a/site/source/worker/workerEngineClientReact.tsx +++ /dev/null @@ -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 -) => { - const [transition, startTransition] = useTransition() - - const [situationVersion, setSituationVersion] = useState(0) - const [workerEngine, setWorkerEngine] = useState>( - () => { - 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 -> - -const WorkerEngineContext = createContext( - 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 - children: React.ReactNode -}) => { - const workerEngine = useSynchronizedWorkerEngine(workerClient) - - useSetupSafeSituation(workerEngine) - - if (workerEngine === undefined) { - return children - } - - return ( - - {children} - - ) -} - -interface Options { - 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 = {} -) => { - 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 = {}) => { - 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() -}