diff --git a/Dockerfile b/Dockerfile index be6e12c5..4cb72db6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,3 +11,6 @@ COPY web/package-lock.json /www RUN npm install --quiet COPY web/src /www/src COPY web/view /www/view +COPY .git /tmp/ +RUN git -C /tmp rev-parse --verify HEAD > /www/git-sha +RUN rm -rf /tmp/.git diff --git a/README.md b/README.md index acf55b20..7c1dfc11 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ Or by using Image type, can just define in EPL preRenderFunc! See eyechart-sacca ## TODOs replace koa-socket-session with something better supported (1.2.0 fails) update to koa-socket-2 -make sure I didn't break shuffle with new randint -- Chunk number of stimmuli returned - +- Chunk number of stimmuli returned by server to lower number of requsets +- make preRenderFunc a generator, store images in localStorage +- render: read from localStorage ### video - Always save video diff --git a/docker-compose.yml b/docker-compose.yml index 5bb0d4e5..d17eecda 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ web: command: node --max-old-space-size=8192 --harmony src/app.js volumes: - /data/eye-candy:/data + - ./web/programs:/www/programs links: - redis redis: diff --git a/web/src/programs/acuity-with-flashes.js b/web/programs/acuity-with-flashes.js similarity index 95% rename from web/src/programs/acuity-with-flashes.js rename to web/programs/acuity-with-flashes.js index 1b4b0afe..cdf3b47c 100644 --- a/web/src/programs/acuity-with-flashes.js +++ b/web/programs/acuity-with-flashes.js @@ -32,7 +32,7 @@ r.shuffle(stimuli) stimuli = flatten(stimuli) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/acuity.js b/web/programs/acuity.js similarity index 76% rename from web/src/programs/acuity.js rename to web/programs/acuity.js index 3bcc431e..04eaff9d 100644 --- a/web/src/programs/acuity.js +++ b/web/programs/acuity.js @@ -22,9 +22,19 @@ function* measureIntegrity(stimuli,every=5*60) { // special function for pre-rendering. This is passed as a string // and run client-side -function preRenderFunc(binaryNoiseNframes, randomSeed) { +function* preRenderFunc(binaryNoiseNframes, randomSeed) { console.log("In preRender...") + function renderFrame(flatPixelArray) { + var canvas = document.createElement("canvas"); + var context = canvas.getContext("2d") + canvas.width = WIDTH + canvas.height = HEIGHT + imageData = new ImageData(flatPixelArray, WIDTH, HEIGHT) + context.putImageData(imageData, 0, 0) + return canvas + } + console.assert(binaryNoiseNframes % 2 == 0) // client-side let r = new DeterministicRandom(randomSeed) @@ -38,59 +48,53 @@ function preRenderFunc(binaryNoiseNframes, randomSeed) { // let binaryNoiseNframes =14 let pixelArrays = [] let singlePixel = Uint8ClampedArray.from(Array(binaryNoiseNframes/2).fill(0).concat(Array(binaryNoiseNframes/2).fill(255))) - for (var p = 0; p < nPixels; p++) { - // benchmark: 50% of time is from shuffle - r.shuffle(singlePixel) - // ... allows for array copy - pixelArrays.push([...singlePixel]) - if (p % 10000 == 0) { - console.log("pushed array for pixel: ", p) - } - } - - // RGBA array https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas - let allFrames = new Uint8ClampedArray(binaryNoiseNframes*nPixels*4) - // values of single pixel over time - // TODO why does making this nPixels/2 leave only ~1/4 of pixels with value?? - for (var p = 0; p < nPixels; p++) { - singlePixel = pixelArrays[p] - for (var n = 0; n < binaryNoiseNframes; n++) { - // For example, to read the blue component's value from the pixel at column 200, row 50 in the image, you would do the following: - // blueComponent = imageData.data[(50 * (imageData.width * 4)) + (200 * 4) + 2] - allFrames[p*4 + n*nPixels*4] = singlePixel[n] // red - allFrames[1+p*4 + n*nPixels*4] = singlePixel[n] // green - allFrames[2+p*4 + n*nPixels*4] = singlePixel[n] // blue - allFrames[3+p*4 + n*nPixels*4] = 255 // alpha - } - if (p % 10000 == 0) { - console.log("pushed array for pixel: ", p) - } - } - function renderFrame(flatPixelArray) { - var canvas = document.createElement("canvas"); - var context = canvas.getContext("2d") - canvas.width = WIDTH - canvas.height = HEIGHT - imageData = new ImageData(flatPixelArray, WIDTH, HEIGHT) - context.putImageData(imageData, 0, 0) - return canvas - } + // chunk N frame increments to avoid memory overflow + let chunkNframes = 100 + let chunkFrames + for (var i = 0; i < binaryNoiseNframes; i = i + chunkNframes) { + // equal to chunkNframes except for final iteration + chunkNframes = Math.min(chunkNframes, binaryNoiseNframes-i) + chunkFrames = new Uint8ClampedArray(chunkNframes*nPixels*4) + + // create balanced pixel assignments + pixelArrays = [] + for (var p = 0; p < nPixels; p++) { + // benchmark: 50% of time is from shuffle + r.shuffle(singlePixel) + // ... allows for array copy + pixelArrays.push([...singlePixel]) + if (p % 10000 == 0) { + console.log("pushed array for pixel: ", p) + } + } - let frames = [] - for (var n = 0; n < binaryNoiseNframes; n++) { - frames.push(renderFrame(allFrames.slice(n*nPixels*4,(n+1)*nPixels*4))) + // assign values of each pixel over time + for (var p = 0; p < nPixels; p++) { + singlePixel = pixelArrays[p] + for (var n = 0; n < chunkNframes; n++) { + // For example, to read the blue component's value from the pixel at column 200, row 50 in the image, you would do the following: + // blueComponent = imageData.data[(50 * (imageData.width * 4)) + (200 * 4) + 2] + chunkFrames[p*4 + n*nPixels*4] = singlePixel[n] // red + chunkFrames[1+p*4 + n*nPixels*4] = singlePixel[n] // green + chunkFrames[2+p*4 + n*nPixels*4] = singlePixel[n] // blue + chunkFrames[3+p*4 + n*nPixels*4] = 255 // alpha + } + if (p % 10000 == 0) { + console.log("pushed array for pixel: ", p) + } + } + // yield each frame from chunk + for (var n = 0; n < chunkNframes; n++) { + yield renderFrame(chunkFrames.slice(n*nPixels*4,(n+1)*nPixels*4)) + } } - console.log("frames[0]", frames[0]) - - return {renders: frames, - yield: {}} } // special object for pre-rendering -const binaryNoiseDuration = 0.1*60 +const binaryNoiseDuration = 5*60 const frameRate = 60 const hz = 5 const binaryNoiseLifespan = 1 / hz @@ -197,7 +201,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) stimuli = celltypeStimuli.concat(stimuli) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/checkerboard-contrast.js b/web/programs/checkerboard-contrast.js similarity index 98% rename from web/src/programs/checkerboard-contrast.js rename to web/programs/checkerboard-contrast.js index 26dac323..1977230a 100644 --- a/web/src/programs/checkerboard-contrast.js +++ b/web/programs/checkerboard-contrast.js @@ -147,7 +147,7 @@ r.shuffle(stimuli) stimuli = insertBreaks(measureIntegrity(flatten(stimuli))) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/checkerboard-durations.js b/web/programs/checkerboard-durations.js similarity index 98% rename from web/src/programs/checkerboard-durations.js rename to web/programs/checkerboard-durations.js index e6030213..904ee64c 100644 --- a/web/src/programs/checkerboard-durations.js +++ b/web/programs/checkerboard-durations.js @@ -97,7 +97,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/checkerboard-flicker.js b/web/programs/checkerboard-flicker.js similarity index 100% rename from web/src/programs/checkerboard-flicker.js rename to web/programs/checkerboard-flicker.js diff --git a/web/src/programs/checkerboard.js b/web/programs/checkerboard.js similarity index 98% rename from web/src/programs/checkerboard.js rename to web/programs/checkerboard.js index 859e148e..1103a663 100644 --- a/web/src/programs/checkerboard.js +++ b/web/programs/checkerboard.js @@ -97,7 +97,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/dev_vidimage_test.js b/web/programs/dev_vidimage_test.js similarity index 92% rename from web/src/programs/dev_vidimage_test.js rename to web/programs/dev_vidimage_test.js index ea0bddc7..8972cf3c 100644 --- a/web/src/programs/dev_vidimage_test.js +++ b/web/programs/dev_vidimage_test.js @@ -15,7 +15,7 @@ for (var i = 0; i < repetitions; i++) { } -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/eyechart-balanced.js b/web/programs/eyechart-balanced.js similarity index 98% rename from web/src/programs/eyechart-balanced.js rename to web/programs/eyechart-balanced.js index 95aa74a1..954fea81 100644 --- a/web/src/programs/eyechart-balanced.js +++ b/web/programs/eyechart-balanced.js @@ -108,7 +108,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/eyechart-saccade.js b/web/programs/eyechart-saccade.js similarity index 97% rename from web/src/programs/eyechart-saccade.js rename to web/programs/eyechart-saccade.js index 2049e867..c1eb91d3 100644 --- a/web/src/programs/eyechart-saccade.js +++ b/web/programs/eyechart-saccade.js @@ -65,7 +65,7 @@ function twoLetterMatrices(nrows) { for (var j = 0; j < ncols; j++) { firstLetterMatrix[i][j] = newLetters[j] secondLetterMatrix[i][j] = newLetters[j+5] - + cohortMatrix[i][j] = cohortID } } @@ -93,7 +93,7 @@ function calcFixationPoints(sizes, ncols) { let rowWidth for (var i = 0; i < nrows; i++) { size = sizes[i] - y = y + 2*size + y = y + 2*size rowWidth = (2*ncols+1) * size if (i==0) { @@ -186,14 +186,13 @@ function preRenderFunc(sizes, reps, ncols, color, letterTensor, let nrows = sizes.length let eyecharts = [] - + for (var i = 0; i < letterTensor.length; i++) { // console.log("letterTensor[i]", letterTensor[i]) let image = renderEyechart(sizes, ncols, letterTensor[i], color) eyecharts.push(image) } - return {renders: eyecharts, - yield: {}} + return {renders: eyecharts} } // special object for pre-rendering @@ -221,8 +220,8 @@ function* gen() { } // TODO add integrity -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (let s of measureIntegrity(gen())) { yield s } -} \ No newline at end of file +} diff --git a/web/src/programs/eyechart-uniform.backup.js b/web/programs/eyechart-uniform.backup.js similarity index 98% rename from web/src/programs/eyechart-uniform.backup.js rename to web/programs/eyechart-uniform.backup.js index 59b17061..38784035 100644 --- a/web/src/programs/eyechart-uniform.backup.js +++ b/web/programs/eyechart-uniform.backup.js @@ -81,7 +81,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/generic_video.js b/web/programs/generic_video.js similarity index 82% rename from web/src/programs/generic_video.js rename to web/programs/generic_video.js index 600fee9e..ce67eff8 100644 --- a/web/src/programs/generic_video.js +++ b/web/programs/generic_video.js @@ -6,7 +6,7 @@ let meta = {} stimuli.push!(new Video(1000, "black", DATADIR+"videos/cropped.mp4", meta)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-contrast.js b/web/programs/grating-contrast.js similarity index 98% rename from web/src/programs/grating-contrast.js rename to web/programs/grating-contrast.js index e6daf1b8..26a4d855 100644 --- a/web/src/programs/grating-contrast.js +++ b/web/programs/grating-contrast.js @@ -126,7 +126,7 @@ r.shuffle(stimuli) stimuli = insertBreaks(measureIntegrity(flatten(stimuli))) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-durations.js b/web/programs/grating-durations.js similarity index 98% rename from web/src/programs/grating-durations.js rename to web/programs/grating-durations.js index 30ad2ff3..80ea9ab1 100644 --- a/web/src/programs/grating-durations.js +++ b/web/programs/grating-durations.js @@ -83,7 +83,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-sinusoidal-contrast.js b/web/programs/grating-sinusoidal-contrast.js similarity index 98% rename from web/src/programs/grating-sinusoidal-contrast.js rename to web/programs/grating-sinusoidal-contrast.js index fdea4657..6de1da90 100644 --- a/web/src/programs/grating-sinusoidal-contrast.js +++ b/web/programs/grating-sinusoidal-contrast.js @@ -126,7 +126,7 @@ r.shuffle(stimuli) stimuli = insertBreaks(measureIntegrity(flatten(stimuli))) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-sinusoidal-durations.js b/web/programs/grating-sinusoidal-durations.js similarity index 98% rename from web/src/programs/grating-sinusoidal-durations.js rename to web/programs/grating-sinusoidal-durations.js index 83ca9bfb..9fa607ab 100644 --- a/web/src/programs/grating-sinusoidal-durations.js +++ b/web/programs/grating-sinusoidal-durations.js @@ -83,7 +83,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-sinusoidal-speeds.js b/web/programs/grating-sinusoidal-speeds.js similarity index 98% rename from web/src/programs/grating-sinusoidal-speeds.js rename to web/programs/grating-sinusoidal-speeds.js index b59a0080..223d8266 100644 --- a/web/src/programs/grating-sinusoidal-speeds.js +++ b/web/programs/grating-sinusoidal-speeds.js @@ -83,7 +83,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-sinusoidal.js b/web/programs/grating-sinusoidal.js similarity index 98% rename from web/src/programs/grating-sinusoidal.js rename to web/programs/grating-sinusoidal.js index 5cdf35af..f5b08a32 100644 --- a/web/src/programs/grating-sinusoidal.js +++ b/web/programs/grating-sinusoidal.js @@ -83,7 +83,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-speeds.js b/web/programs/grating-speeds.js similarity index 98% rename from web/src/programs/grating-speeds.js rename to web/programs/grating-speeds.js index 585ef08c..9f79c02c 100644 --- a/web/src/programs/grating-speeds.js +++ b/web/programs/grating-speeds.js @@ -83,7 +83,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/grating-vs-gray.js b/web/programs/grating-vs-gray.js similarity index 100% rename from web/src/programs/grating-vs-gray.js rename to web/programs/grating-vs-gray.js diff --git a/web/src/programs/grating.js b/web/programs/grating.js similarity index 98% rename from web/src/programs/grating.js rename to web/programs/grating.js index 5cbc943f..06fe864a 100644 --- a/web/src/programs/grating.js +++ b/web/programs/grating.js @@ -83,7 +83,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/kinetics.js b/web/programs/kinetics.js similarity index 100% rename from web/src/programs/kinetics.js rename to web/programs/kinetics.js diff --git a/web/src/programs/letters-inverted.js b/web/programs/letters-inverted.js similarity index 97% rename from web/src/programs/letters-inverted.js rename to web/programs/letters-inverted.js index 0a2c9385..521196b4 100644 --- a/web/src/programs/letters-inverted.js +++ b/web/programs/letters-inverted.js @@ -61,7 +61,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/letters-saccade.js b/web/programs/letters-saccade.js similarity index 97% rename from web/src/programs/letters-saccade.js rename to web/programs/letters-saccade.js index a7df2bcf..62912294 100644 --- a/web/src/programs/letters-saccade.js +++ b/web/programs/letters-saccade.js @@ -82,8 +82,7 @@ function preRenderFunc(sizes, letters, color) { renderedLetters[idx] = image } } - return {renders: renderedLetters, - yield: {}} + return {renders: renderedLetters} } // special object for pre-rendering @@ -121,7 +120,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (let s of stimuli) { yield s } diff --git a/web/src/programs/letters-tiled.js b/web/programs/letters-tiled.js similarity index 97% rename from web/src/programs/letters-tiled.js rename to web/programs/letters-tiled.js index 54d6211f..f880178f 100644 --- a/web/src/programs/letters-tiled.js +++ b/web/programs/letters-tiled.js @@ -67,7 +67,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/letters.js b/web/programs/letters.js similarity index 97% rename from web/src/programs/letters.js rename to web/programs/letters.js index c6ca9278..a06fb197 100644 --- a/web/src/programs/letters.js +++ b/web/programs/letters.js @@ -61,7 +61,7 @@ r.shuffle(stimuli) stimuli = measureIntegrity(flatten(stimuli)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/program-templates/single-video.js b/web/programs/templates/single-video.js similarity index 91% rename from web/src/program-templates/single-video.js rename to web/programs/templates/single-video.js index 9f40e1d9..b4fe4c7b 100644 --- a/web/src/program-templates/single-video.js +++ b/web/programs/templates/single-video.js @@ -15,7 +15,7 @@ for (var i = 0; i < repetitions; i++) { } -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/test.js b/web/programs/test.js similarity index 84% rename from web/src/programs/test.js rename to web/programs/test.js index 54744ae1..d55714e8 100644 --- a/web/src/programs/test.js +++ b/web/programs/test.js @@ -8,7 +8,7 @@ stimuli.push(new Solid(2, "white", meta)) stimuli.push(new Wait(1, meta)) -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/programs/wedge.js b/web/programs/wedge.js similarity index 92% rename from web/src/programs/wedge.js rename to web/programs/wedge.js index 33c8b59c..26af19c3 100644 --- a/web/src/programs/wedge.js +++ b/web/programs/wedge.js @@ -26,7 +26,7 @@ for (let i = 0; i < 5; i++) { stimuli = stimuli -function* stimulusGenerator(renderResults) { +function* stimulusGenerator() { for (s of stimuli) { yield s } diff --git a/web/src/app.js b/web/src/app.js index 45bf253d..c89398b8 100644 --- a/web/src/app.js +++ b/web/src/app.js @@ -23,6 +23,11 @@ const {compileYAMLProgram, compileJSProgram} = require("./epl/eval") const {DATADIR} = require('./vars.js') console.log("DATADIR: " + DATADIR) + +GIT_SHA = fs.readFileSync( + '/www/git-sha', + "utf-8") + const app = new Koa(); app.use(logger()) @@ -151,6 +156,7 @@ function makeLabNotebook(labNotebook) { labNotebook.date = date labNotebook.version = 0.5 labNotebook.flickerVersion = 0.3 + labNotebook.gitSHA = GIT_SHA console.log("labNotebook", labNotebook) return "---\n" + yaml.safeDump(labNotebook) @@ -297,7 +303,7 @@ app.context.render = co.wrap(app.context.render); app.use(async (ctx, next) => { - const programChoices = fs.readdirSync("/www/src/programs/").map(s => s.slice(0, -3)) + const programChoices = fs.readdirSync("/www/programs/").map(s => s.slice(0, -3)) let videoChoices = fs.readdirSync(DATADIR+"videos/") await ctx.render("index", { programChoices, @@ -362,13 +368,13 @@ io.on("load", (ctx, data) => { const regex = /\$\{VID\_SRC\}/gi; const videoSrc = data.video epl = fs.readFileSync( - '/www/src/program-templates/single-video.js', + '/www/programs/templates/single-video.js', "utf-8").replace(regex, "videos/" + videoSrc) } else { // this is likely a security vulnerability but sure is convenient // convention over customization epl = fs.readFileSync( - '/www/src/programs/'+eplProgram+'.js', + '/www/programs/'+eplProgram+'.js', "utf-8") } console.log("socket 'load': compiling for sid", sid) @@ -384,7 +390,7 @@ io.on("load", (ctx, data) => { io.on("renderResults", (ctx, data) => { console.log("socket 'renderResults'") const sid = data.sid - program[sid].initialize(data.renderResults) + program[sid].initialize() io.broadcast("enableSubmitButton") }) diff --git a/web/src/epl/eval.js b/web/src/epl/eval.js index a9519863..e7612999 100644 --- a/web/src/epl/eval.js +++ b/web/src/epl/eval.js @@ -17,14 +17,12 @@ let EPL = Object.assign({log: console.log, JSON: JSON, DATADIR: DATADIR}, {R: R} function compileJSProgram(programJS,seed, windowHeight, windowWidth) { console.log('compiling EPL.') - let renderResults = {} // FOR VM2 (production) const vm = new VM({ sandbox: Object.assign({ windowHeight: windowHeight, windowWidth: windowWidth, seed: seed, - renderResults: renderResults }, EPL), console: 'inherit' }) @@ -35,7 +33,6 @@ function compileJSProgram(programJS,seed, windowHeight, windowWidth) { // windowHeight: windowHeight, // windowWidth: windowWidth, // seed: seed, - // renderResults: renderResults // }, EPL) // const vm = VM.createContext(sandbox) // ---- FOR VM ---- @@ -50,8 +47,8 @@ function compileJSProgram(programJS,seed, windowHeight, windowWidth) { "if (typeof preRenderFunc !== 'undefined') {" + "preRenderFunc.toString()" + "} else {" + - "'function preRenderFunc() {" + - "return {renders: undefined, yield: undefined}" + + "'function* preRenderFunc() {" + + "" + "}'" + "}") let preRenderArgs = vm.run( @@ -62,22 +59,11 @@ function compileJSProgram(programJS,seed, windowHeight, windowWidth) { "}") console.log("eval preRenderFunc") - function initialize(clientResults) { + + function initialize() { console.log("Initializing EPL stimulus generator") - // TODO warning: will this potentially break with multiple connections? - renderResults.yield = clientResults - // console.log("renderResults outside VM:", renderResults) - // let r - // if (renderResults===undefined) { - // r = "undefined" - // } else { - // r = renderResults.toString() - // } - // console.log("renderResults", renderResults.yield) - // we use stimulus index to ensure correct order and - // avoid race condition - // vm.run("log('renderResults inside VM:', renderResults.yield)") - vm.run("let generator = stimulusGenerator(renderResults.yield); " + + + vm.run("let generator = stimulusGenerator(); " + "let s='uninitialized'; let si = 0;"); } diff --git a/web/src/epl/types.js b/web/src/epl/types.js index 375a68bf..0ae0fbe4 100644 --- a/web/src/epl/types.js +++ b/web/src/epl/types.js @@ -121,7 +121,7 @@ class Image extends Stimulus { fixationPoint, metadata) { super(lifespan, backgroundColor, metadata) this.stimulusType = STIMULUS.IMAGE - // image can be a number (index) for client-side `renders` object + // image can be a number (index) for client-side `renders` object that is stored in indexedDB this.image = image // e.g. {x: 0, y: 0} this.fixationPoint = fixationPoint diff --git a/web/static/js/dispatchers.js b/web/static/js/dispatchers.js index dd6db794..24bca10c 100644 --- a/web/static/js/dispatchers.js +++ b/web/static/js/dispatchers.js @@ -123,29 +123,27 @@ function eyeChartDispatcher(lifespan, letterMatrix, size, padding, color) { function imageDispatcher(lifespan, backgroundColor, image, fixationPoint) { - let img let dont_dispatch = false - if (typeof(image)==="number") { - // renders is a special client-side object - img = renders[image] - } else if (typeof(image)==="string") { - // assume image src (get from server) - img = document.createElement("img") - img.onload = (event) => { - if (fixationPoint===undefined) { - fixationPoint = {x: img.width / 2, y: img.height / 2} - } - store.dispatch(setGraphicsAC([{ - graphicType: GRAPHIC.IMAGE, - image: img, - fixationPoint: fixationPoint, - lifespan: lifespan, - age: 0 - }])) + + let img = new Image() + img.onload = (event) => { + if (fixationPoint===undefined) { + fixationPoint = {x: img.width / 2, y: img.height / 2} } + store.dispatch(setGraphicsAC([{ + graphicType: GRAPHIC.IMAGE, + image: img, + fixationPoint: fixationPoint, + lifespan: lifespan, + age: 0 + }])) + } + if (typeof(image)==="string") { + // assume image src (get from server) img.src = image dont_dispatch = true } else { + // TODO can this be deleted? Or maybe used for older letter rendering? img = image } @@ -395,5 +393,15 @@ function newStimulusDispatcher() { async function queueStimulusDispatcher() { const stimulus = await nextStimulus() const stimulusIndex = stimulus.stimulusIndex + let image + if (typeof(stimulus.value.image)==="number") { + // retrieve image from indexedDB + try { + image = await SimpleIDB.get("render-"+stimulus.value.image) + stimulus.value.image = image + } catch (err) { + console.warn("Failed to get preRender: " + err) + } + } store.dispatch(addStimulusAC(stimulus, stimulusIndex)) } diff --git a/web/static/js/simpleIDB.js b/web/static/js/simpleIDB.js new file mode 100644 index 00000000..9182a067 --- /dev/null +++ b/web/static/js/simpleIDB.js @@ -0,0 +1,146 @@ +// https://medium.com/@xon5/replacing-localstorage-with-indexeddb-2e11a759ff0c + +/** SimpleIDB **/ +SimpleIDB = { + initialize() { + return new Promise((resolve, reject) => { + // This first deletes any database of the same name + let deleteRequest = indexedDB.deleteDatabase('eyeCandyDB') + deleteRequest.onerror = function() { + reject(deleteRequest.error) + } + // Then creates a new one + let request = indexedDB.open('eyeCandyDB') + request.onupgradeneeded = function() { + request.result.createObjectStore('myStore') + resolve() + } + request.onerror = function() { + reject(request.error) + } + }) + }, + + get(key) { + return new Promise((resolve, reject) => { + let openRequest = indexedDB.open('eyeCandyDB') + openRequest.onsuccess = function() { + let db = openRequest.result + let transaction = db.transaction('myStore', 'readonly') + let objectStore = transaction.objectStore('myStore') + let getRequest = objectStore.get(key) + getRequest.onsuccess = function() { + resolve(getRequest.result) + } + getRequest.onerror = function() { + reject(getRequest.error) + } + } + openRequest.onerror = function() { + reject(openRequest.error) + } + }) + }, + + clearAll() { + // delete all objects in store + return new Promise((resolve, reject) => { + let openRequest = indexedDB.open('eyeCandyDB') + openRequest.onsuccess = function() { + let db = openRequest.result + let transaction = db.transaction('myStore', 'readwrite') + let objectStore = transaction.objectStore('myStore') + let clearRequest = objectStore.clear() + clearRequest.onsuccess = function() { + resolve() + } + clearRequest.onerror = function() { + reject(clearRequest.error) + } + } + openRequest.onerror = function() { + reject(openRequest.error) + } + }) + }, + + set(key, value) { + return new Promise((resolve, reject) => { + let openRequest = indexedDB.open('eyeCandyDB') + openRequest.onsuccess = function() { + let db = openRequest.result + let transaction = db.transaction('myStore', 'readwrite') + let objectStore = transaction.objectStore('myStore') + let putRequest = objectStore.put(value, key) + putRequest.onsuccess = function() { + resolve() + } + putRequest.onerror = function() { + reject(putRequest.error) + } + } + openRequest.onerror = function() { + reject(openRequest.error) + } + }) + }, + + remove(key) { + return new Promise((resolve, reject) => { + let openRequest = indexedDB.open('eyeCandyDB') + openRequest.onsuccess = function() { + let db = openRequest.result + let transaction = db.transaction('myStore', 'readwrite') + let objectStore = transaction.objectStore('myStore') + let deleteRequest = objectStore.delete(key) + deleteRequest.onsuccess = function() { + resolve() + } + deleteRequest.onerror = function() { + reject(deleteRequest.error) + } + } + openRequest.onerror = function() { + reject(openRequest.error) + } + }) + } +} + + + + +// The rest is just Vue and UI things vvv +// +// new Vue({ +// el: '#app', +// vuetify: new Vuetify(), +// data: () => ({ +// initialized: false, +// key1: '', +// val1: '', +// error1: null, +// key2: '', +// error2: null, +// databaseOutput: null +// }), +// methods: { +// initialize () { +// this.initialized = true +// SimpleIDB.initialize() +// }, +// insertObject () { +// this.error1 = null +// try { +// let jsonVal = (this.val1.includes('{')) ? JSON.parse(this.val1) : this.val1 +// SimpleIDB.set(this.key1, jsonVal) +// } catch(e) { this.error1 = e.message } +// }, +// removeObject () { +// this.error2 = null +// try { +// SimpleIDB.remove(this.key2) +// } catch(e) { this.error2 = e.message } +// } +// } +// }) diff --git a/web/static/js/stimulus.js b/web/static/js/stimulus.js index 322bbadd..1f4d31d9 100644 --- a/web/static/js/stimulus.js +++ b/web/static/js/stimulus.js @@ -4,7 +4,20 @@ REDUX (GLOBAL STATE) const createStore = Redux.createStore const applyMiddleware = Redux.applyMiddleware - +SimpleIDB.initialize() + +// (function() { +// 'use strict'; +// +// //check for support +// if (!('indexedDB' in window)) { +// console.log('This browser doesn\'t support IndexedDB'); +// return; +// } +// +// var dbPromise = idb.open('eye-candy-db', 1); +// +// })(); /*********************************************** MIDDLEWARE @@ -120,23 +133,40 @@ fetch("/get-sid", { // console.log("COOKIE",document.cookie) +async function loadPreRenderForStimuli(stimulusQueue) { + let stimulus + for (s in stimulusQueue) { + stimulus = stimulusQueue[s] + if (typeof(stimulus.value.image)==="number") { + // retrieve image from indexedDB + try { + + stimulus.value.image = await SimpleIDB.get("render-"+stimulus.value.image) + } catch (err) { + console.warn("Failed to get preRender: " + err) + } + } + } + return stimulusQueue +} - -socket.on("run", (stimulusQueue) => { +socket.on("run", async (stimulusQueue) => { + // TODO load preRender images console.log("socket 'run'") + stimulusQueue = await loadPreRenderForStimuli(stimulusQueue) store.dispatch(setStimulusQueueAC(stimulusQueue)) store.dispatch(setStatusAC(STATUS.STARTED)) }) -socket.on("video", (stimulusQueue) => { +socket.on("video", async (stimulusQueue) => { + // TODO load preRender images console.log("socket 'video'") + stimulusQueue = await loadPreRenderForStimuli(stimulusQueue) store.dispatch(setStimulusQueueAC(stimulusQueue)) store.dispatch(setStatusAC(STATUS.VIDEO)) }) -let renders - -socket.on("pre-render", (preRender) => { +socket.on("pre-render", async (preRender) => { // TODO dangerous, insecure // but hey, it's science! // also, this is client side so not *so* bad.. @@ -146,12 +176,25 @@ socket.on("pre-render", (preRender) => { eval(preRender.func) console.log("finished preRender func eval, about to render...") - let renderResults = preRenderFunc(...preRender.args) + let renderGenerator = preRenderFunc(...preRender.args) + let renderItem = renderGenerator.next() + let render = renderItem.value + let n = 0 + while ( renderItem.done===false) { + // note: assumes that render is a canvas + // localStorage.setItem("render-"+n, render.toDataURL()) + try { + await SimpleIDB.set("render-"+n, render.toDataURL()) + } catch (e) { + console.warn("SimpleIDB failed to set render-"+n) + } + // localStorage.setItem("render-"+n, JSON.stringify(render)) + renderItem = renderGenerator.next() + render = renderItem.value + n++ + } console.log("finished render") - renders = renderResults.renders - - socket.emit("renderResults", {renderResults: renderResults.yield, - sid: localStorage.getItem('sid')}) + socket.emit("renderResults", {sid: localStorage.getItem('sid')}) }) @@ -159,8 +202,19 @@ socket.on("reset", () => { console.log("socket 'reset'") // TODO next line causes TypeError: document.querySelector(...) is null on reset store.dispatch(resetAC()) - renders = undefined + // remove preRenders + SimpleIDB.clearAll() + // Object.entries(localStorage).map( + // Object.entries(localStorage).map( + // x => x[0] + // ).filter( + // x => x.substring(0,7)=="render-" + // ).map( + // x => SimpleIDB.remove(x).catch(e => { + // console.warn("SimpleIDB failed to delete " + x) + // })) + // x => localStorage.removeItem(x)) }) socket.on("target", () => { diff --git a/web/static/stimulus.html b/web/static/stimulus.html index 00e39cc7..24c33376 100644 --- a/web/static/stimulus.html +++ b/web/static/stimulus.html @@ -5,6 +5,7 @@ +