mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
convert hub sagas to typed-redux-saga/macro
This commit is contained in:
+49
-53
@@ -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<TakeEffect> {
|
||||
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<BleUartDidWriteAction>(
|
||||
(a: Action) => a.type === BleUartActionType.DidWrite && a.id === id,
|
||||
),
|
||||
didFailToWrite: take<BleUartDidFailToWriteAction>(
|
||||
(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<MpyDidCompileAction>(MpyActionType.DidCompile),
|
||||
mpyFail: take<MpyDidFailToCompileAction>(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<HubChecksumMessageAction>(
|
||||
HubMessageActionType.Checksum,
|
||||
)) as Channel<HubChecksumMessageAction>;
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user