Files
hydra/frontend/web-editor/src/randomizer/Mutator.js
2022-02-09 13:51:22 +01:00

262 lines
7.2 KiB
JavaScript

const {Parser} = require("acorn");
const {generate} = require('astring');
const { defaultTraveler, attachComments, makeTraveler } = require('astravel');
const {UndoStack} = require('./UndoStack.js');
const repl = require('../repl.js')
const glslTransforms = require('hydra-synth/src/glsl/glsl-functions.js')()
class Mutator {
constructor(editor) {
this.editor = editor;
this.undoStack = new UndoStack();
this.initialVector = [];
this.funcTab = {};
this.transMap = {};
this.scanFuncs();
this.dumpDict();
}
dumpList() {
let gslTab = glslTransforms;
gslTab.forEach (v => {
var argList = "";
v.inputs.forEach((a) => {
if (argList != "") argList += ", ";
let argL = a.name + ": " + a.type + " {" + a.default + "}";
argList = argList + argL;
});
// console.log(v.name + " [" + v.type + "] ("+ argList + ")");
});
}
scanFuncs() {
let gslTab = glslTransforms;
gslTab.forEach (f => {
this.transMap[f.name] = f;
if (this.funcTab[f.type] === undefined) {this.funcTab[f.type] = []}
this.funcTab[f.type].push(f);
});
}
dumpDict() {
for(let tn in this.funcTab)
{
this.funcTab[tn].forEach(f => {
var argList = "";
f.inputs.forEach((a) => {
if (argList != "") argList += ", ";
let argL = a.name + ": " + a.type + " {" + a.default + "}";
argList = argList + argL;
});
//console.log(f.name + " [" + f.type + "] ("+ argList + ")");
});
}
}
mutate(options) {
// Get text from CodeMirror.
let text = this.editor.cm.getValue();
this.undoStack.push({text, lastLitX: this.lastLitX});
let needToRun = true;
let tryCounter = 5;
while (needToRun && tryCounter-- >= 0) {
// Parse to AST
var comments = [];
let ast = Parser.parse(text, {
locations: true,
onComment: comments}
);
// Modify the AST.
this.transform(ast, options);
// Put the comments back.
attachComments(ast, comments);
// Generate JS from AST and set back into CodeMirror editor.
let regen = generate(ast, {comments: true});
this.editor.cm.setValue(regen);
try {
// Evaluate the updated expression.
repl.eval(regen, (code, error) => {
// If we got an error, keep trying something else.
if (error) {
console.log("Eval error: " + regen);
}
needToRun = error;
});
} catch (err) {
console.log("Exception caught: " + err);
needToRun = err;
}
}
}
doUndo() {
// If the current text is unsaved, save it so we can redo if need be.
if (this.undoStack.atTop()) {
let text = this.editor.cm.getValue();
this.undoStack.push({text, lastLitX: this.lastLitX});
}
// Then pop-off the info to restore.
if (this.undoStack.canUndo()) {
let {text, lastLitX} = this.undoStack.undo();
this.setText(text);
this.lastLitX = lastLitX;
}
}
doRedo() {
if(this.undoStack.canRedo()) {
let {text, lastLitX} = this.undoStack.redo();
this.setText(text);
this.lastLitX = lastLitX;
}
}
setText(text) {
this.editor.cm.setValue(text);
repl.eval(text, (code, error) => {
});
}
// The options object contains a flag that controls how the
// Literal to mutate is determined. If reroll is false, we
// pick one at random. If reroll is true, we use the same field
// we did last time.
transform(ast, options) {
// An AST traveler that accumulates a list of Literal nodes.
let traveler = makeTraveler({
go: function(node, state) {
if (node.type === 'Literal') {
state.literalTab.push(node);
} else if (node.type === 'MemberExpression') {
if (node.property && node.property.type === 'Literal') {
// numeric array subscripts are ineligable
return;
}
} else if (node.type === 'CallExpression') {
if (node.callee && node.callee.property && node.callee.property.name && node.callee.property.name !== 'out') {
state.functionTab.push(node);
}
}
// Call the parent's `go` method
this.super.go.call(this, node, state);
}
});
let state = {};
state.literalTab = [];
state.functionTab = [];
traveler.go(ast, state);
this.litCount = state.literalTab.length;
this.funCount = state.functionTab.length;
if (this.litCount !== this.initialVector.length) {
let nextVect = [];
for(let i = 0; i < this.litCount; ++i) {
nextVect.push(state.literalTab[i].value);
}
this.initialVector = nextVect;
}
if (options.changeTransform) {
this.glitchTrans(state, options);
}
else this.glitchLiteral(state, options);
}
glitchLiteral(state, options)
{
let litx = 0;
if (options.reroll) {
if (this.lastLitX !== undefined) {
litx = this.lastLitX;
}
} else {
litx = Math.floor(Math.random() * this.litCount);
this.lastLitX = litx;
}
let modLit = state.literalTab[litx];
if (modLit) {
// let glitched = this.glitchNumber(modLit.value);
let glitched = this.glitchRelToInit(modLit.value, this.initialVector[litx]);
let was = modLit.raw;
modLit.value = glitched;
modLit.raw = "" + glitched;
console.log("Literal: " + litx + " changed from: " + was + " to: " + glitched);
}
}
glitchNumber(num) {
if (num === 0) {
num = 1;
}
let range = num * 2;
let rndVal = Math.round(Math.random() * range * 1000) / 1000;
return rndVal;
}
glitchRelToInit(num, initVal) {
if (initVal === undefined) {
return glitchNumber(num);
} if (initVal === 0) {
initVal = 0.5;
}
let rndVal = Math.round(Math.random() * initVal * 2 * 1000) / 1000;
return rndVal;
}
glitchTrans(state, options)
{
/*
state.functionTab.forEach((f)=>{
console.log(f.callee.property.name);
});
*/
let funx = Math.floor(Math.random() * this.funCount);
if (state.functionTab[funx] === undefined || state.functionTab[funx].callee === undefined || state.functionTab[funx].callee.property === undefined) {
console.log("No valid functionTab for index: " + funx);
return;
}
let oldName = state.functionTab[funx].callee.property.name;
if (oldName == undefined) {
console.log("No name for callee");
return;
}
let ftype = this.transMap[oldName].type;
if (ftype == undefined) {
console.log("ftype undefined for: " + oldName);
return;
}
let others = this.funcTab[ftype];
if (others == undefined) {
console.log("no funcTab entry for: " + ftype);
return;
}
let changeX = Math.floor(Math.random() * others.length);
let become = others[changeX].name;
// check blacklisted combinations.
if (oldName === "modulate" && become === "modulateScrollX")
{
console.log("Function: " + funx + " changing from: " + oldName + " can't change to: " + become);
return;
}
state.functionTab[funx].callee.property.name = become;
console.log("Function: " + funx + " changed from: " + oldName + " to: " + become);
}
} // End of class Mutator.
module.exports = Mutator