Apart from career programming and management tasks, I spend my free time doing creative coding. Most of it is programming music with SuperCollider but recently I was also involved in some visual art.
Initial project structure
The idea behind the project is to study the relation between colors by randomly morphing them into each other. The program which does this is the array of program frames which are the color and number of frame executions. In the initial iteration, the capacity of the array is 5.
function generateProgram() {
var numberOfFrames = 500;
var numberOfIterations = 5000;
var previousIteration = 0;
var program = [];
for (var i = 0; i < numberOfFrames; i++) {
var currentIteration = randomInt(numberOfIterations) + previousIteration;
program[i] = {
color: randomHsl(),
latestIteration: currentIteration
}
previousIteration = currentIteration;
}
return program;
}
We're generating colors in HSL format since random colors in RGB look pretty similar and the result wouldn't be as pleasing aesthetically.
Program execution is the infinite loop that selects a couple of random points on the canvas and blends them with the color of the current program frame. Once we've reached the target number of iterations we switch to the next program frame.
function executeProgram(program) {
var points = getInitialCanvas(program);
drawOnCanvas(points);
var iteration = 0;
var currentProgramFrameIndex = 0;
var currentProgramFrame = program[currentProgramFrameIndex];
function run() {
setTimeout(function() {
for (var k = 0; k < 10; k++) {
window.requestAnimationFrame(run);
iteration++;
var i = randomInt(height)-1;
var j = randomInt(width)-1;
points[i][j] = blendColors(points[i][j], currentProgramFrame.color);
if (iteration == currentProgramFrame.latestIteration
&& currentProgramFrameIndex !== program.length - 1) {
currentProgramFrameIndex++;
currentProgramFrame = program[currentProgramFrameIndex];
}
drawPointOnCanvas(i, j, points[i][j]);
}
}, 1);
}
run();
}
We can observe that we're instructing the browser to schedule animation using window.requestAnimationFrame method. This method accepts a callback which will be executed when it is time to paint the animation. So by calling run
method as the callback we're effectively creating the infinite loop.
In order for our infinite loop not to block repainting of the canvas, we call each iteration inside the setTimeout
function.
Using generators
The obvious downside of this approach is that we have a limited amount of frames. Instead, we want our animation to last forever. One of the possible ways is to provide each new program frame via generators
Changes are pretty straightforward. Instead of generating the program as an array of frames, we define a generator function that will return a new frame when requested.
function* generateProgram() {
while (true) {
var numberOfIterations = 5000;
var program = [];
var currentIteration = randomInt(numberOfIterations)
program = {
color: randomHsl(),
latestIteration: currentIteration
}
previousIteration = currentIteration;
yield program;
}
}
To request the frame we instantiate the generator object by calling the function and then calling next
method.
var programGenerator = generateProgram()
var program = programGenerator.next().value;
Now we can request each program frame, once the previous is completed.
function executeProgram(program) {
var points = getInitialCanvas(program);
drawOnCanvas(points);
var iteration = 0;
function run() {
setTimeout(function() {
for (var k = 0; k < 10; k++) {
window.requestAnimationFrame(run);
iteration++;
var i = randomInt(height)-1;
var j = randomInt(width)-1;
points[i][j] = blendColors(points[i][j], program.color);
if (iteration == program.latestIteration) {
program = programGenerator.next().value
}
drawPointOnCanvas(i, j, points[i][j]);
}
}, 1);
}
run();
}
Sidenote: Fixing the memory leak
When you pay attention to this program long enough you might notice that it starts consuming more and more memory.
Upon examining memory profile in my Firefox devtools I can notice that the primary source of allocation is setTimeout
function.
Turns out that calls to requestAnimationFrame
are stacked to map of animation frame callbacks. And if the browser is not keeping up with the growth of callback's map it's starts growing up in size consuming more and more memory.
So the fix is as simple as increasing timeout duration.
Top comments (0)