From 9a39041209d2a8eb16062377b49111e45dc2efae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Mon, 5 Dec 2022 15:09:15 +0100 Subject: [PATCH] [states] refactor states with global states --- ivette/src/dome/renderer/data/states.ts | 4 +- ivette/src/frama-c/states.ts | 164 ++++++++++-------------- 2 files changed, 69 insertions(+), 99 deletions(-) diff --git a/ivette/src/dome/renderer/data/states.ts b/ivette/src/dome/renderer/data/states.ts index cb2aba5085d..a30de7bf0b8 100644 --- a/ivette/src/dome/renderer/data/states.ts +++ b/ivette/src/dome/renderer/data/states.ts @@ -100,6 +100,7 @@ export class GlobalState<A> { constructor(initValue: A) { this.value = initValue; this.emitter = new Emitter(); + this.emitter.setMaxListeners(200); this.getValue = this.getValue.bind(this); this.setValue = this.setValue.bind(this); } @@ -108,7 +109,8 @@ export class GlobalState<A> { getValue(): A { return this.value; } /** Notify callbacks on change. By default, changed are detected - by using _deep_ structural comparison, using `react-fast-compare` comparison. + by using _deep_ structural comparison, using `react-fast-compare` + comparison. @param value the new value of the state @param forced when set to `true`, notify callbacks without comparison. */ diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index b882635b268..9497b6a18c5 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -32,20 +32,15 @@ import React from 'react'; import * as Dome from 'dome'; -import * as Json from 'dome/data/json'; import { Order } from 'dome/data/compare'; import { GlobalState, useGlobalState } from 'dome/data/states'; import { Client, useModel } from 'dome/table/models'; import { CompactModel } from 'dome/table/arrays'; +import { getCurrent, setCurrent } from 'frama-c/kernel/api/project'; import * as Ast from 'frama-c/kernel/api/ast'; import * as Server from './server'; -const PROJECT = new Dome.Event('frama-c.project'); -class STATE extends Dome.Event { - constructor(id: string) { - super(`frama-c.state.${id}`); - } -} +const CurrentProject = new GlobalState<string>('default'); // -------------------------------------------------------------------------- // --- Pretty Printing (Browser Console) @@ -57,28 +52,18 @@ const D = new Dome.Debug('States'); // --- Synchronized Current Project // -------------------------------------------------------------------------- -let currentProject: string | undefined; - Server.onReady(async () => { try { - const sr: Server.GetRequest<null, { id?: string }> = { - kind: Server.RqKind.GET, - name: 'kernel.project.getCurrent', - input: Json.jNull, - output: Json.jObject({ id: Json.jString }), - signals: [], - }; - const current: { id?: string } = await Server.send(sr, null); - currentProject = current.id; - PROJECT.emit(); + CurrentProject.setValue('default'); + const { id } = await Server.send(getCurrent, null); + CurrentProject.setValue(id); } catch (error) { D.error(`Fail to retrieve the current project. ${error}`); } }); Server.onShutdown(() => { - currentProject = ''; - PROJECT.emit(); + CurrentProject.setValue('default'); }); // -------------------------------------------------------------------------- @@ -89,9 +74,9 @@ Server.onShutdown(() => { * Current Project (Custom React Hook). * @return The current project. */ -export function useProject(): string | undefined { - Dome.useUpdate(PROJECT); - return currentProject; +export function useProject(): string { + const [s] = useGlobalState(CurrentProject); + return s; } /** @@ -105,16 +90,9 @@ export function useProject(): string | undefined { export async function setProject(project: string): Promise<void> { if (Server.isRunning()) { try { - const sr: Server.SetRequest<string, null> = { - kind: Server.RqKind.SET, - name: 'kernel.project.setCurrent', - input: Json.jString, - output: Json.jNull, - signals: [], - }; - await Server.send(sr, project); - currentProject = project; - PROJECT.emit(); + await Server.send(setCurrent, project); + const { id } = await Server.send(getCurrent, null); + CurrentProject.setValue(id); } catch (error) { D.error(`Fail to set the current project. ${error}`); } @@ -222,7 +200,7 @@ export function useTags(rq: GetTags): Map<string, Tag> { } // -------------------------------------------------------------------------- -// --- Synchronized States +// --- Synchronized States from API // -------------------------------------------------------------------------- export interface Value<A> { @@ -255,7 +233,7 @@ export interface Array<K, A> { } // -------------------------------------------------------------------------- -// --- Handler for Synchronized St byates +// --- Handler for Synchronized States // -------------------------------------------------------------------------- interface Handler<A> { @@ -265,68 +243,50 @@ interface Handler<A> { setter?: Server.SetRequest<A, null>; } -// shared for all projects -class SyncState<A> { - UPDATE: Dome.Event; +enum SyncStatus { OffLine, Loading, Loaded } + +class SyncState<A> extends GlobalState<A | undefined> { handler: Handler<A>; - upToDate: boolean; - value?: A; + status = SyncStatus.OffLine; constructor(h: Handler<A>) { + super(undefined); this.handler = h; - this.UPDATE = new STATE(h.name); - this.upToDate = false; - this.value = undefined; - this.update = this.update.bind(this); - this.getValue = this.getValue.bind(this); - this.setValue = this.setValue.bind(this); - PROJECT.on(this.update); + this.load = this.load.bind(this); + this.fetch = this.fetch.bind(this); + this.offline = this.offline.bind(this); + Server.onReady(this.load); + Server.onShutdown(this.offline); } - getValue(): A | undefined { - const running = Server.isRunning(); - if (!this.upToDate && running) { - this.update(); - } - return running ? this.value : undefined; + signal(): Server.Signal { return this.handler.signal; } + + load(): void { + if (this.status === SyncStatus.OffLine) this.fetch(); } - async setValue(v: A): Promise<void> { - try { - this.upToDate = true; - this.value = v; - const setter = this.handler.getter; - if (setter) await Server.send(setter, v); - this.UPDATE.emit(); - } catch (error) { - D.error( - `Fail to set value of SyncState '${this.handler.name}'.`, - `${error}`, - ); - this.UPDATE.emit(); - } + offline(): void { + this.status = SyncStatus.OffLine; + this.setValue(undefined); } - async update(): Promise<void> { + async fetch(): Promise<void> { try { - this.upToDate = true; if (Server.isRunning()) { + this.status = SyncStatus.Loading; const v = await Server.send(this.handler.getter, null); - this.value = v; - this.UPDATE.emit(); - } else if (this.value !== undefined) { - this.value = undefined; - this.UPDATE.emit(); + this.status = SyncStatus.Loaded; + this.setValue(v); } } catch (error) { D.error( `Fail to update SyncState '${this.handler.name}'.`, `${error}`, ); - this.value = undefined; - this.UPDATE.emit(); + this.setValue(undefined); } } + } // -------------------------------------------------------------------------- @@ -337,7 +297,10 @@ const syncStates = new Map<string, SyncState<unknown>>(); // Remark: use current project state -function currentSyncState<A>(h: Handler<A>): SyncState<A> { +function lookupSyncState<A>( + currentProject: string, + h: Handler<A> +): SyncState<A> { const id = `${currentProject}@${h.name}`; let s = syncStates.get(id) as SyncState<A> | undefined; if (!s) { @@ -355,20 +318,25 @@ Server.onShutdown(() => syncStates.clear()); /** Synchronization with a (projectified) server state. */ export function useSyncState<A>( - st: State<A>, + state: State<A>, ): [A | undefined, (value: A) => void] { - const s = currentSyncState(st); - Dome.useUpdate(PROJECT, s.UPDATE); - Server.useSignal(s.handler.signal, s.update); - return [s.getValue(), s.setValue]; + Server.useStatus(); + const pr = useProject(); + const st = lookupSyncState(pr, state); + Server.useSignal(st.signal(), st.fetch); + st.load(); + return useGlobalState(st); } /** Synchronization with a (projectified) server value. */ -export function useSyncValue<A>(va: Value<A>): A | undefined { - const s = currentSyncState(va); - Dome.useUpdate(PROJECT, s.UPDATE); - Server.useSignal(s.handler.signal, s.update); - return s.getValue(); +export function useSyncValue<A>(value: Value<A>): A | undefined { + Server.useStatus(); + const pr = useProject(); + const st = lookupSyncState(pr, value); + Server.useSignal(st.signal(), st.fetch); + st.load(); + const [v] = useGlobalState(st); + return v; } // -------------------------------------------------------------------------- @@ -395,7 +363,6 @@ class SyncArray<K, A> { update(): void { if ( !this.upToDate && - currentProject !== undefined && Server.isRunning() ) this.fetch(); } @@ -403,7 +370,6 @@ class SyncArray<K, A> { async fetch(): Promise<void> { if ( this.fetching || - currentProject === undefined || !Server.isRunning() ) return; try { @@ -460,6 +426,7 @@ const syncArrays = new Map<string, SyncArray<unknown, unknown>>(); // Remark: lookup for current project function currentSyncArray<K, A>( + currentProject: string, array: Array<K, A>, ): SyncArray<K, A> { const id = `${currentProject}@${array.name}`; @@ -479,7 +446,7 @@ Server.onShutdown(() => syncArrays.clear()); /** Force a Synchronized Array to reload. */ export function reloadArray<K, A>(arr: Array<K, A>): void { - currentSyncArray(arr).reload(); + currentSyncArray(CurrentProject.getValue(), arr).reload(); } /** @@ -495,8 +462,9 @@ export function useSyncArray<K, A>( arr: Array<K, A>, sync = true, ): CompactModel<K, A> { - Dome.useUpdate(PROJECT); - const st = currentSyncArray(arr); + Server.useStatus(); + const pr = useProject(); + const st = currentSyncArray(pr, arr); Server.useSignal(arr.signal, st.fetch); st.update(); useModel(st.model, sync); @@ -509,7 +477,8 @@ export function useSyncArray<K, A>( export function getSyncArray<K, A>( arr: Array<K, A>, ): CompactModel<K, A> { - const st = currentSyncArray(arr); + const pr = CurrentProject.getValue(); + const st = currentSyncArray(pr, arr); return st.model; } @@ -523,7 +492,8 @@ export function onSyncArray<K, A>( onReload?: () => void, onUpdate?: () => void, ): Client { - const st = currentSyncArray(arr); + const pr = CurrentProject.getValue(); + const st = currentSyncArray(pr, arr); return st.model.link(onReload, onUpdate); } @@ -822,9 +792,7 @@ export async function resetSelection(): Promise<void> { } } -/* Select the main function when the current project changes and the selection - is still empty (which happens at the start of the GUI). */ -PROJECT.on(async () => { +Server.onReady(() => { if (GlobalSelection.getValue() === emptySelection) resetSelection(); }); -- GitLab