From 79a5061455fecabc91ce45458129d4c8a7a8d4f2 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 4 Nov 2025 17:32:42 +0100 Subject: [PATCH 1/7] benchmark github action --- .github/workflows/benchmark.yml | 91 +++++++++++++++++++ apps/typegpu-docs/astro.config.mjs | 3 + .../src/pages/resolve/index.astro | 31 +++++++ 3 files changed, 125 insertions(+) create mode 100644 .github/workflows/benchmark.yml create mode 100644 apps/typegpu-docs/src/pages/resolve/index.astro diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 000000000..6c058f027 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,91 @@ +name: Measure resolve duration + +on: + workflow_dispatch: + +env: + BENCHMARK_REPO: software-mansion-labs/typegpu-benchmarker + +jobs: + measure: + runs-on: ubuntu-latest + + steps: + - name: Clone benchmarking repo + uses: actions/checkout@v5 + with: + repository: ${{ env.BENCHMARK_REPO }} + ref: main + token: ${{ secrets.BENCHMARKER_REPO_ACCESS_TOKEN }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Install Node.js 22.x + uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: "pnpm" + + - name: Install deps + run: | + pnpm install --frozen-lockfile + - name: Install Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Run benchmarks + run: | + pnpm run measure + - name: Save benchmark results across the jobs + uses: actions/upload-artifact@v4 + with: + name: data + path: benchmarks + plot: + runs-on: ubuntu-latest + needs: measure + + steps: + - name: Clone benchmarking repo + uses: actions/checkout@v5 + with: + repository: ${{ env.BENCHMARK_REPO }} + ref: main + token: ${{ secrets.BENCHMARKER_REPO_ACCESS_TOKEN }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Download benchmark data + uses: actions/download-artifact@v4 + with: + name: data + path: benchmarks + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ">=3.11" + + - name: Create venv + run: | + python3 -m venv .venv + - name: Plot + shell: bash + run: | + . .venv/bin/activate + pip install -r requirements.txt + pnpm run plot + - name: Commit and push results and plot + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git add . + git commit -m "Automated benchmark update" || echo "No changes" + git push origin main diff --git a/apps/typegpu-docs/astro.config.mjs b/apps/typegpu-docs/astro.config.mjs index 934f3ab51..7cb4370ca 100644 --- a/apps/typegpu-docs/astro.config.mjs +++ b/apps/typegpu-docs/astro.config.mjs @@ -36,6 +36,9 @@ export default defineConfig({ 'Cross-Origin-Opener-Policy': 'same-origin', }, }, + image: { + domains: ['raw.githubusercontent.com'], + }, markdown: { remarkPlugins: [remarkMath], rehypePlugins: [rehypeMathJax], diff --git a/apps/typegpu-docs/src/pages/resolve/index.astro b/apps/typegpu-docs/src/pages/resolve/index.astro new file mode 100644 index 000000000..24987625d --- /dev/null +++ b/apps/typegpu-docs/src/pages/resolve/index.astro @@ -0,0 +1,31 @@ +--- +import { Image } from "astro:assets"; +import PageLayout from "../../layouts/PageLayout.astro"; +import TypeGPULogoDark from "../../assets/typegpu-logo-dark.svg"; + +const remotePlot = `https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration.png?t=${Date.now()}`; +const title = "TypeGPU — resolve complexity"; +--- + + +
+

+ + TypeGPU Logo + +

— resolve complexity

+

+ +
+ Combined Resolve Duration Plot +
+
+
From f71862ad227c80c7affaf5bdb176b373f6915a8c Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Tue, 4 Nov 2025 17:37:17 +0100 Subject: [PATCH 2/7] deno --- .github/workflows/benchmark.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6c058f027..974cfcd38 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -27,7 +27,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 22.x - cache: "pnpm" + cache: 'pnpm' - name: Install deps run: | @@ -71,7 +71,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: ">=3.11" + python-version: '>=3.11' - name: Create venv run: | From 1326e7b883ca8072f78763bac7b1f281a42c1385 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 19 Nov 2025 13:07:09 +0100 Subject: [PATCH 3/7] plot gallery component with arrows control --- .../src/components/resolve/PlotGallery.tsx | 97 +++++++++++++++++++ .../src/pages/resolve/index.astro | 22 +---- 2 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 apps/typegpu-docs/src/components/resolve/PlotGallery.tsx diff --git a/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx new file mode 100644 index 000000000..1ea331044 --- /dev/null +++ b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx @@ -0,0 +1,97 @@ +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { useEffect, useState } from 'react'; + +const plots = [ + 'https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration-full.png', + 'https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration-full-log.png', + 'https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration-latest5.png', + 'https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration-under10k.png', +]; + +function PlotSlide({ url }: { url: string }) { + return ( +
+ {`${new +
+ ); +} + +const buttonUtilityClasses = + '-translate-y-1/2 absolute top-1/2 rounded-full border border-gray-700 bg-gray-800 p-4 text-gray-150 transition-all duration-300 ease-in-out hover:bg-gray-700 hover:text-white active:bg-gray-500 active:text-white z-1'; +const chevronUtilityClasses = 'w-4 h-4 sm:w-8 sm:h-8'; + +export default function PlotGallery() { + // this is for infinite effect + const extendedPlots = [plots[plots.length - 1], ...plots, plots[0]]; + + const [currentIndex, setCurrentIndex] = useState(1); + const [isTransitioning, setIsTransitioning] = useState(false); + + const nextSlide = () => { + if (isTransitioning) return; + setIsTransitioning(true); + setCurrentIndex((prev) => prev + 1); + }; + + const prevSlide = () => { + if (isTransitioning) return; + setIsTransitioning(true); + setCurrentIndex((prev) => prev - 1); + }; + + const handleTransitionEnd = () => { + setIsTransitioning(false); + + if (currentIndex === 0) { + setCurrentIndex(plots.length); + } else if (currentIndex === extendedPlots.length - 1) { + setCurrentIndex(1); + } + }; + + useEffect(() => { + // TODO: add touch handling + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'ArrowLeft') prevSlide(); + if (event.key === 'ArrowRight') nextSlide(); + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }); + + return ( +
+ + + + +
+ {extendedPlots.map((url, index) => ( + + ))} +
+
+ ); +} diff --git a/apps/typegpu-docs/src/pages/resolve/index.astro b/apps/typegpu-docs/src/pages/resolve/index.astro index 24987625d..c0831fc49 100644 --- a/apps/typegpu-docs/src/pages/resolve/index.astro +++ b/apps/typegpu-docs/src/pages/resolve/index.astro @@ -1,31 +1,19 @@ --- import { Image } from "astro:assets"; import PageLayout from "../../layouts/PageLayout.astro"; +import PlotGallery from "../../components/resolve/PlotGallery.tsx"; import TypeGPULogoDark from "../../assets/typegpu-logo-dark.svg"; - -const remotePlot = `https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration.png?t=${Date.now()}`; -const title = "TypeGPU — resolve complexity"; --- -
-

+
+

- TypeGPU Logo + TypeGPU Logo

— resolve complexity

-
- Combined Resolve Duration Plot -
+
From d87db812bd23b228c0d91c1b102d36efb434b064 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 19 Nov 2025 13:21:04 +0100 Subject: [PATCH 4/7] dots at the bottom --- .../src/components/resolve/PlotGallery.tsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx index 1ea331044..cb793d9b6 100644 --- a/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx +++ b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx @@ -53,6 +53,18 @@ export default function PlotGallery() { } }; + const goToSlide = (index: number) => { + if (isTransitioning) return; + setIsTransitioning(true); + setCurrentIndex(index + 1); + }; + + const getActualIndex = (): number => { + if (currentIndex === 0) return plots.length - 1; + if (currentIndex === extendedPlots.length - 1) return 0; + return currentIndex - 1; + }; + useEffect(() => { // TODO: add touch handling const handleKeyDown = (event: KeyboardEvent) => { @@ -89,7 +101,22 @@ export default function PlotGallery() { onTransitionEnd={handleTransitionEnd} > {extendedPlots.map((url, index) => ( - + + ))} + + +
+ {plots.map((url, index) => ( +
From daabfcd4e34bc22822ea326548e96050dca70e5e Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Wed, 19 Nov 2025 22:38:19 +0100 Subject: [PATCH 5/7] react <3 --- .../src/components/resolve/PlotGallery.tsx | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx index cb793d9b6..ba79d7db6 100644 --- a/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx +++ b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx @@ -1,5 +1,5 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; const plots = [ 'https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration-full.png', @@ -26,44 +26,57 @@ const chevronUtilityClasses = 'w-4 h-4 sm:w-8 sm:h-8'; export default function PlotGallery() { // this is for infinite effect - const extendedPlots = [plots[plots.length - 1], ...plots, plots[0]]; + const extendedPlots = useMemo( + () => [plots[plots.length - 1], ...plots, plots[0]], + [], + ); const [currentIndex, setCurrentIndex] = useState(1); - const [isTransitioning, setIsTransitioning] = useState(false); + const currentIndexRef = useRef(currentIndex); + const [isTransitioning, setIsTransitioning] = useState(false); // this is necassary for dynamic css + const isTransitioningRef = useRef(isTransitioning); // this is necessary for useCallback empty deps - const nextSlide = () => { - if (isTransitioning) return; + const nextSlide = useCallback(() => { + if (isTransitioningRef.current) return; setIsTransitioning(true); - setCurrentIndex((prev) => prev + 1); - }; + isTransitioningRef.current = true; + setCurrentIndex((prev) => prev + 1); // to avoid deps + }, []); - const prevSlide = () => { - if (isTransitioning) return; + const prevSlide = useCallback(() => { + if (isTransitioningRef.current) return; setIsTransitioning(true); + isTransitioningRef.current = true; setCurrentIndex((prev) => prev - 1); - }; + }, []); - const handleTransitionEnd = () => { + const handleTransitionEnd = useCallback(() => { setIsTransitioning(false); + isTransitioningRef.current = false; - if (currentIndex === 0) { + if (currentIndexRef.current === 0) { setCurrentIndex(plots.length); - } else if (currentIndex === extendedPlots.length - 1) { + } else if (currentIndexRef.current === extendedPlots.length - 1) { setCurrentIndex(1); } - }; + }, [extendedPlots]); - const goToSlide = (index: number) => { - if (isTransitioning) return; + const goToSlide = useCallback((index: number) => { + if (isTransitioningRef.current) return; setIsTransitioning(true); + isTransitioningRef.current = true; setCurrentIndex(index + 1); - }; + }, []); const getActualIndex = (): number => { if (currentIndex === 0) return plots.length - 1; if (currentIndex === extendedPlots.length - 1) return 0; return currentIndex - 1; - }; + }; // has to use actual state not the ref to not lag behind + + useEffect(() => { + currentIndexRef.current = currentIndex; + }, [currentIndex]); useEffect(() => { // TODO: add touch handling @@ -73,7 +86,7 @@ export default function PlotGallery() { }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }); + }, [prevSlide, nextSlide]); return (
@@ -111,7 +124,7 @@ export default function PlotGallery() { key={`dot-${index}-${url}`} type='button' onClick={() => goToSlide(index)} - className={`h-4 w-4 rounded-full transition-all duration-300 ease-in-out ${ + className={`h-4 w-4 rounded-full transition-all duration-200 ease-in-out ${ index === getActualIndex() ? 'scale-125 bg-gray-500' : 'bg-gray-800 hover:bg-gray-700' From 1bd8e143e0d1a80ea621fe0fc239bffe6394a201 Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 20 Nov 2025 11:04:01 +0100 Subject: [PATCH 6/7] no scroll on mobile --- apps/typegpu-docs/src/pages/resolve/index.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/typegpu-docs/src/pages/resolve/index.astro b/apps/typegpu-docs/src/pages/resolve/index.astro index c0831fc49..3b5dad717 100644 --- a/apps/typegpu-docs/src/pages/resolve/index.astro +++ b/apps/typegpu-docs/src/pages/resolve/index.astro @@ -6,7 +6,7 @@ import TypeGPULogoDark from "../../assets/typegpu-logo-dark.svg"; --- -
+

TypeGPU Logo From 5a0293a20b5af6b65b009e25bcf16f5300fc693e Mon Sep 17 00:00:00 2001 From: Szymon Szulc Date: Thu, 20 Nov 2025 18:43:15 +0100 Subject: [PATCH 7/7] bye refs --- .../src/components/resolve/PlotGallery.tsx | 49 ++++++++----------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx index ba79d7db6..83e635c5f 100644 --- a/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx +++ b/apps/typegpu-docs/src/components/resolve/PlotGallery.tsx @@ -1,5 +1,5 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; const plots = [ 'https://raw.githubusercontent.com/software-mansion-labs/typegpu-benchmarker/main/plots/combined-resolveDuration-full.png', @@ -32,39 +32,34 @@ export default function PlotGallery() { ); const [currentIndex, setCurrentIndex] = useState(1); - const currentIndexRef = useRef(currentIndex); - const [isTransitioning, setIsTransitioning] = useState(false); // this is necassary for dynamic css - const isTransitioningRef = useRef(isTransitioning); // this is necessary for useCallback empty deps + const [isTransitioning, setIsTransitioning] = useState(false); - const nextSlide = useCallback(() => { - if (isTransitioningRef.current) return; + const nextSlide = useCallback((isTransitioning: boolean) => { + if (isTransitioning) return; setIsTransitioning(true); - isTransitioningRef.current = true; setCurrentIndex((prev) => prev + 1); // to avoid deps }, []); - const prevSlide = useCallback(() => { - if (isTransitioningRef.current) return; + const prevSlide = useCallback((isTransitioning: boolean) => { + console.log(isTransitioning); + if (isTransitioning) return; setIsTransitioning(true); - isTransitioningRef.current = true; setCurrentIndex((prev) => prev - 1); }, []); - const handleTransitionEnd = useCallback(() => { + const handleTransitionEnd = useCallback((index: number) => { setIsTransitioning(false); - isTransitioningRef.current = false; - if (currentIndexRef.current === 0) { + if (index === 0) { setCurrentIndex(plots.length); - } else if (currentIndexRef.current === extendedPlots.length - 1) { + } else if (index === extendedPlots.length - 1) { setCurrentIndex(1); } }, [extendedPlots]); - const goToSlide = useCallback((index: number) => { - if (isTransitioningRef.current) return; + const goToSlide = useCallback((index: number, isTransitioning: boolean) => { + if (isTransitioning) return; setIsTransitioning(true); - isTransitioningRef.current = true; setCurrentIndex(index + 1); }, []); @@ -72,28 +67,24 @@ export default function PlotGallery() { if (currentIndex === 0) return plots.length - 1; if (currentIndex === extendedPlots.length - 1) return 0; return currentIndex - 1; - }; // has to use actual state not the ref to not lag behind - - useEffect(() => { - currentIndexRef.current = currentIndex; - }, [currentIndex]); + }; useEffect(() => { // TODO: add touch handling const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'ArrowLeft') prevSlide(); - if (event.key === 'ArrowRight') nextSlide(); + if (event.key === 'ArrowLeft') prevSlide(isTransitioning); + if (event.key === 'ArrowRight') nextSlide(isTransitioning); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [prevSlide, nextSlide]); + }, [prevSlide, nextSlide, isTransitioning]); return (
@@ -101,7 +92,7 @@ export default function PlotGallery() { @@ -111,7 +102,7 @@ export default function PlotGallery() { isTransitioning ? '' : 'transition-none' // this is necessary for smooth ending }`} style={{ transform: `translateX(-${currentIndex * 100}%)` }} - onTransitionEnd={handleTransitionEnd} + onTransitionEnd={() => handleTransitionEnd(currentIndex)} > {extendedPlots.map((url, index) => ( @@ -123,7 +114,7 @@ export default function PlotGallery() {