From feccce6edfda9edb56e9b57301da8fa95761db5f Mon Sep 17 00:00:00 2001
From: Gordon Brander
Date: Sat, 22 Jun 2024 10:53:17 -0700
Subject: [PATCH 1/4] Rough out propagators lib
---
sketches/2024-06-18-propagator-ui/.gitignore | 24 +
sketches/2024-06-18-propagator-ui/index.html | 13 +
.../package-lock.json | 814 ++++++++++++++++++
.../2024-06-18-propagator-ui/package.json | 15 +
.../2024-06-18-propagator-ui/public/vite.svg | 1 +
sketches/2024-06-18-propagator-ui/src/main.ts | 25 +
.../src/propagators.ts | 210 +++++
.../2024-06-18-propagator-ui/tsconfig.json | 18 +
8 files changed, 1120 insertions(+)
create mode 100644 sketches/2024-06-18-propagator-ui/.gitignore
create mode 100644 sketches/2024-06-18-propagator-ui/index.html
create mode 100644 sketches/2024-06-18-propagator-ui/package-lock.json
create mode 100644 sketches/2024-06-18-propagator-ui/package.json
create mode 100644 sketches/2024-06-18-propagator-ui/public/vite.svg
create mode 100644 sketches/2024-06-18-propagator-ui/src/main.ts
create mode 100644 sketches/2024-06-18-propagator-ui/src/propagators.ts
create mode 100644 sketches/2024-06-18-propagator-ui/tsconfig.json
diff --git a/sketches/2024-06-18-propagator-ui/.gitignore b/sketches/2024-06-18-propagator-ui/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/sketches/2024-06-18-propagator-ui/index.html b/sketches/2024-06-18-propagator-ui/index.html
new file mode 100644
index 000000000..44a933506
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + TS
+
+
+
+
+
+
diff --git a/sketches/2024-06-18-propagator-ui/package-lock.json b/sketches/2024-06-18-propagator-ui/package-lock.json
new file mode 100644
index 000000000..1e6ecbb7b
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/package-lock.json
@@ -0,0 +1,814 @@
+{
+ "name": "propagators",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "propagators",
+ "version": "0.0.0",
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.3.1"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
+ "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz",
+ "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz",
+ "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz",
+ "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz",
+ "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz",
+ "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz",
+ "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz",
+ "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz",
+ "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz",
+ "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz",
+ "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz",
+ "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz",
+ "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz",
+ "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz",
+ "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz",
+ "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+ "dev": true
+ },
+ "node_modules/postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.18.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz",
+ "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.18.0",
+ "@rollup/rollup-android-arm64": "4.18.0",
+ "@rollup/rollup-darwin-arm64": "4.18.0",
+ "@rollup/rollup-darwin-x64": "4.18.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.18.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.18.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.18.0",
+ "@rollup/rollup-linux-arm64-musl": "4.18.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.18.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.18.0",
+ "@rollup/rollup-linux-x64-gnu": "4.18.0",
+ "@rollup/rollup-linux-x64-musl": "4.18.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.18.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.18.0",
+ "@rollup/rollup-win32-x64-msvc": "4.18.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
+ "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz",
+ "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.38",
+ "rollup": "^4.13.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/sketches/2024-06-18-propagator-ui/package.json b/sketches/2024-06-18-propagator-ui/package.json
new file mode 100644
index 000000000..1449b75d9
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "propagators",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.3.1"
+ }
+}
diff --git a/sketches/2024-06-18-propagator-ui/public/vite.svg b/sketches/2024-06-18-propagator-ui/public/vite.svg
new file mode 100644
index 000000000..e7b8dfb1b
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sketches/2024-06-18-propagator-ui/src/main.ts b/sketches/2024-06-18-propagator-ui/src/main.ts
new file mode 100644
index 000000000..b1b0de0eb
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/src/main.ts
@@ -0,0 +1,25 @@
+import {Cell, cell, constant, sink, mul, sub, div} from './propagators.js';
+
+function fahrenheitToCelsius(f: Cell, c: Cell) {
+ const thirtyTwo = constant(32);
+ const five = constant(5);
+ const nine = constant(9);
+ const fMin32 = cell(0);
+ const cMult9 = cell(0);
+
+ sub(f, thirtyTwo, fMin32);
+ mul(fMin32, five, cMult9);
+ div(cMult9, nine, c);
+}
+
+const input = cell(0);
+const output = cell(0);
+
+fahrenheitToCelsius(
+ input,
+ output
+);
+
+sink(output, value => {
+ console.log(value);
+});
\ No newline at end of file
diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts
new file mode 100644
index 000000000..fa7ca3a3e
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts
@@ -0,0 +1,210 @@
+export type Task = {
+ poll(): void;
+};
+
+class Scheduler {
+ #transaction = new Set();
+ #isRunning = false;
+
+ enqueue(propagators: Iterable) {
+ for (const propagator of propagators) {
+ this.#transaction.add(propagator);
+ }
+ this.#wake();
+ }
+
+ #wake() {
+ if (!this.#isRunning) {
+ this.#isRunning = true;
+ const current = this.#transaction;
+ this.#transaction = new Set();
+ for (const task of current) {
+ try {
+ task.poll();
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ this.#isRunning = false
+ }
+ }
+}
+
+const scheduler = new Scheduler();
+
+export type Mergeable = {
+ merge(next: T): T
+};
+
+export const isMergeable = (
+ value: any
+): value is Mergeable => {
+ return value != null && typeof value.merge === "function";
+}
+
+/** Merge a value if type implements merge, or else use last-write-wins */
+export const merge = (
+ prev: T,
+ next: T
+): T => {
+ if (isMergeable(prev)) {
+ return prev.merge(next);
+ }
+ return next;
+}
+
+export type Cancel = () => void;
+
+export type Cancellable = {
+ cancel(): void;
+}
+
+export type AnyCell = {
+ content: T;
+ subscribe(propagator: Task): Cancel;
+}
+
+export const isCell = (
+ value: any
+): value is AnyCell => {
+ return (
+ value !== null &&
+ Object.hasOwn(value, "content") &&
+ typeof value.subscribe === "function"
+ );
+}
+
+export const subscribeAll = (
+ cells: Array>,
+ propagator: Task
+): Cancel => {
+ const cancels = cells.map(cell => cell.subscribe(propagator));
+ return () => {
+ for (const cancel of cancels) {
+ cancel();
+ }
+ }
+}
+
+export class Cell implements AnyCell {
+ #neighbors: Set = new Set();
+ #content: T;
+
+ constructor(initial: T) {
+ this.#content = initial
+ }
+
+ get content() {
+ return this.#content;
+ }
+
+ send(next: T) {
+ const merged = merge(this.#content, next);
+ if (this.#content !== merged) {
+ this.#content = merged;
+ scheduler.enqueue(this.#neighbors);
+ }
+ }
+
+ subscribe(propagator: Task) {
+ this.#neighbors.add(propagator);
+ scheduler.enqueue([propagator]);
+ return () => {
+ this.#neighbors.delete(propagator);
+ }
+ }
+}
+
+export const cell = (initial: T) => new Cell(initial);
+
+/** A read-only view over a cell */
+export class CellView implements AnyCell {
+ #cell: Cell;
+
+ constructor(cell: Cell) {
+ this.#cell = cell;
+ }
+
+ get content() {
+ return this.#cell.content;
+ }
+
+ subscribe(propagator: Task) {
+ return this.#cell.subscribe(propagator);
+ }
+}
+
+export const cellView = (cell: Cell) => new CellView(cell);
+
+export const constant = (value: T): CellView => cellView(cell(value));
+
+export const getContent = (
+ cell: AnyCell
+): T => cell.content;
+
+export const task = (poll: () => void) => ({poll});
+
+const lift = (
+ fn: (...args: Array) => any,
+) => (...cells: Array>): Cancellable => {
+ const output = cells.pop();
+ if (!(output instanceof Cell)) {
+ throw new TypeError("Last argument must be a writeable cell");
+ }
+
+ const lifted = task(() => {
+ const result = fn(...cells.map(getContent))
+ output.send(result);
+ });
+
+ scheduler.enqueue([lifted]);
+
+ const cancel = subscribeAll(cells, lifted);
+
+ return {cancel};
+}
+
+export const add = lift((a: number, b: number) => a + b);
+export const sub = lift((a: number, b: number) => a - b);
+export const mul = lift((a: number, b: number) => a * b);
+export const div = lift((a: number, b: number) => a / b);
+
+export const sink = (
+ cell: AnyCell,
+ callback: (value: T) => void
+) => {
+ return cell.subscribe(task(() => {
+ callback(cell.content);
+ }));
+}
+
+const batched = (
+ job: () => void,
+ schedule: (job: () => void) => void
+) => {
+ let isScheduled = false;
+
+ const perform = () => {
+ isScheduled = false;
+ job();
+ }
+
+ return () => {
+ if (!isScheduled) {
+ isScheduled = true;
+ schedule(perform);
+ }
+ }
+}
+
+/** Render changes froma cell on next frame */
+export const render = (
+ cell: AnyCell,
+ callback: (value: T) => void
+) => {
+ const batchedRender = batched(
+ () => callback(cell.content),
+ requestAnimationFrame
+ )
+ return sink(cell, batchedRender);
+}
\ No newline at end of file
diff --git a/sketches/2024-06-18-propagator-ui/tsconfig.json b/sketches/2024-06-18-propagator-ui/tsconfig.json
new file mode 100644
index 000000000..ba47283b7
--- /dev/null
+++ b/sketches/2024-06-18-propagator-ui/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../typescript/tsconfig.base.json",
+ "compilerOptions": {
+ "lib": ["ESNext", "dom", "dom.iterable"],
+ "target": "ESNext",
+ "outDir": "./lib",
+ "rootDir": "./src",
+ "strict": false,
+ "useDefineForClassFields": false,
+ "paths": {
+ },
+ "types": []
+ },
+ "include": [
+ "src/**/*",
+ ]
+ }
+
\ No newline at end of file
From a58225af8100e6b88857ee2caa02d8d18e379494 Mon Sep 17 00:00:00 2001
From: Gordon Brander
Date: Sat, 22 Jun 2024 11:15:58 -0700
Subject: [PATCH 2/4] Roll up cancels
---
sketches/2024-06-18-propagator-ui/src/main.ts | 19 ++++--
.../src/propagators.ts | 66 ++++++++++++++-----
2 files changed, 64 insertions(+), 21 deletions(-)
diff --git a/sketches/2024-06-18-propagator-ui/src/main.ts b/sketches/2024-06-18-propagator-ui/src/main.ts
index b1b0de0eb..0cf48e23a 100644
--- a/sketches/2024-06-18-propagator-ui/src/main.ts
+++ b/sketches/2024-06-18-propagator-ui/src/main.ts
@@ -1,4 +1,13 @@
-import {Cell, cell, constant, sink, mul, sub, div} from './propagators.js';
+import {
+ Cell,
+ cell,
+ constant,
+ cancellable,
+ sink,
+ mul,
+ sub,
+ div
+} from './propagators.js';
function fahrenheitToCelsius(f: Cell, c: Cell) {
const thirtyTwo = constant(32);
@@ -7,9 +16,11 @@ function fahrenheitToCelsius(f: Cell, c: Cell) {
const fMin32 = cell(0);
const cMult9 = cell(0);
- sub(f, thirtyTwo, fMin32);
- mul(fMin32, five, cMult9);
- div(cMult9, nine, c);
+ return cancellable(
+ sub(f, thirtyTwo, fMin32),
+ mul(fMin32, five, cMult9),
+ div(cMult9, nine, c)
+ );
}
const input = cell(0);
diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts
index fa7ca3a3e..c7a0133cb 100644
--- a/sketches/2024-06-18-propagator-ui/src/propagators.ts
+++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts
@@ -61,7 +61,7 @@ export type Cancellable = {
export type AnyCell = {
content: T;
- subscribe(propagator: Task): Cancel;
+ connect(task: Task): Cancellable;
}
export const isCell = (
@@ -74,19 +74,39 @@ export const isCell = (
);
}
-export const subscribeAll = (
- cells: Array>,
- propagator: Task
-): Cancel => {
- const cancels = cells.map(cell => cell.subscribe(propagator));
- return () => {
- for (const cancel of cancels) {
- cancel();
+/**
+ * Create a bag to hold cancel functions
+ * Returns a cancellable.
+ */
+export const cancellable = (
+ ...cancellables: Array
+): Cancellable => {
+ const cancel = () => {
+ for (const cancellable of cancellables) {
+ cancellable.cancel();
}
}
+ return {cancel};
+};
+
+export const connectAll = (
+ cells: Array>,
+ task: Task
+): Cancellable => {
+ const cancels = cells.map(cell => cell.connect(task));
+ return cancellable(...cancels);
}
+let _cid = 0;
+
+/**
+ * Create a unique client ID using a client-side counter.
+ * DO NOT persist cids. They are only unique for the script lifetime.
+ */
+export const cid = () => `cid${_cid++}`;
+
export class Cell implements AnyCell {
+ cid = cid();
#neighbors: Set = new Set();
#content: T;
@@ -106,12 +126,13 @@ export class Cell implements AnyCell {
}
}
- subscribe(propagator: Task) {
+ connect(propagator: Task) {
this.#neighbors.add(propagator);
scheduler.enqueue([propagator]);
- return () => {
+ const cancel = () => {
this.#neighbors.delete(propagator);
}
+ return {cancel};
}
}
@@ -119,6 +140,7 @@ export const cell = (initial: T) => new Cell(initial);
/** A read-only view over a cell */
export class CellView implements AnyCell {
+ cid = cid();
#cell: Cell;
constructor(cell: Cell) {
@@ -129,8 +151,8 @@ export class CellView implements AnyCell {
return this.#cell.content;
}
- subscribe(propagator: Task) {
- return this.#cell.subscribe(propagator);
+ connect(propagator: Task) {
+ return this.#cell.connect(propagator);
}
}
@@ -144,9 +166,15 @@ export const getContent = (
export const task = (poll: () => void) => ({poll});
+export type AnyPropagator = Cancellable & {
+ cid: string;
+}
+
const lift = (
fn: (...args: Array) => any,
-) => (...cells: Array>): Cancellable => {
+) => (
+ ...cells: Array>
+): AnyPropagator => {
const output = cells.pop();
if (!(output instanceof Cell)) {
throw new TypeError("Last argument must be a writeable cell");
@@ -159,9 +187,12 @@ const lift = (
scheduler.enqueue([lifted]);
- const cancel = subscribeAll(cells, lifted);
+ const {cancel} = connectAll(cells, lifted);
- return {cancel};
+ return {
+ cid: cid(),
+ cancel
+ };
}
export const add = lift((a: number, b: number) => a + b);
@@ -169,11 +200,12 @@ export const sub = lift((a: number, b: number) => a - b);
export const mul = lift((a: number, b: number) => a * b);
export const div = lift((a: number, b: number) => a / b);
+/** Call a callback whenever cell contents changes */
export const sink = (
cell: AnyCell,
callback: (value: T) => void
) => {
- return cell.subscribe(task(() => {
+ return cell.connect(task(() => {
callback(cell.content);
}));
}
From 52c2e42b2eacd12d18e000c6d4df96322b41a873 Mon Sep 17 00:00:00 2001
From: Gordon Brander
Date: Sat, 22 Jun 2024 11:45:08 -0700
Subject: [PATCH 3/4] Change API to be a bit closer to signals
---
.../2024-06-18-propagator-ui/src/propagators.ts | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts
index c7a0133cb..353633efa 100644
--- a/sketches/2024-06-18-propagator-ui/src/propagators.ts
+++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts
@@ -60,7 +60,7 @@ export type Cancellable = {
}
export type AnyCell = {
- content: T;
+ get(): T;
connect(task: Task): Cancellable;
}
@@ -69,7 +69,7 @@ export const isCell = (
): value is AnyCell => {
return (
value !== null &&
- Object.hasOwn(value, "content") &&
+ typeof value.get === "function" &&
typeof value.subscribe === "function"
);
}
@@ -114,7 +114,7 @@ export class Cell implements AnyCell {
this.#content = initial
}
- get content() {
+ get() {
return this.#content;
}
@@ -147,8 +147,8 @@ export class CellView implements AnyCell {
this.#cell = cell;
}
- get content() {
- return this.#cell.content;
+ get() {
+ return this.#cell.get();
}
connect(propagator: Task) {
@@ -162,7 +162,7 @@ export const constant = (value: T): CellView => cellView(cell(value));
export const getContent = (
cell: AnyCell
-): T => cell.content;
+): T => cell.get();
export const task = (poll: () => void) => ({poll});
@@ -206,7 +206,7 @@ export const sink = (
callback: (value: T) => void
) => {
return cell.connect(task(() => {
- callback(cell.content);
+ callback(cell.get());
}));
}
@@ -235,7 +235,7 @@ export const render = (
callback: (value: T) => void
) => {
const batchedRender = batched(
- () => callback(cell.content),
+ () => callback(cell.get()),
requestAnimationFrame
)
return sink(cell, batchedRender);
From db5c1932a8d1683d9abf79814897c839b4ed44ed Mon Sep 17 00:00:00 2001
From: Gordon Brander
Date: Sun, 30 Jun 2024 19:41:51 -0700
Subject: [PATCH 4/4] Fix method name
---
sketches/2024-06-18-propagator-ui/src/propagators.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts
index 353633efa..ab3d7d00f 100644
--- a/sketches/2024-06-18-propagator-ui/src/propagators.ts
+++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts
@@ -70,7 +70,7 @@ export const isCell = (
return (
value !== null &&
typeof value.get === "function" &&
- typeof value.subscribe === "function"
+ typeof value.connect === "function"
);
}