Skip to content
Snippets Groups Projects
Commit 9a390412 authored by Loïc Correnson's avatar Loïc Correnson Committed by David Bühler
Browse files

[states] refactor states with global states

parent cde633c4
No related branches found
No related tags found
No related merge requests found
...@@ -100,6 +100,7 @@ export class GlobalState<A> { ...@@ -100,6 +100,7 @@ export class GlobalState<A> {
constructor(initValue: A) { constructor(initValue: A) {
this.value = initValue; this.value = initValue;
this.emitter = new Emitter(); this.emitter = new Emitter();
this.emitter.setMaxListeners(200);
this.getValue = this.getValue.bind(this); this.getValue = this.getValue.bind(this);
this.setValue = this.setValue.bind(this); this.setValue = this.setValue.bind(this);
} }
...@@ -108,7 +109,8 @@ export class GlobalState<A> { ...@@ -108,7 +109,8 @@ export class GlobalState<A> {
getValue(): A { return this.value; } getValue(): A { return this.value; }
/** Notify callbacks on change. By default, changed are detected /** 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 value the new value of the state
@param forced when set to `true`, notify callbacks without comparison. @param forced when set to `true`, notify callbacks without comparison.
*/ */
......
...@@ -32,20 +32,15 @@ ...@@ -32,20 +32,15 @@
import React from 'react'; import React from 'react';
import * as Dome from 'dome'; import * as Dome from 'dome';
import * as Json from 'dome/data/json';
import { Order } from 'dome/data/compare'; import { Order } from 'dome/data/compare';
import { GlobalState, useGlobalState } from 'dome/data/states'; import { GlobalState, useGlobalState } from 'dome/data/states';
import { Client, useModel } from 'dome/table/models'; import { Client, useModel } from 'dome/table/models';
import { CompactModel } from 'dome/table/arrays'; 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 Ast from 'frama-c/kernel/api/ast';
import * as Server from './server'; import * as Server from './server';
const PROJECT = new Dome.Event('frama-c.project'); const CurrentProject = new GlobalState<string>('default');
class STATE extends Dome.Event {
constructor(id: string) {
super(`frama-c.state.${id}`);
}
}
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// --- Pretty Printing (Browser Console) // --- Pretty Printing (Browser Console)
...@@ -57,28 +52,18 @@ const D = new Dome.Debug('States'); ...@@ -57,28 +52,18 @@ const D = new Dome.Debug('States');
// --- Synchronized Current Project // --- Synchronized Current Project
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
let currentProject: string | undefined;
Server.onReady(async () => { Server.onReady(async () => {
try { try {
const sr: Server.GetRequest<null, { id?: string }> = { CurrentProject.setValue('default');
kind: Server.RqKind.GET, const { id } = await Server.send(getCurrent, null);
name: 'kernel.project.getCurrent', CurrentProject.setValue(id);
input: Json.jNull,
output: Json.jObject({ id: Json.jString }),
signals: [],
};
const current: { id?: string } = await Server.send(sr, null);
currentProject = current.id;
PROJECT.emit();
} catch (error) { } catch (error) {
D.error(`Fail to retrieve the current project. ${error}`); D.error(`Fail to retrieve the current project. ${error}`);
} }
}); });
Server.onShutdown(() => { Server.onShutdown(() => {
currentProject = ''; CurrentProject.setValue('default');
PROJECT.emit();
}); });
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
...@@ -89,9 +74,9 @@ Server.onShutdown(() => { ...@@ -89,9 +74,9 @@ Server.onShutdown(() => {
* Current Project (Custom React Hook). * Current Project (Custom React Hook).
* @return The current project. * @return The current project.
*/ */
export function useProject(): string | undefined { export function useProject(): string {
Dome.useUpdate(PROJECT); const [s] = useGlobalState(CurrentProject);
return currentProject; return s;
} }
/** /**
...@@ -105,16 +90,9 @@ export function useProject(): string | undefined { ...@@ -105,16 +90,9 @@ export function useProject(): string | undefined {
export async function setProject(project: string): Promise<void> { export async function setProject(project: string): Promise<void> {
if (Server.isRunning()) { if (Server.isRunning()) {
try { try {
const sr: Server.SetRequest<string, null> = { await Server.send(setCurrent, project);
kind: Server.RqKind.SET, const { id } = await Server.send(getCurrent, null);
name: 'kernel.project.setCurrent', CurrentProject.setValue(id);
input: Json.jString,
output: Json.jNull,
signals: [],
};
await Server.send(sr, project);
currentProject = project;
PROJECT.emit();
} catch (error) { } catch (error) {
D.error(`Fail to set the current project. ${error}`); D.error(`Fail to set the current project. ${error}`);
} }
...@@ -222,7 +200,7 @@ export function useTags(rq: GetTags): Map<string, Tag> { ...@@ -222,7 +200,7 @@ export function useTags(rq: GetTags): Map<string, Tag> {
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// --- Synchronized States // --- Synchronized States from API
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
export interface Value<A> { export interface Value<A> {
...@@ -255,7 +233,7 @@ export interface Array<K, A> { ...@@ -255,7 +233,7 @@ export interface Array<K, A> {
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// --- Handler for Synchronized St byates // --- Handler for Synchronized States
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
interface Handler<A> { interface Handler<A> {
...@@ -265,68 +243,50 @@ interface Handler<A> { ...@@ -265,68 +243,50 @@ interface Handler<A> {
setter?: Server.SetRequest<A, null>; setter?: Server.SetRequest<A, null>;
} }
// shared for all projects enum SyncStatus { OffLine, Loading, Loaded }
class SyncState<A> {
UPDATE: Dome.Event; class SyncState<A> extends GlobalState<A | undefined> {
handler: Handler<A>; handler: Handler<A>;
upToDate: boolean; status = SyncStatus.OffLine;
value?: A;
constructor(h: Handler<A>) { constructor(h: Handler<A>) {
super(undefined);
this.handler = h; this.handler = h;
this.UPDATE = new STATE(h.name); this.load = this.load.bind(this);
this.upToDate = false; this.fetch = this.fetch.bind(this);
this.value = undefined; this.offline = this.offline.bind(this);
this.update = this.update.bind(this); Server.onReady(this.load);
this.getValue = this.getValue.bind(this); Server.onShutdown(this.offline);
this.setValue = this.setValue.bind(this);
PROJECT.on(this.update);
} }
getValue(): A | undefined { signal(): Server.Signal { return this.handler.signal; }
const running = Server.isRunning();
if (!this.upToDate && running) { load(): void {
this.update(); if (this.status === SyncStatus.OffLine) this.fetch();
}
return running ? this.value : undefined;
} }
async setValue(v: A): Promise<void> { offline(): void {
try { this.status = SyncStatus.OffLine;
this.upToDate = true; this.setValue(undefined);
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();
}
} }
async update(): Promise<void> { async fetch(): Promise<void> {
try { try {
this.upToDate = true;
if (Server.isRunning()) { if (Server.isRunning()) {
this.status = SyncStatus.Loading;
const v = await Server.send(this.handler.getter, null); const v = await Server.send(this.handler.getter, null);
this.value = v; this.status = SyncStatus.Loaded;
this.UPDATE.emit(); this.setValue(v);
} else if (this.value !== undefined) {
this.value = undefined;
this.UPDATE.emit();
} }
} catch (error) { } catch (error) {
D.error( D.error(
`Fail to update SyncState '${this.handler.name}'.`, `Fail to update SyncState '${this.handler.name}'.`,
`${error}`, `${error}`,
); );
this.value = undefined; this.setValue(undefined);
this.UPDATE.emit();
} }
} }
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
...@@ -337,7 +297,10 @@ const syncStates = new Map<string, SyncState<unknown>>(); ...@@ -337,7 +297,10 @@ const syncStates = new Map<string, SyncState<unknown>>();
// Remark: use current project state // 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}`; const id = `${currentProject}@${h.name}`;
let s = syncStates.get(id) as SyncState<A> | undefined; let s = syncStates.get(id) as SyncState<A> | undefined;
if (!s) { if (!s) {
...@@ -355,20 +318,25 @@ Server.onShutdown(() => syncStates.clear()); ...@@ -355,20 +318,25 @@ Server.onShutdown(() => syncStates.clear());
/** Synchronization with a (projectified) server state. */ /** Synchronization with a (projectified) server state. */
export function useSyncState<A>( export function useSyncState<A>(
st: State<A>, state: State<A>,
): [A | undefined, (value: A) => void] { ): [A | undefined, (value: A) => void] {
const s = currentSyncState(st); Server.useStatus();
Dome.useUpdate(PROJECT, s.UPDATE); const pr = useProject();
Server.useSignal(s.handler.signal, s.update); const st = lookupSyncState(pr, state);
return [s.getValue(), s.setValue]; Server.useSignal(st.signal(), st.fetch);
st.load();
return useGlobalState(st);
} }
/** Synchronization with a (projectified) server value. */ /** Synchronization with a (projectified) server value. */
export function useSyncValue<A>(va: Value<A>): A | undefined { export function useSyncValue<A>(value: Value<A>): A | undefined {
const s = currentSyncState(va); Server.useStatus();
Dome.useUpdate(PROJECT, s.UPDATE); const pr = useProject();
Server.useSignal(s.handler.signal, s.update); const st = lookupSyncState(pr, value);
return s.getValue(); Server.useSignal(st.signal(), st.fetch);
st.load();
const [v] = useGlobalState(st);
return v;
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
...@@ -395,7 +363,6 @@ class SyncArray<K, A> { ...@@ -395,7 +363,6 @@ class SyncArray<K, A> {
update(): void { update(): void {
if ( if (
!this.upToDate && !this.upToDate &&
currentProject !== undefined &&
Server.isRunning() Server.isRunning()
) this.fetch(); ) this.fetch();
} }
...@@ -403,7 +370,6 @@ class SyncArray<K, A> { ...@@ -403,7 +370,6 @@ class SyncArray<K, A> {
async fetch(): Promise<void> { async fetch(): Promise<void> {
if ( if (
this.fetching || this.fetching ||
currentProject === undefined ||
!Server.isRunning() !Server.isRunning()
) return; ) return;
try { try {
...@@ -460,6 +426,7 @@ const syncArrays = new Map<string, SyncArray<unknown, unknown>>(); ...@@ -460,6 +426,7 @@ const syncArrays = new Map<string, SyncArray<unknown, unknown>>();
// Remark: lookup for current project // Remark: lookup for current project
function currentSyncArray<K, A>( function currentSyncArray<K, A>(
currentProject: string,
array: Array<K, A>, array: Array<K, A>,
): SyncArray<K, A> { ): SyncArray<K, A> {
const id = `${currentProject}@${array.name}`; const id = `${currentProject}@${array.name}`;
...@@ -479,7 +446,7 @@ Server.onShutdown(() => syncArrays.clear()); ...@@ -479,7 +446,7 @@ Server.onShutdown(() => syncArrays.clear());
/** Force a Synchronized Array to reload. */ /** Force a Synchronized Array to reload. */
export function reloadArray<K, A>(arr: Array<K, A>): void { 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>( ...@@ -495,8 +462,9 @@ export function useSyncArray<K, A>(
arr: Array<K, A>, arr: Array<K, A>,
sync = true, sync = true,
): CompactModel<K, A> { ): CompactModel<K, A> {
Dome.useUpdate(PROJECT); Server.useStatus();
const st = currentSyncArray(arr); const pr = useProject();
const st = currentSyncArray(pr, arr);
Server.useSignal(arr.signal, st.fetch); Server.useSignal(arr.signal, st.fetch);
st.update(); st.update();
useModel(st.model, sync); useModel(st.model, sync);
...@@ -509,7 +477,8 @@ export function useSyncArray<K, A>( ...@@ -509,7 +477,8 @@ export function useSyncArray<K, A>(
export function getSyncArray<K, A>( export function getSyncArray<K, A>(
arr: Array<K, A>, arr: Array<K, A>,
): CompactModel<K, A> { ): CompactModel<K, A> {
const st = currentSyncArray(arr); const pr = CurrentProject.getValue();
const st = currentSyncArray(pr, arr);
return st.model; return st.model;
} }
...@@ -523,7 +492,8 @@ export function onSyncArray<K, A>( ...@@ -523,7 +492,8 @@ export function onSyncArray<K, A>(
onReload?: () => void, onReload?: () => void,
onUpdate?: () => void, onUpdate?: () => void,
): Client { ): Client {
const st = currentSyncArray(arr); const pr = CurrentProject.getValue();
const st = currentSyncArray(pr, arr);
return st.model.link(onReload, onUpdate); return st.model.link(onReload, onUpdate);
} }
...@@ -822,9 +792,7 @@ export async function resetSelection(): Promise<void> { ...@@ -822,9 +792,7 @@ export async function resetSelection(): Promise<void> {
} }
} }
/* Select the main function when the current project changes and the selection Server.onReady(() => {
is still empty (which happens at the start of the GUI). */
PROJECT.on(async () => {
if (GlobalSelection.getValue() === emptySelection) if (GlobalSelection.getValue() === emptySelection)
resetSelection(); resetSelection();
}); });
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment