diff --git a/ivette/src/frama-c/states.js b/ivette/src/frama-c/states.js index 57f5e122ffcde831995848b653ce9e7250591b26..b95864b220cc48ddf0b4b9dcec00740c64d86322 100644 --- a/ivette/src/frama-c/states.js +++ b/ivette/src/frama-c/states.js @@ -30,7 +30,7 @@ export const PROJECT = 'frama-c.project' ; @summary State Notification Events. @description Event `'frama-c.state.<id>'` for project `<id>`. - The prefix `'frama-c-state.'` is exported as `States.STATE` in public API. + The prefix `'frama-c.state.'` is exported as `States.STATE` in public API. */ export const STATE = 'frama-c.state.' ; @@ -39,6 +39,8 @@ export const STATE = 'frama-c.state.' ; // -------------------------------------------------------------------------- var currentProject = undefined ; +var globalStates = {} ; +var globalSync = {} ; Server.onReady(() => { Server.sendGET('kernel.project.getCurrent') @@ -51,6 +53,7 @@ Server.onReady(() => { Server.onShutdown(() => { currentProject = undefined ; globalStates = {} ; + globalSync = {} ; Dome.emit(PROJECT); }); @@ -77,36 +80,32 @@ export function useProject() */ export function setProject(project) { - Server.sendSET( 'kernel.project.setCurrent' , project ); - currentProject = project ; - Dome.emit(PROJECT); -} - -/** - @summary Clear Project Cache. - @param {string} [project] - the project identifier, defaults to current - @description - Remove all projectified values for the specified project - Emits `PROJECT`. - */ -export function clearCache(project) -{ - const theProject = project || currentProject ; - if (theProject) - _.unset( globalStates , [theProject] ); - Dome.emit(PROJECT); + if (Server.isRunning()) { + Server.sendSET( 'kernel.project.setCurrent' , project ); + currentProject = project ; + Dome.emit(PROJECT); + } } // -------------------------------------------------------------------------- // --- Projectified State // -------------------------------------------------------------------------- -var globalStates = {} ; +function getValue(id,project) { + if (!project) return undefined; + return _.get( globalStates, [project,id] ); +} + +function setValue(id,project,value) { + const theProject = project || currentProject ; + if (!theProject) return ; + _.set( globalStates, [project,id], value ); + Dome.emit( STATE + id , value ); +} /** @summary Projectified State (Custom React Hook). @param {string} id - the state identifier (mandatory) - @param {string} [project] - the project identifier, defaults to current @return {array} `[state,setState]` for the specified project @description Returns a getter and a setter for the specified state @@ -116,23 +115,112 @@ var globalStates = {} ; Each state is associated to a specific event `frama-c-state.<id>` which is is used to notify updates. The hook also updates on `PROJECT` notifications. */ -export function useState(id,project) +export function useState(id) { - const theEvent = STATE + id ; - Dome.useUpdate( PROJECT, theEvent ); - const theProject = project || currentProject ; - if (!theProject) return NONE; - const thePath = [theProject,id] ; - const theValue = _.get( globalStates, thePath ); - const setValue = (v) => { - _.set( globalStates, thePath, v ); - Dome.emit(theEvent,v); - }; - return [ theValue, setValue ]; + Dome.useUpdate( PROJECT, STATE + id ); + const project = currentProject ; + const value = getValue(id,project); + return [ value , (v) => setValue(id,project,v) ]; +} + +// -------------------------------------------------------------------------- +// --- Synchronized States +// -------------------------------------------------------------------------- + +class Synchro { + + constructor(id) { + this.id = id ; + this.UPDATE = STATE + id ; + this.signal = id + '.sig' ; + this.get_rq = id + '.get' ; + this.set_rq = id + '.set' ; + this.insync = false ; + this.value = undefined ; + this.update = this.update.bind(this); + this.effect = this.effect.bind(this); + this.setValue = this.setValue.bind(this); + Dome.on( PROJECT , this.update ); + } + + value() { + if (!this.insync && Server.isRunning()) + this.update(); + return this.value ; + } + + setValue(v) { + this.insync = true ; + this.value = v ; + Server.sendSET( this.set_rq , v ); + Dome.emit( this.UPDATE ); + } + + update() { + const project = currentProject ; + this.insync = true ; + Server.sendGET( this.get_rq ).then((v) => { + this.value = v ; + Dome.emit( this.UPDATE ); + }); + } + +} + +const synchros = {} ; +function synchro(id) { + let s = synchros[id] ; + if (!s) s = synchros[id] = new Synchro(id); + return s ; +} + +Server.onReady(() => _.forEach( synchros , (s) => { + if (!s.insync) s.update(); +})); + +/** + @summary Use Synchronized State (Custom React Hook). + @parameter {string} id - name of the server state + @return {Array} `[ value , setValue ]` of the synchronized state + @description + Synchronization with some (projectified) server state: + - sends a `<id>.get` request to obtain the current value of the state; + - sends a `<id>.set` request to update the value of the state; + - listens to `<id>.sig` signal to stay in sync with server updates. + */ +export function useSyncState(id) +{ + let s = synchro(id) ; + Dome.useUpdate( s.UPDATE ); + Server.useSignal( s.signal , s.update ); + return [ s.value() , s.setValue ]; +} + +/** + @summary Use Synchronized Value (Custom React Hook). + @parameter {string} id - name of the server state + @return {any} current `value` of the state + @description + Synchronization with some (projectified) server value: + - sends a `<id>.get` request to obtain the current value of the state; + - listens to `<id>.sig` signal to stay in sync with server updates. + */ +export function useSyncValue(id) +{ + let s = synchro(id) ; + Dome.useUpdate( s.update ); + React.useEffect( s.effect ); + return s.value(); } // -------------------------------------------------------------------------- -export default { useProject, setProject, useState, PROJECT }; +export default { + useProject, setProject, + useState, + useSyncState, + useSyncValue, + PROJECT, STATE +}; // --------------------------------------------------------------------------