@@ -28,6 +28,7 @@ const COLORS = '0123456789abcdef'
2828const MIN_DEPTH = 3
2929const MAX_DEPTH = Infinity
3030const MAX_DRAW_CALLS = 1e4 // number of shapes to draw per frame
31+ const MAX_DRAW_TIME_MS = 15 // how long to draw a frame in ms
3132const MAX_QUEUE_SIZE = 1e6
3233const MIN_PATTERN_SIZE = 0.0005 // ignore patterns where either side is smaller than this
3334const DEBUG = true as boolean
@@ -85,30 +86,6 @@ const measure = (f: () => void): number => {
8586 return performance . now ( ) - start
8687}
8788
88- // ---
89-
90- const drawScreen = (
91- ctx : CanvasRenderingContext2D ,
92- screenSize : Size ,
93- absolutePattern : AbsolutePattern ,
94- strokeStyle : string
95- ) : void => {
96- const viewportPattern = mapPatternToViewportSpace ( absolutePattern , screenSize )
97- const [ p1 , p2 , p3 , p4 ] = getPatternPoints ( viewportPattern )
98-
99- ctx . strokeStyle = strokeStyle
100-
101- ctx . beginPath ( )
102- ctx . moveTo ( ...p1 )
103- ctx . lineTo ( ...p2 )
104- ctx . lineTo ( ...p3 )
105- ctx . lineTo ( ...p4 )
106- ctx . closePath ( )
107-
108- ctx . fill ( )
109- ctx . stroke ( )
110- }
111-
11289type QueueEntry = {
11390 currentPattern : AbsolutePattern
11491 depth : number
@@ -136,7 +113,9 @@ function* generateDrawQueue(state: State): Generator<QueueEntry, void, void> {
136113
137114 // Don't add patterns that are too deep.
138115 if ( entry . depth >= MAX_DEPTH ) {
139- console . log ( `MAX_DEPTH (${ MAX_DEPTH } ) reached at depth ${ entry . depth } . Breaking from generateDrawQueue loop.` )
116+ console . log (
117+ `MAX_DEPTH (${ MAX_DEPTH } ) reached at depth ${ entry . depth } . Breaking from generateDrawQueue loop.`
118+ )
140119 break
141120 }
142121
@@ -158,7 +137,58 @@ function* generateDrawQueue(state: State): Generator<QueueEntry, void, void> {
158137 }
159138 }
160139
161- console . log ( `generateDrawQueue done. Total iterations: ${ iterations } . Final queue size: ${ patternQueue . size } ` )
140+ console . log (
141+ `generateDrawQueue done. Total iterations: ${ iterations } . Final queue size: ${ patternQueue . size } `
142+ )
143+ }
144+
145+ type Chunk = {
146+ depth : number
147+ patterns : AbsolutePattern [ ]
148+ }
149+
150+ function * generateChunkedDraws ( state : State , chunkSize : number ) : Generator < Chunk , void , void > {
151+ const drawQueueIterator = generateDrawQueue ( state )
152+
153+ let chunk : Chunk = {
154+ depth : 0 ,
155+ patterns : [ ] ,
156+ }
157+
158+ while ( true ) {
159+ const iteratorResult = drawQueueIterator . next ( )
160+
161+ if ( iteratorResult . done ) {
162+ if ( chunk . patterns . length > 0 ) {
163+ yield chunk
164+ }
165+
166+ break
167+ }
168+
169+ const entry = iteratorResult . value
170+
171+ if ( chunk . depth !== entry . depth ) {
172+ if ( chunk . patterns . length > 0 ) {
173+ yield chunk
174+ }
175+
176+ chunk = {
177+ depth : entry . depth ,
178+ patterns : [ entry . currentPattern ] ,
179+ }
180+ } else {
181+ chunk . patterns . push ( entry . currentPattern )
182+ }
183+
184+ if ( chunk . patterns . length >= chunkSize ) {
185+ yield chunk
186+ chunk = {
187+ depth : entry . depth ,
188+ patterns : [ ] ,
189+ }
190+ }
191+ }
162192}
163193
164194export function * drawFrameIncrementally (
@@ -168,28 +198,60 @@ export function* drawFrameIncrementally(
168198 ctx . clearRect ( 0 , 0 , ctx . canvas . width , ctx . canvas . height )
169199 ctx . fillStyle = 'black'
170200 ctx . lineWidth = 1
171-
201+
172202 const screenSize = getScreenSize ( ctx )
173203
174- const drawQueueIterator = generateDrawQueue ( state )
204+ const chunkedDrawsIterator = generateChunkedDraws ( state , 1000 )
205+
206+ let iterations = 0
175207
176208 while ( true ) {
209+ iterations += 1
210+
177211 let drawScreenCalls = 0
178212
179213 const duration = measure ( ( ) => {
180- // Manual iteration
214+ const start = performance . now ( )
215+
181216 while ( true ) {
182- const iteratorResult = drawQueueIterator . next ( )
217+ const iteratorResult = chunkedDrawsIterator . next ( )
183218
184219 if ( iteratorResult . done ) break
185220
186- const { currentPattern, depth } = iteratorResult . value
187-
188- drawScreenCalls += 1
189-
190- drawScreen ( ctx , screenSize , currentPattern , COLORS [ Math . min ( COLORS . length - 1 , depth ) ] ! )
191-
192- if ( depth > MIN_DEPTH && drawScreenCalls >= MAX_DRAW_CALLS ) break
221+ const { depth, patterns } = iteratorResult . value
222+
223+ drawScreenCalls += patterns . length
224+
225+ // Draw screen
226+ ctx . strokeStyle = COLORS [ Math . min ( COLORS . length - 1 , depth ) ] !
227+ ctx . beginPath ( )
228+
229+ for ( const absolutePattern of patterns ) {
230+ const viewportPattern = mapPatternToViewportSpace ( absolutePattern , screenSize )
231+ const [ p1 , p2 , p3 , p4 ] = getPatternPoints ( viewportPattern )
232+
233+ ctx . moveTo ( ...p1 )
234+ ctx . lineTo ( ...p2 )
235+ ctx . lineTo ( ...p3 )
236+ ctx . lineTo ( ...p4 )
237+ ctx . closePath ( )
238+ }
239+
240+ ctx . fill ( )
241+ ctx . stroke ( )
242+
243+ if ( iterations === 1 ) {
244+ // On the first iteration, draw until passing both MIN_DEPTH and MAX_DRAW_CALLS.
245+ // We don't want to use a time-based limit here because it would cause flickering.
246+ if ( depth > MIN_DEPTH && drawScreenCalls >= MAX_DRAW_CALLS ) {
247+ break
248+ }
249+ } else {
250+ // On subsequent iterations, draw until MAX_DRAW_TIME_MS is reached.
251+ if ( performance . now ( ) - start > MAX_DRAW_TIME_MS ) {
252+ break
253+ }
254+ }
193255 }
194256 } )
195257
0 commit comments