Browse Source

Merge branch 'master' of git.indy.io:indy/seni

master
Inderjit Gill 1 month ago
parent
commit
004cffed4f
16 changed files with 1113 additions and 427 deletions
  1. +11
    -0
      client-ts/build.bat
  2. +332
    -0
      client-ts/src/Controller.ts
  3. +65
    -0
      client-ts/src/History.ts
  4. +168
    -0
      client-ts/src/Job.ts
  5. +12
    -0
      client-ts/src/JobType.ts
  6. +27
    -0
      client-ts/src/Log.ts
  7. +24
    -0
      client-ts/src/SeniMode.ts
  8. +127
    -0
      client-ts/src/Timer.ts
  9. +84
    -0
      client-ts/src/misc.js
  10. +111
    -0
      client-ts/src/stuff.js
  11. +68
    -48
      client/ts/src/Matrix.ts
  12. +23
    -182
      client/ts/src/index.js
  13. +4
    -152
      client/ts/src/sketch.js
  14. +19
    -9
      client/ts/tsconfig-main.json
  15. +14
    -5
      client/ts/tsconfig-sketch.json
  16. +24
    -31
      www/worker.js

+ 11
- 0
client-ts/build.bat View File

@@ -0,0 +1,11 @@
@echo off

setlocal EnableDelayedExpansion

rem builds index.js by default

if "%1" == "sketch" (
tsc --project tsconfig-sketch.json
) else (
tsc --project tsconfig-main.json
)

+ 332
- 0
client-ts/src/Controller.ts View File

@@ -0,0 +1,332 @@
/*
* Seni
* Copyright (C) 2019 Inderjit Gill <email@indy.io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

///<reference path='Job.ts'/>
///<reference path='SeniMode.ts'/>

enum Action {
SetMode,
SetGenotype,
SetScript,
SetScriptId,
SetSelectedIndices,
InitialGeneration,
NextGeneration,
ShuffleGeneration,
SetState,
SetGalleryItems,
GalleryOldestToDisplay,
}

class State {
public highResolution: [number, number];
public populationSize: number;
public mutationRate: number;

public currentMode: SeniMode;

public galleryLoaded: boolean;
public galleryOldestToDisplay: number;
public galleryItems: any;
public galleryDisplaySize: number; // the number of gallery sketches to display everytime 'load more' is clicked

public previouslySelectedGenotypes: any;
public selectedIndices: Array<number>;
public scriptId: number | undefined;
public script: string | undefined;
public genotypes: Array<any>;
public traits: Array<any>;

public genotype: any;

static createInitialState(): State {
let s = new this();

s.highResolution = [2048, 2048];
s.populationSize = 24;
s.mutationRate = 0.1;
s.currentMode = SeniMode.gallery;
s.galleryLoaded = false;
s.galleryOldestToDisplay = 9999;
s.galleryItems = {};
s.galleryDisplaySize = 20;
s.previouslySelectedGenotypes = [];
s.selectedIndices = [];
s.scriptId = undefined;
s.script = undefined;
s.genotypes = [];
s.traits = [];
s.genotype = undefined;

return s;
}

clone(): State {
let s = new State();

s.highResolution = this.highResolution;
s.populationSize = this.populationSize;
s.mutationRate = this.mutationRate;
s.currentMode = this.currentMode;
s.galleryLoaded = this.galleryLoaded;
s.galleryOldestToDisplay = this.galleryOldestToDisplay;
s.galleryItems = this.galleryItems;
s.galleryDisplaySize = this.galleryDisplaySize;
s.previouslySelectedGenotypes = this.previouslySelectedGenotypes;
s.selectedIndices = this.selectedIndices;
s.scriptId = this.scriptId;
s.script = this.script;
s.genotypes = this.genotypes;
s.traits = this.traits;
s.genotype = this.genotype;

return s;
}

constructor() {
}
}

class Controller {
currentState: State;

constructor(initialState: State) {
this.currentState = initialState;
}

async applySetMode(state: State, { mode }: { mode: SeniMode }) { // note: this doesn't need to be async?
const newState = state.clone();
newState.currentMode = mode;

this.currentState = newState;
return this.currentState;
}

async applySetGenotype(state: State, { genotype }: { genotype: Array<any> }) {
const newState = state.clone();
newState.genotype = genotype;

this.currentState = newState;
return this.currentState;
}

async applySetScript(state: State, { script }: { script: string | undefined }) {

const newState = state.clone();
newState.script = script;

const { validTraits, traits } = await Job.request(JobType.jobBuildTraits, {
script: newState.script
}, undefined);

if (validTraits) {
newState.traits = traits;
} else {
newState.traits = [];
}

this.currentState = newState;
return this.currentState;
}

async applySetScriptId(state: State, { id }: { id: number | undefined }) { // todo: is this undefined required?
const newState = state.clone();
newState.scriptId = id;

this.currentState = newState;
return this.currentState;
}

async applySetSelectedIndices(state: State, { selectedIndices }: { selectedIndices: Array<number> }) {
const newState = state.clone();
newState.selectedIndices = selectedIndices || [];

this.currentState = newState;
return this.currentState;
}

// todo: should populationSize be passed in the action?
async applyInitialGeneration(state: State) {
const newState = state.clone();
let { genotypes } = await Job.request(JobType.jobInitialGeneration, {
traits: newState.traits,
populationSize: newState.populationSize
}, undefined);

newState.genotypes = genotypes;
newState.previouslySelectedGenotypes = [];
newState.selectedIndices = [];

this.currentState = newState;
return this.currentState;
}

async applyGalleryOldestToDisplay(state: State, { oldestId }: { oldestId: number }) {
const newState = state.clone();
newState.galleryOldestToDisplay = oldestId;

this.currentState = newState;
return this.currentState;
}

async applySetGalleryItems(state: State, { galleryItems }: { galleryItems: any }) {
const newState = state.clone();

newState.galleryItems = {};
galleryItems.forEach((item: any) => {
newState.galleryItems[item.id] = item;
});
if (galleryItems.length === 0) {
console.error("galleryItems is empty?");
}

newState.galleryLoaded = true;
newState.galleryOldestToDisplay = galleryItems[0].id;

this.currentState = newState;
return this.currentState;
}

async applyShuffleGeneration(state: State, { rng }: { rng: any }) {
const newState = state.clone();
const prev = newState.previouslySelectedGenotypes;

if (prev.length === 0) {
const s = await this.applyInitialGeneration(newState);

this.currentState = s;
return this.currentState;
} else {
const { genotypes } = await Job.request(JobType.jobNewGeneration, {
genotypes: prev,
populationSize: newState.populationSize,
traits: newState.traits,
mutationRate: newState.mutationRate,
rng
}, undefined);

newState.genotypes = genotypes;
newState.selectedIndices = [];

this.currentState = newState;
return this.currentState;
}
}

async applyNextGeneration(state: State, { rng }: { rng: any }) {
const newState = state.clone();
const pg = newState.genotypes;
const selectedIndices = newState.selectedIndices;
const selectedGenos = [];

for (let i = 0; i < selectedIndices.length; i++) {
selectedGenos.push(pg[selectedIndices[i]]);
}

const { genotypes } = await Job.request(JobType.jobNewGeneration, {
genotypes: selectedGenos,
populationSize: newState.populationSize,
traits: newState.traits,
mutationRate: newState.mutationRate,
rng
}, undefined);

const previouslySelectedGenotypes = genotypes.slice(0, selectedIndices.length);

newState.genotypes = genotypes;
newState.previouslySelectedGenotypes = previouslySelectedGenotypes;
newState.selectedIndices = [];

this.currentState = newState;
return this.currentState;
}

async applySetState(newState: State) {
this.currentState = newState;
return this.currentState;
}

logMode(mode: SeniMode) {
let name = '';
switch (mode) {
case SeniMode.gallery:
name = 'gallery';
break;
case SeniMode.edit:
name = 'edit';
break;
case SeniMode.evolve:
name = 'evolve';
break;
default:
name = 'unknown';
break;
}
Log.log(`${Action.SetMode}: ${name}`);
}

reducer(state: State, action: any) {
switch (action.__type) {
case Action.SetMode:
if (Log.logToConsole) {
this.logMode(action.mode);
}
return this.applySetMode(state, action);
case Action.SetGenotype:
// SET_GENOTYPE is only used during the download dialog rendering
// when the render button is clicked on an image in the evolve gallery
//
return this.applySetGenotype(state, action);
case Action.SetScript:
return this.applySetScript(state, action);
case Action.SetScriptId:
return this.applySetScriptId(state, action);
case Action.SetSelectedIndices:
return this.applySetSelectedIndices(state, action);
case Action.InitialGeneration:
return this.applyInitialGeneration(state);
case Action.NextGeneration:
return this.applyNextGeneration(state, action);
case Action.ShuffleGeneration:
return this.applyShuffleGeneration(state, action);
case Action.SetState:
Log.log(`${Action.SetState}: ${action.state}`);
return this.applySetState(action.state);
case Action.GalleryOldestToDisplay:
return this.applyGalleryOldestToDisplay(state, action);
case Action.SetGalleryItems:
return this.applySetGalleryItems(state, action);
default:
return this.applySetState(state);
}
}

getState() {
return this.currentState;
}

dispatch(action: Action, data: any) {
if (data === undefined) {
data = {};
}
data.__type = action;

Log.log(`dispatch: action = ${data.__type}`);
return this.reducer(this.currentState, data);
}
}

+ 65
- 0
client-ts/src/History.ts View File

@@ -0,0 +1,65 @@
/*
* Seni
* Copyright (C) 2019 Inderjit Gill <email@indy.io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

///<reference path='Log.ts'/>
///<reference path='Controller.ts'/>

namespace SeniHistory {
function senModeAsString(state: State): string {
const mode = state.currentMode;

switch (mode) {
case SeniMode.gallery:
return 'gallery';
case SeniMode.edit:
if (state.scriptId) {
return state.scriptId.toString();
} else {
return "Error: currentMode is edit but there is no state.scriptId?";
}
case SeniMode.evolve:
return 'evolve';
default:
return 'error unknown SeniMode value';
}
}

function buildState(appState: State): [State, string] {
const state = appState;
const uri = `#${senModeAsString(state)}`;

return [state, uri];
}

export function pushState(appState: State) {
const [state, uri] = buildState(appState);
Log.log('historyPushState');
history.pushState(state, "", uri);
}

export function replaceState(appState: State) {
const [state, uri] = buildState(appState);
Log.log('historyReplace');
history.replaceState(state, "", uri);
}

export function restoreState(state: State) {
Log.log('historyRestore');
return state;
}
}

+ 168
- 0
client-ts/src/Job.ts View File

@@ -0,0 +1,168 @@
/*
* Seni
* Copyright (C) 2019 Inderjit Gill <email@indy.io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

///<reference path='Log.ts'/>
///<reference path='JobType.ts'/>

namespace Job {
let numWorkers: number = 0;
const promiseWorkers: Array<PromiseWorker> = [];

function findAvailableWorker(): Promise<PromiseWorker> {
return new Promise((resolve, _reject) => {
setTimeout(function go() {
for (let i=0;i<numWorkers;i++) {
if (promiseWorkers[i].isInitialised() === true &&
promiseWorkers[i].isWorking() === false) {
resolve(promiseWorkers[i].employ());
return;
}
}
// todo?: reject if waiting too long?
setTimeout(go, 100);
});
});
}

export async function request(type: JobType, data: any, worker_id: number | undefined) {
try {
let worker = undefined;
if (worker_id === undefined) {
worker = await findAvailableWorker();
Log.log(`assigning ${type} to worker ${worker.getId()}`);
} else {
worker = promiseWorkers[worker_id];
Log.log(`explicitly assigning ${type} to worker ${worker.getId()}`);
}

const result = await worker.postMessage(type, data);
Log.log(`result ${type} id:${worker.getId()}`);

if(!data.__retain) {
worker.release();
}

result.__worker_id = worker.getId();

return result;
} catch (error) {
// handle error
console.error(`worker (job:${type}): error of ${error}`);
return undefined; // ???
}
}

export function setup(numWorkersParam: number, path: string) {
numWorkers = numWorkersParam;

Log.log(`workers::path = ${path}`);
Log.log(`workers::numWorkers = ${numWorkers}`);

for (let i = 0; i < numWorkers; i++) {
promiseWorkers[i] = new PromiseWorker(i, path);
}
}
}

class PromiseWorker {
worker: Worker;
id: number;
initialised: boolean;
working: boolean;
reject: any;
resolve: any;

constructor(id: number, workerUrl: string) {
const self = this;

// <2019-04-13 Sat>
// would be good to use module syntax in the worker.js file.
// this would enable a more modern way of instantiating the wasm module
// see https://rustwasm.github.io/docs/wasm-bindgen/examples/without-a-bundler.html?highlight=export,memory#without-a-bundler
//
// This should be enabled with:
// this.worker = new Worker(workerUrl, {type:'module'});
//
// unfortunately there is a bug in Chromium preventing this:
// https://bugs.chromium.org/p/chromium/issues/detail?id=680046
// original info from:
// https://www.codedread.com/blog/archives/2017/10/19/web-workers-can-be-es6-modules-too/

this.worker = new Worker(workerUrl);
this.id = id;
this.initialised = false; // true when the worker has loaded it's wasm file
this.working = false;
this.reject = undefined;
this.resolve = undefined;

this.worker.addEventListener('message', event => {

const [status, result] = event.data;

if (status.systemInitialised) {
self.initialised = true;
Log.log(`worker ${self.id} initialised`);
return;
}

if (status.error) {
self.reject(new Error(status.error.message));
} else {
self.resolve(result);
}
});
}

postMessage(type: JobType, data: any): Promise<any> {
const self = this;

return new Promise((resolve, reject) => {
self.resolve = resolve;
self.reject = reject;

if (type === JobType.jobRender_2_ReceiveBitmapData) {
// ImageData is not a transferrable type
self.worker.postMessage({ type, data });
} else {
self.worker.postMessage({ type, data });
}
});
}

employ(): PromiseWorker {
this.working = true;
return this;
}

release(): PromiseWorker {
this.working = false;
return this;
}

isInitialised(): boolean {
return this.initialised;
}

isWorking(): boolean {
return this.working;
}

getId(): number {
return this.id;
}
}

+ 12
- 0
client-ts/src/JobType.ts View File

@@ -0,0 +1,12 @@
enum JobType {
jobRender_1_Compile,
jobRender_2_ReceiveBitmapData,
jobRender_3_RenderPackets,
jobUnparse,
jobBuildTraits,
jobInitialGeneration,
jobNewGeneration,
jobGenerateHelp,
jobSingleGenotypeFromSeed,
jobSimplifyScript,
}

+ 27
- 0
client-ts/src/Log.ts View File

@@ -0,0 +1,27 @@
/*
* Seni
* Copyright (C) 2019 Inderjit Gill <email@indy.io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

namespace Log {
export let logToConsole: boolean = true;

export function log(msg: string) {
if (logToConsole) {
console.log(msg);
}
}
}

+ 24
- 0
client-ts/src/SeniMode.ts View File

@@ -0,0 +1,24 @@
/*
* Seni
* Copyright (C) 2019 Inderjit Gill <email@indy.io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

enum SeniMode {
gallery = 0,
edit,
evolve,
numSeniModes,
}

+ 127
- 0
client-ts/src/Timer.ts View File

@@ -0,0 +1,127 @@
/*
* Seni
* Copyright (C) 2019 Inderjit Gill <email@indy.io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

///<reference path='Log.ts'/>

namespace Timer {

const db:any = {};
const printPrecision = 2;

export class DbEntry {
id: string;
num: number;
sum: number;
last: number;
min: number;
max: number;

constructor(id: string) {
this.id = id;
this.num = 0;
this.sum = 0;
this.last = 0;
this.min = 100000;
this.max = 0;
}
}

export class Stats {
current: number;
average: number;
min: number;
max: number;
num: number;

constructor(entry: DbEntry) {
this.current = entry.last;
this.average = (entry.sum / entry.num);
this.min = entry.min;
this.max = entry.max;
this.num = entry.num;
}
}

export function getStats(entry: DbEntry): Stats | null {
if (entry.num === 0) {
return null;
}

return new Stats(entry);
}


export function addTiming(entry: DbEntry, duration: number): DbEntry {
entry.num += 1;
entry.sum += duration;
entry.last = duration;
if (duration < entry.min) {
entry.min = duration;
}
if (duration > entry.max) {
entry.max = duration;
}
return entry;
}

export function useDBEntry(id: string): DbEntry {
if (!db[id]) {
db[id] = new DbEntry(id);
}

return db[id];
}

export function startTiming(): (arg0: string) => void {
if (Log.logToConsole) {
const before = performance.now();
// return the 'stop' function
return (id: string) => {
const entry = useDBEntry(id);

const after = performance.now();
const duration = after - before;

addTiming(entry, duration);

const stats = getStats(entry);

if (stats) {
const eid = entry.id;
const cur = stats.current.toFixed(printPrecision);
const avg = stats.average.toFixed(printPrecision);
const min = stats.min.toFixed(printPrecision);
const max = stats.max.toFixed(printPrecision);
const num = stats.num;

const msg1 = `${eid}: ${cur}ms `;
const msg2 = `(Mean: ${avg}, Min: ${min}, Max: ${max} N:${num})`;

Log.log(msg1 + msg2);
}
};
} else {
// do nothing
return (_id: string) => {};
}
}

export function getTimingEntry(id: string): DbEntry {
return db[id];
}
}

+ 84
- 0
client-ts/src/misc.js View File

@@ -0,0 +1,84 @@
async function getJSON(url) {
const res = await fetch(url);
const json = await res.json();
return json;
}


function getRequiredElement(id) {
const element = document.getElementById(id);
if (!element) {
console.error(`required element ${id} not found in dom`);
}
return element;
}

function addClass(id, clss) {
const e = getRequiredElement(id);
e.classList.add(clss);
}

function removeClass(id, clss) {
const e = getRequiredElement(id);
e.classList.remove(clss);
}

function setOpacity(id, opacity) {
const e = getRequiredElement(id);
e.style.opacity = opacity;
}

function getURIParameters() {
const argPairs = window.location.search.substring(1).split("&");

return argPairs.reduce((acc, kv) => {
let [key, value] = kv.split("=");
if (key === URI_SEED) {
acc[key] = parseInt(value, 10);
} else {
acc[key] = value;
}

return acc;
}, {});
}

function addClickEvent(id, fn) {
const element = document.getElementById(id);

if (element) {
element.addEventListener('click', fn);
} else {
console.error('cannot addClickEvent for', id);
}
}

function countDigits(num) {
if(num < 10) {
return 1;
} else if (num < 100) {
return 2;
} else if (num < 1000) {
return 3;
} else if (num < 10000) {
return 4;
} else {
console.error(`countDigits given an insanely large number: ${num}`);
return 5;
}
}

// https://developer.mozilla.org/en-US/docs/Web/Events/resize
function throttle(type, name, obj) {
const obj2 = obj || window;
let running = false;
const func = () => {
if (running) { return; }
running = true;
requestAnimationFrame(() => {
obj2.dispatchEvent(new CustomEvent(name));
running = false;
});
};
obj2.addEventListener(type, func);
}

+ 111
- 0
client-ts/src/stuff.js View File

@@ -0,0 +1,111 @@
// based on code from:
// https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e
function sequentialPromises(funcs) {
return funcs.reduce((promise, func) =>
promise.then(result => func().then(Array.prototype.concat.bind(result))),
Promise.resolve([]));
}

// todo: is this the best way of getting image data for a web worker?
// is there a way for the webworker to do this without having to interact with the DOM?
// note: don't call this on a sequence of bitmaps
function loadBitmapImageData(url) {
return new Promise(function(resolve, reject) {
const element = document.getElementById('bitmap-canvas');
const context = element.getContext('2d');
const img = new Image();

img.onload = () => {
element.width = img.width;
element.height = img.height;

context.drawImage(img, 0, 0);

const imageData = context.getImageData(0, 0, element.width, element.height);

resolve(imageData);
};
img.onerror = () => {
reject();
};

img.src = normalize_bitmap_url(url);
});
}

function normalize_bitmap_url(url) {
const re = /^[\w-/]+.png/;

if (url.match(re)) {
// requesting a bitmap just by filename, so get it from /img/immutable/
return "img/immutable/" + url;
} else {
// change nothing, try and fetch the url
return url;
}
}

function sleepy(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, timeout);
});
}

async function renderJob(parameters) {
// 1. compile the program in a web worker
// 2. (retain the id for this worker)
// 3. after compilation, the worker will return a list of bitmaps that are
// required by the program and are not in the web worker's bitmap-cache
// 4. sequentially load in the bitmaps and send their data to the worker
// 5. can now request a render which will return the render packets

// request a compile job but make sure to retain the worker as it will be performing the rendering
//
parameters.__retain = true;
const { bitmapsToTransfer, __worker_id } = await Job.request(JobType.jobRender_1_Compile, parameters);

// convert each bitmap path to a function that returns a promise
//
const bitmap_loading_funcs = bitmapsToTransfer.map(filename => async () => {
Log.log(`worker ${__worker_id}: bitmap request: ${filename}`);

const imageData = await loadBitmapImageData(filename);
// make an explicit job request to the same worker
return Job.request(JobType.jobRender_2_ReceiveBitmapData, { filename, imageData, __retain: true }, __worker_id);
});

// seqentially execute the promises that load in bitmaps and send the bitmap data to a particular worker
//
await sequentialPromises(bitmap_loading_funcs);

// now make an explicit job request to the same worker that has recieved the bitmap data
// note: no __retain as we want the worker to be returned to the available pool
const renderPacketsResult = await Job.request(JobType.jobRender_3_RenderPackets, {}, __worker_id);

return renderPacketsResult;
}

function compatibilityHacks() {
// Safari doesn't have Number.parseInt (yet)
// Safari is the new IE
if (Number.parseInt === undefined) {
Number.parseInt = parseInt;
}
}

async function loadShaders(scriptUrls) {
const fetchPromises = scriptUrls.map(s => fetch(s));
const responses = await Promise.all(fetchPromises);

const textPromises = responses.map(r => r.text());
const shaders = await Promise.all(textPromises);

const res = {};
for (const [i, url] of scriptUrls.entries()) {
res[url] = shaders[i];
}

return res;
}

+ 68
- 48
client/ts/src/Matrix.ts View File

@@ -1,54 +1,74 @@
// --------------------------------------------------------------------------------
// matrix
/*
* Seni
* Copyright (C) 2019 Inderjit Gill <email@indy.io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

function create() {
const out = new Float32Array(16);
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = 1;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 1;
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
namespace Matrix {
export function create(): Float32Array {

return out;
}
const out = new Float32Array(16);
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = 1;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 1;
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;

function ortho(out, left, right, bottom, top, near, far) {
const lr = 1 / (left - right);
const bt = 1 / (bottom - top);
const nf = 1 / (near - far);
return out;
}

out[0] = -2 * lr;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = -2 * bt;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 2 * nf;
out[11] = 0;
out[12] = (left + right) * lr;
out[13] = (top + bottom) * bt;
out[14] = (far + near) * nf;
out[15] = 1;
export function ortho(out: Float32Array,
left: number,
right: number,
bottom: number,
top: number,
near: number,
far: number): Float32Array {

return out;
}
const lr = 1 / (left - right);
const bt = 1 / (bottom - top);
const nf = 1 / (near - far);

const Matrix = {
create,
ortho,
};
out[0] = -2 * lr;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = -2 * bt;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 2 * nf;
out[11] = 0;
out[12] = (left + right) * lr;
out[13] = (top + bottom) * bt;
out[14] = (far + near) * nf;
out[15] = 1;

return out;
}
}

+ 23
- 182
client/ts/src/index.js View File

@@ -28,11 +28,6 @@ const gState = {
let gUI = {};
let gGLRenderer = undefined;

async function getJSON(url) {
const res = await fetch(url);
const json = await res.json();
return json;
}

function getScriptFromEditor() {
return gUI.editor.getValue();
@@ -152,7 +147,7 @@ function updateSelectionUI(state) {
}

async function renderGeometryBuffers(meta, memory, buffers, imageElement, w, h, sectionDim, section) {
const stopFn = startTiming();
const stopFn = Timer.startTiming();

await gGLRenderer.renderGeometryToTexture(meta, gState.render_texture_width, gState.render_texture_height, memory, buffers, sectionDim, section);
gGLRenderer.renderTextureToScreen(meta, w, h);
@@ -170,7 +165,7 @@ async function renderGeneration(state) {
let hackTitle = "hackTitle";
const promises = [];

const stopFn = startTiming();
const stopFn = Timer.startTiming();

const dim = phenotypes[0].phenotypeElement.clientWidth;

@@ -196,7 +191,7 @@ async function renderGeneration(state) {
// invoked when the evolve screen is displayed after the edit screen
async function setupEvolveUI(controller) {
showPhenotypeSpinners(controller.getState());
const state = await controller.dispatch(actionInitialGeneration);
const state = await controller.dispatch(Action.InitialGeneration);
// render the phenotypes
updateSelectionUI(state);
await renderGeneration(state);
@@ -211,64 +206,9 @@ function showScriptInEditor(state) {
editor.refresh();
}

// based on code from:
// https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e
function sequentialPromises(funcs) {
return funcs.reduce((promise, func) =>
promise.then(result => func().then(Array.prototype.concat.bind(result))),
Promise.resolve([]));
}

// todo: is this the best way of getting image data for a web worker?
// is there a way for the webworker to do this without having to interact with the DOM?
// note: don't call this on a sequence of bitmaps
function loadBitmapImageData(url) {
return new Promise((resolve, reject) => {
const element = document.getElementById('bitmap-canvas');
const context = element.getContext('2d');
const img = new Image();

img.onload = () => {
element.width = img.width;
element.height = img.height;

context.drawImage(img, 0, 0);

const imageData = context.getImageData(0, 0, element.width, element.height);

resolve(imageData);
};
img.onerror = () => {
reject();
};

img.src = normalize_bitmap_url(url);
});
}

function sleepy(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, timeout);
});
}

function normalize_bitmap_url(url) {
const re = /^[\w-/]+.png/;

if (url.match(re)) {
// requesting a bitmap just by filename, so get it from /img/immutable/
return "img/immutable/" + url;
} else {
// change nothing, try and fetch the url
return url;
}
}

// note: this is returning a Promise
async function renderScript(parameters, imageElement) {
const stopFn = startTiming();
const stopFn = Timer.startTiming();

let width = parameters.assumeWidth ? parameters.assumeWidth : imageElement.clientWidth;
let height = parameters.assumeHeight ? parameters.assumeHeight : imageElement.clientHeight;
@@ -283,42 +223,6 @@ async function renderScript(parameters, imageElement) {
}
}

async function renderJob(parameters) {
// 1. compile the program in a web worker
// 2. (retain the id for this worker)
// 3. after compilation, the worker will return:
// a: A list of textures to load onto the GPU
// b: A list of bitmaps that are required by the program
// and are not in the web worker's bitmap-cache.
// 4. sequentially load in the bitmaps and send their data to the worker
// 5. can now request a render which will return the render packets

// request a compile job but make sure to retain the worker as it will be performing the rendering
//
parameters.__retain = true;
const { bitmapsToTransfer, __worker_id } = await Job.request(jobRender_1_Compile, parameters);

// convert each bitmap path to a function that returns a promise
//
const bitmap_loading_funcs = bitmapsToTransfer.map(filename => async () => {
log(`worker ${__worker_id}: bitmap request: ${filename}`);

const imageData = await loadBitmapImageData(filename);
// make an explicit job request to the same worker
return Job.request(jobRender_2_ReceiveBitmapData, { filename, imageData, __retain: true }, __worker_id);
});

// seqentially execute the promises that load in bitmaps and send the bitmap data to a particular worker
//
await sequentialPromises(bitmap_loading_funcs);

// now make an explicit job request to the same worker that has recieved the bitmap data
// note: no __retain as we want the worker to be returned to the available pool
const renderPacketsResult = await Job.request(jobRender_3_RenderPackets, {}, __worker_id);

return renderPacketsResult;
}

async function renderEditorScript(state) {
const imageElement = gUI.renderImage;
await renderScript({
@@ -365,7 +269,7 @@ async function ensureMode(controller, mode) {
removePhenotypeSpinners(currentState);
}

const state = await controller.dispatch(actionSetMode, { mode });
const state = await controller.dispatch(Action.SetMode, { mode });
SeniHistory.pushState(state);

if (mode === SeniMode.evolve) {
@@ -380,16 +284,6 @@ async function ensureMode(controller, mode) {
}
}

function addClickEvent(id, fn) {
const element = document.getElementById(id);

if (element) {
element.addEventListener('click', fn);
} else {
console.error('cannot addClickEvent for', id);
}
}

function getIdNumberFromDom(element, regexp) {
let e = element;
while (e) {
@@ -429,7 +323,7 @@ function downloadDialogHide() {
// in a ww and updates the controller again
//
function setScript(controller, script) {
return controller.dispatch(actionSetScript, { script });
return controller.dispatch(Action.SetScript, { script });
}

async function showEditFromEvolve(controller, element) {
@@ -438,12 +332,12 @@ async function showEditFromEvolve(controller, element) {
if (index !== -1) {
const state = controller.getState();
const genotypes = state.genotypes;
const { script } = await Job.request(jobUnparse, {
const { script } = await Job.request(JobType.jobUnparse, {
script: state.script,
genotype: genotypes[index]
});

await controller.dispatch(actionSetScript, { script });
await controller.dispatch(Action.SetScript, { script });
await ensureMode(controller, SeniMode.edit);
}
}
@@ -462,7 +356,7 @@ async function onNextGen(controller) {
}
}

let state = await controller.dispatch(actionSetSelectedIndices, { selectedIndices });
let state = await controller.dispatch(Action.SetSelectedIndices, { selectedIndices });
if (selectedIndices.length === 0) {
// no phenotypes were selected
return;
@@ -473,7 +367,7 @@ async function onNextGen(controller) {

showPhenotypeSpinners(state);

state = await controller.dispatch(actionNextGeneration, { rng: 4242 });
state = await controller.dispatch(Action.NextGeneration, { rng: 4242 });
if (state === undefined) {
return;
}
@@ -519,8 +413,8 @@ async function loadScriptWithId(controller, id) {
const response = await fetch(`gallery/${id}`);
const script = await response.text();

await controller.dispatch(actionSetScript, { script });
await controller.dispatch(actionSetScriptId, { id });
await controller.dispatch(Action.SetScript, { script });
await controller.dispatch(Action.SetScriptId, { id });
await ensureMode(controller, SeniMode.edit);
}

@@ -547,7 +441,7 @@ function resizeContainers() {
async function evalMainScript(controller) {
try {
const script = getScriptFromEditor();
const state = await controller.dispatch(actionSetScript, { script });
const state = await controller.dispatch(Action.SetScript, { script });
await renderEditorScript(state);
} catch (error) {
console.error(`evalMainScript error: ${error}`);
@@ -583,21 +477,6 @@ function createEditor(controller, editorTextArea) {
});
}

function countDigits(num) {
if(num < 10) {
return 1;
} else if (num < 100) {
return 2;
} else if (num < 1000) {
return 3;
} else if (num < 10000) {
return 4;
} else {
console.error(`countDigits given an insanely large number: ${num}`);
return 5;
}
}

function filenameForPng(filename, image_dim, i) {
// remove .png if there is any
let name = filename.match(/\.png$/) ? filename.slice(0, -4) : filename;
@@ -664,7 +543,7 @@ function setupUI(controller) {
event.preventDefault();
// get the latest script from the editor
const script = getScriptFromEditor();
const state = await controller.dispatch(actionSetScript, { script });
const state = await controller.dispatch(Action.SetScript, { script });
SeniHistory.replaceState(state);
await ensureMode(controller, SeniMode.evolve);
} catch (error) {
@@ -683,7 +562,7 @@ function setupUI(controller) {
event.preventDefault();
showPhenotypeSpinners(controller.getState());
const rng = Math.random() * 9999;
const state = await controller.dispatch(actionShuffleGeneration, { rng });
const state = await controller.dispatch(Action.ShuffleGeneration, { rng });
updateSelectionUI(state);
await renderGeneration(state);
} catch (error) {
@@ -715,7 +594,7 @@ function setupUI(controller) {
const genotypes = controller.getState().genotypes;
const genotype = genotypes[index];

await controller.dispatch(actionSetGenotype, { genotype });
await controller.dispatch(Action.SetGenotype, { genotype });

downloadDialogShow();
}
@@ -753,7 +632,7 @@ function setupUI(controller) {

loader.classList.remove('hidden');

const stopFn = startTiming();
const stopFn = Timer.startTiming();

const { meta, memory, buffers } = await renderJob({
script: state.script,
@@ -774,7 +653,7 @@ function setupUI(controller) {
loader.classList.add('hidden');

// todo: is this the best place to reset the genotype?
await controller.dispatch(actionSetGenotype, { genotype: undefined });
await controller.dispatch(Action.SetGenotype, { genotype: undefined });
});

addClickEvent('download-dialog-button-close', event => {
@@ -824,7 +703,7 @@ function setupUI(controller) {
try {
if (event.state) {
const savedState = SeniHistory.restoreState(event.state);
const state = await controller.dispatch('SET_STATE', { state: savedState });
const state = await controller.dispatch(Action.SetState, { state: savedState });
await updateUI(state);
if (state.currentMode === SeniMode.evolve) {
await restoreEvolveUI(controller);
@@ -836,7 +715,7 @@ function setupUI(controller) {
}
} catch (error) {
// handle error
console.error(`${actionSetState}: error of ${error}`);
console.error(`${Action.SetState}: error of ${error}`);
}
});

@@ -846,7 +725,7 @@ function setupUI(controller) {
async function getGallery(controller) {
const galleryItems = await getJSON('gallery');

await controller.dispatch(actionSetGalleryItems, { galleryItems });
await controller.dispatch(Action.SetGalleryItems, { galleryItems });
await createGalleryDisplayChunk(controller);
}

@@ -942,7 +821,7 @@ async function createGalleryDisplayChunk(controller) {
}

await Promise.all(promises);
await controller.dispatch(actionGalleryOldestToDisplay, { oldestId: least});
await controller.dispatch(Action.GalleryOldestToDisplay, { oldestId: least});
}

function allocateWorkers(state) {
@@ -956,21 +835,6 @@ function allocateWorkers(state) {
Job.setup(numWorkers, 'worker.js');
}

// https://developer.mozilla.org/en-US/docs/Web/Events/resize
function throttle(type, name, obj) {
const obj2 = obj || window;
let running = false;
const func = () => {
if (running) { return; }
running = true;
requestAnimationFrame(() => {
obj2.dispatchEvent(new CustomEvent(name));
running = false;
});
};
obj2.addEventListener(type, func);
}

function setupResizeability() {
// define a version of the resize event which fires less frequently
throttle('resize', 'throttledResize');
@@ -982,31 +846,8 @@ function setupResizeability() {
resizeContainers();
}

function compatibilityHacks() {
// Safari doesn't have Number.parseInt (yet)
// Safari is the new IE
if (Number.parseInt === undefined) {
Number.parseInt = parseInt;
}
}

async function loadShaders(scriptUrls) {
const fetchPromises = scriptUrls.map(s => fetch(s));
const responses = await Promise.all(fetchPromises);

const textPromises = responses.map(r => r.text());
const shaders = await Promise.all(textPromises);

const res = {};
for (const [i, url] of scriptUrls.entries()) {
res[url] = shaders[i];
}

return res;
}

async function main() {
const state = createInitialState();
const state = State.createInitialState();
const controller = new Controller(state);

allocateWorkers(state);

+ 4
- 152
client/ts/src/sketch.js View File

@@ -103,52 +103,6 @@ async function renderGeometryBuffers(meta, memory, buffers, destWidth, destHeigh
await displayOnImageElements(display);
}

// based on code from:
// https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e
function sequentialPromises(funcs) {
return funcs.reduce((promise, func) =>
promise.then(result => func().then(Array.prototype.concat.bind(result))),
Promise.resolve([]));
}

// todo: is this the best way of getting image data for a web worker?
// is there a way for the webworker to do this without having to interact with the DOM?
// note: don't call this on a sequence of bitmaps
function loadBitmapImageData(url) {
return new Promise(function(resolve, reject) {
const element = document.getElementById('bitmap-canvas');
const context = element.getContext('2d');
const img = new Image();

img.onload = () => {
element.width = img.width;
element.height = img.height;

context.drawImage(img, 0, 0);

const imageData = context.getImageData(0, 0, element.width, element.height);

resolve(imageData);
};
img.onerror = () => {
reject();
};

img.src = normalize_bitmap_url(url);
});
}

function normalize_bitmap_url(url) {
const re = /^[\w-/]+.png/;

if (url.match(re)) {
// requesting a bitmap just by filename, so get it from /img/immutable/
return "img/immutable/" + url;
} else {
// change nothing, try and fetch the url
return url;
}
}

async function renderScript(parameters, display) {
console.log(`renderScript (demandCanvasSize = ${gState.demandCanvasSize})`);
@@ -156,59 +110,13 @@ async function renderScript(parameters, display) {
await renderGeometryBuffers(meta, memory, buffers, gState.demandCanvasSize, gState.demandCanvasSize, display);
}

async function renderJob(parameters) {
// 1. compile the program in a web worker
// 2. (retain the id for this worker)
// 3. after compilation, the worker will return a list of bitmaps that are
// required by the program and are not in the web worker's bitmap-cache
// 4. sequentially load in the bitmaps and send their data to the worker
// 5. can now request a render which will return the render packets

// request a compile job but make sure to retain the worker as it will be performing the rendering
//
parameters.__retain = true;
const { bitmapsToTransfer, __worker_id } = await Job.request(jobRender_1_Compile, parameters);

// convert each bitmap path to a function that returns a promise
//
const bitmap_loading_funcs = bitmapsToTransfer.map(filename => async () => {
const imageData = await loadBitmapImageData(filename);
console.log(`worker ${__worker_id}: bitmap request: ${filename}`);
// make an explicit job request to the same worker
return Job.request(jobRender_2_ReceiveBitmapData, { filename, imageData, __retain: true }, __worker_id);
});

// seqentially execute the promises that load in bitmaps and send the bitmap data to a particular worker
//
await sequentialPromises(bitmap_loading_funcs);

// now make an explicit job request to the same worker that has recieved the bitmap data
// note: no __retain as we want the worker to be returned to the available pool
const renderPacketsResult = await Job.request(jobRender_3_RenderPackets, {}, __worker_id);

return renderPacketsResult;
}

function getSeedValue(element) {
const res = parseInt(element.value, 10);
return res;
}

async function fetchScript(id) {
const response = await fetch(`/gallery/${id}`);
return response.text();
}

function getRequiredElement(id) {
const element = document.getElementById(id);
if (!element) {
console.error(`required element ${id} not found in dom`);
}
return element;
}

async function showSimplifiedScript(fullScript) {
const { script } = await Job.request(jobSimplifyScript, {
const { script } = await Job.request(JobType.jobSimplifyScript, {
script: fullScript
});

@@ -216,16 +124,6 @@ async function showSimplifiedScript(fullScript) {
simplifiedScriptElement.textContent = script;
}

function addClass(id, clss) {
const e = getRequiredElement(id);
e.classList.add(clss);
}

function removeClass(id, clss) {
const e = getRequiredElement(id);
e.classList.remove(clss);
}

function showId(id) {
removeClass(id, 'seni-hide');
}
@@ -234,11 +132,6 @@ function hideId(id) {
addClass(id, 'seni-hide');
}

function setOpacity(id, opacity) {
const e = getRequiredElement(id);
e.style.opacity = opacity;
}

async function performSlideshow() {
if (gState.mode === MODE_SLIDESHOW) {
const scriptElement = getRequiredElement('sketch-script');
@@ -279,9 +172,6 @@ function resetImageElements() {
removeClass(IMG_1, 'seni-fade-in-slideshow');
}




function moveContainerInsideParent(parentId, forceLargest) {
const canvasContainerId = 'sketch-canvas-container';
const canvasContainer = getRequiredElement(canvasContainerId);
@@ -380,29 +270,6 @@ function animationEndListener1(event) {
}
}

function compatibilityHacks() {
// Safari doesn't have Number.parseInt (yet)
// Safari is the new IE
if (Number.parseInt === undefined) {
Number.parseInt = parseInt;
}
}

function getURIParameters() {
const argPairs = window.location.search.substring(1).split("&");

return argPairs.reduce((acc, kv) => {
let [key, value] = kv.split("=");
if (key === URI_SEED) {
acc[key] = parseInt(value, 10);
} else {
acc[key] = value;
}

return acc;
}, {});
}

function updateGlobalsFromURI() {
const uriParameters = getURIParameters();

@@ -449,10 +316,10 @@ async function renderNormal(display) {
await showSimplifiedScript(script);
await renderScript({ script }, display);
} else {
const { traits } = await Job.request(jobBuildTraits, { script });
const { genotype } = await Job.request(jobSingleGenotypeFromSeed, { traits, seed: gState.seed });
const { traits } = await Job.request(JobType.jobBuildTraits, { script });
const { genotype } = await Job.request(JobType.jobSingleGenotypeFromSeed, { traits, seed: gState.seed });

const unparsed = await Job.request(jobUnparse, { script, genotype });
const unparsed = await Job.request(JobType.jobUnparse, { script, genotype });
await showSimplifiedScript(unparsed.script);
await renderScript({ script, genotype }, display);
}
@@ -462,21 +329,6 @@ async function updateSketch(display) {
await renderNormal(display);
}

async function loadShaders(scriptUrls) {
const fetchPromises = scriptUrls.map(s => fetch(s));
const responses = await Promise.all(fetchPromises);

const textPromises = responses.map(r => r.text());
const shaders = await Promise.all(textPromises);

const res = {};
for (const [i, url] of scriptUrls.entries()) {
res[url] = shaders[i];
}

return res;
}

async function main() {
updateGlobalsFromURI();


+ 19
- 9
client/ts/tsconfig-main.json View File

@@ -1,21 +1,31 @@
{
"compilerOptions": {
"allowJs": true,
"target": "es6",
"outFile": "../../www/index.js",
"sourceMap": true
"lib": ["ES2018", "DOM"],
"outFile": "../www/index.js",
"allowJs": true,
"sourceMap": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"strictNullChecks": true,
"removeComments": true
},
"include": [
"./src/log.js",
"./src/Log.ts",
"./src/Matrix.ts",
"./src/renderer.js",
"./src/history.js",
"./src/History.ts",
"./src/codemirror-seni.js",
"./src/editor.js",
"./src/controller.js",
"./src/timer.js",
"./src/seni-mode.js",
"./src/job.js",
"./src/Controller.ts",
"./src/Timer.ts",
"./src/SeniMode.ts",
"./src/JobType.ts",
"./src/Job.ts",
"./src/stuff.js",
"./src/misc.js",
"./src/index.js"
]
}

+ 14
- 5
client/ts/tsconfig-sketch.json View File

@@ -1,15 +1,24 @@
{
"compilerOptions": {
"allowJs": true,
"target": "es6",
"outFile": "../../www/sketch.js",
"sourceMap": true
"outFile": "../www/sketch.js",
"allowJs": true,
"sourceMap": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"strictNullChecks": true,
"removeComments": true
},
"include": [
"./src/log.js",
"./src/Log.ts",
"./src/Matrix.ts",
"./src/renderer.js",
"./src/job.js",
"./src/JobType.ts",
"./src/Job.ts",
"./src/stuff.js",
"./src/misc.js",
"./src/sketch.js"
]
}

+ 24
- 31
www/worker.js View File

@@ -22,17 +22,20 @@ importScripts('client.js');
let seniBridge = undefined;
let seniMemory = undefined;

const jobRender_1_Compile = 'RENDER_1_COMPILE';
const jobRender_2_ReceiveBitmapData = 'RENDER_2_RECEIVEBITMAPDATA';
const jobRender_3_RenderPackets = 'RENDER_3_RENDERPACKETS';
const jobUnparse = 'UNPARSE';
const jobBuildTraits = 'BUILD_TRAITS';
const jobInitialGeneration = 'INITIAL_GENERATION';
const jobNewGeneration = 'NEW_GENERATION';
const jobGenerateHelp = 'GENERATE_HELP';
const jobSingleGenotypeFromSeed = 'SINGLE_GENOTYPE_FROM_SEED';
const jobSimplifyScript = 'SIMPLIFY_SCRIPT';
const jobReceiveBitmapData = 'RECEIVE_BITMAP_DATA';
// javascript output from typescript compilation of JobType.ts
let JobType;
(function (JobType) {
JobType[JobType["jobRender_1_Compile"] = 0] = "jobRender_1_Compile";
JobType[JobType["jobRender_2_ReceiveBitmapData"] = 1] = "jobRender_2_ReceiveBitmapData";
JobType[JobType["jobRender_3_RenderPackets"] = 2] = "jobRender_3_RenderPackets";
JobType[JobType["jobUnparse"] = 3] = "jobUnparse";
JobType[JobType["jobBuildTraits"] = 4] = "jobBuildTraits";
JobType[JobType["jobInitialGeneration"] = 5] = "jobInitialGeneration";
JobType[JobType["jobNewGeneration"] = 6] = "jobNewGeneration";
JobType[JobType["jobGenerateHelp"] = 7] = "jobGenerateHelp";
JobType[JobType["jobSingleGenotypeFromSeed"] = 8] = "jobSingleGenotypeFromSeed";
JobType[JobType["jobSimplifyScript"] = 9] = "jobSimplifyScript";
})(JobType || (JobType = {}));

function compile({ script, genotype }) {
if (genotype) {
@@ -220,34 +223,24 @@ const options = {

function messageHandler(type, data) {
switch (type) {
case jobRender_1_Compile:
// console.log("jobRender_1_Compile");
case JobType["jobRender_1_Compile"]:
return compile(data);
case jobRender_2_ReceiveBitmapData:
// console.log("jobRender_2_ReceiveBitmapData");
case JobType["jobRender_2_ReceiveBitmapData"]:
return receiveBitmapData(data);
case jobRender_3_RenderPackets:
// console.log("jobRender_3_RenderPackets");
case JobType["jobRender_3_RenderPackets"]:
return renderPackets(data);
case jobUnparse:
// console.log("jobUnparse");
case JobType["jobUnparse"]:
return unparse(data);
case jobBuildTraits:
// console.log("jobBuildTraits");
case JobType["jobBuildTraits"]:
return buildTraits(data);
case jobInitialGeneration:
// console.log("jobInitialGeneration");
case JobType["jobInitialGeneration"]:
return createInitialGeneration(data);
case jobSingleGenotypeFromSeed:
// console.log("jobSingleGenotypeFromSeed");
case JobType["jobSingleGenotypeFromSeed"]:
return singleGenotypeFromSeed(data);
case jobSimplifyScript:
// console.log("jobSimplifyScript");
case JobType["jobSimplifyScript"]:
return simplifyScript(data);
case jobNewGeneration:
// console.log("jobNewGeneration");
case JobType["jobNewGeneration"]:
return newGeneration(data);

default:
// throw unknown type
throw new Error(`worker.js: Unknown type: ${type}`);
@@ -269,7 +262,7 @@ addEventListener('message', event => {

const [status, result] = messageHandler(type, data);

if (type === jobRender_3_RenderPackets) {
if (type === JobType["jobRender_3_RenderPackets"]) {
const transferrable = [];

if (result.buffers && result.buffers.length > 0) {

Loading…
Cancel
Save