<commit_before>/**
 * Module Dependencies
 */

var _ = require('lodash'),
    async = require('async'),
    Errors = require('waterline-errors'),
    WaterlineCriteria = require('waterline-criteria'),
    Utils = require('../utils'),
    Schema = require('./schema'),
    Aggregate = require('../aggregates'),
    hasOwnProperty = Utils.object.hasOwnProperty;

/**
 * An Interface for interacting with Redis.
 */

var Database = module.exports = function() {

  // Hold Schema for the database
  this.schema = new Schema();

  return this;
};

///////////////////////////////////////////////////////////////////////////////////////////
/// PUBLIC METHODS
///////////////////////////////////////////////////////////////////////////////////////////

/**
 * Configure the "database" for a collection.
 *
 * @param {String} collectionName
 * @param {Object} config
 * @param {Object} schema
 * @param {Function} callback
 * @api public
 */

Database.prototype.configure = function configure(collectionName, config, schema, cb) {
  var name = collectionName.toLowerCase();

  if(this.schema._schema[name]) return cb();

  this.schema.registerCollection(name, schema, config, cb);

};

/**
 * Describe the schema for a collection.
 *
 * @param {String} collectionName
 * @param {Function} callback
 * @api public
 */

Database.prototype.describe = function describe(collectionName, cb) {
  var name = collectionName.toLowerCase(),
      desc = this.schema.retrieve(name);

  if(!desc) return cb(Errors.adapter.collectionNotRegistered);
  cb(null, desc);
};

/**
 * Drop a collection and all of it's keys.
 *
 * @param {String} collectionName
 * @param {Array} relations
 * @param {Function} callback
 * @api public
 */

Database.prototype.drop = function drop(collectionName, relations, cb) {

  var name = collectionName.toLowerCase();

  // Grab the raw connection for this collection
  var connection = this.schema._connections[name];

  // Cache key values
  var primary = this.schema._primary[name];
  var index = this.schema.indexKey(name, primary);

  // Build an array to hold the collections keys
  var keys = [];

  // Expand Sequence Keys
  this.schema._sequences[name].forEach(function(sequence) {
    keys.push(sequence.keyName);
  });

  // Expand Indices
  this.schema._indices[name].forEach(function(idx) {
    keys.push(idx.keyName);
  });

  // Add the main primary key index to the keys array
  keys.push(index);

  connection.smembers(index, function(err, values) {
    if(err) return cb(err);

    // Add the member keys onto the keys array
    keys = keys.concat(values);

    async.each(keys, function(key, next) {
      connection.del(key, next);
    }, cb);
  });

};

/**
 * Find a record or set of records based on a criteria object.
 *
 * @param {String} collectionName
 * @param {Object} criteria
 * @param {Function} callback
 * @api public
 */

Database.prototype.find = function find(collectionName, criteria, cb) {
  var self = this,
      name = collectionName.toLowerCase(),
      options = {},
      recordKey;

  // Find the attribute used as a primary key
  var primary = this.schema._primary[name];

  // Find the index key used to keep track of all the keys in this collections
  var indexKey = this.schema.indexKey(name, primary);

  // Grab the raw connection for this collection
  var connection = this.schema._connections[name];

  // If the primary key is contained in the criteria, a NoSQL key can be
  // constructed and we can simply grab the values. This would be a findOne.
  if(Utils.present(criteria[primary])) {
    recordKey = this.schema.recordKey(name, primary, criteria);

    connection.get(recordKey, function(err, record) {
      if(err) return cb(err);
      if(!record) return cb(Errors.adapter.notFound);

      try {
        record = JSON.parse(record);
      } catch (e) {
        return cb(e);
      }

      cb(null, self.schema.parse(name, record));
    });

    return;
  }

  async.auto({

    // Cache any SKIP, LIMIT, SORT criteria params
    // these will be used later after any criteria matches are found
    cleanseCriteria: function(next) {

      if(hasOwnProperty(criteria, 'skip')) {
        options.skip = _.cloneDeep(criteria.skip);
        delete criteria.skip;
      }

      if(hasOwnProperty(criteria, 'limit')) {
        options.limit = _.cloneDeep(criteria.limit);
        delete criteria.limit;
      }

      if(hasOwnProperty(criteria, 'sort')) {
        options.sort = _.cloneDeep(criteria.sort);
        delete criteria.sort;
      } else {
        // Sort by primaryKey asc
        options.sort = {};
        options.sort[primary] = 1;
      }

      next();
    },

    // Get all the record keys belonging to this collection
    getSetMembers: function(next) {
      connection.smembers(indexKey, function(err, members) {
        if(err) return next(err);
        next(null, members);
      });
    },

    // Filter the records to only include those that match the criteria,
    // using Waterline-Criteria for the actual matching logic.
    filterKeys: ['cleanseCriteria', 'getSetMembers', function(next, results) {
      var members = results.getSetMembers,
          models = [];

      // If no members were found, the result set will always be empty
      if(members.length === 0) return next();

      function search(member, done) {
        connection.get(member, function(err, result) {
          if(err) return done(err);

          try {
            result = JSON.parse(result);
          } catch (e) {
            return done(e);
          }

          // Cast Result Data
          result = self.schema.parse(name, result);

          // Build up a dataset for use with waterline-criteria
          var data = {};
          data[name] = [result];

          // Check for match using waterline-criteria
          var resultSet = WaterlineCriteria(name, data, criteria);
          models = models.concat(resultSet.results);

          done();
        });
      }

      // For each member grab the values and run a filter on it.
      // Not the most effecient way to query but redis has no query language.
      async.each(members, search, function(err) {
        if(err) return next(err);
        next(null, models);
      });
    }],

    // Process Models
    // Runs any sorting, skipping, limits, etc and processes any Aggregate options.
    processModels: ['filterKeys', function(next, results) {
      var resultSet = results.filterKeys;

      // If any processing options were supplied, re-run Waterline-Criteria with the filtered
      // results set. This will process any sorting and pagination options.
      if(hasOwnProperty(options, 'sort') || hasOwnProperty(options, 'limit') || hasOwnProperty(options, 'skip')) {

        // Build up a dataset for use with waterline-criteria
        var data = {};
        data[name] = resultSet;

        options.where = {};
        resultSet = WaterlineCriteria(name, data, options).results;
      }

      // Process Aggregate Options
      var aggregate = new Aggregate(criteria, resultSet);

      if(aggregate.error) return next(aggregate.error);
      next(null, aggregate.results);
    }]

  }, function(err, results) {
    if(err) return cb(err);
    cb(null, results.processModels);
  });
};

/**
 * Create a new record from `data`
 *
 * @param {String} collectionName
 * @param {Object} data
 * @param {Function} callback
 * @api public
 */

Database.prototype.create = function create(collectionName, data, cb) {
  var self = this,
      name = collectionName.toLowerCase(),
      sequences = self.schema._sequences[name],
      primary = self.schema._primary[name];

  async.auto({

    // Check Unique constraints
    checkUniqueConstraints: function(next) {
      self._uniqueConstraint(name, data, function(err) {
        if(err) return next(err);
        next();
      });
    },

    // Check that the primary key exists or is auto-incrementing
    checkPrimaryKeyExists: function(next) {

      var sequenceNames = sequences.map(function(sequence) { return sequence.name; });

      if(!Utils.present(data[primary]) && !~sequenceNames.indexOf(primary)) {
        return next(Errors.adapter.primaryKeyMissing);
      }

      next();
    },

    // Check that if the primary key is auto-incrementing the user didn't supply a value
    checkPrimaryKeyValid: function(next) {

      var sequenceNames = sequences.map(function(sequence) { return sequence.name; });

      for(i = 0, len = sequences.length; i < len; i = i + 1) {
        if(Utils.present(data[sequenceNames[i]])) {
          return next(Errors.adapter.invalidAutoIncrement);
        }
      }

      next();
    },

    // Increment Sequences
    incrementSequences: [
      'checkUniqueConstraints', 'checkPrimaryKeyExists', 'checkPrimaryKeyValid',
      function(next) {

        function incrementSequence(item, nextItem) {
          item.increment(nextItem);
        }

        async.each(sequences, incrementSequence, next);
      }
    ],

    // Update Data values with auto-incrementing values
    setSequenceValue: ['incrementSequences', function(next) {

      function setSequenceValue(sequence, nextItem) {
        sequence.get(function(err, value) {
          if(err) return nextItem(err);

          data[sequence.name] = parseInt(value, 10);
          nextItem();
        });
      }

      async.each(sequences, setSequenceValue, function(err) {
        if(err) return next(err);
        next();
      });

    }],

    // Save Key Data
    saveKeyData: ['setSequenceValue', function(next, results) {

      // Grab the key to use for this record
      var noSqlKey = self.schema.recordKey(name, primary, data[primary]);
      noSqlKey = Utils.sanitize(noSqlKey);

      // Grab the raw connection for this collection
      var connection = self.schema._connections[collectionName];

      // Stringify data for storage
      var parsedData;
      try {
        parsedData = JSON.stringify(data);
      } catch(e) {
        return next(e);
      }

      connection.set(noSqlKey, parsedData, function(err) {

        // Find the newly created record and return it
        connection.get(noSqlKey, function(err, values) {
          if(err) return next(err);

          // Parse JSON into an object
          try {
            values = JSON.parse(values);
          } catch(e) {
            return next(e);
          }

          next(null, self.schema.parse(name, values));
        });
      });
    }],

    // Add Primary Key to indexed set
    addToSet: ['saveKeyData', function(next, results) {

      var index = self.schema.indexKey(name, primary);

      // Grab the key to use for this record
      var noSqlKey = self.schema.recordKey(name, primary, data[primary]);

      // Grab the raw connection for this collection
      var connection = self.schema._connections[collectionName];

      connection.sadd(index, noSqlKey, function(err) {
        if(err) return next(err);
        next();
      });

    }]

  }, function(err, results) {
    if(err) return cb(err);
    cb(null, results.saveKeyData);
  });

};

///////////////////////////////////////////////////////////////////////////////////////////
/// CONSTRAINTS
///////////////////////////////////////////////////////////////////////////////////////////

/**
 * Check to ensure the data is unique to the schema.
 *
 * @param {String} collectionName
 * @param {Object} data
 * @param {Function} callback
 */

Database.prototype._uniqueConstraint = function _uniqueConstraint(collectionName, data, cb) {

  // Grab the raw connection for this collection
  var connection = this.schema._connections[collectionName];

  // Grab the indices for this collection
  var indices = this.schema._indices[collectionName];

  // If there are no indices for this collection, return true
  if(indices.length === 0) return cb(null, true);

  // Create a MULTI wrapper for this connection
  var multi = connection.multi();

  // For each sequence, add an query to check if the index exists
  indices.forEach(function(idx) {
    multi.sismember(idx.keyName, data[idx.name]);
  });

  // Run the MULTI wrapped transaction checking that the results are all falsy
  multi.exec(function(err, results) {
    if(err) return cb(err);

    var unique = true;
    results = results || [];

    // For each of the results, if any are truthy then the value is not unique
    results.forEach(function(res) {
      if(res) unique = false;
    });

    // If unique is false return an error
    if(!unique) return cb(Errors.adapter.notUnique);

    // If everything was unique, create each of the indexes
    async.each(indices, function(idx, next) {
      idx.index(data[idx.name], next);
    }, function(err) {
      if(err) return cb(err);
      cb();
    });
  });
};
<commit_msg>fix issue finding by primary key<commit_after>/**
 * Module Dependencies
 */

var _ = require('lodash'),
    async = require('async'),
    Errors = require('waterline-errors'),
    WaterlineCriteria = require('waterline-criteria'),
    Utils = require('../utils'),
    Schema = require('./schema'),
    Aggregate = require('../aggregates'),
    hasOwnProperty = Utils.object.hasOwnProperty;

/**
 * An Interface for interacting with Redis.
 */

var Database = module.exports = function() {

  // Hold Schema for the database
  this.schema = new Schema();

  return this;
};

///////////////////////////////////////////////////////////////////////////////////////////
/// PUBLIC METHODS
///////////////////////////////////////////////////////////////////////////////////////////

/**
 * Configure the "database" for a collection.
 *
 * @param {String} collectionName
 * @param {Object} config
 * @param {Object} schema
 * @param {Function} callback
 * @api public
 */

Database.prototype.configure = function configure(collectionName, config, schema, cb) {
  var name = collectionName.toLowerCase();

  if(this.schema._schema[name]) return cb();

  this.schema.registerCollection(name, schema, config, cb);

};

/**
 * Describe the schema for a collection.
 *
 * @param {String} collectionName
 * @param {Function} callback
 * @api public
 */

Database.prototype.describe = function describe(collectionName, cb) {
  var name = collectionName.toLowerCase(),
      desc = this.schema.retrieve(name);

  if(!desc) return cb(Errors.adapter.collectionNotRegistered);
  cb(null, desc);
};

/**
 * Drop a collection and all of it's keys.
 *
 * @param {String} collectionName
 * @param {Array} relations
 * @param {Function} callback
 * @api public
 */

Database.prototype.drop = function drop(collectionName, relations, cb) {

  var name = collectionName.toLowerCase();

  // Grab the raw connection for this collection
  var connection = this.schema._connections[name];

  // Cache key values
  var primary = this.schema._primary[name];
  var index = this.schema.indexKey(name, primary);

  // Build an array to hold the collections keys
  var keys = [];

  // Expand Sequence Keys
  this.schema._sequences[name].forEach(function(sequence) {
    keys.push(sequence.keyName);
  });

  // Expand Indices
  this.schema._indices[name].forEach(function(idx) {
    keys.push(idx.keyName);
  });

  // Add the main primary key index to the keys array
  keys.push(index);

  connection.smembers(index, function(err, values) {
    if(err) return cb(err);

    // Add the member keys onto the keys array
    keys = keys.concat(values);

    async.each(keys, function(key, next) {
      connection.del(key, next);
    }, cb);
  });

};

/**
 * Find a record or set of records based on a criteria object.
 *
 * @param {String} collectionName
 * @param {Object} criteria
 * @param {Function} callback
 * @api public
 */

Database.prototype.find = function find(collectionName, criteria, cb) {
  var self = this,
      name = collectionName.toLowerCase(),
      options = {},
      recordKey;

  // Find the attribute used as a primary key
  var primary = this.schema._primary[name];

  // Find the index key used to keep track of all the keys in this collections
  var indexKey = this.schema.indexKey(name, primary);

  // Grab the raw connection for this collection
  var connection = this.schema._connections[name];

  // If the primary key is contained in the criteria, a NoSQL key can be
  // constructed and we can simply grab the values. This would be a findOne.
  if(Utils.present(criteria[primary])) {
    recordKey = this.schema.recordKey(name, primary, criteria[primary]);

    connection.get(recordKey, function(err, record) {
      if(err) return cb(err);
      if(!record) return cb(Errors.adapter.notFound);

      try {
        record = JSON.parse(record);
      } catch (e) {
        return cb(e);
      }

      cb(null, self.schema.parse(name, record));
    });

    return;
  }

  async.auto({

    // Cache any SKIP, LIMIT, SORT criteria params
    // these will be used later after any criteria matches are found
    cleanseCriteria: function(next) {

      if(hasOwnProperty(criteria, 'skip')) {
        options.skip = _.cloneDeep(criteria.skip);
        delete criteria.skip;
      }

      if(hasOwnProperty(criteria, 'limit')) {
        options.limit = _.cloneDeep(criteria.limit);
        delete criteria.limit;
      }

      if(hasOwnProperty(criteria, 'sort')) {
        options.sort = _.cloneDeep(criteria.sort);
        delete criteria.sort;
      } else {
        // Sort by primaryKey asc
        options.sort = {};
        options.sort[primary] = 1;
      }

      next();
    },

    // Get all the record keys belonging to this collection
    getSetMembers: function(next) {
      connection.smembers(indexKey, function(err, members) {
        if(err) return next(err);
        next(null, members);
      });
    },

    // Filter the records to only include those that match the criteria,
    // using Waterline-Criteria for the actual matching logic.
    filterKeys: ['cleanseCriteria', 'getSetMembers', function(next, results) {
      var members = results.getSetMembers,
          models = [];

      // If no members were found, the result set will always be empty
      if(members.length === 0) return next();

      function search(member, done) {
        connection.get(member, function(err, result) {
          if(err) return done(err);

          try {
            result = JSON.parse(result);
          } catch (e) {
            return done(e);
          }

          // Cast Result Data
          result = self.schema.parse(name, result);

          // Build up a dataset for use with waterline-criteria
          var data = {};
          data[name] = [result];

          // Check for match using waterline-criteria
          var resultSet = WaterlineCriteria(name, data, criteria);
          models = models.concat(resultSet.results);

          done();
        });
      }

      // For each member grab the values and run a filter on it.
      // Not the most effecient way to query but redis has no query language.
      async.each(members, search, function(err) {
        if(err) return next(err);
        next(null, models);
      });
    }],

    // Process Models
    // Runs any sorting, skipping, limits, etc and processes any Aggregate options.
    processModels: ['filterKeys', function(next, results) {
      var resultSet = results.filterKeys;

      // If any processing options were supplied, re-run Waterline-Criteria with the filtered
      // results set. This will process any sorting and pagination options.
      if(hasOwnProperty(options, 'sort') || hasOwnProperty(options, 'limit') || hasOwnProperty(options, 'skip')) {

        // Build up a dataset for use with waterline-criteria
        var data = {};
        data[name] = resultSet;

        options.where = {};
        resultSet = WaterlineCriteria(name, data, options).results;
      }

      // Process Aggregate Options
      var aggregate = new Aggregate(criteria, resultSet);

      if(aggregate.error) return next(aggregate.error);
      next(null, aggregate.results);
    }]

  }, function(err, results) {
    if(err) return cb(err);
    cb(null, results.processModels);
  });
};

/**
 * Create a new record from `data`
 *
 * @param {String} collectionName
 * @param {Object} data
 * @param {Function} callback
 * @api public
 */

Database.prototype.create = function create(collectionName, data, cb) {
  var self = this,
      name = collectionName.toLowerCase(),
      sequences = self.schema._sequences[name],
      primary = self.schema._primary[name];

  async.auto({

    // Check Unique constraints
    checkUniqueConstraints: function(next) {
      self._uniqueConstraint(name, data, function(err) {
        if(err) return next(err);
        next();
      });
    },

    // Check that the primary key exists or is auto-incrementing
    checkPrimaryKeyExists: function(next) {

      var sequenceNames = sequences.map(function(sequence) { return sequence.name; });

      if(!Utils.present(data[primary]) && !~sequenceNames.indexOf(primary)) {
        return next(Errors.adapter.primaryKeyMissing);
      }

      next();
    },

    // Check that if the primary key is auto-incrementing the user didn't supply a value
    checkPrimaryKeyValid: function(next) {

      var sequenceNames = sequences.map(function(sequence) { return sequence.name; });

      for(i = 0, len = sequences.length; i < len; i = i + 1) {
        if(Utils.present(data[sequenceNames[i]])) {
          return next(Errors.adapter.invalidAutoIncrement);
        }
      }

      next();
    },

    // Increment Sequences
    incrementSequences: [
      'checkUniqueConstraints', 'checkPrimaryKeyExists', 'checkPrimaryKeyValid',
      function(next) {

        function incrementSequence(item, nextItem) {
          item.increment(nextItem);
        }

        async.each(sequences, incrementSequence, next);
      }
    ],

    // Update Data values with auto-incrementing values
    setSequenceValue: ['incrementSequences', function(next) {

      function setSequenceValue(sequence, nextItem) {
        sequence.get(function(err, value) {
          if(err) return nextItem(err);

          data[sequence.name] = parseInt(value, 10);
          nextItem();
        });
      }

      async.each(sequences, setSequenceValue, function(err) {
        if(err) return next(err);
        next();
      });

    }],

    // Save Key Data
    saveKeyData: ['setSequenceValue', function(next, results) {

      // Grab the key to use for this record
      var noSqlKey = self.schema.recordKey(name, primary, data[primary]);
      noSqlKey = Utils.sanitize(noSqlKey);

      // Grab the raw connection for this collection
      var connection = self.schema._connections[collectionName];

      // Stringify data for storage
      var parsedData;
      try {
        parsedData = JSON.stringify(data);
      } catch(e) {
        return next(e);
      }

      connection.set(noSqlKey, parsedData, function(err) {

        // Find the newly created record and return it
        connection.get(noSqlKey, function(err, values) {
          if(err) return next(err);

          // Parse JSON into an object
          try {
            values = JSON.parse(values);
          } catch(e) {
            return next(e);
          }

          next(null, self.schema.parse(name, values));
        });
      });
    }],

    // Add Primary Key to indexed set
    addToSet: ['saveKeyData', function(next, results) {

      var index = self.schema.indexKey(name, primary);

      // Grab the key to use for this record
      var noSqlKey = self.schema.recordKey(name, primary, data[primary]);

      // Grab the raw connection for this collection
      var connection = self.schema._connections[collectionName];

      connection.sadd(index, noSqlKey, function(err) {
        if(err) return next(err);
        next();
      });

    }]

  }, function(err, results) {
    if(err) return cb(err);
    cb(null, results.saveKeyData);
  });

};

///////////////////////////////////////////////////////////////////////////////////////////
/// CONSTRAINTS
///////////////////////////////////////////////////////////////////////////////////////////

/**
 * Check to ensure the data is unique to the schema.
 *
 * @param {String} collectionName
 * @param {Object} data
 * @param {Function} callback
 */

Database.prototype._uniqueConstraint = function _uniqueConstraint(collectionName, data, cb) {

  // Grab the raw connection for this collection
  var connection = this.schema._connections[collectionName];

  // Grab the indices for this collection
  var indices = this.schema._indices[collectionName];

  // If there are no indices for this collection, return true
  if(indices.length === 0) return cb(null, true);

  // Create a MULTI wrapper for this connection
  var multi = connection.multi();

  // For each sequence, add an query to check if the index exists
  indices.forEach(function(idx) {
    multi.sismember(idx.keyName, data[idx.name]);
  });

  // Run the MULTI wrapped transaction checking that the results are all falsy
  multi.exec(function(err, results) {
    if(err) return cb(err);

    var unique = true;
    results = results || [];

    // For each of the results, if any are truthy then the value is not unique
    results.forEach(function(res) {
      if(res) unique = false;
    });

    // If unique is false return an error
    if(!unique) return cb(Errors.adapter.notUnique);

    // If everything was unique, create each of the indexes
    async.each(indices, function(idx, next) {
      idx.index(data[idx.name], next);
    }, function(err) {
      if(err) return cb(err);
      cb();
    });
  });
};
<|endoftext|>


<commit_before>
/**
 * control class
 *
 * @class control
 */
"use strict"

var prime = require("prime/index"),
	Component = require('../component'),
	Element = require('../element'),
	Options = require('prime-util/prime/options'),
	Emitter = require("prime/emitter"),
	bind = require('../module/bind'),
	merge = require("deepmerge"),
	$ = require("elements"),
	zen = require('elements/zen');

var	_log = __debug('material:button');
//	_log.defineLevel('DEBUG');

var Button = prime({

	inherits: Component,

	mixin: [Options, Emitter, bind],

	name: 'button',
	options: {
		name: 'button',
		type: null, // push, file
		ink: true,
		element: {
			tag: 'span'
		},
		bind: {
			'sensor.click': '_onClick',
			'sensor.dblclick': '_onDblClick',
			'sensor.mousedown': '_onClick',
			'sensor.mouseup': '_onMouseUp',
			'sensor.mouseleave': '_onMouseLeave'
		}
	},

	/**
	 * Constructor
	 * @param  {Object} options [description]
	 * @return {Object}         [description]
	 */
	constructor: function(options){
		_log.debug('constructor', options);

		this.options = merge(Button.parent.options, this.options);
		this.setOptions(options);

		_log.debug('option ', this.options);

		this.emit('init');

		this.init();
		this.build();

		if (this.options.bind) {
			this.bind(this.options.bind);
		}

		return this;
	},

	/**
	 * [set description]
	 */
	set: function() {},

	/**
	 * [build description]
	 * @return {void}
	 */
	build: function() {
		Button.parent.build.call(this);

		_log.debug('build', this.element);

		var opts = this.options;
		var type = opts.type;

		opts.label = opts.label || opts.n;

		if (type === null) {
			type = 'icon-text';
		}

		/*if (opts.text && type != 'icon') {
			this.element.set('html', opts.text);
		}*/
		//var text = opts.type.match(/text/g);

		if (opts.name) {
			this.element.setAttribute('data-name', opts.name);
		}

		//this.element.attributes('title', opts.text);

		if (opts.icon) {
			this._initIcon(type, opts.icon || opts.name);
		}

		var pos = 'bottom';
		if (type === 'text-icon') pos = 'top';

		if (this.options.label !== null) {
			var text = opts.label || opts.text;
			this.label = new Element('span.ui-text').inject(this.element, pos);
			this.label.text(text);
		}

		if (opts.ink) {
			_log.debug('call _initSensor');
			this._initSensor();
		} else {
			this.sensor = this.element;
		}
	},

	/**
	 * [_initIcon description]
	 * @param  {string} type
	 * @return {string}
	 */
	_initIcon: function(type, name) {
		_log.debug('_initIcon', type, name);

		var tag = 'span';
		var code = name;
		var klss = null;

		var prop = {
			'class': 'ui-icon'
		};

		this.icon = new Element(tag, prop).inject(this.element);

		if (mnml.icon.mdi[name]) {
			//_log.debug('mdi');
			klss = 'icon-mdi';
			code = mnml.icon.mdi[name];
		} else if (mnml.icon.font[name]) {
			//_log.debug('iocn font name', name);
			klss = 'icon-font';
			code = mnml.icon.font[name];
		}

		if (klss) {
			this.icon.addClass(klss);
		}

		if (code) {
			this.icon.addClass(code);
		}
	},

	/**
	 * [_initText description]
	 * @param  {string} type
	 * @return {void}
	 */
	_initLabel: function(type) {
		var opts = this.options;

	},

	/**
	 * [_initText description]
	 * @return {void}
	 */
	_initSensor: function() {
		_log.debug('_initSensor', this.element);

		this.sensor = new Element('div', {
			'class': 'ui-sensor'
		}).inject(this.element);

		_log.debug('sensor', this.sensor);
	},

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {Object}
	 */
	_initInk: function(e) {
		_log.debug('_onElementMouseDown', e);

		var element = this.element;

		var x = e.offsetX;
		var y = e.offsetY;

		var height = element.compute('height').replace('px', '');
		var width = element.compute('width').replace('px', '');

		var ink = this.ink = new Element('span', {
			class: 'ui-ink',
		}).inject(element, 'top');

		this.ink.style({
			left: x,
			top: y
		});

		this._touchInk(ink, x, y, width, height);

		this.emit('mousedown');
	},

	/**
	 * [_initEffect description]
	 * @param  {string} ink
	 * @param  {string} x
	 * @param  {string} y
	 * @param  {Object} coord
	 * @return {void}
	 */
	_touchInk: function(ink, x, y, width, height) {
		_log.debug('_touchInk', x, y, width, height);

		var size = height;
		var top = 0;
		var duration = '1s';

		this.ink = ink;

		if (width > size) {
			size = width;
			top = (height - width) / 2;
		}

		this.ink.animate({'height': size+'px'}, {duration: duration}, {equation: 'ease-out'});
		this.ink.animate({'width': size+'px'}, {duration: duration}, {equation: 'ease-out'});
		this.ink.animate({'left': '0px'}, {duration: duration}, {equation: 'ease-out'});
		this.ink.animate({'top': top}, {duration: duration}, {equation: 'ease-out'});
		this.ink.animate({'opacity': '0'}, {duration: duration, equation: 'ease-out'});

		var wait = duration.replace('s','')*1000;

		setTimeout(function() {
			ink.destroy();
		}, wait);
	},

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {void}
	 */
	_onClick: function(e) {
		_log.debug('_onClick', e);

		if (this.state === 'disabled') {
			return;
		}

		var opts = this.options;

		e.stopPropagation();

		if (opts.emit && this.state !== 'disabled') {
			this.emit(opts.emit);
		}

		this._initInk(e);

		this.emit('press', opts.emit);
		this.emit('pressed', opts.emit);
	},

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {void}
	 */
	_onDblClick: function(e) {
		_log.debug('_onDblClick', e);
		var opts = this.options;

		e.stopPropagation();

		if (opts.emit && this.state !== 'disabled') {
			this.emit('dblpress', opts.emit);
		}

		this.emit('dblpressed', opts.emit);
	},

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {void}
	 */
	_onMouseLeave: function(e) {
		_log.debug('_onDblClick', e);
		var opts = this.options;

		e.stopPropagation();

		if (opts.emit && this.state !== 'disabled') {
			this.emit('dblpress', opts.emit);
		}

		this.emit('dblpressed', opts.emit);
	},

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {void}
	 */
	_onMouseEnter: function(e) {
		_log.debug('_onDblClick', e);
		var opts = this.options;

		e.stopPropagation();

		if (opts.emit && this.state !== 'disabled') {
			this.emit('dblpress', opts.emit);
		}

		this.emit('dblpressed', opts.emit);
	},

	/**
	 * [_onElementMouseUp description]
	 * @return {void}
	 */
	_onMouseUp: function(e) {
		//_log.debug('_onElementMouseUp', e);

		if (this.options.type === 'check') {
			if (this.state === 'checked') {
				this.setState(null);
			} else {
				this.setState('checked');
			}
		}

		//this.react.destroy();
	}

});

module.exports = Button;
<commit_msg>clean up and doc<commit_after>
/**
 * Button control class
 * @class Button
 * @extends {Control}
 * @param {Object} options Default options for view
 * @since 0.0.1
 * @author Jerome Vial
 *
 * @type {Class}
 */
'use strict';

var prime = require("prime/index"),
	Component = require('../component'),
	Element = require('../element'),
	Options = require('prime-util/prime/options'),
	Emitter = require("prime/emitter"),
	bind = require('../module/bind'),
	merge = require("deepmerge"),
	$ = require("elements"),
	zen = require('elements/zen');

var	_log = __debug('material:button');
//	_log.defineLevel('DEBUG');

var Button = prime({

	inherits: Component,

	mixin: [Options, Emitter, bind],

	name: 'button',
	options: {
		name: 'button',
		type: null, // push, file
		element: {
			tag: 'span'
		},
		ink: {
			duration: '.5s',
			equation: 'ease-out'
		},
		bind: {
			'sensor.click': 'press',
			'sensor.dblclick': '_onDblClick',
			'sensor.mousedown': '_onMouseDown',
			'sensor.mouseup': '_onMouseUp',
			'sensor.mouseleave': '_onMouseLeave',
			'sensor.mouseenter': '_onMouseEnter'
		}
	},

	/**
	 * Constructor
	 * @param  {Object} options [description]
	 * @return {Object}         [description]
	 */
	constructor: function(options){
		_log.debug('constructor', options);

		this.options = merge(Button.parent.options, this.options);
		this.setOptions(options);

		_log.debug('option ', this.options);

		this.emit('init');

		this.init();
		this.build();

		if (this.options.bind) {
			this.bind(this.options.bind);
		}

		return this;
	},

	/**
	 * [build description]
	 * @return {void}
	 */
	build: function() {
		Button.parent.build.call(this);

		_log.debug('build', this.element);

		var opts = this.options;
		var type = opts.type;

		opts.label = opts.label || opts.n;

		if (type === null) {
			type = 'icon-text';
		}

		if (opts.name) {
			this.element.setAttribute('data-name', opts.name);
		}

		this.element.setAttribute('title', opts.text);

		if (opts.icon) {
			this._initIcon(type, opts.icon || opts.name);
		}

		this._initLabel(type);

		if (opts.ink) {
			_log.debug('call _initSensor');
			this._initSensor();
		} else {
			this.sensor = this.element;
		}
	},

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {void}
	 */
	press: function(e) {
		_log.debug('press', e);
		e.stopPropagation();
		var opts = this.options;

		if (this.state === 'disabled') {
			return;
		}

		this._initInk(e);

		if (opts.emit) {
			this.emit(opts.emit);
		}

		this.emit('press', opts.emit);
		this.emit('pressed', opts.emit);

		return this;
	},

	/**
	 * [_initIcon description]
	 * @param  {string} type
	 * @return {string}
	 */
	_initIcon: function(type, name) {
		_log.debug('_initIcon', type, name);

		var tag = 'span';
		var code = name;
		var klss = null;

		var prop = {
			'class': 'ui-icon'
		};

		this.icon = new Element('span', prop).inject(this.element);

		if (klss) {
			this.icon.addClass(klss);
		}

		if (code) {
			this.icon.addClass(code);
		}
	},

	/**
	 * [_initLabel description]
	 * @param  {string} type
	 * @return {void}
	 */
	_initLabel: function(type) {
		var options = this.options;

		var position = 'bottom';
		if (type === 'text-icon')  {
			position = 'top';
		}

		if (this.options.label !== null) {
			var text = options.label || options.text;
			this.label = new Element('span.ui-text').inject(this.element, position);
			this.label.text(text);
		}
	},

	/**
	 * [_initText description]
	 * @return {void}
	 */
	_initSensor: function() {
		_log.debug('_initSensor', this.element);

		this.sensor = new Element('div.ui-sensor')
			.inject(this.element);
	},

	// INK

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {Object}
	 */
	_initInk: function(e) {
		_log.debug('_onElementMouseDown', e);

		var element = this.element;

		var x = e.offsetX;
		var y = e.offsetY;

		var height = element.compute('height').replace('px', '');
		var width = element.compute('width').replace('px', '');

		var ink = this.ink = new Element('span.ui-ink').inject(element, 'top');

		this.ink.style({
			left: x - 5,
			top: y - 5
		});

		this._touchInk(ink, width, height);

		this.emit('mousedown');
	},

	/**
	 * [_initEffect description]
	 * @param  {string} ink
	 * @param  {string} x
	 * @param  {string} y
	 * @param  {Object} coord
	 * @return {void}
	 */
	_touchInk: function(ink, width, height) {
		_log.debug('_touchInk', width, height);

		var size = height;
		var top = 0;
		var options = this.options.ink;

		this.ink = ink;

		if (width > size) {
			size = width;
			top = (height - width) / 2;
		}

		this.animate({
			height: size+'px',
			width: size+'px',
			left: '0',
			top: top,
			opacity: '0'
		}, options.duration, options.equation);

		var wait = options.duration.replace('s','')*1000;

		setTimeout(function() {
			ink.destroy();
		}, wait);
	},

	/**
	 * [animate description]
	 * @param  {[type]} object   [description]
	 * @param  {[type]} duration [description]
	 * @param  {[type]} equation [description]
	 * @return {[type]}          [description]
	 */
	animate: function(object, duration, equation) {

		for (var key in object) {
			 if (object.hasOwnProperty(key)) {
			 	var prop = {};
			 	prop[key] = object[key];

				this.ink.animate(
					prop,
					{duration: duration},
					{equation: equation}
				);
			}
		}
	},

	// EVENTS

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {void}
	 */
	_onDblClick: function(e) {
		_log.debug('_onDblClick', e);
		var opts = this.options;

		e.stopPropagation();

		this.emit('dblpressed', opts.emit);
		return this;
	},

	/**
	 * [_onElementMouseUp description]
	 * @return {void}
	 */
	_onMouseDown: function(e) {
		_log.debug('_onMouseDown', e);

		return this;
	},

	/**
	 * [_onElementMouseUp description]
	 * @return {void}
	 */
	_onMouseUp: function(e) {
		_log.debug('_onMouseUp', e);

		return this;
	},

	/**
	 * [_onElementMouseUp description]
	 * @return {void}
	 */
	_onMouseLeave: function(e) {
		_log.debug('_onMouseLeave', e);

		console.log('mouseleave', e);

		return this;
	},

	/**
	 * [_onElementMouseDown description]
	 * @param  {event} e
	 * @return {void}
	 */
	_onMouseEnter: function(e) {
		_log.debug('_onMouseEnter', e);
		var opts = this.options;

		this.emit('mouseenter', opts.emit);

		return this;
	}

});

module.exports = Button;
<|endoftext|>


<commit_before>#!/usr/bin/env node

var dists = require('../dockerfiles').dists
var request = require('request')
var url = "https://hub.docker.com/v2/repositories/nodesource/"
var url_tail = "/tags"
var async = require('async')
var columnify = require('columnify')
var colors = require('colors/safe')
var full_version = /^.*[0-9]*\.[0-9]\.[0-9]$/

var output = []

var count = 0
console.log(colors.green("ADD (missing on registry)")+"\n"+colors.red("REMOVE (missing locally)")+"\n")
for(dist in dists) {
  for(release in dists[dist]) {
    count++
    if( dist == 'centos' ||
      dist == 'fedora') patchedRelease = dist+release
    else patchedRelease = release
    ;(function scopeVars(dist,release) {
      getTags(patchedRelease, function doneGettingTags(e,tags) {
        if(e) return console.log(e)
        for(project in dists[dist][release]) {
          for(version in dists[dist][release][project]) {
            var tag = version
            if(project !== 'node') tag = project+'-'+tag
            var location = tags.indexOf(tag)
            if(location === -1) {
              output.push({
                Name: colors.green('master'),
                Dockerfile: colors.green('/'+dist+'/'+release+'/'+project+'/'+version),
                Tag: colors.green(tag)
              })
            } else {
              tags.splice(location,1)
            }
          } // end version
        } // end project
        for( tag in Object.keys(tags) ) {
          if(!tags[tag].match(full_version)) continue;
          output.push({
            Name: colors.red('master'),
            Dockerfile: colors.red('/'+dist+'/'+release+'/???'),
            Tag: colors.red(tags[tag])
          })
        }
        count--
        if(count===0) console.log(columnify(output))
      }) // end getTags
    })(dist,release)
  } // end release
} // end dist

function getTags(release,cb) {
  var tags = []
  var full_url = url + release + url_tail
  async.during(function getPartialTagList(cb) {
    return cb(null,full_url)
  },function doNothing(cb) {
    request(full_url,function(e,req,body) {
      if(e) return cb(e)
      try {
        body =JSON.parse(body)
        var new_tags = body.results.map(function getTagsFromResult(c) {
          return c.name
        })
        tags = tags.concat(new_tags)
        full_url = body.next
        return cb()
      } catch(e) {
        return cb(e)
      }
    })
  },function doneGettingTags(e) {
    return cb(e,tags)
  })
}
<commit_msg>Support tags on images<commit_after>#!/usr/bin/env node

var dists = require('../dockerfiles').dists
var request = require('request')
var url = "https://hub.docker.com/v2/repositories/nodesource/"
var url_tail = "/build"
var async = require('async')
var columnify = require('columnify')
var colors = require('colors/safe')
var full_version = /^.*[0-9]*\.[0-9]\.[0-9]$/

console.log(colors.green("ADD (missing on registry)"))
console.log(colors.red("REMOVE (missing locally)"))
console.log(colors.yellow("MODIFY (incorrect entry)"))
console.log()

// count keeps track of how many images we are going to process so that we
// know when to print our report
var count = 0

// output holds all of the table data for our final report, so we can pass
// to columnify for pretty output
var output = []

// Alias jessie => node
dists["alias"] = {"node":dists["jessie"]}

for(dist in dists) {

  for(release in dists[dist]) {

    count++

    // Patched release fixes the release name to match the Docker hub tag
    var patchedRelease = ""
    if( dist == 'centos' || dist == 'fedora') {
      patchedRelease = dist+release
    } else if( release === "node") {
      patchedRelease = "jessie"
    } else {
      patchedRelease = release
    }

    // Scope the variables dist and release so they survive yielding
    // to the event loop.
    ;(function scopeVars(dist,release) {

      getTags(patchedRelease, function doneGettingTags(e,tags) {
        if(e) return console.log(e.stack)

        var patchedRelease = ""
        if( dist == 'centos' || dist == 'fedora') {
          patchedRelease = dist+release
        } else {
          patchedRelease = release
        }

        for(project in dists[dist][release]) {

          var versions = Object.keys(dists[dist][release][project])

          // No need to process projects with no tags, simplifies logic later
          if(versions.length === 0) continue;

          // Get a hash of all tags that should exist
          var localTags = generateTags(dist,release,project,versions)

          // Ensure local tags exist externally
          verifyTags(localTags,tags,output)
        }

        // Any tags which haven't been found locally are broken
        for( tag in Object.keys(tags) ) {
          output.push({
            Repo: colors.red(patchedRelease),
            Name: colors.red(tags[tag].branch),
            Dockerfile: colors.red(tags[tag].path),
            Tag: colors.red(tags[tag].tag)
          })
        }

        // Finished processing a release
        count--

        // If we are finished processing all releases, print report.
        if(count===0) {
          console.log(columnify(output))
        }

      }) // end getTags

    })(dist,release)

  } // end release

} // end dist

/* Compute all tags, including aliases */
function generateTags(dist,release,project,versions) {
  // Versions holds an array of arrays. Each index of the subarray
  // correpsonds to the major, minor, and patch respectively. This
  // simplifies the logic later.
  var versions = Object.keys(dists[dist][release][project])
    .sort()
    .map(function(v) {
      return v.split(".")
    })

  // Get the prefix for a tag if necessary
  var tagPrefix = (project === "node") ? "" : project + "-"

  var majors = versions.reduce(function(p,c) {
    if(c[0]==="0") return p
    p[tagPrefix+c[0]] = {
      dist: dist,
      release: release,
      project: project,
      version: c.join('.'),
      path: '/'+dist+'/'+release+'/'+project+'/'+c.join('.')
    }
    return p
  },[])

  var minors = versions.reduce(function(p,c) {
    p[tagPrefix+c[0]+"."+c[1]] = {
      dist: dist,
      release: release,
      project: project,
      version: c.join('.'),
      path: '/'+dist+'/'+release+'/'+project+'/'+c.join('.')
    }
    return p
  },[])

  var patches = versions.reduce(function(p,c) {
    p[tagPrefix+c.join('.')] = {
      dist: dist,
      release: release,
      project: project,
      version: c.join('.'),
      path: '/'+dist+'/'+release+'/'+project+'/'+c.join('.')
    }
    return p
  },[])

  var latest = []
  var latestVersion = versions.pop().join(".")

  if(project === "node") {
    latest["latest"] = {
      dist: dist,
      release: release,
      project: project,
      version: latestVersion,
      path: '/'+dist+'/'+release+'/'+project+'/'+latestVersion
    }
  } else {
    latest[project] = {
      dist: dist,
      release: release,
      project: project,
      version: latestVersion,
      path: '/'+dist+'/'+release+'/'+project+'/'+latestVersion
    }
  }

  versions = objectConcat(majors, minors, patches, latest)
  return versions
}

function verifyTags(versions, tags, output) {
  for( version in versions ) {
    var location = tags.map(function(v) {
      return v.tag
    }).indexOf(version)
    if(location === -1) {
      var release = (versions[version].dist === "centos" || versions[version].dist === "fedora") ?
        versions[version].dist + versions[version].release : versions[version].release
      output.push({
        Repo: colors.green(release),
        Name: colors.green('master'),
        Dockerfile: colors.green(versions[version].path),
        Tag: colors.green(version)
      })
    }
    else {
      verifyEntry(versions[version],tags[location],output)
      tags.splice(location,1)
    }
  }
}

function verifyEntry(local,remote,output) {
  var release = (local.dist === "centos" || local.dist === "fedora") ?
    local.dist + local.release : local.release
  var obj = {
    Repo: colors.yellow(release),
    Name: colors.yellow('master'),
    Dockerfile: colors.yellow(remote.path),
    Tag: colors.yellow(remote.tag)
  }

  var log = false

  if(remote.branch !== 'master') {
    obj.Name = color.yellow(remote.branch+"=>master")
    log = true
  }
  if(remote.path !== local.path) {
    obj.Dockerfile = colors.yellow(remote.path+" => "+local.path)
    log = true
  }

  if(log) {
    output.push(obj)
  }
}

function getTags(release,cb) {
  var tags = []
  var full_url = url + release + url_tail

  request(full_url,function(e,req,body) {
    if(e) return cb(e)
    try {
      body =JSON.parse(body)
      var new_tags = body.build_tags.map(function getTagsFromResult(c) {
        return {
          "tag": c.name,
          "branch" : c.source_name,
          "path": c.dockerfile_location
        }
      })
      tags = tags.concat(new_tags)
      full_url = body.next
      return cb(null,tags)
    } catch(e) {
      return cb(e)
    }
  })
}

function objectConcat() {
  var ret = {};
  var len = arguments.length;
  for (var i=0; i<len; i++) {
    for (p in arguments[i]) {
      if (arguments[i].hasOwnProperty(p)) {
        ret[p] = arguments[i][p];
      }
    }
  }
  return ret;
}
<|endoftext|>


<commit_before>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../util/Pool.js";
import * as Recyclable from "../util/Recyclable.js";
//import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import * as NeuralNet from "../Conv/NeuralNet.js";
import * as DEvolution from "../DEvolution.js";
import { Proxy as NeuralWorker_Proxy } from "./NeuralWorker_Proxy.js";
import { Mode as NeuralWorker_Mode } from "./NeuralWorker_Mode.js";

//!!! ...unfinished... (2022/05/25)
// Perhaps, Using SharedArrayBuffer to pass input image between different web worker.
//

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 *
 * 1. For NeuralWorker_Mode.Singleton.Ids.ONE_WORKER__Xxx
 *
 * Since it is slow to transfer data between CPU and GPU (for WebGL), it seems not
 * feasible to use multiple workers (which will transfer data between CPU and GPU
 * multiple times) to process both competition side of an diffential evolution
 * iteration.
 *
 * So these ONE_WORKER__Xxx mode combine two neural networks of both competition
 * sides in one web worker. Although they compute serially (not parallelly), they
 * still may be faster than compute them in two worker because the reduced memory
 * transferring between CPU and GPU.
 *
 *
 * 2. For NeuralWorker_Mode.Singleton.Ids.TWO_WORKER__Xxx
 *
 * Every worker handles one neural network. When .ImageData_process_async() is called,
 * the input (usually a large memory block) will be transffered to the 1st worker to
 * start computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same time. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' results in a promise. The
 * promise will resolve with an array of Float32Array. Every Float32Array is the
 * output of one neural network.
 *
 *
 *
 * @member {string} weightsSpreadsheetId
 *   The Google Sheets spreadsheetId of neural network weights. Every worker will
 * load weights from the spreadsheet to initialize one neural network.
 *
 * @member {string} weightsAPIKey
 *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
 *   - If null, Google Visualization Table Query API will be used.
 *   - If not null, Google Sheets API v4 will be used.
 *
 * @member {numner} nNeuralWorker_ModeId
 *   The numeric identifier of neural worker mode (i.e.
 * NeuralWorker.Mode.Singleton.Ids.Xxx).
 *
 * @member {number} neuralNetCount
 *   There are how many neural networks created. It is always 2 (because of differential
 * evolution) no matter how totalWorkerCount is.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {DEvolution.VersusSummary} evolutionVersusSummary
 *   The range list of all differential evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    this.evolutionVersusSummary_dispose();
    this.disposeWorkers();

    this.hardwareConcurrency = undefined;
    this.neuralNetCount = undefined;

    this.nNeuralWorker_ModeId = undefined;
    this.weightsAPIKey = undefined;
    this.weightsSpreadsheetId = undefined;

    super.disposeResources();
  }

  /**
   *
   */
   disposeWorkers() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }
  }

  /**
   * Initialize this worker proxy controller. It will create two neural networks in
   * one or two web worker(s).
   *
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async init_async( weightsSpreadsheetId, weightsAPIKey, nNeuralWorker_ModeId ) {

    // 0.
    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;
    this.nNeuralWorker_ModeId = nNeuralWorker_ModeId;

    this.neuralNetCount = 2; // Always two neural network (for differential evolution).
    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //
    // Note1: How could the two neural networks determine all the actions of so many
    //        game objects? The method is to let the output of one neural network
    //        contains all actions of all game objects.
    //
    // Note2: For NeuralWorker_Mode whose ( bFill == false ), only half of the output
    //        actions (i.e. actions of one alignment in the game world) will be used
    //        in one time.
    //
    let totalWorkerCount = NeuralWorker_Mode.workerCount_get( nNeuralWorker_ModeId );

    // 1. Differential Evolution Versus Summary.
    this.evolutionVersusSummary_dispose();
    this.evolutionVersusSummary = DEvolution.VersusSummary.Pool.get_or_create_by(
      this.weightsSpreadsheetId, this.weightsAPIKey );

    // 2. Web workers.
    let initOkArray;
    {
      // 2.0 Prepare container of all worker proxy.
      {
        if ( this.workerProxyArray )
          this.workerProxyArray.clear();
        else
          this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();

        this.workerProxyArray.length = totalWorkerCount;
      }

      // 2.1 Create workers.
      let initPromiseArray = new Array( totalWorkerCount );
      for ( let i = 0; i < totalWorkerCount; ++i ) {
        this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
        initPromiseArray[ i ] = this.workerProxyArray[ i ].initWorker_async( i );
      }

      initOkArray = await Promise.all( initPromiseArray );
    }

    // 2.2 Summary workers.
    let initOk = initOkArray.reduce(
      ( previousValue, currentValue ) => ( previousValue && currentValue ),
      true
    );

    // 3. Determine .apply_async
    NeuralWorker_Proxies.setup_apply.call( this );

    return initOk;
  }

  /** */
  evolutionVersusSummary_dispose() {
    if ( this.evolutionVersusSummary ) {
      this.evolutionVersusSummary.disposeResources_and_recycleToPool();
      this.evolutionVersusSummary = null;
    }
  }

//!!! ...unfinished... (2022/09/21)

  /** Load all differential evolution versus weights ranges. */
  async evolutionVersusSummary_load_async() {

    this.evolutionVersusSummary.rangeArray_load_async();

//!!! ...unfinished... (2022/08/27)

  }

  /**
   * Create neural networks in the web worker body.
   *
   * @param {NeuralNet.ParamsBase[]} neuralNetParamsBaseArray
   *   An array of configurations for the neural network to be created. These
   * configurations (exclude the array) will be owned (i.e. kept and destroyed)
   * by this NeuralWorker.Proxy.
   *
   * @param {ArrayBuffer[]} weightArrayBufferArray
   *   An array of every neural network's weights. Every element  will be interpreted
   * as Float32Array.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async NeuralNetArray_create_async( neuralNetParamsBaseArray, weightArrayBufferArray ) {

    if ( neuralNetParamsBaseArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `neuralNetParamsBaseArray.length ( ${neuralNetParamsBaseArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    if ( weightArrayBufferArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `weightArrayBufferArray.length ( ${weightArrayBufferArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let createOk;

    // 1. Every worker create one neural network.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let createPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        createPromiseArray[ i ] = this.workerProxyArray[ i ].NeuralNetArray_create_async(
          [ neuralNetParamsBaseArray[ i ] ], [ weightArrayBufferArray[ i ] ] );
      }

      let createOkArray = await Promise.all( createPromiseArray );

      createOk = createOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker creates all neural networks.
    } else {
      createOk = await this.workerProxyArray[ 0 ].NeuralNetArray_create_async(
        neuralNetParamsBaseArray, weightArrayBufferArray );
    }

    return createOk;
  }

  /**
   * @param {integer[]} markValueArray
   *   An array of values representing every neural network playing which alignment.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async alignmentMarkArray_setValue_async( markValueArray ) {

    if ( markValueArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.alignmentMarkArray_setValue_async(): `
        + `markValueArray.length ( ${markValueArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let resultOk;

    // 1. Every worker set one alignment mark value.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let resultPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        resultPromiseArray[ i ]
          = this.workerProxyArray[ i ].alignmentMarkArray_setValue_async(
            [ markValueArray[ i ] ] );
      }

      let resultOkArray = await Promise.all( resultPromiseArray );

      resultOk = resultOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker sets all alignment mark values.
    } else {
      resultOk = await this.workerProxyArray[ 0 ].alignmentMarkArray_setValue_async(
        markValueArray );
    }

    return resultOk;
  }

  /**
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. It will be scaled to the correct shape
   * before passed into the neural network.
   *
   * @return {Promise}
   *   Return a promise resolved to an array of Float32Array representing the neural
   * networks' result.
   */
  ImageData_process_async( sourceImageData ) {
    return this.apply_async( sourceImageData );
  }

  /**
   * Setup .apply_async according to .nNeuralWorker_ModeId.
   *
   * @param {NeuralWorker_Proxies} this
   */
  static setup_apply() {
    switch ( this.nNeuralWorker_ModeId ) {
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__FILL: // (0)
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__NO_FILL: // (1)
        this.apply_async
          = NeuralWorker_Proxies.apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__FILL: // (2)
      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__NO_FILL: // (3)
        this.apply_async
          = NeuralWorker_Proxies.apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__TWO_SCALE__NO_FILL: // (4)
        this.apply_async = NeuralWorker_Proxies.apply__TWO_WORKER__TWO_SCALE__NO_FILL;
        break;

      default:
        throw Error( `NeuralWorker_Proxies.setup_apply(): `
          + `Unknown nNeuralWorker_ModeId ( ${nNeuralWorker_ModeId} ).`
        );
        break;
    }
  }

  /** */
  static async apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_promise = this.workerProxyArray[ 0 ]
      .ImageData_scale_once_process_multiple_async( sourceImageData, bFill );

    let worker0_value_Float32ArrayArray = await worker0_promise;
    return worker0_value_Float32ArrayArray;
  }

  /** */
  static async apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_fork_fillable_process_asyncGenerator( sourceImageData, bFill );

    let { done: worker0_done_false, value: worker0_value_Int32Array }
      = await worker0_resulter.next();

    let worker1_promise = this.workerProxyArray[ 1 ]
      .Int32Array_fillable_process_async( worker0_value_Int32Array, bFill );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      worker1_value_Float32Array
    ] = await Promise.all( [ worker0_resulter.next(), worker1_promise ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

  /** */
  static async apply__TWO_WORKER__TWO_SCALE__NO_FILL( sourceImageData ) {
    const worker0_bFork = true;
    const worker1_bFork = false;

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        sourceImageData, worker0_bFork );

    let { done: worker0_done_false, value: worker0_value_ImageData }
      = await worker0_resulter.next();

    let worker1_resulter = this.workerProxyArray[ 1 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        worker0_value_ImageData, worker1_bFork );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      { done: worker1_done_true, value: worker1_value_Float32Array },
    ] = await Promise.all( [ worker0_resulter.next(), worker1_resulter.next() ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

  get totalWorkerCount() {
    return this.workerProxyArray.length;
  }

}

<commit_msg>workerProxyArray_dispose<commit_after>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../util/Pool.js";
import * as Recyclable from "../util/Recyclable.js";
//import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import * as NeuralNet from "../Conv/NeuralNet.js";
import * as DEvolution from "../DEvolution.js";
import { Proxy as NeuralWorker_Proxy } from "./NeuralWorker_Proxy.js";
import { Mode as NeuralWorker_Mode } from "./NeuralWorker_Mode.js";

//!!! ...unfinished... (2022/05/25)
// Perhaps, Using SharedArrayBuffer to pass input image between different web worker.
//

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 *
 * 1. For NeuralWorker_Mode.Singleton.Ids.ONE_WORKER__Xxx
 *
 * Since it is slow to transfer data between CPU and GPU (for WebGL), it seems not
 * feasible to use multiple workers (which will transfer data between CPU and GPU
 * multiple times) to process both competition side of an diffential evolution
 * iteration.
 *
 * So these ONE_WORKER__Xxx mode combine two neural networks of both competition
 * sides in one web worker. Although they compute serially (not parallelly), they
 * still may be faster than compute them in two worker because the reduced memory
 * transferring between CPU and GPU.
 *
 *
 * 2. For NeuralWorker_Mode.Singleton.Ids.TWO_WORKER__Xxx
 *
 * Every worker handles one neural network. When .ImageData_process_async() is called,
 * the input (usually a large memory block) will be transffered to the 1st worker to
 * start computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same time. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' results in a promise. The
 * promise will resolve with an array of Float32Array. Every Float32Array is the
 * output of one neural network.
 *
 *
 *
 * @member {string} weightsSpreadsheetId
 *   The Google Sheets spreadsheetId of neural network weights. Every worker will
 * load weights from the spreadsheet to initialize one neural network.
 *
 * @member {string} weightsAPIKey
 *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
 *   - If null, Google Visualization Table Query API will be used.
 *   - If not null, Google Sheets API v4 will be used.
 *
 * @member {numner} nNeuralWorker_ModeId
 *   The numeric identifier of neural worker mode (i.e.
 * NeuralWorker.Mode.Singleton.Ids.Xxx).
 *
 * @member {number} neuralNetCount
 *   There are how many neural networks created. It is always 2 (because of differential
 * evolution) no matter how totalWorkerCount is.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {number} totalWorkerCount
 *   There are how many web worker(s) created.
 *
 * @member {DEvolution.VersusSummary} evolutionVersusSummary
 *   The range list of all differential evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    this.evolutionVersusSummary_dispose();
    this.workerProxyArray_dispose();

    this.hardwareConcurrency = undefined;
    this.neuralNetCount = undefined;

    this.nNeuralWorker_ModeId = undefined;
    this.weightsAPIKey = undefined;
    this.weightsSpreadsheetId = undefined;

    super.disposeResources();
  }

  /**
   * Initialize this worker proxy controller. It will create two neural networks in
   * one or two web worker(s).
   *
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async init_async( weightsSpreadsheetId, weightsAPIKey, nNeuralWorker_ModeId ) {

    // 0.
    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;
    this.nNeuralWorker_ModeId = nNeuralWorker_ModeId;

    this.neuralNetCount = 2; // Always two neural network (for differential evolution).
    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //
    // Note1: How could the two neural networks determine all the actions of so many
    //        game objects? The method is to let the output of one neural network
    //        contains all actions of all game objects.
    //
    // Note2: For NeuralWorker_Mode whose ( bFill == false ), only half of the output
    //        actions (i.e. actions of one alignment in the game world) will be used
    //        in one time.
    //
    let totalWorkerCount = NeuralWorker_Mode.workerCount_get( nNeuralWorker_ModeId );

    // 1. Differential Evolution Versus Summary.
    this.evolutionVersusSummary_dispose();
    this.evolutionVersusSummary = DEvolution.VersusSummary.Pool.get_or_create_by(
      this.weightsSpreadsheetId, this.weightsAPIKey );

    // 2. Web workers.
    let initOkArray;
    {
      // 2.0 Prepare container of all worker proxy.
      {
        if ( this.workerProxyArray )
          this.workerProxyArray.clear(); // Release old worker proxy.
        else
          this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();

        this.workerProxyArray.length = totalWorkerCount;
      }

      // 2.1 Create workers.
      let initPromiseArray = new Array( totalWorkerCount );
      for ( let i = 0; i < totalWorkerCount; ++i ) {
        this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
        initPromiseArray[ i ] = this.workerProxyArray[ i ].initWorker_async( i );
      }

      initOkArray = await Promise.all( initPromiseArray );
    }

    // 2.2 Summary workers.
    let initOk = initOkArray.reduce(
      ( previousValue, currentValue ) => ( previousValue && currentValue ),
      true
    );

    // 3. Determine .apply_async
    NeuralWorker_Proxies.setup_apply.call( this );

    return initOk;
  }

  /** */
  workerProxyArray_dispose() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }
  }

  /** */
  evolutionVersusSummary_dispose() {
    if ( this.evolutionVersusSummary ) {
      this.evolutionVersusSummary.disposeResources_and_recycleToPool();
      this.evolutionVersusSummary = null;
    }
  }

//!!! ...unfinished... (2022/09/21)

  /** Load all differential evolution versus weights ranges. */
  async evolutionVersusSummary_load_async() {

    this.evolutionVersusSummary.rangeArray_load_async();

//!!! ...unfinished... (2022/08/27)

  }

  /**
   * Create neural networks in the web worker body.
   *
   * @param {NeuralNet.ParamsBase[]} neuralNetParamsBaseArray
   *   An array of configurations for the neural network to be created. These
   * configurations (exclude the array) will be owned (i.e. kept and destroyed)
   * by this NeuralWorker.Proxy.
   *
   * @param {ArrayBuffer[]} weightArrayBufferArray
   *   An array of every neural network's weights. Every element  will be interpreted
   * as Float32Array.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async NeuralNetArray_create_async( neuralNetParamsBaseArray, weightArrayBufferArray ) {

    if ( neuralNetParamsBaseArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `neuralNetParamsBaseArray.length ( ${neuralNetParamsBaseArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    if ( weightArrayBufferArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.NeuralNetArray_create_async(): `
        + `weightArrayBufferArray.length ( ${weightArrayBufferArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let createOk;

    // 1. Every worker create one neural network.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let createPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        createPromiseArray[ i ] = this.workerProxyArray[ i ].NeuralNetArray_create_async(
          [ neuralNetParamsBaseArray[ i ] ], [ weightArrayBufferArray[ i ] ] );
      }

      let createOkArray = await Promise.all( createPromiseArray );

      createOk = createOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker creates all neural networks.
    } else {
      createOk = await this.workerProxyArray[ 0 ].NeuralNetArray_create_async(
        neuralNetParamsBaseArray, weightArrayBufferArray );
    }

    return createOk;
  }

  /**
   * @param {integer[]} markValueArray
   *   An array of values representing every neural network playing which alignment.
   *
   * @return {Promise}
   *   Return a promise:
   *   - Resolved to true, if success.
   *   - Resolved to false, if failed.
   */
  async alignmentMarkArray_setValue_async( markValueArray ) {

    if ( markValueArray.length != this.neuralNetCount )
      throw Error( `NeuralWorker.Proxies.alignmentMarkArray_setValue_async(): `
        + `markValueArray.length ( ${markValueArray.length} ) `
        + `should be ${this.neuralNetCount}.`
      );

    let resultOk;

    // 1. Every worker set one alignment mark value.
    if ( this.workerProxyArray.length > 1 ) { // (i.e. two workers)

      let resultPromiseArray = new Array( this.workerProxyArray.length );
      for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
        resultPromiseArray[ i ]
          = this.workerProxyArray[ i ].alignmentMarkArray_setValue_async(
            [ markValueArray[ i ] ] );
      }

      let resultOkArray = await Promise.all( resultPromiseArray );

      resultOk = resultOkArray.reduce(
        ( previousValue, currentValue ) => ( previousValue && currentValue ),
        true
      );

    // 2. The only worker sets all alignment mark values.
    } else {
      resultOk = await this.workerProxyArray[ 0 ].alignmentMarkArray_setValue_async(
        markValueArray );
    }

    return resultOk;
  }

  /**
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. It will be scaled to the correct shape
   * before passed into the neural network.
   *
   * @return {Promise}
   *   Return a promise resolved to an array of Float32Array representing the neural
   * networks' result.
   */
  ImageData_process_async( sourceImageData ) {
    return this.apply_async( sourceImageData );
  }

  /**
   * Setup .apply_async according to .nNeuralWorker_ModeId.
   *
   * @param {NeuralWorker_Proxies} this
   */
  static setup_apply() {
    switch ( this.nNeuralWorker_ModeId ) {
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__FILL: // (0)
      case NeuralWorker_Mode.Ids.ONE_WORKER__ONE_SCALE__NO_FILL: // (1)
        this.apply_async
          = NeuralWorker_Proxies.apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__FILL: // (2)
      case NeuralWorker_Mode.Ids.TWO_WORKER__ONE_SCALE__NO_FILL: // (3)
        this.apply_async
          = NeuralWorker_Proxies.apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL;
        break;

      case NeuralWorker_Mode.Ids.TWO_WORKER__TWO_SCALE__NO_FILL: // (4)
        this.apply_async = NeuralWorker_Proxies.apply__TWO_WORKER__TWO_SCALE__NO_FILL;
        break;

      default:
        throw Error( `NeuralWorker_Proxies.setup_apply(): `
          + `Unknown nNeuralWorker_ModeId ( ${nNeuralWorker_ModeId} ).`
        );
        break;
    }
  }

  /** */
  static async apply__ONE_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_promise = this.workerProxyArray[ 0 ]
      .ImageData_scale_once_process_multiple_async( sourceImageData, bFill );

    let worker0_value_Float32ArrayArray = await worker0_promise;
    return worker0_value_Float32ArrayArray;
  }

  /** */
  static async apply__TWO_WORKER__ONE_SCALE__FILL__OR__NO_FILL( sourceImageData ) {
    let bFill = NeuralWorker_Mode.bFill_get( this.nNeuralWorker_ModeId );

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_fork_fillable_process_asyncGenerator( sourceImageData, bFill );

    let { done: worker0_done_false, value: worker0_value_Int32Array }
      = await worker0_resulter.next();

    let worker1_promise = this.workerProxyArray[ 1 ]
      .Int32Array_fillable_process_async( worker0_value_Int32Array, bFill );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      worker1_value_Float32Array
    ] = await Promise.all( [ worker0_resulter.next(), worker1_promise ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

  /** */
  static async apply__TWO_WORKER__TWO_SCALE__NO_FILL( sourceImageData ) {
    const worker0_bFork = true;
    const worker1_bFork = false;

    let worker0_resulter = this.workerProxyArray[ 0 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        sourceImageData, worker0_bFork );

    let { done: worker0_done_false, value: worker0_value_ImageData }
      = await worker0_resulter.next();

    let worker1_resulter = this.workerProxyArray[ 1 ]
      .ImageData_scale_forkable_process_asyncGenerator(
        worker0_value_ImageData, worker1_bFork );

    let [
      { done: worker0_done_true, value: worker0_value_Float32Array },
      { done: worker1_done_true, value: worker1_value_Float32Array },
    ] = await Promise.all( [ worker0_resulter.next(), worker1_resulter.next() ] );

    return [ worker0_value_Float32Array, worker1_value_Float32Array ];
  }

  get totalWorkerCount() {
    return this.workerProxyArray.length;
  }

}

<|endoftext|>


<commit_before>/**
 * @file:   Resolver class capable of identifying nodes in a given tree by
 *          pattern matching.
 *
 * @see:    * Sebastian Rönnau, Christian Pauli, and Uwe M. Borghoff. Merging
 *            changes in XML documents using reliable context fingerprints:
 *            http://dx.doi.org/10.1145/1410140.1410151
 *          * Original Sourcecode:
 *            https://launchpad.net/xcc
 *
 * @module  resolver
 */

/** @ignore */
var tree = require('./tree');

/** @ignore */
var contextmatcher = require('./contextmatcher');

/**
 * Construct new resolver result instance.
 * @constructor
 */
function ResolverResult(refnode, base, tail) {
    this.anchor = new tree.Anchor(refnode, base, tail[0]);
    this.tail = tail.splice(1);
}

/**
 * Return true if the path has been resolved completely.
 */
ResolverResult.prototype.isComplete = function() {
    return this.tail.length === 0;
}

/**
 * Return the target node. This method will return undefined if the resolved
 * path pointed at a non-existant node.
 */
ResolverResult.prototype.getTarget = function() {
    if (this.isComplete()) {
        return this.anchor.target;
    }
}

/**
 * Return the child index of the target node. This method will return undefined
 * if the resolution was not complete. It also returns undefined if the path
 * points at the root node (tail.length === 0).
 */
ResolverResult.prototype.getTargetIndex = function() {
    if (this.isComplete()) {
        return this.anchor.index;
    }
}

/**
 * Return the anchor node, i.e. the parent of the target. This method will
 * return undefined if the resolution was not complete. It also returns
 * undefined if the path points at the root node (tail.length === 0).
 */
ResolverResult.prototype.getAnchor = function() {
    if (this.isComplete()) {
        return this.anchor.base;
    }
}

/**
 * Constructor for ContextResolver instances.
 *
 * @param refnode   A tree.Node, typically the root node
 * @param nodeindex An index class capable of accessing nodes by offset to
 *                  other nodes. Typically an instance of
 *                  DocumentOrderIndex should be used for this purpose.
 * @param radius    The search radius for the fuzzy matching algorithm
 * @param threshold The threshold of the fuzzy matching algorithm. A value
 *                  between 0.5 and 1. The authors of the xcc patching
 *                  algorithm recommend 0.7.
 * @param matcher   (optional) A matcher instance. Defaults to a
 *                  WeightedContextMatcher with radius=4.
 *
 * @constructor
 */
function ContextResolver(refnode, nodeindex, radius, threshold, matcher) {
    this.refnode = refnode;
    this.nodeindex = nodeindex;

    if (typeof radius === 'undefined') {
        radius = 4;
    }
    this.r = radius;

    if (typeof threshold === 'undefined') {
        threshold = 0.7;
    }
    this.t = threshold;

    this.matcher = matcher || new contextmatcher.WeightedContextMatcher(4);

    // Install custom equality tester for matcher
    this.matcher.equal = (function(that){
        return function(subject, offset, value, flag) {
            if (flag) {
                return that.equalContent(that.nodeindex.get(subject, offset), value, flag);
            }
            else {
                return that.equalContext(that.nodeindex.get(subject, offset), value);
            }
        };
    }(this));

    this.resolver = new exports.TopDownPathResolver(refnode);
}

/**
 * Compare a document node against a content node from the patch. Return
 * true if the docnode matches the patnode.
 *
 * Override this method if you use something different than the value
 * property of tree.Node.
 *
 * @param docnode   A candidate node from the document
 * @param patnode   A body-node from the pattern
 */
ContextResolver.prototype.equalContent = function(docnode, patnode) {
    return docnode === undefined ? patnode === undefined :
        docnode && patnode && docnode.value === patnode.value;
};

/**
 * Compare a document node against a context node value. Return true if
 * the value of docnode matches the pattern value.
 *
 * Override this method if you use something different than the value
 * property of tree.Node.
 *
 * @param docnode   A candidate node from the document
 * @param patnode   The value from a context node
 */
ContextResolver.prototype.equalContext = function(docnode, value) {
    return docnode === undefined ? value === undefined :
        docnode.value === value;
};

/**
 * Given an anchor and a nodeindex, this method identifies the node which
 * matches the anchor as close as possible.
 */
ContextResolver.prototype.getClosestNode = function(anchor, nodeindex) {
    var result, siblings, lastsib;

    if (anchor.target) {
        result = anchor.target;
    }
    else {
        siblings = anchor.base.children;
        if (siblings.length === 0) {
            // First guess has no children. Just use that.
            result = anchor.base;
        }
        else if (anchor.index < 0) {
            result = nodeindex.get(siblings[0], -1);
        }
        else if (anchor.index < siblings.length) {
            // Start with the appointed child node
            result = siblings[anchor.index];
        }
        else {
            // Resort to the last node in the subtree under the preceeding
            // sibling if top-down resolver did not came through to the very
            // last path component.
            lastsib = siblings[siblings.length-1];
            result = nodeindex.get(lastsib, nodeindex.size(lastsib) - 1);
        }
    }

    return result;
}

/**
 * Locate a node at the given path starting at refnode. Try to locate the
 * target within a given radius using the fingerprint values if direct
 * lookup failed.
 *
 * @param   path        An array of numbers. Each value represents an index
 *                      into the childrens of a node in top-down order.
 * @param   body        An array containing the node sequence in question.
 *                      When resolving the location of insert operations,
 *                      the array is empty.  For updates, the array will
 *                      consist of exactly one node. Remove operations may
 *                      consist of one or more nodes.
 * @param   head        Leading context: An array containing the values of
 *                      leading nodes in the same generation.
 * @param   tail        Trailing context: An array containing the values of
 *                      trailing nodes in the same generation.
 * @param   type        Operation type. This parameter is passed to the
 *                      equalContent callback.
 *
 * @returns A result object with two properties: node holds the resolved
 * tree.Node and tail the unresolved part of path. Returns undefined on
 * failure.
 */
ContextResolver.prototype.find = function(path, body, head, tail, type) {
    var guess, node, siblings, lastsib, i, q = 0, f, best, bestnode, result,
        flatbody;

    // Need a trueish value in order to differentiate context from content
    if (typeof type === 'undefined') {
        type = true;
    }

    if (path.length === 0) {
        // We are operating on the root node, initial guess is trivial.
        node = this.refnode;
    }
    else {
        // Start with an initial guess using the top-down path resolver.
        guess = this.resolver.resolve(path);
        node = this.getClosestNode(guess.anchor, this.nodeindex);
    }

    // concatenate all nodes contained in body into one array
    flatbody = [];
    body.forEach(function(n) {
        Array.prototype.push.apply(flatbody, this.nodeindex.flatten(n));
    }, this);

    // context verification and fuzzy matching
    if (node) {
        this.matcher.setPattern(flatbody, head, tail);
        for (i = -this.r; i <= this.r; i++) {
            f = this.matcher.matchQuality(node, i, type);
            if (f > q && f >= this.t) {
                q = f;
                best = i;
            }
        }
    }

    if (typeof best !== 'undefined') {
        if ((bestnode = this.nodeindex.get(node, best)) && bestnode.depth === path.length) {
            // construct the trail of nodes up to refnode and return it
            result = new ResolverResult(this.refnode, bestnode.par,
                    [bestnode.childidx].slice(0, path.length));
        }
        else if ((bestnode = this.nodeindex.get(node, best-1)) && bestnode.depth >= path.length - 1) {
            // strip path components until we get to the parent of the position
            // in question
            while (bestnode.depth > path.length - 1) {
                bestnode = bestnode.par;
            }

            result = new ResolverResult(this.refnode, bestnode,
                    [bestnode.children.length]);
        }
    }

    return result;
};

/**
 * Create a new instance of top-down path resolver
 *
 * @constructor
 */
function TopDownPathResolver(refnode) {
    this.refnode = refnode;
}

/**
 * Try to resolve the given path top-down. Return an object containing the last
 * internal node which was resolved properly as well as the unresolved tail of
 * the path. Note that leaf nodes are represented by their parent and a tail
 * containing their child-index.
 *
 * @param path  Array of integers
 * @returns A result object with two properties: node holds the resolved
 * tree.Node and tail the unresolved part of path.
 */
TopDownPathResolver.prototype.resolve = function(path) {
    var node = this.refnode,
        tail = path.slice();

    while (tail.length > 1) {
        if (node.children[tail[0]]) {
            node = node.children[tail[0]];
            tail.shift();
        }
        else {
            break;
        }
    }

    return new ResolverResult(this.refnode, node, tail);
};

exports.ResolverResult = ResolverResult;
exports.ContextResolver = ContextResolver;
exports.TopDownPathResolver = TopDownPathResolver;
<commit_msg>Extract identification of best match to separate method<commit_after>/**
 * @file:   Resolver class capable of identifying nodes in a given tree by
 *          pattern matching.
 *
 * @see:    * Sebastian Rönnau, Christian Pauli, and Uwe M. Borghoff. Merging
 *            changes in XML documents using reliable context fingerprints:
 *            http://dx.doi.org/10.1145/1410140.1410151
 *          * Original Sourcecode:
 *            https://launchpad.net/xcc
 *
 * @module  resolver
 */

/** @ignore */
var tree = require('./tree');

/** @ignore */
var contextmatcher = require('./contextmatcher');

/**
 * Construct new resolver result instance.
 * @constructor
 */
function ResolverResult(refnode, base, tail) {
    this.anchor = new tree.Anchor(refnode, base, tail[0]);
    this.tail = tail.splice(1);
}

/**
 * Return true if the path has been resolved completely.
 */
ResolverResult.prototype.isComplete = function() {
    return this.tail.length === 0;
}

/**
 * Return the target node. This method will return undefined if the resolved
 * path pointed at a non-existant node.
 */
ResolverResult.prototype.getTarget = function() {
    if (this.isComplete()) {
        return this.anchor.target;
    }
}

/**
 * Return the child index of the target node. This method will return undefined
 * if the resolution was not complete. It also returns undefined if the path
 * points at the root node (tail.length === 0).
 */
ResolverResult.prototype.getTargetIndex = function() {
    if (this.isComplete()) {
        return this.anchor.index;
    }
}

/**
 * Return the anchor node, i.e. the parent of the target. This method will
 * return undefined if the resolution was not complete. It also returns
 * undefined if the path points at the root node (tail.length === 0).
 */
ResolverResult.prototype.getAnchor = function() {
    if (this.isComplete()) {
        return this.anchor.base;
    }
}

/**
 * Constructor for ContextResolver instances.
 *
 * @param refnode   A tree.Node, typically the root node
 * @param nodeindex An index class capable of accessing nodes by offset to
 *                  other nodes. Typically an instance of
 *                  DocumentOrderIndex should be used for this purpose.
 * @param radius    The search radius for the fuzzy matching algorithm
 * @param threshold The threshold of the fuzzy matching algorithm. A value
 *                  between 0.5 and 1. The authors of the xcc patching
 *                  algorithm recommend 0.7.
 * @param matcher   (optional) A matcher instance. Defaults to a
 *                  WeightedContextMatcher with radius=4.
 *
 * @constructor
 */
function ContextResolver(refnode, nodeindex, radius, threshold, matcher) {
    this.refnode = refnode;
    this.nodeindex = nodeindex;

    if (typeof radius === 'undefined') {
        radius = 4;
    }
    this.r = radius;

    if (typeof threshold === 'undefined') {
        threshold = 0.7;
    }
    this.t = threshold;

    this.matcher = matcher || new contextmatcher.WeightedContextMatcher(4);

    // Install custom equality tester for matcher
    this.matcher.equal = (function(that){
        return function(subject, offset, value, flag) {
            if (flag) {
                return that.equalContent(that.nodeindex.get(subject, offset), value, flag);
            }
            else {
                return that.equalContext(that.nodeindex.get(subject, offset), value);
            }
        };
    }(this));

    this.resolver = new exports.TopDownPathResolver(refnode);
}

/**
 * Compare a document node against a content node from the patch. Return
 * true if the docnode matches the patnode.
 *
 * Override this method if you use something different than the value
 * property of tree.Node.
 *
 * @param docnode   A candidate node from the document
 * @param patnode   A body-node from the pattern
 */
ContextResolver.prototype.equalContent = function(docnode, patnode) {
    return docnode === undefined ? patnode === undefined :
        docnode && patnode && docnode.value === patnode.value;
};

/**
 * Compare a document node against a context node value. Return true if
 * the value of docnode matches the pattern value.
 *
 * Override this method if you use something different than the value
 * property of tree.Node.
 *
 * @param docnode   A candidate node from the document
 * @param patnode   The value from a context node
 */
ContextResolver.prototype.equalContext = function(docnode, value) {
    return docnode === undefined ? value === undefined :
        docnode.value === value;
};

/**
 * Given an anchor and a nodeindex, this method identifies the node which
 * matches the anchor as close as possible.
 */
ContextResolver.prototype.getClosestNode = function(anchor, nodeindex) {
    var result, siblings, lastsib;

    if (anchor.target) {
        result = anchor.target;
    }
    else {
        siblings = anchor.base.children;
        if (siblings.length === 0) {
            // First guess has no children. Just use that.
            result = anchor.base;
        }
        else if (anchor.index < 0) {
            result = nodeindex.get(siblings[0], -1);
        }
        else if (anchor.index < siblings.length) {
            // Start with the appointed child node
            result = siblings[anchor.index];
        }
        else {
            // Resort to the last node in the subtree under the preceeding
            // sibling if top-down resolver did not came through to the very
            // last path component.
            lastsib = siblings[siblings.length-1];
            result = nodeindex.get(lastsib, nodeindex.size(lastsib) - 1);
        }
    }

    return result;
}

/**
 * Locate a node at the given path starting at refnode. Try to locate the
 * target within a given radius using the fingerprint values if direct
 * lookup failed.
 *
 * @param   path        An array of numbers. Each value represents an index
 *                      into the childrens of a node in top-down order.
 * @param   body        An array containing the node sequence in question.
 *                      When resolving the location of insert operations,
 *                      the array is empty.  For updates, the array will
 *                      consist of exactly one node. Remove operations may
 *                      consist of one or more nodes.
 * @param   head        Leading context: An array containing the values of
 *                      leading nodes in the same generation.
 * @param   tail        Trailing context: An array containing the values of
 *                      trailing nodes in the same generation.
 * @param   type        Operation type. This parameter is passed to the
 *                      equalContent callback.
 *
 * @returns A result object with two properties: node holds the resolved
 * tree.Node and tail the unresolved part of path. Returns undefined on
 * failure.
 */
ContextResolver.prototype.find = function(path, body, head, tail, type) {
    var guess, node, i, q = 0, f, best, bestnode, result, flatbody;

    // Need a trueish value in order to differentiate context from content
    if (typeof type === 'undefined') {
        type = true;
    }

    if (path.length === 0) {
        // We are operating on the root node, initial guess is trivial.
        node = this.refnode;
    }
    else {
        // Start with an initial guess using the top-down path resolver.
        guess = this.resolver.resolve(path);
        node = this.getClosestNode(guess.anchor, this.nodeindex);
    }

    // concatenate all nodes contained in body into one array
    flatbody = [];
    body.forEach(function(n) {
        Array.prototype.push.apply(flatbody, this.nodeindex.flatten(n));
    }, this);

    // context verification and fuzzy matching
    if (node) {
        this.matcher.setPattern(flatbody, head, tail);
        for (i = -this.r; i <= this.r; i++) {
            f = this.matcher.matchQuality(node, i, type);
            if (f > q && f >= this.t) {
                q = f;
                best = i;
            }
        }
    }

    if (typeof best !== 'undefined') {
        if ((bestnode = this.nodeindex.get(node, best)) && bestnode.depth === path.length) {
            // construct the trail of nodes up to refnode and return it
            result = new ResolverResult(this.refnode, bestnode.par,
                    [bestnode.childidx].slice(0, path.length));
        }
        else if ((bestnode = this.nodeindex.get(node, best-1)) && bestnode.depth >= path.length - 1) {
            // strip path components until we get to the parent of the position
            // in question
            while (bestnode.depth > path.length - 1) {
                bestnode = bestnode.par;
            }

            result = new ResolverResult(this.refnode, bestnode,
                    [bestnode.children.length]);
        }
    }

    return result;
};

/**
 * Create a new instance of top-down path resolver
 *
 * @constructor
 */
function TopDownPathResolver(refnode) {
    this.refnode = refnode;
}

/**
 * Try to resolve the given path top-down. Return an object containing the last
 * internal node which was resolved properly as well as the unresolved tail of
 * the path. Note that leaf nodes are represented by their parent and a tail
 * containing their child-index.
 *
 * @param path  Array of integers
 * @returns A result object with two properties: node holds the resolved
 * tree.Node and tail the unresolved part of path.
 */
TopDownPathResolver.prototype.resolve = function(path) {
    var node = this.refnode,
        tail = path.slice();

    while (tail.length > 1) {
        if (node.children[tail[0]]) {
            node = node.children[tail[0]];
            tail.shift();
        }
        else {
            break;
        }
    }

    return new ResolverResult(this.refnode, node, tail);
};

exports.ResolverResult = ResolverResult;
exports.ContextResolver = ContextResolver;
exports.TopDownPathResolver = TopDownPathResolver;
<|endoftext|>