gpu.js/src/backend/function-node-base.js
2017-07-03 00:29:01 +05:30

308 lines
9.0 KiB
JavaScript

const utils = require('../core/utils');
const parser = require('../core/parser').parser;
/**
* @class FunctionNodeBase
*
* [INTERNAL] Represents a single function, inside JS, webGL, or openGL.
*
* This handles all the raw state, converted state, etc. Of a single function.
*
* @param functionName - {String} Name of the function
* @param jsFunction - {JS Function} The JS Function the node represents
* @param jsFunctionString - {String} jsFunction.toString()
* @param paramNames - {[String,...]} Parameter names of the function
* @param paramTypes - {[String,...]} Shader land parameters type assumption
* @param isRootKernel - {Boolean} Special indicator, for kernel function
* @param webglFunctionString - {String} webgl converted function string
* @param openglFunctionString - {String} opengl converted function string
* @param calledFunctions - {[String,...]} List of all the functions called
* @param initVariables - {[String,...]} List of variables initialized in the function
* @param readVariables - {[String,...]} List of variables read operations occur
* @param writeVariables - {[String,...]} List of variables write operations occur
*
*/
module.exports = class BaseFunctionNode {
//
// Constructor
//----------------------------------------------------------------------------------------------------
/**
* @name FunctionNodeBase
*
* @constructor Builds the function with the given JS function, and argument type array.
*
* @param gpu {GPU} The GPU instance
* @param functionName {String} Function name to assume, if its null, it attempts to extract from the function
* @param jsFunction {JS Function / String} JS Function to do conversion
* @param paramTypes {[String,...]|{variableName: Type}} Parameter type array, assumes all parameters are 'float' if null
* @param returnType {String} The return type, assumes 'float' if null
*
*/
constructor(functionName, jsFunction, options, paramTypes, returnType) {
//
// Internal vars setup
//
this.calledFunctions = [];
this.calledFunctionsArguments = {};
this.initVariables = [];
this.readVariables = [];
this.writeVariables = [];
this.addFunction = null;
this.isRootKernel = false;
this.isSubKernel = false;
this.parent = null;
this.debug = null;
this.prototypeOnly = null;
this.constants = null;
if (options) {
if (options.hasOwnProperty('debug')) {
this.debug = options.debug;
}
if (options.hasOwnProperty('prototypeOnly')) {
this.prototypeOnly = options.prototypeOnly;
}
if (options.hasOwnProperty('constants')) {
this.constants = options.constants;
}
if (options.hasOwnProperty('loopMaxIterations')) {
this.loopMaxIterations = options.loopMaxIterations;
}
}
//
// Missing jsFunction object exception
//
if (!jsFunction) {
throw 'jsFunction, parameter is missing';
}
//
// Setup jsFunction and its string property + validate them
//
this.jsFunctionString = jsFunction.toString();
if (!utils.isFunctionString(this.jsFunctionString)) {
console.error('jsFunction, to string conversion check failed: not a function?', this.jsFunctionString);
throw 'jsFunction, to string conversion check failed: not a function?';
}
if (!utils.isFunction(jsFunction)) {
//throw 'jsFunction, is not a valid JS Function';
this.jsFunction = null;
} else {
this.jsFunction = jsFunction;
}
//
// Setup the function name property
//
this.functionName = functionName ||
(jsFunction && jsFunction.name) ||
utils.getFunctionNameFromString(this.jsFunctionString);
if (!(this.functionName)) {
throw 'jsFunction, missing name argument or value';
}
//
// Extract parameter name, and its argument types
//
this.paramNames = utils.getParamNamesFromString(this.jsFunctionString);
if (paramTypes) {
if (Array.isArray(paramTypes)) {
if (paramTypes.length !== this.paramNames.length) {
throw 'Invalid argument type array length, against function length -> (' +
paramTypes.length + ',' +
this.paramNames.length +
')';
}
this.paramTypes = paramTypes;
} else if (typeof paramTypes === 'object') {
const paramVariableNames = Object.keys(paramTypes);
if (paramTypes.hasOwnProperty('returns')) {
this.returnType = paramTypes.returns;
paramVariableNames.splice(paramVariableNames.indexOf('returns'), 1);
}
if (paramVariableNames.length > 0 && paramVariableNames.length !== this.paramNames.length) {
throw 'Invalid argument type array length, against function length -> (' +
paramVariableNames.length + ',' +
this.paramNames.length +
')';
} else {
this.paramTypes = this.paramNames.map((key) => {
if (paramTypes.hasOwnProperty(key)) {
return paramTypes[key];
} else {
return 'float';
}
});
}
}
} else {
this.paramTypes = [];
//TODO: Remove when we have proper type detection
// for (let a = 0; a < this.paramNames.length; ++a) {
// this.paramTypes.push();
// }
}
//
// Return type handling
//
if (!this.returnType) {
this.returnType = returnType || 'float';
}
}
setAddFunction(fn) {
this.addFunction = fn;
return this;
}
//
// Core function
//----------------------------------------------------------------------------------------------------
/**
* @name getJSFunction
*
* Gets and return the stored JS Function.
* Note: that this internally eval the function, if only the string was provided on construction
*
* Returns:
* {JS Function} The function object
*
*/
getJsFunction() {
if (this.jsFunction) {
return this.jsFunction;
}
if (this.jsFunctionString) {
this.jsFunction = eval(this.jsFunctionString);
return this.jsFunction;
}
throw 'Missing jsFunction, and jsFunctionString parameter';
}
/**
* @name getJS_AST
*
* Parses the class function JS, and returns its Abstract Syntax Tree object.
*
* This is used internally to convert to shader code
*
* @param inParser {JISON Parser} Parser to use, assumes in scope 'parser' if null
*
* Returns:
* {AST Object} The function AST Object, note that result is cached under this.jsFunctionAST;
*
*/
getJsAST(inParser) {
if (this.jsFunctionAST) {
return this.jsFunctionAST;
}
inParser = inParser || parser;
if (inParser === null) {
throw 'Missing JS to AST parser';
}
const prasedObj = parser.parse('var ' + this.functionName + ' = ' + this.jsFunctionString + ';');
if (prasedObj === null) {
throw 'Failed to parse JS code via JISON';
}
// take out the function object, outside the var declarations
const funcAST = prasedObj.body[0].declarations[0].init;
this.jsFunctionAST = funcAST;
return funcAST;
}
/**
* @name getFunctionString
*
* Returns the converted webgl shader function equivalent of the JS function
*
* Returns:
* {String} webgl function string, result is cached under this.webGlFunctionString
*
*/
getFunctionString() {
this.generate();
return this.functionString;
}
/**
* @name setFunctionString
*
* Set the functionString value, overwriting it
*
* @param functionString {String} Shader code string, representing the function
*
*/
setFunctionString(functionString) {
this.functionString = functionString;
}
/**
* @name getParamType
*
* Return the type of parameter sent to subKernel/Kernel.
*
* @param paramName {String} Name of the parameter
*
* Returns:
* {String} Type of the parameter
*
*/
getParamType(paramName) {
const paramIndex = this.paramNames.indexOf(paramName);
if (paramIndex === -1) return null;
if (!this.parent) return null;
if (this.paramTypes[paramIndex]) return this.paramTypes[paramIndex];
const calledFunctionArguments = this.parent.calledFunctionsArguments[this.functionName];
for (let i = 0; i < calledFunctionArguments.length; i++) {
const calledFunctionArgument = calledFunctionArguments[i];
if (calledFunctionArgument[paramIndex] !== null) {
return this.paramTypes[paramIndex] = calledFunctionArgument[paramIndex].type;
}
}
return null;
}
/**
* @name getUserParamName
*
* Return the name of the *user parameter*(subKernel parameter) corresponding
* to the parameter supplied to the kernel
*
* @param paramName {String} Name of the parameter
*
* Returns:
* {String} Name of the parameter
*
*/
getUserParamName(paramName) {
const paramIndex = this.paramNames.indexOf(paramName);
if (paramIndex === -1) return null;
if (!this.parent) return null;
const calledFunctionArguments = this.parent.calledFunctionsArguments[this.functionName];
for (let i = 0; i < calledFunctionArguments.length; i++) {
const calledFunctionArgument = calledFunctionArguments[i];
if (calledFunctionArgument[paramIndex] !== null) {
return calledFunctionArgument[paramIndex].name;
}
}
return null;
}
generate(options) {
throw new Error('generate not defined on BaseFunctionNode');
}
};