From fc2093b102f9e062b682d5f42b18261616dfd5d4 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Mon, 6 Feb 2023 16:23:48 +0000 Subject: [PATCH 1/2] Migrate to GitHub actions (#98) Sticking with Node 14 for now to match Emscripten. --- .circleci/config.yml | 143 ----------------------------------- .github/workflows/build.yml | 52 +++++++++++++ .github/workflows/pr-url.yml | 12 +++ bin/print-ci-env.js | 12 +++ 4 files changed, 76 insertions(+), 143 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/pr-url.yml create mode 100644 bin/print-ci-env.js diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 1925d260..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,143 +0,0 @@ -version: 2.1 -orbs: - queue: eddiewebb/queue@1.6.2 - -_default_workflow: &default_workflow - context: - - "GitHub Packages Read" - - "CircleCI API" - - "AWS Web" -_defaults: &defaults - working_directory: ~/repo -_docker_defaults: &docker_defaults - image: cimg/node:16.14 -_steps: - queue_until_front_of_line: &queue_until_front_of_line - # Ensures we don't deploy concurrently - # See https://github.com/eddiewebb/circleci-queue - queue/until_front_of_line: - time: "60" - restore_npm_cache: &restore_npm_cache - restore_cache: - keys: - - npm-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} - - npm-v1-{{ .Branch }}- - - npm-v1- - save_npm_cache: &save_npm_cache - save_cache: - paths: - - .npm-cache - key: npm-v1-{{ .Branch }}-{{ checksum "package-lock.json" }} - install_aws_cli: &install_aws_cli - run: sudo apt-get update && sudo apt-get install awscli - install_dependencies: &install_dependencies - run: npm ci --cache .npm-cache && sudo npm config set @microbit-foundation:registry https://npm.pkg.github.com/microbit-foundation && sudo npm i -g @microbit-foundation/website-deploy-aws@0.3.0 @microbit-foundation/website-deploy-aws-config@0.5.0-dev.90 @microbit-foundation/circleci-npm-package-versioner@1 - update_version: &update_version - run: npm run ci:update-version - build: &build - run: - name: Build - command: ./ci-build.sh - deploy: &deploy - run: - name: Deploy - environment: - NODE_PATH: /usr/local/lib/node_modules - command: npm run deploy - invalidate: &invalidate - run: - name: Invalidate CloudFront distribution - command: "npm run invalidate" - configure_registry_auth: &configure_registry_auth - run: - name: Configure registry auth - # One for each user as we do global and local installs. - command: echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> ~/repo/.npmrc && sudo echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" | sudo tee -a /root/.npmrc > /dev/null - -jobs: - review: - <<: *defaults - docker: - - <<: *docker_defaults - environment: - STAGE: REVIEW - REVIEW_CLOUDFRONT_DISTRIBUTION_ID: "E2DW5F7PA9W7JD" - steps: - - checkout - - *restore_npm_cache - - *configure_registry_auth - - *install_aws_cli - - *install_dependencies - - *update_version - - *save_npm_cache - - *build - - *queue_until_front_of_line - - *deploy - - *invalidate - - staging: - <<: *defaults - docker: - - <<: *docker_defaults - environment: - STAGE: STAGING - STAGING_CLOUDFRONT_DISTRIBUTION_ID: "E15FPP46STH15O" - steps: - - checkout - - *restore_npm_cache - - *configure_registry_auth - - *install_aws_cli - - *install_dependencies - - *update_version - - *save_npm_cache - - *build - - *queue_until_front_of_line - - *deploy - - *invalidate - - production: - <<: *defaults - docker: - - <<: *docker_defaults - environment: - STAGE: PRODUCTION - PRODUCTION_CLOUDFRONT_DISTRIBUTION_ID: "E15FPP46STH15O" - steps: - - checkout - - *restore_npm_cache - - *configure_registry_auth - - *install_aws_cli - - *install_dependencies - - *update_version - - *save_npm_cache - - *build - # This doesn't work for tags. Don't release more than one at once! - # - *queue_until_front_of_line - - *deploy - - *invalidate - -workflows: - version: 2 - review: - jobs: - - review: - <<: *default_workflow - filters: - branches: - ignore: main - staging: - jobs: - - staging: - <<: *default_workflow - filters: - branches: - only: main - production: - jobs: - - production: - <<: *default_workflow - filters: - tags: - only: /^v.*/ - branches: - ignore: /.*/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..1ca82596 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,52 @@ +name: build + +on: + release: + types: [created] + push: + branches: + - "**" + +# This is conservative: ideally we'd include branch and stage in this key +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency +concurrency: deploy-python-simulator + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + env: + AWS_DEFAULT_REGION: eu-west-1 + PRODUCTION_CLOUDFRONT_DISTRIBUTION_ID: E15FPP46STH15O + STAGING_CLOUDFRONT_DISTRIBUTION_ID: E15FPP46STH15O + REVIEW_CLOUDFRONT_DISTRIBUTION_ID: E2DW5F7PA9W7JD + + steps: + # Note: This workflow will not run on forks without modification; we're open to making steps + # that rely on our deployment infrastructure conditional. Please open an issue. + - uses: actions/checkout@v3 + - name: Configure node + uses: actions/setup-node@v3 + with: + node-version: 14.x + cache: "npm" + registry-url: "https://npm.pkg.github.com" + - run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: npm install --no-save @microbit-foundation/website-deploy-aws@0.3.0 @microbit-foundation/website-deploy-aws-config@0.7.1 @microbit-foundation/circleci-npm-package-versioner@1 + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: node ./bin/print-ci-env.js >> $GITHUB_ENV + - run: npm run ci:update-version + - run: ./ci-build.sh + - run: npm run deploy + env: + AWS_ACCESS_KEY_ID: ${{ secrets.WEB_DEPLOY_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.WEB_DEPLOY_AWS_SECRET_ACCESS_KEY }} + - run: npm run invalidate + env: + AWS_ACCESS_KEY_ID: ${{ secrets.WEB_DEPLOY_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.WEB_DEPLOY_AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/pr-url.yml b/.github/workflows/pr-url.yml new file mode 100644 index 00000000..3af7d49c --- /dev/null +++ b/.github/workflows/pr-url.yml @@ -0,0 +1,12 @@ +name: "pr-url" +on: + pull_request: + types: [opened] +jobs: + pr-url: + runs-on: ubuntu-latest + steps: + - uses: microbit-foundation/action-pr-url-template@v0.1.2 + with: + uri-template: "https://review-python-simulator.usermbit.org/{branch}/" + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/bin/print-ci-env.js b/bin/print-ci-env.js new file mode 100644 index 00000000..93a86388 --- /dev/null +++ b/bin/print-ci-env.js @@ -0,0 +1,12 @@ +#!/usr/bin/env node +const ref = process.env.GITHUB_REF; +let stage; +if (ref === "refs/heads/main") { + stage = "STAGING"; +} else if (ref.startsWith("refs/tags/v")) { + stage = "PRODUCTION"; +} else { + stage = "REVIEW"; +} + +console.log(`STAGE=${stage}`); From a3e92f7ddd15b7a0d110c739f30890d93eb59c43 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Mon, 6 Feb 2023 18:29:23 +0000 Subject: [PATCH 2/2] Fix Safari AudioContext initialisation. (#99) Current version seem to require the user interaction to be on the stack when creating/resuming the context. Switch to a single context and resume it if it starts suspended. Add some clean-up so we can keep the single context for the lifetime of the app (across stop/start). Closes #97 --- src/board/audio/index.ts | 26 +++++++++++++++++++++----- src/board/index.ts | 10 +++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/board/audio/index.ts b/src/board/audio/index.ts index 1c46fd99..132aed91 100644 --- a/src/board/audio/index.ts +++ b/src/board/audio/index.ts @@ -27,11 +27,9 @@ export class Audio { defaultAudioCallback, speechAudioCallback, }: AudioOptions) { - this.context = new AudioContext({ - // The highest rate is the sound expression synth. - sampleRate: 44100, - }); - + if (!this.context) { + throw new Error("Context must be pre-created from a user event"); + } this.muteNode = this.context.createGain(); this.muteNode.gain.setValueAtTime( this.muted ? 0 : 1, @@ -62,6 +60,16 @@ export class Audio { ); } + async createAudioContextFromUserInteraction(): Promise { + this.context = new AudioContext({ + // The highest rate is the sound expression synth. + sampleRate: 44100, + }); + if (this.context.state === "suspended") { + return this.context.resume(); + } + } + playSoundExpression(expr: string) { const soundEffects = parseSoundEffects(replaceBuiltinSound(expr)); const onDone = () => { @@ -138,6 +146,9 @@ export class Audio { boardStopped() { this.stopOscillator(); + this.speech?.dispose(); + this.soundExpression?.dispose(); + this.default?.dispose(); } private stopOscillator() { @@ -188,4 +199,9 @@ class BufferedAudio { } source.start(startTime); } + + dispose() { + // Prevent calls into WASM when the buffer nodes finish. + this.callback = () => {}; + } } diff --git a/src/board/index.ts b/src/board/index.ts index ed53b6b3..deff7fa7 100644 --- a/src/board/index.ts +++ b/src/board/index.ts @@ -230,9 +230,10 @@ export class Board { this.initializePlayButton(); // We start stopped. this.displayStoppedState(); - this.playButton.addEventListener("click", () => - this.notifications.onRequestFlash() - ); + this.playButton.addEventListener("click", async () => { + await this.audio.createAudioContextFromUserInteraction(); + this.notifications.onRequestFlash(); + }); this.updateTranslationsInternal(); this.notifications.onReady(this.getState()); @@ -481,6 +482,9 @@ export class Board { * @returns A promise that resolves when the simulator is stopped. */ async stop(brief: boolean = false): Promise { + // Preemptively stop audio so that we don't call into WASM for more data + this.audio.boardStopped(); + if (this.panicTimeout) { clearTimeout(this.panicTimeout); this.panicTimeout = null;