Skip to content

Commit

Permalink
feat(client-electron): cache fragments during calibration
Browse files Browse the repository at this point in the history
fixes #81
  • Loading branch information
marcincichocki authored May 16, 2021
1 parent 85d1048 commit 3a6882a
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 67 deletions.
2 changes: 1 addition & 1 deletion configs/webpack.config.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';

export const config: webpack.Configuration = {
mode: 'development',
entry: join(__dirname, '../src/client-electron/main/main.ts'),
entry: join(__dirname, '../src/client-electron/main/index.ts'),
target: 'electron-main',
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
Expand Down
2 changes: 1 addition & 1 deletion configs/webpack.config.renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import webpack from 'webpack';

export const config: webpack.Configuration = {
mode: 'development',
entry: join(__dirname, '../src/client-electron/renderer/renderer.tsx'),
entry: join(__dirname, '../src/client-electron/renderer/index.tsx'),
target: 'electron-renderer',
output: {
path: join(__dirname, '../dist'),
Expand Down
2 changes: 1 addition & 1 deletion configs/webpack.config.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import webpack from 'webpack';

export const config: webpack.Configuration = {
mode: 'development',
entry: join(__dirname, '../src/client-electron/worker/worker.ts'),
entry: join(__dirname, '../src/client-electron/worker/index.ts'),
target: 'electron-renderer',
output: {
path: join(__dirname, '../dist'),
Expand Down
9 changes: 5 additions & 4 deletions src/client-electron/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface Response<T = any> {
origin: Origin;
}

export function createDispatcher(origin: Origin) {
export function createAsyncRequestDispatcher(origin: Origin) {
return <TRes, TReq = any>(action: Omit<Request<TReq>, 'origin' | 'uuid'>) =>
new Promise<TRes>((resolve) => {
const uuid = uuidv4();
Expand All @@ -91,9 +91,10 @@ export function createDispatcher(origin: Origin) {
});
}

export const rendererDispatcher = createDispatcher('renderer');
export const rendererAsyncRequestDispatcher =
createAsyncRequestDispatcher('renderer');

export function createListener(origin: Origin) {
export function createAsyncRequestListener(origin: Origin) {
return (handler: (req: Request) => Promise<any>) => {
ipc.on('async-request', async (event, req: Request) => {
const data = await handler(req);
Expand All @@ -108,7 +109,7 @@ export function createListener(origin: Origin) {
};
}

export const workerListener = createListener('worker');
export const workerAsyncRequestListener = createAsyncRequestListener('worker');

export interface TestThresholdData {
fileName: string;
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ReactDOM from 'react-dom';
import { render } from 'react-dom';
import { App } from './app';
import { GlobalStyles } from './styles/global';
import { HashRouter as Router } from 'react-router-dom';

ReactDOM.render(
render(
<>
<GlobalStyles />
<Router>
Expand Down
15 changes: 14 additions & 1 deletion src/client-electron/renderer/pages/Calibrate.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FC } from 'react';
import { rendererAsyncRequestDispatcher as dispatch } from '@/client-electron/common';
import { FC, useEffect } from 'react';
import { MdKeyboardBackspace } from 'react-icons/md';
import { Link, Route, useRouteMatch } from 'react-router-dom';
import styled from 'styled-components';
Expand All @@ -18,11 +19,23 @@ const Heading = styled.h1<{ active: boolean }>`
margin: 0;
`;

function useContainerInit(fileName: string) {
useEffect(() => {
// FIXME: tiny race condition. Disable button until fragments are ready.
dispatch({ type: 'TEST_THRESHOLD_INIT', data: fileName });

return () => {
dispatch({ type: 'TEST_THRESHOLD_DISPOSE' });
};
}, []);
}

export const Calibrate: FC = () => {
const entry = useHistoryEntryFromParam();

if (!entry) return null;

useContainerInit(entry.fileName);
const { path, params } = useRouteMatch<{ entryId: string }>();
const { time, distance } = transformTimestamp(entry.startedAt);

Expand Down
4 changes: 2 additions & 2 deletions src/client-electron/renderer/pages/CalibrateFragment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BreachProtocolFragmentResults, FragmentId } from '@/core';
import { FC, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import { rendererDispatcher } from '../../common';
import { rendererAsyncRequestDispatcher as dispatch } from '../../common';
import { fromCamelCase } from '../common';
import {
Col,
Expand Down Expand Up @@ -55,7 +55,7 @@ export const CalibrateFragment: FC<CalibrateFragmentProps> = ({ entry }) => {
async function onTestThreshold(value: any) {
setLoading(true);

const result = await rendererDispatcher<
const result = await dispatch<
BreachProtocolFragmentResults[number],
TestThresholdData
>({
Expand Down
10 changes: 10 additions & 0 deletions src/client-electron/worker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { BreachProtocolWorker } from './worker';

const worker = new BreachProtocolWorker();

// TODO: error handling in worker.
worker.bootstrap();

window.addEventListener('unload', () => {
worker.dispose();
});
144 changes: 89 additions & 55 deletions src/client-electron/worker/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,88 +4,122 @@ import {
BreachProtocolDaemonsFragment,
BreachProtocolGridFragment,
BreachProtocolOCRFragment,
FragmentId,
SharpImageContainer,
} from '@/core';
import { ipcRenderer as ipc } from 'electron';
import { listDisplays } from 'screenshot-desktop';
import { ipcRenderer as ipc, IpcRendererEvent } from 'electron';
import { listDisplays, ScreenshotDisplayOutput } from 'screenshot-desktop';
import sharp from 'sharp';
import {
Action,
Request,
TestThresholdData,
workerListener,
workerAsyncRequestListener,
WorkerStatus,
} from '../common';
import { BreachProtocolAutosolver } from './autosolver';

function updateStatus(payload: WorkerStatus) {
dispatch({ type: 'SET_STATUS', payload });
}
export class BreachProtocolWorker {
private activeDisplayId: string = null;

function dispatch(action: Omit<Action, 'origin'>) {
ipc.send('state', { ...action, origin: 'worker' });
}
private disposeAsyncRequestListener: () => void = null;

private fragments: {
grid: BreachProtocolGridFragment<sharp.Sharp>;
daemons: BreachProtocolDaemonsFragment<sharp.Sharp>;
bufferSize: BreachProtocolBufferSizeFragment<sharp.Sharp>;
} = null;

async bootstrap() {
this.registerListeners();

this.updateStatus(WorkerStatus.Bootstrap);

// TODO: change lang, or remove it completly.
setLang('en');
const displays = await listDisplays();
this.activeDisplayId = displays[0].id;

ipc.on('SET_ACTIVE_DISPLAY', (event, state) => {
screenId = state.id;
});
this.dispatch({ type: 'SET_DISPLAYS', payload: displays });
this.dispatch({ type: 'SET_ACTIVE_DISPLAY', payload: displays[0] });

const disposeAsyncRequestListener = workerListener(handleAsyncRequest);
await BreachProtocolOCRFragment.initScheduler();

async function handleAsyncRequest(req: Request) {
switch (req.type) {
case 'TEST_THRESHOLD':
return testThreshold(req);
default:
this.updateStatus(WorkerStatus.Ready);
ipc.send('worker:ready');
}
}

function getFragment(id: FragmentId, container: SharpImageContainer) {
switch (id) {
case 'grid':
return new BreachProtocolGridFragment(container);
case 'daemons':
return new BreachProtocolDaemonsFragment(container);
case 'bufferSize':
return new BreachProtocolBufferSizeFragment(container);
dispose() {
ipc.removeAllListeners('worker:solve');
ipc.removeAllListeners('SET_ACTIVE_DISPLAY');

this.disposeAsyncRequestListener();
this.disposeTestThreshold();
}
}

async function testThreshold(req: Request<TestThresholdData>) {
const instance = sharp(req.data.fileName);
const container = await SharpImageContainer.create(instance);
const fragment = getFragment(req.data.fragmentId, container);
private registerListeners() {
ipc.on('worker:solve', this.onWorkerSolve.bind(this));
ipc.on('SET_ACTIVE_DISPLAY', this.onSetActiveDisplay.bind(this));

return fragment.recognize(req.data.threshold, false);
}
this.disposeAsyncRequestListener = workerAsyncRequestListener(
this.handleAsyncRequest.bind(this)
);
}

let screenId: string = null;
private async onWorkerSolve() {
this.updateStatus(WorkerStatus.Working);

async function bootstrap() {
updateStatus(WorkerStatus.Bootstrap);
const bpa = new BreachProtocolAutosolver(this.activeDisplayId);
await bpa.solve();

setLang('en');
const displays = await listDisplays();
screenId = displays[0].id;
this.dispatch({ type: 'ADD_HISTORY_ENTRY', payload: bpa.toJSON() });
this.updateStatus(WorkerStatus.Ready);
}

dispatch({ type: 'SET_DISPLAYS', payload: displays });
dispatch({ type: 'SET_ACTIVE_DISPLAY', payload: displays[0] });
private onSetActiveDisplay(
e: IpcRendererEvent,
display: ScreenshotDisplayOutput
) {
this.activeDisplayId = display.id;
}

await BreachProtocolOCRFragment.initScheduler();
private async handleAsyncRequest(req: Request) {
switch (req.type) {
case 'TEST_THRESHOLD_INIT':
return this.initTestThreshold(req);
case 'TEST_THRESHOLD_DISPOSE':
return this.disposeTestThreshold();
case 'TEST_THRESHOLD':
return this.testThreshold(req);
default:
}
}

ipc.send('worker:ready');
updateStatus(WorkerStatus.Ready);
}
private async initTestThreshold(req: Request<string>) {
const instance = sharp(req.data);
const container = await SharpImageContainer.create(instance);

bootstrap();
this.fragments = {
grid: new BreachProtocolGridFragment(container),
daemons: new BreachProtocolDaemonsFragment(container),
bufferSize: new BreachProtocolBufferSizeFragment(container),
};
}

ipc.on('worker:solve', async () => {
updateStatus(WorkerStatus.Working);
private disposeTestThreshold() {
this.fragments = null;
}

const bpa = new BreachProtocolAutosolver(screenId);
await bpa.solve();
private async testThreshold(req: Request<TestThresholdData>) {
const fragment = this.fragments[req.data.fragmentId];

dispatch({ type: 'ADD_HISTORY_ENTRY', payload: bpa.toJSON() });
updateStatus(WorkerStatus.Ready);
});
return fragment.recognize(req.data.threshold, false);
}

private updateStatus(payload: WorkerStatus) {
this.dispatch({ type: 'SET_STATUS', payload });
}

private dispatch(action: Omit<Action, 'origin'>) {
ipc.send('state', { ...action, origin: 'worker' });
}
}

0 comments on commit 3a6882a

Please sign in to comment.