Skip to content

Commit

Permalink
feat(autosave): implement autosave for models (#147)
Browse files Browse the repository at this point in the history
* feat(autosave): add initial implementation

* fix(autoSave): fix multiple bugs

Signed-off-by: Bill ZHANG <[email protected]>

* fix(App): fix bugs that cause worker init twice in dev mode

Signed-off-by: Bill ZHANG <[email protected]>

---------

Signed-off-by: Bill ZHANG <[email protected]>
  • Loading branch information
Lutra-Fs authored Sep 30, 2023
1 parent 057f175 commit 54b97d6
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 7 deletions.
11 changes: 9 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,22 @@ function App(): React.ReactElement {
);
setSimWorker(worker);

return () => {
worker.terminate();
}
}, []);

useEffect(() => {
const message: IncomingMessage = {
func: 'init',
args: [
'/initData/pvf_incomp_44_nonneg/pvf_incomp_44_nonneg_0.json',
'/model/bno_small_new_web/model.json',
],
};
worker.postMessage(message);
}, []);
if (simWorker === null) return;
simWorker.postMessage(message);
}, [simWorker]);

let mainPageComponent;
switch (page) {
Expand Down
Empty file added src/components/restorePopUp.tsx
Empty file.
6 changes: 3 additions & 3 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { Canvas } from '@react-three/fiber';
import styled from 'styled-components';
import { useEffect, useMemo } from 'react';
import { type OutgoingMessage } from '../workers/modelWorkerMessage';
import { type IncomingMessage, type OutgoingMessage } from '../workers/modelWorkerMessage';
import { type ModelSave } from '../services/model/modelService';

const SimulatorContainer = styled.div`
Expand Down Expand Up @@ -72,8 +72,8 @@ export default function Home(props: IndexProp): React.ReactElement {
case 'init':
console.log('worker initialised');
worker.postMessage({
type: 'start'
});
func: 'start'
} satisfies IncomingMessage);
break;
case 'output':
for (const x of outputSubs)
Expand Down
111 changes: 111 additions & 0 deletions src/services/autoSave/autoSaveService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// a service that auto saves the current model with given interval
// to IndexedDB

import { type ModelSave } from '../model/modelService';

export default class AutoSaveService {
saveInterval: number;
intervalObj: ReturnType<typeof setInterval> | undefined;
maxAutoSaves: number;
getModelSerialized: () => ModelSave;
db!: IDBDatabase;
ready = false;
constructor(
getModelSerialized: () => ModelSave,
saveInterval = 10000,
maxAutoSaves = 5,
) {
this.saveInterval = saveInterval;
this.maxAutoSaves = maxAutoSaves;
this.getModelSerialized = getModelSerialized;
this.intervalObj = undefined;
const dbRequest = indexedDB.open('modelAutoSave', 1);
dbRequest.onupgradeneeded = () => {
const db = dbRequest.result;
this.db = db;
const objectStore = this.db.createObjectStore('modelSave', {
autoIncrement: true,
});
objectStore.transaction.oncomplete = () => {
console.log('Successfully created object store');
};
};
dbRequest.onsuccess = () => {
console.log('Successfully opened IndexedDB');
this.db = dbRequest.result;
this.ready = true;
};
dbRequest.onerror = () => {
throw new Error('Failed to open IndexedDB');
};
}

startAutoSave(): void {
if (this.intervalObj !== null && this.intervalObj !== undefined) {
throw new Error('Auto save already started');
}
if (!this.ready) {
throw new Error('IndexedDB not ready');
}
this.intervalObj = setInterval(() => {
const serialisationData = this.getModelSerialized();
const modelSaveString = JSON.stringify(serialisationData);
console.log('saving model');
const transaction = this.db.transaction(['modelSave'], 'readwrite');
transaction.onerror = () => {
throw new Error('Failed to open transaction');
};
const objectStore = transaction.objectStore('modelSave');
const request = objectStore.add(modelSaveString);
request.onsuccess = () => {
console.log('successfully saved model');
};
request.onerror = () => {
throw new Error('Failed to save model');
};
const request2 = objectStore.count();
request2.onsuccess = () => {
const count = request2.result;
console.log('count', count);
if (count > this.maxAutoSaves) {
console.log('deleting old model');
// use while loop to delete all but the last 5 models
const request3 = objectStore.getAllKeys();
request3.onsuccess = () => {
const keys = request3.result;
console.log('keys', keys);
const keysToDelete = keys.slice(0, count - this.maxAutoSaves);
console.log('keysToDelete', keysToDelete);
keysToDelete.forEach((key) => {
const request4 = objectStore.delete(key);
request4.onsuccess = () => {
console.log('successfully deleted model');
};
request4.onerror = () => {
throw new Error('Failed to delete model');
};
});
}
}
};
request2.onerror = () => {
throw new Error('Failed to count models');
};
transaction.oncomplete = () => {
console.log('Successfully saved model and possibly deleted old models');
}
}, this.saveInterval);
}

pauseAutoSave(): void {
setTimeout(() => {
console.log('pausing auto save');
clearInterval(this.intervalObj);
this.intervalObj = undefined;
}, 0);
}

close(): void {
this.db.close();
}
}
6 changes: 4 additions & 2 deletions src/services/model/modelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ export async function createModelService(
export function modelSerialize(
url: string,
model: ModelService | null,
): ModelSave | null {
if (model == null) return null;
): ModelSave {
if (model == null) {
throw new Error('model is null, cannot serialise, check model is initialised or not');
}
// export a JSON as ModelSave

return {
Expand Down
23 changes: 23 additions & 0 deletions src/workers/modelWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
// modelDeserialize
} from '../services/model/modelService';
import { type IncomingMessage } from './modelWorkerMessage';
import AutoSaveService from '../services/autoSave/autoSaveService';

let modelService: ModelService | null = null;
let autoSaveService: AutoSaveService | null = null;

interface UpdateForceArgs {
loc: Vector2;
Expand All @@ -36,6 +38,9 @@ export function onmessage(
getServiceFromInitCond(this, dataPath, modelurl)
.then((service) => {
modelService = service;
autoSaveService = new AutoSaveService(() => {
return modelSerialize(modelurl, modelService);
});
this.postMessage({ type: 'init', success: true });
})
.catch((e) => {
Expand All @@ -48,12 +53,30 @@ export function onmessage(
throw new Error('modelService is null');
}
modelService.startSimulation();
if (autoSaveService != null) {
try {
autoSaveService.startAutoSave();
} catch (e) {
// if error is not ready, retry in 1 second
const error = e as Error;
if (error.message === 'IndexedDB not ready') {
setTimeout(() => {
autoSaveService?.startAutoSave();
}, 500);
} else {
throw e;
}
}
}
break;
case 'pause':
if (modelService == null) {
throw new Error('modelService is null');
}
modelService.pauseSimulation();
if (autoSaveService != null) {
autoSaveService.pauseAutoSave();
}
break;
case 'updateForce':
updateForce(data.args as UpdateForceArgs);
Expand Down

0 comments on commit 54b97d6

Please sign in to comment.