From 430643242e260c537ff33ed6f0ea0b8ee145912e Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 23 Jan 2021 18:47:48 -0600 Subject: [PATCH] convert hub sagas to typed-redux-saga/macro --- src/sagas/hub.ts | 102 +++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/src/sagas/hub.ts b/src/sagas/hub.ts index ba1ba949..c7578deb 100644 --- a/src/sagas/hub.ts +++ b/src/sagas/hub.ts @@ -1,11 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2021 The Pybricks Authors -import { Ace } from 'ace-builds'; -import { Channel } from 'redux-saga'; import { - RaceEffect, - TakeEffect, + SagaGenerator, actionChannel, getContext, put, @@ -13,14 +10,13 @@ import { select, take, takeEvery, -} from 'redux-saga/effects'; +} from 'typed-redux-saga/macro'; import { Action } from '../actions'; import { BleDeviceActionType } from '../actions/ble'; import { BleUartActionType, BleUartDidFailToWriteAction, BleUartDidWriteAction, - BleUartWriteAction, write, } from '../actions/ble-uart'; import { @@ -41,21 +37,29 @@ import { } from '../actions/mpy'; import { SafeTxCharLength } from '../protocols/nrf-uart'; import { RootState } from '../reducers'; +import { defined } from '../utils'; import { xor8 } from '../utils/math'; const downloadChunkSize = 100; -function waitForWrite(id: number): RaceEffect { - return race([ - take((a: Action) => a.type === BleUartActionType.DidWrite && a.id === id), - take((a: Action) => a.type === BleUartActionType.DidFailToWrite && a.id === id), - ]); +function* waitForWrite( + id: number, +): SagaGenerator<{ + didWrite: BleUartDidWriteAction | undefined; + didFailToWrite: BleUartDidFailToWriteAction | undefined; +}> { + return yield* race({ + didWrite: take( + (a: Action) => a.type === BleUartActionType.DidWrite && a.id === id, + ), + didFailToWrite: take( + (a: Action) => a.type === BleUartActionType.DidFailToWrite && a.id === id, + ), + }); } function* downloadAndRun(_action: HubDownloadAndRunAction): Generator { - const editor = (yield select( - (s: RootState) => s.editor.current, - )) as Ace.EditSession | null; + const editor = yield* select((s: RootState) => s.editor.current); // istanbul ignore next: it is a bug to dispatch this action with no current editor if (editor === null) { @@ -64,48 +68,45 @@ function* downloadAndRun(_action: HubDownloadAndRunAction): Generator { } const script = editor.getValue(); - yield put(compile(script, ['-mno-unicode'])); - const [mpy, mpyFail] = (yield race([ - take(MpyActionType.DidCompile), - take(MpyActionType.DidFailToCompile), - ])) as [MpyDidCompileAction, MpyDidFailToCompileAction]; + yield* put(compile(script, ['-mno-unicode'])); + const { mpy, mpyFail } = yield* race({ + mpy: take(MpyActionType.DidCompile), + mpyFail: take(MpyActionType.DidFailToCompile), + }); if (mpyFail) { return; } + defined(mpy); + // let everyone know the runtime is busy loading the program - yield put(updateStatus(HubRuntimeStatusType.Loading)); + yield* put(updateStatus(HubRuntimeStatusType.Loading)); - const checksumChannel = (yield actionChannel( + const checksumChannel = yield* actionChannel( HubMessageActionType.Checksum, - )) as Channel; + ); - const nextMessageId = (yield getContext('nextMessageId')) as () => number; + const nextMessageId = yield* getContext<() => number>('nextMessageId'); // first send payload size as big-endian 32-bit integer const sizeBuf = new Uint8Array(4); const sizeView = new DataView(sizeBuf.buffer); sizeView.setUint32(0, mpy.data.byteLength, true); - const writeAction = (yield put( - write(nextMessageId(), sizeBuf), - )) as BleUartWriteAction; - const [, didFailToWrite] = (yield waitForWrite(writeAction.id)) as [ - BleUartDidWriteAction, - BleUartDidFailToWriteAction, - ]; + const writeAction = yield* put(write(nextMessageId(), sizeBuf)); + const { didFailToWrite } = yield* waitForWrite(writeAction.id); if (didFailToWrite) { - yield put(updateStatus(HubRuntimeStatusType.Error)); + yield* put(updateStatus(HubRuntimeStatusType.Error)); return; } - const checksumAction = (yield take(checksumChannel)) as HubChecksumMessageAction; + const checksumAction = yield* take(checksumChannel); if (checksumAction.checksum !== (0xff ^ xor8(sizeBuf))) { console.error( `bad checksum ${checksumAction.checksum} vs ${0xff ^ xor8(sizeBuf)}`, ); - yield put(updateStatus(HubRuntimeStatusType.Error)); + yield* put(updateStatus(HubRuntimeStatusType.Error)); return; } @@ -117,56 +118,51 @@ function* downloadAndRun(_action: HubDownloadAndRunAction): Generator { // we can actually only write 20 bytes at a time for (let j = 0; j < chunk.length; j += SafeTxCharLength) { - const writeAction = (yield put( + const writeAction = yield* put( write(nextMessageId(), chunk.slice(j, j + SafeTxCharLength)), - )) as BleUartWriteAction; - const [, didFailToWrite] = (yield waitForWrite(writeAction.id)) as [ - BleUartDidWriteAction, - BleUartDidFailToWriteAction, - ]; + ); + const { didFailToWrite } = yield* waitForWrite(writeAction.id); if (didFailToWrite) { - yield put(updateStatus(HubRuntimeStatusType.Error)); + yield* put(updateStatus(HubRuntimeStatusType.Error)); return; } // TODO: dispatch progress } - const checksumAction = (yield take( - checksumChannel, - )) as HubChecksumMessageAction; + const checksumAction = yield* take(checksumChannel); if (checksumAction.checksum !== (0xff ^ xor8(chunk))) { console.error( `bad checksum ${checksumAction.checksum} vs ${0xff ^ xor8(chunk)}`, ); - yield put(updateStatus(HubRuntimeStatusType.Error)); + yield* put(updateStatus(HubRuntimeStatusType.Error)); return; } } // let everyone know the runtime is done loading the program - yield put(updateStatus(HubRuntimeStatusType.Loaded)); + yield* put(updateStatus(HubRuntimeStatusType.Loaded)); } // SPACE, SPACE, SPACE, SPACE const startReplCommand = new Uint8Array([0x20, 0x20, 0x20, 0x20]); function* startRepl(_action: HubReplAction): Generator { - const nextMessageId = (yield getContext('nextMessageId')) as () => number; - yield put(write(nextMessageId(), startReplCommand)); + const nextMessageId = yield* getContext<() => number>('nextMessageId'); + yield* put(write(nextMessageId(), startReplCommand)); } // CTRL+C, CTRL+C, CTRL+D const stopCommand = new Uint8Array([0x03, 0x03, 0x04]); function* stop(_action: HubStopAction): Generator { - const nextMessageId = (yield getContext('nextMessageId')) as () => number; - yield put(write(nextMessageId(), stopCommand)); + const nextMessageId = yield* getContext<() => number>('nextMessageId'); + yield* put(write(nextMessageId(), stopCommand)); } export default function* (): Generator { - yield takeEvery(HubActionType.DownloadAndRun, downloadAndRun); - yield takeEvery(HubActionType.Repl, startRepl); - yield takeEvery(HubActionType.Stop, stop); + yield* takeEvery(HubActionType.DownloadAndRun, downloadAndRun); + yield* takeEvery(HubActionType.Repl, startRepl); + yield* takeEvery(HubActionType.Stop, stop); // calling stop right after connecting should get the hub into a known state - yield takeEvery(BleDeviceActionType.DidConnect, stop); + yield* takeEvery(BleDeviceActionType.DidConnect, stop); }