From 4c3f4246a4579e0b425c9deadc30aea8eb7bb790 Mon Sep 17 00:00:00 2001
From: Bradlee Speice <bradlee@speice.io>
Date: Sun, 24 Nov 2024 18:59:11 -0500
Subject: [PATCH] Incremental rendering

---
 .../1-introduction/chaosGame.js               | 48 ++++++++++------
 .../1-introduction/scope.tsx                  | 22 +-------
 .../src/Canvas.tsx                            | 55 ++++++++++---------
 3 files changed, 64 insertions(+), 61 deletions(-)

diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js
index 49f004f..ca42723 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js
@@ -1,24 +1,40 @@
-// Hint: try increasing the iteration count
-const iterations = 10000;
+function Gasket() {
+    // Hint: try increasing the iteration count
+    const iterations = 10000;
 
-// Hint: negating `x` and `y` creates some interesting images
-const functions = [
-    (x, y) => [x / 2, y / 2],
-    (x, y) => [(x + 1) / 2, y / 2],
-    (x, y) => [x / 2, (y + 1) / 2]
-]
+    // Hint: negating `x` and `y` creates some interesting images
+    const functions = [
+        (x, y) => [x / 2, y / 2],
+        (x, y) => [(x + 1) / 2, y / 2],
+        (x, y) => [x / 2, (y + 1) / 2]
+    ]
 
-function chaosGame(image) {
-    var [x, y] = [randomBiUnit(), randomBiUnit()];
+    const image = new ImageData(600, 600);
+    function* chaosGame() {
+        var [x, y] = [randomBiUnit(), randomBiUnit()];
 
-    for (var count = 0; count < iterations; count++) {
-        const i = randomInteger(0, functions.length);
-        [x, y] = functions[i](x, y);
+        for (var count = 0; count < iterations; count++) {
+            const i = randomInteger(0, functions.length);
+            [x, y] = functions[i](x, y);
 
-        if (count > 20) {
-            plot(x, y, image);
+            if (count > 20) {
+                plot(x, y, image);
+            }
+
+            if (count % 1000 === 0) {
+                yield image;
+            }
         }
+
+        yield image;
     }
+
+    return (
+        <Canvas
+            width={image.width}
+            height={image.height}
+            painter={chaosGame()}/>
+    )
 }
 
-render(<Gasket renderFn={chaosGame}/>)
+render(<Gasket/>)
diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx b/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx
index eeefb2c..d5d2d99 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx
@@ -1,30 +1,12 @@
-import React, {useContext, useEffect} from "react";
 import randomBiUnit from './biunit';
 import plot from './plot';
 import randomInteger from './randint';
-import Canvas, {ImageContext} from "../src/Canvas";
-
-interface Props {
-    renderFn: (image: ImageData) => void;
-}
-function Render({renderFn}: Props) {
-    const {width, height, setImage} = useContext(ImageContext);
-    const image = new ImageData(width, height);
-
-    useEffect(() => {
-        renderFn(image);
-        setImage(image);
-    }, []);
-
-    return <></>;
-}
+import Canvas from "../src/Canvas";
 
 const Scope = {
     plot,
     randomBiUnit,
     randomInteger,
-    Canvas,
-    Gasket: ({renderFn}: Props) =>
-        <Canvas width={600} height={600}><Render renderFn={renderFn}/></Canvas>
+    Canvas
 }
 export default Scope;
\ No newline at end of file
diff --git a/blog/2024-11-15-playing-with-fire/src/Canvas.tsx b/blog/2024-11-15-playing-with-fire/src/Canvas.tsx
index 2835555..35f3c7a 100644
--- a/blog/2024-11-15-playing-with-fire/src/Canvas.tsx
+++ b/blog/2024-11-15-playing-with-fire/src/Canvas.tsx
@@ -1,49 +1,54 @@
-import React, {createContext, useCallback, useContext, useEffect, useRef, useState} from "react";
+import React, {useCallback, useEffect, useState} from "react";
 import {useColorMode} from "@docusaurus/theme-common";
 
-interface IImageContext {
-    width: number;
-    height: number;
-    setImage: (image: ImageData) => void;
-}
-export const ImageContext = createContext<IImageContext>(null);
-
 interface Props {
     width: number;
     height: number;
+    painter: Iterator<ImageData>;
     children?: React.ReactNode;
 }
-export default function Canvas({width, height, children}) {
-    const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D | null>(null);
+export default function Canvas({width, height, painter, children}: Props) {
+    const {colorMode} = useColorMode();
+    const [image, setImage] = useState<[ImageData]>(null);
+
+    const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
     const canvasRef = useCallback(node => {
         if (node !== null) {
             setCanvasCtx(node.getContext("2d"));
         }
     }, []);
 
-    const {colorMode} = useColorMode();
-    const [image, setImage] = useState<ImageData>(new ImageData(width, height));
     const paintImage = new ImageData(width, height);
-
-    useEffect(() => {
-        if (!canvasCtx) {
+    const paint = () => {
+        if (!canvasCtx || !image) {
             return;
         }
 
-        for (const [index, value] of image.data.entries()) {
-            // If dark mode is active, invert the color scheme
-            const adjustValue = colorMode === 'light' ? value : 255 - value;
-
-            // Alpha values never change
-            paintImage.data[index] = index % 4 === 3 ? value : adjustValue;
+        for (const [index, value] of image[0].data.entries()) {
+            if (index % 4 === 3) {
+                // Alpha values are copied as-is
+                paintImage.data[index] = value;
+            } else {
+                // If dark mode is active, invert the color
+                paintImage.data[index] = colorMode === 'light' ? value : 255 - value;
+            }
         }
 
-        console.log("Painting image");
         canvasCtx.putImageData(paintImage, 0, 0);
-    }, [canvasCtx, colorMode, image]);
+    }
+    useEffect(paint, [colorMode, image]);
+
+    const animate = () => {
+        const nextImage = painter.next().value;
+        if (nextImage) {
+            setImage([nextImage])
+            requestAnimationFrame(animate);
+        }
+    }
+    useEffect(animate, [canvasCtx]);
 
     return (
-        <ImageContext.Provider value={{width, height, setImage}}>
+        <>
             <canvas
                 ref={canvasRef}
                 width={width}
@@ -54,6 +59,6 @@ export default function Canvas({width, height, children}) {
                 }}
             />
             {children}
-        </ImageContext.Provider>
+        </>
     )
 }
\ No newline at end of file