From b8f013f4adb1eb556c940e9308bf46883dfbeb60 Mon Sep 17 00:00:00 2001 From: c-bata Date: Mon, 11 Mar 2024 12:34:21 +0900 Subject: [PATCH] [standalone_app] Use React.Context instead of Recoil --- package.json | 2 +- standalone_app/package-lock.json | 88 +++----- standalone_app/package.json | 3 +- standalone_app/src/browser_app_entry.tsx | 6 +- .../src/components/StorageLoader.tsx | 17 +- .../src/components/StorageProvider.tsx | 31 +++ standalone_app/src/components/StudyDetail.tsx | 28 ++- standalone_app/src/components/StudyList.tsx | 29 ++- standalone_app/src/journalStorage.ts | 23 +- standalone_app/src/sqlite3.ts | 197 ++++++++++-------- standalone_app/src/state.ts | 6 - standalone_app/src/types/index.d.ts | 11 + standalone_app/src/vscode_entry.tsx | 35 +--- 13 files changed, 251 insertions(+), 225 deletions(-) create mode 100644 standalone_app/src/components/StorageProvider.tsx delete mode 100644 standalone_app/src/state.ts diff --git a/package.json b/package.json index 4785f085..9c214ab9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Dashboard for Optuna", "main": "index.js", "scripts": { - "fmt": "biome format --write .", + "fmt": "biome format --write . && biome check standalone_app --apply", "lint": "npm run lint:eslint && npm run lint:biome", "lint:eslint": "eslint . --ext .ts,.tsx --max-warnings 0", "lint:biome": "biome format . && biome ci standalone_app", diff --git a/standalone_app/package-lock.json b/standalone_app/package-lock.json index 1c9a76a9..b105f5bf 100644 --- a/standalone_app/package-lock.json +++ b/standalone_app/package-lock.json @@ -20,8 +20,7 @@ "plotly.js-dist-min": "^2.30.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.22.2", - "recoil": "^0.7.7" + "react-router-dom": "^6.22.3" }, "devDependencies": { "@types/plotly.js": "^2.29.2", @@ -1325,9 +1324,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz", - "integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", "engines": { "node": ">=14.0.0" } @@ -2577,11 +2576,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -3165,11 +3159,11 @@ } }, "node_modules/react-router": { - "version": "6.22.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz", - "integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", "dependencies": { - "@remix-run/router": "1.15.2" + "@remix-run/router": "1.15.3" }, "engines": { "node": ">=14.0.0" @@ -3179,12 +3173,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz", - "integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", "dependencies": { - "@remix-run/router": "1.15.2", - "react-router": "6.22.2" + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" }, "engines": { "node": ">=14.0.0" @@ -3221,25 +3215,6 @@ "node": ">= 10.13.0" } }, - "node_modules/recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -4638,9 +4613,9 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@remix-run/router": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz", - "integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==" + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==" }, "@rollup/rollup-android-arm-eabi": { "version": "4.12.1", @@ -5586,11 +5561,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6012,20 +5982,20 @@ "dev": true }, "react-router": { - "version": "6.22.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz", - "integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", "requires": { - "@remix-run/router": "1.15.2" + "@remix-run/router": "1.15.3" } }, "react-router-dom": { - "version": "6.22.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz", - "integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", "requires": { - "@remix-run/router": "1.15.2", - "react-router": "6.22.2" + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" } }, "react-transition-group": { @@ -6048,14 +6018,6 @@ "resolve": "^1.20.0" } }, - "recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "requires": { - "hamt_plus": "1.0.2" - } - }, "regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", diff --git a/standalone_app/package.json b/standalone_app/package.json index c3c471e9..b59aabd0 100644 --- a/standalone_app/package.json +++ b/standalone_app/package.json @@ -32,7 +32,6 @@ "plotly.js-dist-min": "^2.30.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.22.2", - "recoil": "^0.7.7" + "react-router-dom": "^6.22.3" } } diff --git a/standalone_app/src/browser_app_entry.tsx b/standalone_app/src/browser_app_entry.tsx index 705200cc..f2d2b0f1 100644 --- a/standalone_app/src/browser_app_entry.tsx +++ b/standalone_app/src/browser_app_entry.tsx @@ -1,13 +1,13 @@ import React from "react" import ReactDOM from "react-dom/client" -import { RecoilRoot } from "recoil" import { App } from "./components/App" +import { StorageProvider } from "./components/StorageProvider" import "./index.css" ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + - + ) diff --git a/standalone_app/src/components/StorageLoader.tsx b/standalone_app/src/components/StorageLoader.tsx index 2b3640d6..fd108976 100644 --- a/standalone_app/src/components/StorageLoader.tsx +++ b/standalone_app/src/components/StorageLoader.tsx @@ -13,17 +13,15 @@ import React, { MouseEventHandler, useRef, useState, + useContext, } from "react" -import { useSetRecoilState } from "recoil" -import { loadJournalStorage } from "../journalStorage" -import { loadSQLite3Storage } from "../sqlite3" -import { studiesState } from "../state" +import { StorageContext, getStorage } from "./StorageProvider" export const StorageLoader: FC = () => { const theme = useTheme() const [dragOver, setDragOver] = useState(false) + const { setStorage } = useContext(StorageContext) - const setStudies = useSetRecoilState(studiesState) const inputRef = useRef(null) const loadStorageFromFile = (file: File): void => { @@ -31,13 +29,8 @@ export const StorageLoader: FC = () => { r.addEventListener("load", () => { const arrayBuffer = r.result as ArrayBuffer | null if (arrayBuffer !== null) { - const header = new Uint8Array(arrayBuffer, 0, 16) - const headerString = new TextDecoder().decode(header) - if (headerString === "SQLite format 3\u0000") { - loadSQLite3Storage(arrayBuffer, setStudies) - } else { - loadJournalStorage(arrayBuffer, setStudies) - } + const s = getStorage(arrayBuffer) + setStorage(s) } }) r.readAsArrayBuffer(file) diff --git a/standalone_app/src/components/StorageProvider.tsx b/standalone_app/src/components/StorageProvider.tsx new file mode 100644 index 00000000..1ac3c23b --- /dev/null +++ b/standalone_app/src/components/StorageProvider.tsx @@ -0,0 +1,31 @@ +import React, { FC, createContext, useState } from "react" +import { JournalFileStorage } from "../journalStorage" +import { SQLite3Storage } from "../sqlite3" + +export const StorageContext = createContext<{ + storage: OptunaStorage | null + setStorage: (storage: OptunaStorage) => void +}>({ + storage: null, + setStorage: () => {}, +}) + +export const getStorage = (arrayBuffer: ArrayBuffer): OptunaStorage => { + const header = new Uint8Array(arrayBuffer, 0, 16) + const headerString = new TextDecoder().decode(header) + if (headerString === "SQLite format 3\u0000") { + return new SQLite3Storage(arrayBuffer) + } + return new JournalFileStorage(arrayBuffer) +} + +export const StorageProvider: FC<{ + children: React.ReactNode +}> = ({ children }) => { + const [storage, setStorage] = useState(null) + return ( + + {children} + + ) +} diff --git a/standalone_app/src/components/StudyDetail.tsx b/standalone_app/src/components/StudyDetail.tsx index ad8bdfc0..2cb0baf6 100644 --- a/standalone_app/src/components/StudyDetail.tsx +++ b/standalone_app/src/components/StudyDetail.tsx @@ -13,30 +13,36 @@ import { useTheme, } from "@mui/material" import Grid2 from "@mui/material/Unstable_Grid2" -import React, { FC } from "react" +import React, { FC, useContext, useState, useEffect } from "react" import { Link, useParams } from "react-router-dom" -import { useRecoilValue } from "recoil" -import { studiesState } from "../state" import { PlotHistory } from "./PlotHistory" import { PlotImportance } from "./PlotImportance" import { PlotIntermediateValues } from "./PlotIntermediateValues" +import { StorageContext } from "./StorageProvider" import { TrialTable } from "./TrialTable" -const useStudyValue = (idx: number): Study | null => { - const studies = useRecoilValue(studiesState) - return studies[idx] || null -} - export const StudyDetail: FC<{ toggleColorMode: () => void }> = ({ toggleColorMode }) => { const theme = useTheme() const { idx } = useParams<{ idx: string }>() const idxNumber = parseInt(idx || "", 10) - const study = useStudyValue(idxNumber) + + const { storage } = useContext(StorageContext) + const [study, setStudy] = useState(null) + useEffect(() => { + const fetchStudy = async () => { + if (storage === null) { + return + } + const study = await storage.getStudy(idxNumber) + setStudy(study) + } + fetchStudy() + }, [storage, idxNumber]) return ( -
+ <> -
+ ) } diff --git a/standalone_app/src/components/StudyList.tsx b/standalone_app/src/components/StudyList.tsx index a8c9bca4..d90717c9 100644 --- a/standalone_app/src/components/StudyList.tsx +++ b/standalone_app/src/components/StudyList.tsx @@ -19,23 +19,40 @@ import { useTheme, } from "@mui/material" import { styled } from "@mui/system" -import React, { FC, useState, useMemo, useDeferredValue } from "react" +import React, { + FC, + useEffect, + useContext, + useState, + useMemo, + useDeferredValue, +} from "react" import { Link } from "react-router-dom" -import { useRecoilValue } from "recoil" -import { studiesState } from "../state" import { StorageLoader } from "./StorageLoader" +import { StorageContext } from "./StorageProvider" export const StudyList: FC<{ toggleColorMode: () => void }> = ({ toggleColorMode }) => { const theme = useTheme() - const studies = useRecoilValue(studiesState) + const { storage } = useContext(StorageContext) + const [studies, setStudies] = useState([]) const [_studyFilterText, setStudyFilterText] = useState("") const [sortBy, setSortBy] = useState<"id-asc" | "id-desc">("id-asc") const studyFilterText = useDeferredValue(_studyFilterText) + useEffect(() => { + const fetchStudies = async () => { + if (storage === null) { + return + } + const studies = await storage.getStudies() + setStudies(studies) + } + fetchStudies() + }, [storage]) const filteredStudies = useMemo(() => { - const studyFilter = (row: Study): boolean => { + const studyFilter = (row: StudySummary): boolean => { const keywords = studyFilterText.split(" ") return !keywords.every((k) => { if (k === "") { @@ -44,7 +61,7 @@ export const StudyList: FC<{ return row.study_name.indexOf(k) >= 0 }) } - let filteredStudies: Study[] = studies.filter((s) => !studyFilter(s)) + let filteredStudies: StudySummary[] = studies.filter((s) => !studyFilter(s)) if (sortBy === "id-desc") { filteredStudies = filteredStudies.reverse() } diff --git a/standalone_app/src/journalStorage.ts b/standalone_app/src/journalStorage.ts index e2fcd83c..8de7af61 100644 --- a/standalone_app/src/journalStorage.ts +++ b/standalone_app/src/journalStorage.ts @@ -1,5 +1,3 @@ -import { SetterOrUpdater } from "recoil" - // JournalStorage enum JournalOperation { CREATE_STUDY = 0, @@ -329,10 +327,20 @@ class JournalStorage { } } -export const loadJournalStorage = ( - arrayBuffer: ArrayBuffer, - setter: SetterOrUpdater -): void => { +export class JournalFileStorage implements OptunaStorage { + studies: Study[] + constructor(arrayBuffer: ArrayBuffer) { + this.studies = loadJournalStorage(arrayBuffer) + } + getStudies = async (): Promise => { + return this.studies + } + getStudy = async (idx: number): Promise => { + return this.studies[idx] || null + } +} + +export const loadJournalStorage = (arrayBuffer: ArrayBuffer): Study[] => { const decoder = new TextDecoder("utf-8") const logs = decoder.decode(arrayBuffer).split("\n") @@ -383,6 +391,5 @@ export const loadJournalStorage = ( } } - const studies = journalStorage.getStudies() - setter((prev) => [...prev, ...studies]) + return journalStorage.getStudies() } diff --git a/standalone_app/src/sqlite3.ts b/standalone_app/src/sqlite3.ts index aac99150..2a8261c5 100644 --- a/standalone_app/src/sqlite3.ts +++ b/standalone_app/src/sqlite3.ts @@ -1,6 +1,5 @@ // @ts-ignore import sqlite3InitModule from "@sqlite.org/sqlite-wasm" -import { SetterOrUpdater } from "recoil" type SQLite3DB = { exec(options: { @@ -10,44 +9,57 @@ type SQLite3DB = { }): void } -export const loadSQLite3Storage = ( - arrayBuffer: ArrayBuffer, - setter: SetterOrUpdater -): void => { - sqlite3InitModule({ - // biome-ignore lint/suspicious/noExplicitAny: - print: (...args: any): void => { - console.log(args) - }, - // biome-ignore lint/suspicious/noExplicitAny: - printErr: (...args: any): void => { - console.log(args) - }, - // @ts-ignore - }).then((sqlite3) => { - const p = sqlite3.wasm.allocFromTypedArray(arrayBuffer) - const db = new sqlite3.oo1.DB() - const rc = sqlite3.capi.sqlite3_deserialize( +export class SQLite3Storage implements OptunaStorage { + db: Promise + summaries_cache: StudySummary[] | null + constructor(arrayBuffer: ArrayBuffer) { + this.db = this.initDB(arrayBuffer) + this.summaries_cache = null + } + + async initDB(arrayBuffer: ArrayBuffer): Promise { + return sqlite3InitModule({ + print: console.log, + printErr: console.log, // @ts-ignore - db.pointer, - "main", - p, - arrayBuffer.byteLength, - arrayBuffer.byteLength, - sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE - ) - db.checkRc(rc) - try { - const schemaVersion = getSchemaVersion(db) - if (!isSupportedSchema(schemaVersion)) { - return - } - const studies = getStudies(db, schemaVersion) - setter((prev) => [...prev, ...studies]) - } finally { - db.close() + }).then((sqlite3) => { + const p = sqlite3.wasm.allocFromTypedArray(arrayBuffer) + const db = new sqlite3.oo1.DB() + const rc = sqlite3.capi.sqlite3_deserialize( + // @ts-ignore + db.pointer, + "main", + p, + arrayBuffer.byteLength, + arrayBuffer.byteLength, + sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE + ) + db.checkRc(rc) + return db + }) + } + + getStudies = async (): Promise => { + const db = await this.db + this.summaries_cache = getStudySummaries(db) + return this.summaries_cache + } + + getStudy = async (idx: number): Promise => { + const db = await this.db + const schemaVersion = getSchemaVersion(db) + if (!isSupportedSchema(schemaVersion)) { + return null } - }) + if (this.summaries_cache === null) { + this.summaries_cache = getStudySummaries(db) + } + const summary = this.summaries_cache[idx] + if (summary === undefined) { + return null + } + return getStudy(db, schemaVersion, summary) + } } const getSchemaVersion = (db: SQLite3DB): string => { @@ -84,8 +96,8 @@ const isGreaterSchemaVersion = ( return left > right } -const getStudies = (db: SQLite3DB, schemaVersion: string): Study[] => { - const studies: Study[] = [] +const getStudySummaries = (db: SQLite3DB): StudySummary[] => { + const summaries: StudySummary[] = [] db.exec({ sql: "SELECT s.study_id, s.study_name, sd.direction, sd.objective" + @@ -99,64 +111,73 @@ const getStudies = (db: SQLite3DB, schemaVersion: string): Study[] => { vals[2] === "MINIMIZE" ? "minimize" : "maximize" const objective = vals[3] - const trials = getTrials(db, studyId, schemaVersion) - const union_search_space: SearchSpaceItem[] = [] - const union_user_attrs: AttributeSpec[] = [] - let intersection_search_space: Set = new Set() - - for (const trial of trials) { - const userAttrs = getTrialUserAttributes(db, trial.trial_id) - for (const attr of userAttrs) { - if (union_user_attrs.findIndex((s) => s.key === attr.key) === -1) { - union_user_attrs.push({ key: attr.key, sortable: false }) - } - } - - const params = getTrialParams(db, trial.trial_id) - const param_names = new Set() - for (const param of params) { - param_names.add(param.name) - if ( - union_search_space.findIndex((s) => s.name === param.name) === -1 - ) { - union_search_space.push({ name: param.name }) - } - } - if (intersection_search_space.size === 0) { - // biome-ignore lint/complexity/noForEach: - param_names.forEach((s) => { - intersection_search_space.add({ - name: s, - }) - }) - } else { - intersection_search_space = new Set( - Array.from(intersection_search_space).filter((s) => - param_names.has(s.name) - ) - ) - } - trial.params = params - trial.user_attrs = userAttrs - } - if (objective === 0) { - studies.push({ + summaries.push({ study_id: studyId, study_name: studyName, directions: [direction], - union_search_space: union_search_space, - intersection_search_space: Array.from(intersection_search_space), - union_user_attrs: union_user_attrs, - trials: trials, }) return } - const index = studies.findIndex((s) => s.study_id === studyId) - studies[index].directions.push(direction) + const index = summaries.findIndex((s) => s.study_id === studyId) + summaries[index].directions.push(direction) }, }) - return studies + return summaries +} + +const getStudy = ( + db: SQLite3DB, + schemaVersion: string, + summary: StudySummary +): Study => { + const study: Study = { + study_id: summary.study_id, + study_name: summary.study_name, + directions: summary.directions, + union_search_space: [], + intersection_search_space: [], + union_user_attrs: [], + trials: [], + } + + let intersection_search_space: Set = new Set() + study.trials = getTrials(db, summary.study_id, schemaVersion) + for (const trial of study.trials) { + const userAttrs = getTrialUserAttributes(db, trial.trial_id) + for (const attr of userAttrs) { + if (study.union_user_attrs.findIndex((s) => s.key === attr.key) === -1) { + study.union_user_attrs.push({ key: attr.key, sortable: false }) + } + } + + const params = getTrialParams(db, trial.trial_id) + const param_names = new Set() + for (const param of params) { + param_names.add(param.name) + if ( + study.union_search_space.findIndex((s) => s.name === param.name) === -1 + ) { + study.union_search_space.push({ name: param.name }) + } + } + if (intersection_search_space.size === 0) { + // biome-ignore lint/complexity/noForEach: + param_names.forEach((s) => { + intersection_search_space.add({ name: s }) + }) + } else { + intersection_search_space = new Set( + Array.from(intersection_search_space).filter((s) => + param_names.has(s.name) + ) + ) + } + trial.params = params + trial.user_attrs = userAttrs + } + study.intersection_search_space = Array.from(intersection_search_space) + return study } const getTrials = ( diff --git a/standalone_app/src/state.ts b/standalone_app/src/state.ts deleted file mode 100644 index a1bcc858..00000000 --- a/standalone_app/src/state.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { atom } from "recoil" - -export const studiesState = atom({ - key: "studies", - default: [], -}) diff --git a/standalone_app/src/types/index.d.ts b/standalone_app/src/types/index.d.ts index 4bc34bf5..1d258f36 100644 --- a/standalone_app/src/types/index.d.ts +++ b/standalone_app/src/types/index.d.ts @@ -4,6 +4,11 @@ type TrialState = "Running" | "Complete" | "Pruned" | "Fail" | "Waiting" type TrialStateFinished = "Complete" | "Fail" | "Pruned" type StudyDirection = "maximize" | "minimize" | "not_set" +type OptunaStorage = { + getStudies: () => Promise + getStudy: (idx: number) => Promise +} + type FloatDistribution = { type: "FloatDistribution" low: number @@ -46,6 +51,12 @@ type AttributeSpec = { sortable: boolean } +type StudySummary = { + study_id: number + study_name: string + directions: StudyDirection[] +} + type Study = { study_id: number study_name: string diff --git a/standalone_app/src/vscode_entry.tsx b/standalone_app/src/vscode_entry.tsx index dad1868c..66c522a5 100644 --- a/standalone_app/src/vscode_entry.tsx +++ b/standalone_app/src/vscode_entry.tsx @@ -1,22 +1,15 @@ -import React, { FC, useEffect } from "react" +import React, { FC, useEffect, useContext } from "react" import ReactDOM from "react-dom/client" -import { RecoilRoot, SetterOrUpdater, useSetRecoilState } from "recoil" import { App } from "./components/App" +import { + StorageContext, + StorageProvider, + getStorage, +} from "./components/StorageProvider" import "./index.css" -import { loadJournalStorage } from "./journalStorage" -import { loadSQLite3Storage } from "./sqlite3" -import { studiesState } from "./state" export const AppWrapper: FC = () => { - const setStudies = useSetRecoilState(studiesState) - - // TODO(c-bata): Fix the type annotation - const onceSetStudies: SetterOrUpdater = ( - setter: (currVal: Study[]) => Study[] - ): void => { - const studies = setter([]) - setStudies(studies) - } + const { setStorage } = useContext(StorageContext) // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { @@ -27,8 +20,6 @@ export const AppWrapper: FC = () => { let len: number let bytes: Uint8Array let arrayBuffer: ArrayBuffer - let header: Uint8Array - let headerString: string switch (message.type) { case "optunaStorage": @@ -40,13 +31,7 @@ export const AppWrapper: FC = () => { bytes[i] = binaryString.charCodeAt(i) } arrayBuffer = bytes.buffer - header = new Uint8Array(arrayBuffer, 0, 16) - headerString = new TextDecoder().decode(header) - if (headerString === "SQLite format 3\u0000") { - loadSQLite3Storage(arrayBuffer, onceSetStudies) - } else { - loadJournalStorage(arrayBuffer, onceSetStudies) - } + setStorage(getStorage(arrayBuffer)) break } }) @@ -56,8 +41,8 @@ export const AppWrapper: FC = () => { ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + - + )