diff --git a/ivette/src/dome/renderer/table/arrays.ts b/ivette/src/dome/renderer/table/arrays.ts index 45d3d48bb99709d39c26556b3c684a7f7473d24f..16d1e29ba1fab4d11c435201113488eb2ed505c2 100644 --- a/ivette/src/dome/renderer/table/arrays.ts +++ b/ivette/src/dome/renderer/table/arrays.ts @@ -454,6 +454,11 @@ export class ArrayModel<Key, Row> this.index.forEach((p) => { fn(p.row); }); } + /** Number of non-filtered entries. */ + length(): number { + return this.index.size; + } + } // -------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index 112823a87b61d85c2852ee8e1c0fc134d3137ff5..8b9d6c424731d74161137968376c0676e43073dc 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -725,7 +725,7 @@ export default function ASTview(): JSX.Element { React.useEffect(() => Hovered.set(view, hov?.marker ?? ''), [view, hov]); // Updating CodeMirror when the <properties> synchronized array is changed. - const props = States.useSyncArray(Properties.status).getArray(); + const props = States.useSyncArrayData(Properties.status); React.useEffect(() => PropertiesStatuses.set(view, props), [view, props]); // Updating CodeMirror when the <propStatusTags> map is changed. @@ -733,7 +733,7 @@ export default function ASTview(): JSX.Element { React.useEffect(() => Tags.set(view, tags), [view, tags]); // Updating CodeMirror when the <markersInfo> synchronized array is changed. - const info = States.useSyncArray(Ast.markerAttributes); + const info = States.useSyncArrayModel(Ast.markerAttributes); const getData = React.useCallback((key) => info.getData(key), [info]); React.useEffect(() => GetMarkerData.set(view, getData), [view, getData]); diff --git a/ivette/src/frama-c/kernel/Globals.tsx b/ivette/src/frama-c/kernel/Globals.tsx index a9d73184eb74e3f5516aca3a010e846ea3201ee6..582875e68f93ec393c1b83c06cd1442d90202be2 100644 --- a/ivette/src/frama-c/kernel/Globals.tsx +++ b/ivette/src/frama-c/kernel/Globals.tsx @@ -32,8 +32,6 @@ import { alpha } from 'dome/data/compare'; import { Section, Item } from 'dome/frame/sidebars'; import { Button } from 'dome/controls/buttons'; import * as Toolbars from 'dome/frame/toolbars'; -import * as Models from 'dome/table/models'; -import * as Arrays from 'dome/table/arrays'; import * as States from 'frama-c/states'; import * as Kernel from 'frama-c/kernel/api/ast'; @@ -106,10 +104,8 @@ type functionsData = type FctKey = Json.key<'#functions'>; function computeFcts( - ker: Arrays.CompactModel<FctKey,Kernel.functionsData>, - eva: Arrays.CompactModel<FctKey,Eva.functionsData>, - _kstamp: number, - _estamp: number, + ker: States.ArrayProxy<FctKey,Kernel.functionsData>, + eva: States.ArrayProxy<FctKey,Eva.functionsData>, ): functionsData[] { const arr: functionsData[] = []; ker.forEach((kf) => { @@ -123,14 +119,9 @@ export default function Globals(): JSX.Element { // Hooks const [selection, updateSelection] = States.useSelection(); - const kerFcts = States.useSyncArray(Kernel.functions); - const evaFcts = States.useSyncArray(Eva.functions); - const kerStamp = Models.useModel(kerFcts); - const evaStamp = Models.useModel(evaFcts); - const fcts = React.useMemo( - () => computeFcts(kerFcts,evaFcts,kerStamp,evaStamp), - [kerFcts,evaFcts,kerStamp,evaStamp] - ); + const ker = States.useSyncArrayProxy(Kernel.functions); + const eva = States.useSyncArrayProxy(Eva.functions); + const fcts = React.useMemo(() => computeFcts(ker,eva), [ker,eva]); const { useFlipSettings } = Dome; const [stdlib, flipStdlib] = useFlipSettings('ivette.globals.stdlib', false); diff --git a/ivette/src/frama-c/kernel/Messages.tsx b/ivette/src/frama-c/kernel/Messages.tsx index b1b64a290679d8398443d117bc6ec6ad3ba4a857..b6421cda8143e70571f39fa79109e1bcc4d4aa35 100644 --- a/ivette/src/frama-c/kernel/Messages.tsx +++ b/ivette/src/frama-c/kernel/Messages.tsx @@ -426,7 +426,7 @@ export default function RenderMessages(): JSX.Element { return m; }); - const data = States.useSyncArray(Kernel.message).getArray(); + const data = States.useSyncArrayData(Kernel.message); React.useEffect(() => { model.removeAllData(); diff --git a/ivette/src/frama-c/kernel/Properties.tsx b/ivette/src/frama-c/kernel/Properties.tsx index 81d95fd79304c40ea31f29e735797659ca125438..476cb53b1d048dafa7efb1e76d457d081c8b5bd4 100644 --- a/ivette/src/frama-c/kernel/Properties.tsx +++ b/ivette/src/frama-c/kernel/Properties.tsx @@ -597,8 +597,8 @@ function FilterRatio({ model }: { model: PropertyModel }): JSX.Element { // --- Properties Table // ------------------------------------------------------------------------- -type PropsModel = Arrays.CompactModel<PropKey,Properties.statusData>; -type EvapsModel = Arrays.CompactModel<PropKey,Eva.propertiesData>; +type PropsModel = States.ArrayProxy<PropKey,Properties.statusData>; +type EvapsModel = States.ArrayProxy<PropKey,Eva.propertiesData>; function populateModel( model: PropertyModel, @@ -618,13 +618,11 @@ export default function RenderProperties(): JSX.Element { // Hooks const model = React.useMemo(() => new PropertyModel(), []); - const props = States.useSyncArray(Properties.status); - const evaps = States.useSyncArray(Eva.properties); - const pstamp = Models.useModel(props); - const estamp = Models.useModel(evaps); + const props = States.useSyncArrayProxy(Properties.status); + const evaps = States.useSyncArrayProxy(Eva.properties); React.useEffect(() => { populateModel(model,props,evaps); - },[model,props,evaps,pstamp,estamp]); + },[model,props,evaps]); const [selection, updateSelection] = States.useSelection(); diff --git a/ivette/src/frama-c/kernel/SourceCode.tsx b/ivette/src/frama-c/kernel/SourceCode.tsx index 0d143b7fa6d808b58014f22c90ca235e5e448fac..03bc698ca28a19b79d76c41dd4625b66a6ccafd6 100644 --- a/ivette/src/frama-c/kernel/SourceCode.tsx +++ b/ivette/src/frama-c/kernel/SourceCode.tsx @@ -211,15 +211,14 @@ function useFctSource(file: string): string { // Build a callback that retrieves a location's source information. function useSourceGetter(): GetSource { - const attributes = States.useSyncArray(Ast.markerAttributes); - const functionsData = States.useSyncArray(Ast.functions).getArray(); + const getAttr = States.useSyncArrayGetter(Ast.markerAttributes); + const functionsData = States.useSyncArrayData(Ast.functions); return React.useCallback(({ fct, marker }) => { - const markerSloc = (marker !== undefined && marker !== '') ? - attributes.getData(marker)?.sloc : undefined; + const markerSloc = getAttr(marker)?.sloc; const fctSloc = (fct !== undefined && fct !== '') ? functionsData.find((e) => e.name === fct)?.sloc : undefined; return markerSloc ?? fctSloc; - }, [attributes, functionsData]); + }, [getAttr, functionsData]); } // ----------------------------------------------------------------------------- diff --git a/ivette/src/frama-c/plugins/dive/index.tsx b/ivette/src/frama-c/plugins/dive/index.tsx index 780a12a345f4dbe2864009e8159294a9d8e767cf..ff649431bd351750fd29b73e8e3d93a03f18de23 100644 --- a/ivette/src/frama-c/plugins/dive/index.tsx +++ b/ivette/src/frama-c/plugins/dive/index.tsx @@ -582,7 +582,7 @@ const GraphView = React.forwardRef<GraphViewRef | undefined, GraphViewProps>( const [dive, setDive] = useState(() => new Dive()); const [selection, updateSelection] = States.useSelection(); - const graph = States.useSyncArray(API.graph, true).getArray(); + const graph = States.useSyncArrayData(API.graph); function setCy(cy: Cytoscape.Core): void { if (cy !== dive.cy) diff --git a/ivette/src/frama-c/plugins/eva/Coverage.tsx b/ivette/src/frama-c/plugins/eva/Coverage.tsx index cad3b95c3560ed073255ce6986df945123b196e8..0a048970850663ee84404e3c369409b220e97509 100644 --- a/ivette/src/frama-c/plugins/eva/Coverage.tsx +++ b/ivette/src/frama-c/plugins/eva/Coverage.tsx @@ -22,14 +22,12 @@ import React from 'react'; import { Table, Column } from 'dome/table/views'; -import { CompactModel } from 'dome/table/arrays'; import * as Arrays from 'dome/table/arrays'; import * as Compare from 'dome/data/compare'; import * as Ivette from 'ivette'; import * as States from 'frama-c/states'; - -import * as Ast from 'frama-c/kernel/api/ast'; import * as Eva from 'frama-c/plugins/eva/api/general'; + import CoverageMeter, { percent } from './CoverageMeter'; type stats = Eva.functionStatsData; @@ -70,21 +68,15 @@ const ordering: Arrays.ByColumns<stats> = { ), }; -class Model extends CompactModel<Ast.fct, stats> { - constructor() { - super(Eva.functionStats.getkey); - this.setColumnOrder(ordering); - this.setSorting({ sortBy: 'coverage', sortDirection: 'ASC' }); - } -} - export function CoverageTable(): JSX.Element { - const data = States.useSyncArray(Eva.functionStats).getArray(); - const [selection, updateSelection] = States.useSelection(); - const model = React.useMemo(() => new Model(), []); - React.useEffect(() => model.replaceAllDataWith(data), [model, data]); + const model = States.useSyncArrayModel(Eva.functionStats); + React.useEffect(() => { + model.setColumnOrder(ordering); + model.setSorting({ sortBy: 'coverage', sortDirection: 'ASC' }); + }, [model]); + const [selection, updateSelection] = States.useSelection(); const onSelection = ({ key }: stats): void => { updateSelection({ location: { fct: key } }); }; diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index 1078b51f4fe09741899393d9d7b85ca30552b3b2..01ff09edb8a3ee3f62550d267015abf3e6e2bdcd 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -401,27 +401,95 @@ export function reloadArray<K, A>(arr: Array<K, A>): void { currentSyncArray(arr).reload(); } +/** Access to Synchronized Array elements. */ +export interface ArrayProxy<K,A> { + length: number; + getData(elt: K | undefined): (A | undefined); + forEach(fn: (row: A, elt: K) => void): void; +} + +// --- Utility functions + +function arrayGet<K,A>( + model: CompactModel<K,A>, + elt: K | undefined, + _stamp: number, +): A | undefined { + return elt ? model.getData(elt) : undefined; +} + +function arrayProxy<K,A>( + model: CompactModel<K,A>, + _stamp: number, +): ArrayProxy<K,A> { + return { + length: model.length(), + getData: (elt) => elt ? model.getData(elt) : undefined, + forEach: (fn) => model.forEach((r) => fn(r,model.getkey(r))), + }; +} + +// ---- Hooks + /** - Use Synchronized Array (Custom React Hook). + Use Synchronized Array as a low level, ready to use, Table Compact Model. - Unless specified, the hook makes the component re-render on every - update. Disabling this automatic re-rendering can be an option when - using the model to make a table view, which automatically synchronizes on - model updates. - @param sync Whether the component re-renders on updates (default is `true`). + Warning: to be in sync with the array, one shall subscribe to model events, + eg. by using `useModel()` hook, like `<Table/>` element does. */ -export function useSyncArray<K, A>( - arr: Array<K, A>, - sync = true, +export function useSyncArrayModel<K, A>( + arr: Array<K, A> ): CompactModel<K, A> { Server.useStatus(); const st = currentSyncArray(arr); Server.useSignal(arr.signal, st.fetch); st.online(); - useModel(st.model, sync); return st.model; } +/** Use Synchronized Array as a data array. */ +export function useSyncArrayData<K, A>(arr: Array<K, A>): A[] +{ + return useSyncArrayModel(arr).getArray(); +} + +/** Use Synchronized Array element. */ +export function useSyncArrayElt<K, A>( + arr: Array<K, A>, + elt: K | undefined, +): A | undefined { + const model = useSyncArrayModel(arr); + const stamp = useModel(model); + return React.useMemo( + () => arrayGet(model, elt, stamp), + [model, elt, stamp] + ); +} + +/** Use Synchronized Array as an element data getter. */ +export function useSyncArrayGetter<K, A>( + arr: Array<K, A> +): (elt: K | undefined) => (A | undefined) { + const model = useSyncArrayModel(arr); + const stamp = useModel(model); + return React.useCallback( + (elt) => arrayGet(model, elt, stamp), + [model, stamp] + ); +} + +/** Use Synchronized Array as an array proxy. */ +export function useSyncArrayProxy<K, A>( + arr: Array<K, A> +): ArrayProxy<K,A> { + const model = useSyncArrayModel<K, A>(arr); + const stamp = useModel(model); + return React.useMemo( + () => arrayProxy(model, stamp), + [model, stamp] + ); +} + /** Return the associated array model. */ @@ -749,11 +817,8 @@ export type attributes = Ast.markerAttributesData; /** Access the marker attributes from AST. */ export function useMarker(marker: Ast.marker | undefined): attributes { - const marks = useSyncArray(Ast.markerAttributes); - if (marker === undefined) return Ast.markerAttributesDataDefault; - const attrs = marks.getData(marker); - if (attrs === undefined) return Ast.markerAttributesDataDefault; - return attrs; + const marks = useSyncArrayElt(Ast.markerAttributes, marker); + return marks ?? Ast.markerAttributesDataDefault; } // --------------------------------------------------------------------------