1- import { combinePatterns , getBoundariesFromPattern , getScreenSize } from './functions'
1+ import {
2+ combinePatterns ,
3+ getBoundariesFromPattern ,
4+ getScreenSize ,
5+ mapPatternToViewportSpace ,
6+ mutateBoundariesFromPattern ,
7+ } from './functions'
28import { Queue } from './queue'
3- import { AbsoluteNumber , AbsolutePattern , Boundaries , State } from './types'
9+ import { Boundaries , Size , State , ViewportNumber , ViewportPattern } from './types'
410
511// -- constants
612
@@ -12,34 +18,38 @@ const COLORS = '0123456789abcdef'
1218
1319const MIN_DEPTH = 3
1420const MAX_DEPTH = Infinity
15- const MAX_PREVIEW_DRAW_CALLS = 1e4 // number of shapes to draw per preview frame
21+ const MAX_PREVIEW_DRAW_CALLS = 5e3 // number of shapes to draw per preview frame
1622const MAX_DRAW_TIME_MS = 15 // how long to draw a frame in ms
1723const MAX_QUEUE_SIZE = 1e6
18- const MIN_PATTERN_SIZE = 0.0005 // ignore patterns where either side is smaller than this
24+ // const MIN_PATTERN_SIZE = 0.0005 // ignore patterns where either side is smaller than this
25+ const MIN_PATTERN_SIZE_PX = 1 // ignore patterns where either side is smaller than this
1926const DEBUG = true as boolean
2027
2128// We'll ignore everything that's a bit outside the viewport.
2229// This is not really accurate, but should be OK for our purposes.
23- const VALID_BOUNDARIES : Boundaries < AbsoluteNumber > = {
24- xMin : - 0.1 as AbsoluteNumber ,
25- xMax : 1.1 as AbsoluteNumber ,
26- yMin : - 0.1 as AbsoluteNumber ,
27- yMax : 1.1 as AbsoluteNumber ,
30+ function getViewportBoundaries ( screenSize : Size ) : Boundaries < ViewportNumber > {
31+ return {
32+ xMin : ( - 0.1 * screenSize [ 0 ] ) as ViewportNumber ,
33+ xMax : ( 1.1 * screenSize [ 0 ] ) as ViewportNumber ,
34+ yMin : ( - 0.1 * screenSize [ 1 ] ) as ViewportNumber ,
35+ yMax : ( 1.1 * screenSize [ 1 ] ) as ViewportNumber ,
36+ }
2837}
2938
30- const isValidPattern = ( pattern : AbsolutePattern ) : boolean => {
31- const { xMin, xMax, yMin, yMax } = getBoundariesFromPattern ( pattern )
32-
39+ const isValidPattern = (
40+ patternBoundaries : Boundaries < ViewportNumber > ,
41+ viewportBoundaries : Boundaries < ViewportNumber >
42+ ) : boolean => {
3343 return (
3444 // Pattern fulfills minimum size.
35- xMax - xMin >= MIN_PATTERN_SIZE &&
36- yMax - yMin >= MIN_PATTERN_SIZE &&
45+ patternBoundaries . xMax - patternBoundaries . xMin >= MIN_PATTERN_SIZE_PX &&
46+ patternBoundaries . yMax - patternBoundaries . yMin >= MIN_PATTERN_SIZE_PX &&
3747 // X and Y ranges overlap with the valid boundaries.
3848 // We will only render patterns if at least one corner is inside the valid boundaries.
39- xMin <= VALID_BOUNDARIES . xMax &&
40- xMax >= VALID_BOUNDARIES . xMin &&
41- yMin <= VALID_BOUNDARIES . yMax &&
42- yMax >= VALID_BOUNDARIES . yMin
49+ patternBoundaries . xMin <= viewportBoundaries . xMax &&
50+ patternBoundaries . xMax >= viewportBoundaries . xMin &&
51+ patternBoundaries . yMin <= viewportBoundaries . yMax &&
52+ patternBoundaries . yMax >= viewportBoundaries . yMin
4353 )
4454}
4555
@@ -52,7 +62,7 @@ const measure = (f: () => void): number => {
5262}
5363
5464type QueueEntry = {
55- currentPattern : AbsolutePattern
65+ currentPattern : ViewportPattern
5666 depth : number
5767}
5868
@@ -62,20 +72,64 @@ const patternQueue = new Queue<QueueEntry>({
6272 size : MAX_QUEUE_SIZE ,
6373} )
6474
65- function * generateDrawQueue ( state : State ) : Generator < QueueEntry , void , void > {
75+ /**
76+ * Creates a generator that yields drawable patterns one by one.
77+ *
78+ * It starts with the initial screens and recursively applies all defined patterns
79+ * from the state. Each yielded pattern includes its generation depth.
80+ * The generation proceeds in a breadth-first manner.
81+ *
82+ * @param state The current application state, containing screens and patterns.
83+ * @param screenSize The current size of the screen/viewport.
84+ * @returns A generator yielding `QueueEntry` objects ({ currentPattern: ViewportPattern, depth: number }).
85+ */
86+ function * streamDrawablePatterns ( {
87+ state,
88+ screenSize,
89+ } : {
90+ state : State
91+ screenSize : Size
92+ } ) : Generator < QueueEntry , void , void > {
6693 console . log (
6794 `generateDrawQueue start. Initial screens: ${ state . screens . length } , Patterns: ${ state . patterns . length } `
6895 )
6996
97+ const viewportBoundaries = getViewportBoundaries ( screenSize )
98+
7099 patternQueue . clear ( )
71100
72101 for ( const screen of state . screens ) {
73102 patternQueue . push ( {
74- currentPattern : screen ,
103+ currentPattern : mapPatternToViewportSpace ( screen , screenSize ) ,
75104 depth : 0 ,
76105 } )
77106 }
78107
108+ // Optimization: Only allocate boundaries object once and mutate it in place.
109+ // Make sure to update this object before using it!
110+ const patternBoundaries : Boundaries < ViewportNumber > = {
111+ xMin : 0 as ViewportNumber ,
112+ xMax : 0 as ViewportNumber ,
113+ yMin : 0 as ViewportNumber ,
114+ yMax : 0 as ViewportNumber ,
115+ }
116+
117+ function isValidPattern ( pattern : ViewportPattern ) : boolean {
118+ mutateBoundariesFromPattern ( pattern , patternBoundaries )
119+
120+ return (
121+ // Pattern fulfills minimum size.
122+ patternBoundaries . xMax - patternBoundaries . xMin >= MIN_PATTERN_SIZE_PX &&
123+ patternBoundaries . yMax - patternBoundaries . yMin >= MIN_PATTERN_SIZE_PX &&
124+ // X and Y ranges overlap with the valid boundaries.
125+ // We will only render patterns if at least one corner is inside the valid boundaries.
126+ patternBoundaries . xMin <= viewportBoundaries . xMax &&
127+ patternBoundaries . xMax >= viewportBoundaries . xMin &&
128+ patternBoundaries . yMin <= viewportBoundaries . yMax &&
129+ patternBoundaries . yMax >= viewportBoundaries . yMin
130+ )
131+ }
132+
79133 let iterations = 0
80134 while ( patternQueue . size > 0 ) {
81135 iterations += 1
@@ -93,11 +147,11 @@ function* generateDrawQueue(state: State): Generator<QueueEntry, void, void> {
93147 }
94148
95149 for ( const pattern of state . patterns ) {
96- const virtualScreen = combinePatterns ( entry . currentPattern , pattern )
150+ const viewportPattern = combinePatterns ( entry . currentPattern , pattern )
97151
98- if ( isValidPattern ( virtualScreen ) ) {
152+ if ( isValidPattern ( viewportPattern ) ) {
99153 patternQueue . push ( {
100- currentPattern : virtualScreen ,
154+ currentPattern : viewportPattern ,
101155 depth : entry . depth + 1 ,
102156 } )
103157
@@ -119,11 +173,30 @@ function* generateDrawQueue(state: State): Generator<QueueEntry, void, void> {
119173
120174type Chunk = {
121175 depth : number
122- patterns : AbsolutePattern [ ]
176+ patterns : ViewportPattern [ ]
123177}
124178
125- function * generateChunkedDraws ( state : State , chunkSize : number ) : Generator < Chunk , void , void > {
126- const drawQueueIterator = generateDrawQueue ( state )
179+ /**
180+ * Creates a generator that groups drawable patterns from `streamDrawablePatterns` into chunks.
181+ *
182+ * Patterns are batched together based on their generation depth or a maximum
183+ * chunk size. This is useful for processing or rendering patterns in groups.
184+ *
185+ * @param state The current application state.
186+ * @param chunkSize The maximum number of patterns to include in a single chunk.
187+ * @param screenSize The current size of the screen/viewport.
188+ * @returns A generator yielding `Chunk` objects ({ depth: number, patterns: ViewportPattern[] }).
189+ */
190+ function * streamBatchedDrawablePatterns ( {
191+ state,
192+ chunkSize,
193+ screenSize,
194+ } : {
195+ state : State
196+ chunkSize : number
197+ screenSize : Size
198+ } ) : Generator < Chunk , void , void > {
199+ const drawQueueIterator = streamDrawablePatterns ( { state, screenSize } )
127200
128201 let chunk : Chunk = {
129202 depth : 0 ,
@@ -176,7 +249,7 @@ export function* drawFrameIncrementally(
176249
177250 const screenSize = getScreenSize ( ctx )
178251
179- const chunkedDrawsIterator = generateChunkedDraws ( state , 1000 )
252+ const batchedPatternsIterator = streamBatchedDrawablePatterns ( { state, chunkSize : 1000 , screenSize } )
180253
181254 let iterations = 0
182255
@@ -189,7 +262,7 @@ export function* drawFrameIncrementally(
189262 const start = performance . now ( )
190263
191264 while ( true ) {
192- const iteratorResult = chunkedDrawsIterator . next ( )
265+ const iteratorResult = batchedPatternsIterator . next ( )
193266
194267 if ( iteratorResult . done ) break
195268
@@ -201,32 +274,24 @@ export function* drawFrameIncrementally(
201274 ctx . strokeStyle = COLORS [ Math . min ( COLORS . length - 1 , depth ) ] !
202275 ctx . beginPath ( )
203276
204- // Optimization: Only allocate variables once.
205- let xMin : number
206- let yMin : number
207- let xMax : number
208- let yMax : number
209- let x1 : number
210- let y1 : number
211- let x2 : number
212- let y2 : number
213-
214- for ( const absolutePattern of patterns ) {
215- // Convert to viewport space.
216- x1 = Math . round ( absolutePattern . anchor [ 0 ] * screenSize [ 0 ] )
217- y1 = Math . round ( absolutePattern . anchor [ 1 ] * screenSize [ 1 ] )
218- x2 = Math . round ( absolutePattern . target [ 0 ] * screenSize [ 0 ] )
219- y2 = Math . round ( absolutePattern . target [ 1 ] * screenSize [ 1 ] )
220-
221- // Get boundaries.
222- // Optimization: Do not use getBoundariesFromPattern because it creates too many objects.
223- xMin = Math . min ( x1 , x2 )
224- yMin = Math . min ( y1 , y2 )
225- xMax = Math . max ( x1 , x2 )
226- yMax = Math . max ( y1 , y2 )
277+ // Optimization: Only allocate boundaries object once and mutate it in place.
278+ const boundaries : Boundaries < ViewportNumber > = {
279+ xMin : 0 as ViewportNumber ,
280+ xMax : 0 as ViewportNumber ,
281+ yMin : 0 as ViewportNumber ,
282+ yMax : 0 as ViewportNumber ,
283+ }
284+
285+ for ( const viewportPattern of patterns ) {
286+ mutateBoundariesFromPattern ( viewportPattern , boundaries )
227287
228288 // Draw rectangle.
229- ctx . rect ( xMin , yMin , xMax - xMin , yMax - yMin )
289+ ctx . rect (
290+ boundaries . xMin ,
291+ boundaries . yMin ,
292+ boundaries . xMax - boundaries . xMin ,
293+ boundaries . yMax - boundaries . yMin
294+ )
230295 }
231296
232297 ctx . fill ( )
0 commit comments