<commit_before>// Load in dependencies
var gui = require('nw.gui');

// Set up app hooks
var appWebkit = gui.Window.get();

// Generate a window for Google Music
// https://github.com/rogerwang/node-webkit/wiki/Window
// TODO: Can we change window.location for the same effect?
// TODO: Probably but the reason they usually open a subwindow is so we can act on a close event instead of exiting immediately
var googleWindow = window.open('https://play.google.com/music');
var googleWebkit = gui.Window.get(googleWindow);

// TODO: When window closes, close process

// TODO: Once full-formed, break out class as a node module so it can be reused in browsers with no penalty
// TODO: Create class for managing application state that emits events
// TODO: Set up `tray.tooltip` to update on song change
// TODO: Add methods for play/pause/forward/next/shuffle/repeat that we can bind to menus and media keys
// TODO: Fill out package.json in full
// TODO: Add tray menu for all methods we want

// TODO: Inherit from EventEmitter
// Define a constructor to interact with a GoogleMusic window
function GoogleMusic(win) {
  this.win = win;
}
GoogleMusic.prototype = {
  playPause: function () {
    // Find and click the play pause button
    // DEV: We cannot cache this in the constructor because we can be behind login wall
    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.click
    var $playPause = this.win.document.querySelector('button[data-id="play-pause"]');
    $playPause.click();
  }
};

// Create a GoogleMusic instance for our window
var googleMusic = new GoogleMusic(googleWindow);

// TODO: Look into icon for taskbar item https://github.com/rogerwang/node-webkit/wiki/Icons

// Set up minification
// Modified from https://github.com/rogerwang/node-webkit/wiki/Minimize-to-tray

// Create our tray icon
// DEV: Icon via http://lorempixel.com/32/32/abstract/3/
// https://github.com/rogerwang/node-webkit/wiki/Tray
var tray = new gui.Tray({icon: 'lib/icon.png'});

// Add a menu to our tray with bindings for GoogleMusic
var trayMenu = new gui.Menu();
// TODO: Add/remove play/pause depending on if music is playing or not
trayMenu.append(new gui.MenuItem({
  label: 'Play/Pause',
  click: function () {
    googleMusic.playPause();
  }
}));
tray.menu = trayMenu;

// When the tray icon is clicked, show the window
// TODO: Should be a toggle
tray.on('click', function maximizeFromTray () {
  googleWebkit.restore();
  googleWebkit.setShowInTaskbar(true);
});

// When we minimize our window, hide it
googleWebkit.on('minimize', function minimizeToTray () {
  googleWebkit.minimize();
  googleWebkit.setShowInTaskbar(false);
});
<commit_msg>Added support for closing the app when we close the window<commit_after>// Load in dependencies
var gui = require('nw.gui');

// Set up app
var app = gui.App;

// Generate a window for Google Music
// https://github.com/rogerwang/node-webkit/wiki/Window
// TODO: Can we change window.location for the same effect?
// TODO: Probably but the reason they usually open a subwindow is so we can act on a close event instead of exiting immediately
var googleWindow = window.open('https://play.google.com/music');
var googleWebkit = gui.Window.get(googleWindow);

// TODO: When window closes, close process

// TODO: Once full-formed, break out class as a node module so it can be reused in browsers with no penalty
// TODO: Create class for managing application state that emits events
// TODO: Set up `tray.tooltip` to update on song change
// TODO: Add methods for play/pause/forward/next/shuffle/repeat that we can bind to menus and media keys
// TODO: Fill out package.json in full
// TODO: Add tray menu for all methods we want

// TODO: Inherit from EventEmitter
// Define a constructor to interact with a GoogleMusic window
function GoogleMusic(win) {
  this.win = win;
}
GoogleMusic.prototype = {
  playPause: function () {
    // Find and click the play pause button
    // DEV: We cannot cache this in the constructor because we can be behind login wall
    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.click
    var $playPause = this.win.document.querySelector('button[data-id="play-pause"]');
    $playPause.click();
  }
};

// Create a GoogleMusic instance for our window
var googleMusic = new GoogleMusic(googleWindow);

// TODO: Look into icon for taskbar item https://github.com/rogerwang/node-webkit/wiki/Icons

// Set up minification
// Modified from https://github.com/rogerwang/node-webkit/wiki/Minimize-to-tray

// Create our tray icon
// DEV: Icon via http://lorempixel.com/32/32/abstract/3/
// https://github.com/rogerwang/node-webkit/wiki/Tray
var tray = new gui.Tray({icon: 'lib/icon.png'});

// Add a menu to our tray with bindings for GoogleMusic
var trayMenu = new gui.Menu();
// TODO: Add/remove play/pause depending on if music is playing or not
trayMenu.append(new gui.MenuItem({
  label: 'Play/Pause',
  click: function () {
    googleMusic.playPause();
  }
}));
tray.menu = trayMenu;

// When the tray icon is clicked, show the window
// TODO: Should be a toggle
tray.on('click', function maximizeFromTray () {
  googleWebkit.restore();
});

// When we minimize our window, hide it
googleWebkit.on('minimize', function minimizeToTray () {
  googleWebkit.minimize();
});

// When we close the googleWebkit window, close the app
// https://github.com/rogerwang/node-webkit/wiki/Window#closed
googleWebkit.on('closed', function closeApp () {
  app.quit();
});
<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating continuously, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen.
 * Note that the number of images falling is constant, so if the screen is stretched out vertically (tall thin window)
 * they will be less dense.
 *
 * There are two ways to run the animation step function.  The client code can manually call step(dt), or the client
 * code can pass in an Events instance that triggers events on 'step'. In the latter case, the listener will
 * automatically be removed when the animation is complete.
 *
 * For details about the development of the RewardNode, please see https://github.com/phetsims/vegas/issues/4
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import Bounds2 from '../../dot/js/Bounds2.js';
import ScreenView from '../../joist/js/ScreenView.js';
import merge from '../../phet-core/js/merge.js';
import FaceNode from '../../scenery-phet/js/FaceNode.js';
import StarNode from '../../scenery-phet/js/StarNode.js';
import CanvasNode from '../../scenery/js/nodes/CanvasNode.js';
import Node from '../../scenery/js/nodes/Node.js';
import Tandem from '../../tandem/js/Tandem.js';
import vegas from './vegas.js';

// constants
const DEBUG = false; // shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
const MAX_SPEED = 200; // The maximum speed an image can fall in screen pixels per second.

class RewardNode extends CanvasNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {
    options = merge( {

      // Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      // {Node[]} Nodes to appear in the reward node.  They will be cached as images to improve performance.
      // The simulation should override this array to provide images specific to the simulation.
      // If null, the default is initialized below.
      nodes: null,

      // If you pass in a stepEmitter {Emitter}, it will drive the animation
      stepEmitter: null
    }, options );

    if ( options.nodes === null ) {
      options.nodes = RewardNode.createRandomNodes( [
        new FaceNode( 40, { headStroke: 'black', headLineWidth: 1.5 } ),
        new StarNode()
      ], 150 );
    }

    super( options );

    // @private
    this.options = options;

    // @private - Store each reward, which has an imageWrapper (see above), x, y, speed. It is not an image, it is not
    // a node, but it is one of the things that animates as falling in the RewardNode and its associated data.
    this.rewards = null;

    // @private - Bounds in which to render the canvas, which represents the full window. See below for how this is computed based
    // on ScreenView bounds and relative transforms
    this.canvasDisplayBounds = new Bounds2( 0, 0, 0, 0 );

    // @private - If you pass in a stepEmitter, it will drive the animation
    this.stepEmitterListener = dt => this.step( dt );
    options.stepEmitter && options.stepEmitter.addListener( this.stepEmitterListener );

    // @private {Node[]}
    // Cache each unique node as an image for faster rendering in canvas.  Use an intermediate imageWrapper since the
    // images will be returned later asynchronously. And we need a place to store them, and know when they have arrived.
    this.imageWrappers = [];

    // find the unique nodes in the array
    const uniqueNodes = _.uniq( options.nodes );
    uniqueNodes.forEach( ( node, i ) => {
      this.imageWrappers.push( {
        // The image to be rendered in the canvas, will be filled in by toImage callback
        image: null,

        // Record the width and height so the nodes can be positioned before the toImage call has completed
        width: node.width,
        height: node.height,

        // The node itself is recorded in the imageWrapper so the imageWrapper can be looked up based on the original node
        node: node
      } );

      const parent = new Node( { children: [ node ], scale: options.scaleForResolution } );
      parent.toImage( image => {
        this.imageWrappers[ i ].image = image;
        parent.dispose(); // not needed anymore, see https://github.com/phetsims/area-model-common/issues/128
      } );
    } );

    // @private these will be set by init
    this.screenView = null; // {ScreenView}
    this.updateBounds = null; // {function}

    // @private {boolean} Some initialization must occur after this node is attached to the scene graph,
    // see documentation for initialize() method below.
    this.isInitialized = false;

    // @private {function} For PhET-iO brand only: make sure this Node is initialized when state is being set for PhET-iO
    this.initializer = () => this.initialize();
    Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.addListener( this.initializer );

    // @private
    this.disposeRewardNode = () => {
      options.stepEmitter && options.stepEmitter.removeListener( this.stepEmitterListener );
      this.screenView && this.screenView.transformEmitter.removeListener( this.updateBounds );
      Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.removeListener( this.initializer );
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeRewardNode();
    super.dispose();
  }

  /**
   * Convenience factory method to create an array of the specified nodes in an even distribution.
   * @param {Node[]} nodes array of nodes to use
   * @param {number} count
   * @returns {Node[]}
   * @public
   */
  static createRandomNodes( nodes, count ) {
    const array = [];
    for ( let i = 0; i < count; i++ ) {
      array.push( nodes[ i % nodes.length ] );
    }
    return array;
  }

  /**
   * Paint the rewards on the canvas
   * @param {CanvasRenderingContext2D} context
   * @private
   */
  paintCanvas( context ) {

    // If the debugging flag is on, show the bounds of the canvas
    if ( DEBUG ) {
      const bounds = this.canvasDisplayBounds;

      // Fill the canvas with gray
      context.fillStyle = 'rgba(50,50,50,0.5)';
      context.fillRect( bounds.minX, bounds.minY, bounds.width, bounds.height );

      // Stroke the canvas border with blue
      context.strokeStyle = '#0000ff';
      context.lineWidth = 5;
      context.strokeRect( bounds.minX, bounds.minY, bounds.width, bounds.height );
    }
    context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

    // Display the rewards, but check that they exist first.  They do not exist when attached to the timer with stepEmitter
    if ( this.rewards ) {
      for ( let i = 0; i < this.rewards.length; i++ ) {
        const reward = this.rewards[ i ];
        if ( reward.imageWrapper.image ) {
          context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
        }
      }
    }
  }

  /**
   * Finds the first parent that is a ScreenView so we can listen for its transform, see https://github.com/phetsims/vegas/issues/4
   * @returns {Node}
   * @private
   */
  getScreenView() {
    return this.getUniqueTrail( node => node instanceof ScreenView ).rootNode();
  }

  /**
   * Only init after being attached to the scene graph, since we must ascertain the local bounds are such that they take
   * up the global screen.
   * 1. listen to the size of the scene/display
   * 2. record the trail between the scene and your CanvasNode, and
   * 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).
   *
   * @jonathanolson said: for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on
   * changes to be precise (since you need them anyways to draw).
   * @private
   */
  initialize() {
    if ( !this.isInitialized && this.getUniqueTrail().nodes[ 0 ] ) {
      this.screenView = this.getScreenView();

      // Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
      this.updateBounds = () => {

        const local = this.globalToLocalBounds( phet.joist.display.bounds );
        this.setCanvasBounds( local );

        // Also, store the bounds in the options so the debug flag can render the bounds
        this.canvasDisplayBounds = local;
      };

      // When the ScreenView transform changes, update the bounds.  This prevents a "behind by one" problem, see https://github.com/phetsims/vegas/issues/4
      this.screenView.transformEmitter.addListener( this.updateBounds );

      // Set the initial bounds
      this.updateBounds();

      // Initialize the reward nodes now that we have bounds
      this.rewards = this.options.nodes.map( node => {

        //find the image wrapper corresponding to the node
        const imageWrapper = _.find( this.imageWrappers, imageWrapper => imageWrapper.node === node );
        return {
          imageWrapper: imageWrapper,
          x: this.sampleImageXValue( imageWrapper ),
          y: this.sampleImageYValue( imageWrapper ),
          speed: ( phet.joist.random.nextDouble() + 1 ) * MAX_SPEED
        };
      } );

      this.isInitialized = true;
    }
  }

  /**
   * Selects a random X value for the image when it is created.
   * @param {Node} imageWrapper
   * @returns {number}
   * @private
   */
  sampleImageXValue( imageWrapper ) {
    return ( phet.joist.random.nextDouble() * this.canvasDisplayBounds.width + this.canvasDisplayBounds.left ) *
           this.options.scaleForResolution - imageWrapper.width / 2;
  }

  /**
   * Selects a random Y value for the image when it is created, or when it goes back to the top of the screen
   * @param {Node} imageWrapper
   * @returns {number}
   * @private
   */
  sampleImageYValue( imageWrapper ) {

    // Start things about 1 second off the top of the screen
    return this.canvasDisplayBounds.top - phet.joist.random.nextDouble() * this.canvasDisplayBounds.height * 2 -
           MAX_SPEED - imageWrapper.height;
  }

  /**
   * Moves the rewards down according to their speed.
   * @param {number} dt
   * @public
   */
  step( dt ) {
    this.initialize();

    // Update all of the rewards
    const maxY = this.canvasDisplayBounds.height * this.options.scaleForResolution;
    for ( let i = 0; i < this.rewards.length; i++ ) {
      const reward = this.rewards[ i ];

      // Move each node straight down at constant speed
      reward.y += reward.speed * dt;

      // Move back to the top after the node falls off the bottom
      if ( reward.y > maxY ) {
        reward.x = this.sampleImageXValue( reward.imageWrapper );
        reward.y = this.sampleImageYValue( reward.imageWrapper );
      }
    }
    this.invalidatePaint();
  }
}

vegas.register( 'RewardNode', RewardNode );
export default RewardNode;<commit_msg>create dotRandom and use instead of phet.joist.random, https://github.com/phetsims/joist/issues/685<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating continuously, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen.
 * Note that the number of images falling is constant, so if the screen is stretched out vertically (tall thin window)
 * they will be less dense.
 *
 * There are two ways to run the animation step function.  The client code can manually call step(dt), or the client
 * code can pass in an Events instance that triggers events on 'step'. In the latter case, the listener will
 * automatically be removed when the animation is complete.
 *
 * For details about the development of the RewardNode, please see https://github.com/phetsims/vegas/issues/4
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import Bounds2 from '../../dot/js/Bounds2.js';
import dotRandom from '../../dot/js/dotRandom.js';
import ScreenView from '../../joist/js/ScreenView.js';
import merge from '../../phet-core/js/merge.js';
import FaceNode from '../../scenery-phet/js/FaceNode.js';
import StarNode from '../../scenery-phet/js/StarNode.js';
import CanvasNode from '../../scenery/js/nodes/CanvasNode.js';
import Node from '../../scenery/js/nodes/Node.js';
import Tandem from '../../tandem/js/Tandem.js';
import vegas from './vegas.js';

// constants
const DEBUG = false; // shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
const MAX_SPEED = 200; // The maximum speed an image can fall in screen pixels per second.

class RewardNode extends CanvasNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {
    options = merge( {

      // Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      // {Node[]} Nodes to appear in the reward node.  They will be cached as images to improve performance.
      // The simulation should override this array to provide images specific to the simulation.
      // If null, the default is initialized below.
      nodes: null,

      // If you pass in a stepEmitter {Emitter}, it will drive the animation
      stepEmitter: null
    }, options );

    if ( options.nodes === null ) {
      options.nodes = RewardNode.createRandomNodes( [
        new FaceNode( 40, { headStroke: 'black', headLineWidth: 1.5 } ),
        new StarNode()
      ], 150 );
    }

    super( options );

    // @private
    this.options = options;

    // @private - Store each reward, which has an imageWrapper (see above), x, y, speed. It is not an image, it is not
    // a node, but it is one of the things that animates as falling in the RewardNode and its associated data.
    this.rewards = null;

    // @private - Bounds in which to render the canvas, which represents the full window. See below for how this is computed based
    // on ScreenView bounds and relative transforms
    this.canvasDisplayBounds = new Bounds2( 0, 0, 0, 0 );

    // @private - If you pass in a stepEmitter, it will drive the animation
    this.stepEmitterListener = dt => this.step( dt );
    options.stepEmitter && options.stepEmitter.addListener( this.stepEmitterListener );

    // @private {Node[]}
    // Cache each unique node as an image for faster rendering in canvas.  Use an intermediate imageWrapper since the
    // images will be returned later asynchronously. And we need a place to store them, and know when they have arrived.
    this.imageWrappers = [];

    // find the unique nodes in the array
    const uniqueNodes = _.uniq( options.nodes );
    uniqueNodes.forEach( ( node, i ) => {
      this.imageWrappers.push( {
        // The image to be rendered in the canvas, will be filled in by toImage callback
        image: null,

        // Record the width and height so the nodes can be positioned before the toImage call has completed
        width: node.width,
        height: node.height,

        // The node itself is recorded in the imageWrapper so the imageWrapper can be looked up based on the original node
        node: node
      } );

      const parent = new Node( { children: [ node ], scale: options.scaleForResolution } );
      parent.toImage( image => {
        this.imageWrappers[ i ].image = image;
        parent.dispose(); // not needed anymore, see https://github.com/phetsims/area-model-common/issues/128
      } );
    } );

    // @private these will be set by init
    this.screenView = null; // {ScreenView}
    this.updateBounds = null; // {function}

    // @private {boolean} Some initialization must occur after this node is attached to the scene graph,
    // see documentation for initialize() method below.
    this.isInitialized = false;

    // @private {function} For PhET-iO brand only: make sure this Node is initialized when state is being set for PhET-iO
    this.initializer = () => this.initialize();
    Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.addListener( this.initializer );

    // @private
    this.disposeRewardNode = () => {
      options.stepEmitter && options.stepEmitter.removeListener( this.stepEmitterListener );
      this.screenView && this.screenView.transformEmitter.removeListener( this.updateBounds );
      Tandem.PHET_IO_ENABLED && phet.phetio.phetioEngine.phetioStateEngine.stateSetEmitter.removeListener( this.initializer );
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeRewardNode();
    super.dispose();
  }

  /**
   * Convenience factory method to create an array of the specified nodes in an even distribution.
   * @param {Node[]} nodes array of nodes to use
   * @param {number} count
   * @returns {Node[]}
   * @public
   */
  static createRandomNodes( nodes, count ) {
    const array = [];
    for ( let i = 0; i < count; i++ ) {
      array.push( nodes[ i % nodes.length ] );
    }
    return array;
  }

  /**
   * Paint the rewards on the canvas
   * @param {CanvasRenderingContext2D} context
   * @private
   */
  paintCanvas( context ) {

    // If the debugging flag is on, show the bounds of the canvas
    if ( DEBUG ) {
      const bounds = this.canvasDisplayBounds;

      // Fill the canvas with gray
      context.fillStyle = 'rgba(50,50,50,0.5)';
      context.fillRect( bounds.minX, bounds.minY, bounds.width, bounds.height );

      // Stroke the canvas border with blue
      context.strokeStyle = '#0000ff';
      context.lineWidth = 5;
      context.strokeRect( bounds.minX, bounds.minY, bounds.width, bounds.height );
    }
    context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

    // Display the rewards, but check that they exist first.  They do not exist when attached to the timer with stepEmitter
    if ( this.rewards ) {
      for ( let i = 0; i < this.rewards.length; i++ ) {
        const reward = this.rewards[ i ];
        if ( reward.imageWrapper.image ) {
          context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
        }
      }
    }
  }

  /**
   * Finds the first parent that is a ScreenView so we can listen for its transform, see https://github.com/phetsims/vegas/issues/4
   * @returns {Node}
   * @private
   */
  getScreenView() {
    return this.getUniqueTrail( node => node instanceof ScreenView ).rootNode();
  }

  /**
   * Only init after being attached to the scene graph, since we must ascertain the local bounds are such that they take
   * up the global screen.
   * 1. listen to the size of the scene/display
   * 2. record the trail between the scene and your CanvasNode, and
   * 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).
   *
   * @jonathanolson said: for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on
   * changes to be precise (since you need them anyways to draw).
   * @private
   */
  initialize() {
    if ( !this.isInitialized && this.getUniqueTrail().nodes[ 0 ] ) {
      this.screenView = this.getScreenView();

      // Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
      this.updateBounds = () => {

        const local = this.globalToLocalBounds( phet.joist.display.bounds );
        this.setCanvasBounds( local );

        // Also, store the bounds in the options so the debug flag can render the bounds
        this.canvasDisplayBounds = local;
      };

      // When the ScreenView transform changes, update the bounds.  This prevents a "behind by one" problem, see https://github.com/phetsims/vegas/issues/4
      this.screenView.transformEmitter.addListener( this.updateBounds );

      // Set the initial bounds
      this.updateBounds();

      // Initialize the reward nodes now that we have bounds
      this.rewards = this.options.nodes.map( node => {

        //find the image wrapper corresponding to the node
        const imageWrapper = _.find( this.imageWrappers, imageWrapper => imageWrapper.node === node );
        return {
          imageWrapper: imageWrapper,
          x: this.sampleImageXValue( imageWrapper ),
          y: this.sampleImageYValue( imageWrapper ),
          speed: ( dotRandom.nextDouble() + 1 ) * MAX_SPEED
        };
      } );

      this.isInitialized = true;
    }
  }

  /**
   * Selects a random X value for the image when it is created.
   * @param {Node} imageWrapper
   * @returns {number}
   * @private
   */
  sampleImageXValue( imageWrapper ) {
    return ( dotRandom.nextDouble() * this.canvasDisplayBounds.width + this.canvasDisplayBounds.left ) *
           this.options.scaleForResolution - imageWrapper.width / 2;
  }

  /**
   * Selects a random Y value for the image when it is created, or when it goes back to the top of the screen
   * @param {Node} imageWrapper
   * @returns {number}
   * @private
   */
  sampleImageYValue( imageWrapper ) {

    // Start things about 1 second off the top of the screen
    return this.canvasDisplayBounds.top - dotRandom.nextDouble() * this.canvasDisplayBounds.height * 2 -
           MAX_SPEED - imageWrapper.height;
  }

  /**
   * Moves the rewards down according to their speed.
   * @param {number} dt
   * @public
   */
  step( dt ) {
    this.initialize();

    // Update all of the rewards
    const maxY = this.canvasDisplayBounds.height * this.options.scaleForResolution;
    for ( let i = 0; i < this.rewards.length; i++ ) {
      const reward = this.rewards[ i ];

      // Move each node straight down at constant speed
      reward.y += reward.speed * dt;

      // Move back to the top after the node falls off the bottom
      if ( reward.y > maxY ) {
        reward.x = this.sampleImageXValue( reward.imageWrapper );
        reward.y = this.sampleImageYValue( reward.imageWrapper );
      }
    }
    this.invalidatePaint();
  }
}

vegas.register( 'RewardNode', RewardNode );
export default RewardNode;<|endoftext|>


<commit_before>//  Copyright 2002-2014, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen
 *
 * Notes:
 * 1. Instead of continuous animation, the animation is shown as a transient behavior that shows for a few seconds and then disappears.
 *    This is to encourage the student to move to the next challenge, instead of becoming mesmerized by a perpetual animation.
 * 2. TODO: I would like to add optional support for rotation, scaling or other visual candy
 * 3. TODO: It is not exactly right to use the layout bounds, we should instead use the visible screen bounds.  EnergySkatePark has an example of computing the visual screen bounds.
 * 4. TODO: The RewardNode automatically detaches from its parents after the animation is complete?  Or perhaps this is normally coupled to the "continue" button in the LevelCompletedDialogNode?
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var CanvasNode = require( 'SCENERY/nodes/CanvasNode' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var StarNode = require( 'SCENERY_PHET/StarNode' );
  var FaceNode = require( 'SCENERY_PHET/FaceNode' );
  var Image = require( 'SCENERY/nodes/Image' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Text = require( 'SCENERY/nodes/Text' );

  //This debug flag shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
  var debug = true;

  function RewardNode( options ) {

    this.options = options = _.extend( {

      //Bounds in which to render the canvas.  TODO: Should be full screen
      canvasBounds: new Bounds2( 0, 0, ScreenView.DEFAULT_LAYOUT_BOUNDS.width, ScreenView.DEFAULT_LAYOUT_BOUNDS.height ),

      //Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      //Nodes to appear in the reward node.  They will be cached as images to improve performance
      nodes: [
        new FaceNode( 40, {headStroke: 'black', headLineWidth: 1.5} ),
        new StarNode()
      ],

      //Total number of nodes to display
      rewardNodeCount: 100
    }, options );

    //Cache the nodes as images.  Use an intermediate imageWrapper since the images will be returned later asynchronously
    //And we need a place to store them, and know when they have arrived
    var imageWrappers = [];
    options.nodes.forEach( function( node, i ) {
      imageWrappers.push( {image: null} );
      node.scale( options.scaleForResolution );
      node.toImage( function( image ) {
        imageWrappers[i].image = image;
      } );
    } );

    //Store each reward, which has an imageWrapper (see above), x, y, speed
    this.rewards = [];
    for ( var i = 0; i < options.rewardNodeCount; i++ ) {
      this.rewards.push( {
        imageWrapper: imageWrappers[i % imageWrappers.length],
        x: (Math.random() * options.canvasBounds.width + options.canvasBounds.left) * options.scaleForResolution,
        y: options.canvasBounds.top - Math.random() * options.canvasBounds.height - 200,
        speed: (Math.random() + 1) * 200
      } );
    }

    CanvasNode.call( this, options );

    //Some initialization must occur after this node is attached to the scene graph, see documentation for RewardNode.init below.
    this.inited = false;
  }

  return inherit( CanvasNode, RewardNode, {

    //Paint the rewards on the canvas
    // @param {CanvasContextWrapper} wrapper
    paintCanvas: function( wrapper ) {
      var context = wrapper.context;

      //If the debugging flag is on, show the bounds of the canvas
      if ( debug ) {
        //Fill the canvas with gray
        context.fillStyle = 'rgba(50,50,50,0.5)';
        context.fillRect( 0, 0, this.options.canvasBounds.width, this.options.canvasBounds.height );

        //Stroke the canvas border with blue
        context.strokeStyle = "#0000ff";
        context.lineWidth = 5;
        context.strokeRect( 0, 0, this.options.canvasBounds.width, this.options.canvasBounds.height );
      }
      context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

      for ( var i = 0; i < this.rewards.length; i++ ) {
        var reward = this.rewards[i];
        if ( reward.imageWrapper.image ) {
          context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
        }
      }
    },

    getScene: function() {
      //Find the root of the scene tree
      var node = this;
      while ( node ) {
        assert && assert( node._parents[1] === undefined, 'globalToLocalPoint unable to work for DAG' );
        if ( node._parents[0] ) {
          node = node._parents[0];
        }
        else {
          break;
        }
      }

      //TODO: add assertion to make sure node is a scenery.Scene
      return node;
    },

    //Only init after being attached to the scene graph, since we must ascertain the local bounds such that they take up the global screen.
    //@jonathanolson suggested getting the bounds right by doing the following:
    // 1. listen to the size of the scene/display
    // 2. record the trail between the scene and your CanvasNode, and
    // 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).

    // Later @jonathanolson continued:
    // I don't recall how Scenery will handle Bounds2.EVERYTHING (probably assertion fails) right now, but that can change
    // for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on changes to be precise (since you need them anyways to draw)
    init: function() {
      var rewardNode = this;

      var scene = this.getScene();

      var bounds = scene.sceneBounds;
      console.log( bounds );

      //Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
      //TODO: The bounds are not working yet and I (@samreid) don't know why
      scene.addEventListener( 'resize', function() {

//        debugger;
        var globalBounds = scene.sceneBounds;
        var local = rewardNode.globalToLocalBounds( globalBounds );

        rewardNode.setCanvasBounds( new Bounds2( 0, 0, local.width, local.height ) );
        rewardNode.setTranslation( local.minX, local.minY );

        console.log( 'resized', local );

        //Also, store the bounds in the options so the debug flag can render the bounds
        rewardNode.options.canvasBounds = local;
      } );
    },

    //Move the rewards down according to their speed
    step: function( dt ) {
      if ( !this.inited && this.getScene() !== null ) {
        this.init();
        this.inited = true;
      }
      for ( var i = 0; i < this.rewards.length; i++ ) {
        var reward = this.rewards[i];
        reward.y += reward.speed * dt;
      }
      this.invalidatePaint();
    }
  } );
} );<commit_msg>Bounds fix, still has out-of-order issue<commit_after>//  Copyright 2002-2014, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen
 *
 * Notes:
 * 1. Instead of continuous animation, the animation is shown as a transient behavior that shows for a few seconds and then disappears.
 *    This is to encourage the student to move to the next challenge, instead of becoming mesmerized by a perpetual animation.
 * 2. TODO: I would like to add optional support for rotation, scaling or other visual candy
 * 3. TODO: It is not exactly right to use the layout bounds, we should instead use the visible screen bounds.  EnergySkatePark has an example of computing the visual screen bounds.
 * 4. TODO: The RewardNode automatically detaches from its parents after the animation is complete?  Or perhaps this is normally coupled to the "continue" button in the LevelCompletedDialogNode?
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var CanvasNode = require( 'SCENERY/nodes/CanvasNode' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var StarNode = require( 'SCENERY_PHET/StarNode' );
  var FaceNode = require( 'SCENERY_PHET/FaceNode' );
  var Image = require( 'SCENERY/nodes/Image' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Text = require( 'SCENERY/nodes/Text' );

  //This debug flag shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
  var debug = true;

  function RewardNode( options ) {

    this.options = options = _.extend( {

      //Bounds in which to render the canvas.  TODO: Should be full screen
      canvasBounds: new Bounds2( 0, 0, ScreenView.DEFAULT_LAYOUT_BOUNDS.width, ScreenView.DEFAULT_LAYOUT_BOUNDS.height ),

      //Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      //Nodes to appear in the reward node.  They will be cached as images to improve performance
      nodes: [
        new FaceNode( 40, {headStroke: 'black', headLineWidth: 1.5} ),
        new StarNode()
      ],

      //Total number of nodes to display
      rewardNodeCount: 100
    }, options );

    //Cache the nodes as images.  Use an intermediate imageWrapper since the images will be returned later asynchronously
    //And we need a place to store them, and know when they have arrived
    var imageWrappers = [];
    options.nodes.forEach( function( node, i ) {
      imageWrappers.push( {image: null} );
      node.scale( options.scaleForResolution );
      node.toImage( function( image ) {
        imageWrappers[i].image = image;
      } );
    } );

    //Store each reward, which has an imageWrapper (see above), x, y, speed
    this.rewards = [];
    for ( var i = 0; i < options.rewardNodeCount; i++ ) {
      this.rewards.push( {
        imageWrapper: imageWrappers[i % imageWrappers.length],
        x: (Math.random() * options.canvasBounds.width + options.canvasBounds.left) * options.scaleForResolution,
        y: options.canvasBounds.top - Math.random() * options.canvasBounds.height - 200,
        speed: (Math.random() + 1) * 200
      } );
    }

    CanvasNode.call( this, options );

    //Some initialization must occur after this node is attached to the scene graph, see documentation for RewardNode.init below.
    this.inited = false;
  }

  return inherit( CanvasNode, RewardNode, {

    //Paint the rewards on the canvas
    // @param {CanvasContextWrapper} wrapper
    paintCanvas: function( wrapper ) {
      var context = wrapper.context;

      //If the debugging flag is on, show the bounds of the canvas
      if ( debug ) {
        var bounds = this.options.canvasBounds;

        //Fill the canvas with gray
        context.fillStyle = 'rgba(50,50,50,0.5)';
        context.fillRect( bounds.minX, bounds.minY, bounds.width, bounds.height );

        //Stroke the canvas border with blue
        context.strokeStyle = "#0000ff";
        context.lineWidth = 5;
        context.strokeRect( bounds.minX, bounds.minY, bounds.width, bounds.height );
      }
      context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

      for ( var i = 0; i < this.rewards.length; i++ ) {
        var reward = this.rewards[i];
        if ( reward.imageWrapper.image ) {
          context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
        }
      }
    },

    getScene: function() {
      //Find the root of the scene tree
      var node = this;
      while ( node ) {
        assert && assert( node._parents[1] === undefined, 'globalToLocalPoint unable to work for DAG' );
        if ( node._parents[0] ) {
          node = node._parents[0];
        }
        else {
          break;
        }
      }

      //TODO: add assertion to make sure node is a scenery.Scene
      return node;
    },

    //Only init after being attached to the scene graph, since we must ascertain the local bounds such that they take up the global screen.
    //@jonathanolson suggested getting the bounds right by doing the following:
    // 1. listen to the size of the scene/display
    // 2. record the trail between the scene and your CanvasNode, and
    // 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).

    // Later @jonathanolson continued:
    // I don't recall how Scenery will handle Bounds2.EVERYTHING (probably assertion fails) right now, but that can change
    // for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on changes to be precise (since you need them anyways to draw)
    init: function() {
      var rewardNode = this;

      var scene = this.getScene();

      var bounds = scene.sceneBounds;
      console.log( bounds );

      //Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
      //TODO: The bounds are not working yet and I (@samreid) don't know why
      scene.addEventListener( 'resize', function() {

//        debugger;
        var globalBounds = scene.sceneBounds;
        var local = rewardNode.globalToLocalBounds( globalBounds );

        rewardNode.setCanvasBounds( local );

        console.log( 'resized', local );
        console.log( rewardNode.localToGlobalBounds( rewardNode.localBounds ) );
        console.log( scene.sceneBounds );

        //Also, store the bounds in the options so the debug flag can render the bounds
        rewardNode.options.canvasBounds = local;
      } );
    },

    //Move the rewards down according to their speed
    step: function( dt ) {
      if ( !this.inited && this.getScene() !== null ) {
        this.init();
        this.inited = true;
      }
      for ( var i = 0; i < this.rewards.length; i++ ) {
        var reward = this.rewards[i];
        reward.y += reward.speed * dt;
      }
      this.invalidatePaint();
    }
  } );
} );
<|endoftext|>


<commit_before>var terminal = require('child_process').spawn("bash"),
  pem = require("pem"),
  quote = require('shell-quote').quote,
  fs = require("fs"),
  _ = require("lodash");

module.exports = {
  genericBuildAndCreate: function (ip, dockerFileLocation, port, ssh_key, privateKeyLocation) {
    console.log("ip = %j", ip);
    console.log("dockerFileLocation = %j", dockerFileLocation);
    console.log("port = %j", port);
    console.log("ssh_key = %j", ssh_key);
    console.log("privateKeyLocation = %j", privateKeyLocation);

    pem.createCertificate({days:1, selfSigned:true, serviceKey: ((privateKeyLocation && fs.readFileSync(privateKeyLocation)) || null) }, function(err, keys){

      console.log("cert = " + keys.certificate);
      if(!privateKeyLocation) { console.log("priv = %j", keys.serviceKey); }

      var filename = "cert.pem";
      var newFolder = __dirname + "/../.tmp";
      var newFile = newFolder + "/" + filename;

      // http://stackoverflow.com/questions/21194934/node-how-to-create-a-directory-if-doesnt-exist/21196961#21196961
      if (!fs.existsSync(newFolder)){ fs.mkdirSync(newFolder); }

      fs.writeFileSync(newFile, keys.certificate, "UTF-8", {'flags': 'w+'});

      terminal.stdin.write(
        "./quick-deploy/quick-deploy" +
        " -i " + ip +
        " -p " + port +
        " -d " + dockerFileLocation +
        " -k " + (ssh_key || "$HOME/.ssh/id_rsa") +
        " -f " + newFile +
        " -e " + "CERT_FILENAME=" + filename +
        "\n");

      var output = "";

      function getElementWithText(outputLines, text){ return _.findIndex(outputLines, function (line) {return line.indexOf(text) !== -1;}); }

      function getDataFromOutput(outputString) {
        var outputLines = outputString.split("\n")

        function findLinePreviousToDone() { return outputLines[getElementWithText(outputLines, "DONE!!!") - 1]; }

        function getDockerMachineName() {
          var splitMachineLine = outputLines[getElementWithText(outputLines, "MACHINE_NAME")].split(" ");
          return splitMachineLine[splitMachineLine.length - 1];
        }

        function getClientMacaroon() {
          var splitClientMacaroonLine = outputLines[getElementWithText(outputLines, "client_macaroon=")].split("client_macaroon=");
          return splitClientMacaroonLine[splitClientMacaroonLine.length - 1];
        }

        return {
          findContainerId: function(){ return findLinePreviousToDone().substring(0, 12); }, //first 12 digits is container id
          getDockerMachineName: getDockerMachineName,
          getClientMacaroon: getClientMacaroon
        };
      }

      function handleImageRunning() {
        var outputFns = getDataFromOutput(output);

        var containerId = outputFns.findContainerId();
        var machineName = outputFns.getDockerMachineName();

        console.log("containerId = " + containerId);
        console.log("machineName = " + machineName);

        terminal.stdin.write('eval "$(docker-machine env ' + machineName + ')"\n');
        setTimeout(function() {
          terminal.stdin.write("docker logs " + containerId + "\n");
          terminal.stdin.write("echo docker logs " + containerId + "\n");
          terminal.stdin.end();
        }, 2000);
      }

      function handleMacaroonFound(){
        var outputFns = getDataFromOutput(output);

        var clientMacaroon = outputFns.getClientMacaroon();
        console.log("clientMacaroon = " + clientMacaroon);
      }

      terminal.stdout.on('data', function (data) {    // register one or more handlers
        console.log('stdout: ' + data);
        output += data;

        if(data && data.toString().indexOf("DONE!!!") !== -1){handleImageRunning();}
        if(data && data.toString().indexOf("client_macaroon") !== -1){handleMacaroonFound();}
      });

      terminal.stderr.on('data', function (data) { console.log('stderr: ' + data); });
      terminal.on('exit', function (code) { console.log('child process exited with code ' + code); });
    });
  }
};
<commit_msg>callback function with result<commit_after>var terminal = require('child_process').spawn("bash"),
  pem = require("pem"),
  quote = require('shell-quote').quote,
  fs = require("fs"),
  _ = require("lodash");

module.exports = {
  genericBuildAndCreate: function (ip, dockerFileLocation, port, ssh_key, privateKeyLocation, callback) {
    console.log("ip = %j", ip);
    console.log("dockerFileLocation = %j", dockerFileLocation);
    console.log("port = %j", port);
    console.log("ssh_key = %j", ssh_key);
    console.log("privateKeyLocation = %j", privateKeyLocation);

    pem.createCertificate({days:1, selfSigned:true, serviceKey: ((privateKeyLocation && fs.readFileSync(privateKeyLocation)) || null) }, function(err, keys){

      console.log("cert = " + keys.certificate);
      if(!privateKeyLocation) { console.log("priv = %j", keys.serviceKey); }

      var filename = "cert.pem";
      var newFolder = __dirname + "/../.tmp";
      var newFile = newFolder + "/" + filename;

      // http://stackoverflow.com/questions/21194934/node-how-to-create-a-directory-if-doesnt-exist/21196961#21196961
      if (!fs.existsSync(newFolder)){ fs.mkdirSync(newFolder); }

      fs.writeFileSync(newFile, keys.certificate, "UTF-8", {'flags': 'w+'});

      terminal.stdin.write(
        "./quick-deploy/quick-deploy" +
        " -i " + ip +
        " -p " + port +
        " -d " + dockerFileLocation +
        " -k " + (ssh_key || "$HOME/.ssh/id_rsa") +
        " -f " + newFile +
        " -e " + "CERT_FILENAME=" + filename +
        "\n");

      var output = "";

      function getElementWithText(outputLines, text){ return _.findIndex(outputLines, function (line) {return line.indexOf(text) !== -1;}); }

      function getDataFromOutput(outputString) {
        var outputLines = outputString.split("\n")

        function findLinePreviousToDone() { return outputLines[getElementWithText(outputLines, "DONE!!!") - 1]; }

        function getDockerMachineName() {
          var splitMachineLine = outputLines[getElementWithText(outputLines, "MACHINE_NAME")].split(" ");
          return splitMachineLine[splitMachineLine.length - 1];
        }

        function getClientMacaroon() {
          var splitClientMacaroonLine = outputLines[getElementWithText(outputLines, "client_macaroon=")].split("client_macaroon=");
          return splitClientMacaroonLine[splitClientMacaroonLine.length - 1];
        }

        return {
          findContainerId: function(){ return findLinePreviousToDone().substring(0, 12); }, //first 12 digits is container id
          getDockerMachineName: getDockerMachineName,
          getClientMacaroon: getClientMacaroon
        };
      }

      function handleImageRunning() {
        var outputFns = getDataFromOutput(output);

        var containerId = outputFns.findContainerId();
        var machineName = outputFns.getDockerMachineName();

        console.log("containerId = " + containerId);
        console.log("machineName = " + machineName);

        terminal.stdin.write('eval "$(docker-machine env ' + machineName + ')"\n');
        setTimeout(function() {
          terminal.stdin.write("docker logs " + containerId + "\n");
          terminal.stdin.write("echo docker logs " + containerId + "\n");
          terminal.stdin.end();
        }, 2000);
      }

      function handleMacaroonFound(){
        var outputFns = getDataFromOutput(output);

        var clientMacaroon = outputFns.getClientMacaroon();

        console.log("clientMacaroon = " + clientMacaroon);
        callback(clientMacaroon);
      }

      terminal.stdout.on('data', function (data) {    // register one or more handlers
        console.log('stdout: ' + data);
        output += data;

        if(data && data.toString().indexOf("DONE!!!") !== -1){handleImageRunning();}
        if(data && data.toString().indexOf("client_macaroon") !== -1){handleMacaroonFound();}
      });

      terminal.stderr.on('data', function (data) { console.log('stderr: ' + data); });
      terminal.on('exit', function (code) { console.log('child process exited with code ' + code); });
    });
  }
};
<|endoftext|>


<commit_before>'use strict';

const EventEmitter = require('events').EventEmitter;

const Http = require('http');
const Pg = require('pg');
const Util = require('util');

const Sql = require('./sql');
const Symbols = require('./symbols');

const internals = {};
internals.timeout = Util.promisify(setTimeout);

// we wrap the user supplied function to guarantee
// that it does not throw and returns a boolean
// representing if it failed (or timed out)
internals.wrapSubscriber = (fn, timeout) => {

    return (job) => {

        return Promise.race([
            (async () => {

                try {
                    await fn(job);
                    return false;
                }
                catch (err) {
                    return true;
                }
            })(),
            (async () => {

                await internals.timeout(timeout);
                return true;
            })()
        ]);
    };
};

internals.gatherResults = async (fn, jobs) => {

    // run subscribe fn in parallel for each row
    const results = await Promise.all(jobs.map(async (job) => {

        return {
            id: job.id,
            repeat: job.repeat_every,
            failed: await fn(Object.assign({}, job))
        };
    }));

    // gather results so we can do bulk actions
    return results.reduce((acc, result) => {

        if (result.failed) {
            acc.failed.push(result.id);
        }
        else if (result.repeat) {
            acc.reset.push(result.id);
        }
        else {
            acc.passed.push(result.id);
        }

        return acc;
    }, { passed: [], reset: [], failed: [] });
};

class Porker extends EventEmitter {
    constructor({ connection, queue, errorThreshold = 1, retryDelay = '5 minutes', timeout = 15000, concurrency = 1, healthcheckPort = null } = {}) {

        super();

        if (!queue) {
            throw new Error('Missing required parameter: queue');
        }

        this.queue = queue;
        this.errorThreshold = errorThreshold;
        this.retryDelay = retryDelay;
        this.timeout = timeout;
        this.concurrency = concurrency;
        this.healthcheckPort = healthcheckPort;

        Object.defineProperties(this, {
            [Symbols.client]: {
                value: new Pg.Client(connection),
                writable: true
            },
            [Symbols.worker]: {
                value: new Pg.Client(connection),
                writable: true
            },
            [Symbols.workerMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.subscriberMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.subscriberResolver]: {
                value: () => {},
                writable: true
            },
            [Symbols.retryWorker]: {
                value: new Pg.Client(connection),
                writable: true
            },
            [Symbols.retryWorkerMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.retrierMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.retrierResolver]: {
                value: () => {},
                writable: true
            },
            [Symbols.queries]: {
                value: Sql.queries(this)
            },
            [Symbols.healthcheck]: {
                value: Http.createServer((req, res) => {

                    // $lab:coverage:off$
                    const connected = this[Symbols.client].readyForQuery &&
                                      (this[Symbols.subscriber] ? this[Symbols.worker].readyForQuery : true) &&
                                      (this[Symbols.retrier] ? this[Symbols.retryWorker].readyForQuery : true);
                    // $lab:coverage:on$

                    res.writeHead(connected ? 200 : 400);
                    return res.end();
                })
            }
        });

        if (this.healthcheckPort) {
            this[Symbols.healthcheck].listen(this.healthcheckPort);
        }
    }

    connect() {

        if (!this[Symbols.connectMutex]) {
            Object.defineProperty(this, Symbols.connectMutex, {
                value: new Promise(async (resolve) => {

                    this[Symbols.client].once('connect', () => {

                        this.emit('ready');
                        return resolve();
                    });

                    await this[Symbols.client].connect();
                })
            });
        }

        return this[Symbols.connectMutex];
    }

    async create() {

        await this.connect();

        return this[Symbols.client].query(this[Symbols.queries].createTable);
    }

    async drop() {

        await this.connect();

        return this[Symbols.client].query(this[Symbols.queries].dropTable);
    }

    async publish(jobs, { priority = 0, repeat } = {}) {

        await this.connect();

        const list = [].concat(jobs);
        const res = await this[Symbols.client].query(this[Symbols.queries].insertJobs(list), [priority, repeat, ...list]);
        await this[Symbols.client].query(this[Symbols.queries].notifyQueue);

        return res.rows.map((row) => {

            return row.id;
        });
    }

    async unpublish(jobs) {

        await this.connect();

        return this[Symbols.client].query(this[Symbols.queries].completeJobs, [[].concat(jobs)]);
    }

    async subscribe(fn) {

        if (this[Symbols.subscriber]) {
            throw new Error('A subscriber has already been added to this queue');
        }

        Object.defineProperty(this, Symbols.subscriber, {
            value: internals.wrapSubscriber(fn, this.timeout)
        });

        await new Promise(async (resolve) => {

            this[Symbols.worker].once('connect', () => {

                return resolve();
            });

            await this[Symbols.worker].connect();
        });

        await this[Symbols.worker].query(this[Symbols.queries].listenPublishes);
        this[Symbols.subscriberMutex] = new Promise((resolve) => {

            this[Symbols.subscriberResolver] = resolve;
        });

        this[Symbols.worker].on('notification', (msg) => {

            this[Symbols.work]();
        });

        this.emit('subscriberReady');
        this[Symbols.work]();
    }

    async [Symbols.work]() {

        await this[Symbols.workerMutex];
        this[Symbols.workerMutex] = (async () => {

            this[Symbols.subscriberMutex] = new Promise((resolve) => {

                this[Symbols.subscriberResolver] = resolve;
            });

            let didWork = false;
            while (!this[Symbols.stopped]) {
                await this[Symbols.worker].query('BEGIN');
                const nextJobs = await this[Symbols.worker].query(this[Symbols.queries].findNextRun);

                if (nextJobs.rowCount === 0) {
                    await this[Symbols.worker].query('ROLLBACK');
                    break;
                }

                // Delay before next job should be run
                const timeout = nextJobs.rows[0].next_run - Date.now();
                if (timeout > 0) {
                    await this[Symbols.worker].query('ROLLBACK');
                    this[Symbols.workTimer] = setTimeout(() => {

                        this[Symbols.work]();
                    }, timeout);
                    break;
                }

                // We have a recurring job, so go ahead and schedule that
                const future = nextJobs.rows.find((row) => row.future_run);
                if (future) {
                    this[Symbols.workTimer] = setTimeout(() => {

                        this[Symbols.work]();
                    }, future.future_run - Date.now());
                }

                didWork = true;
                const jobs = await this[Symbols.worker].query(this[Symbols.queries].lockJobs, [nextJobs.rows.map((row) => row.id)]);
                const results = await internals.gatherResults(this[Symbols.subscriber], jobs.rows.map((row) => Object.assign({}, row)));

                await this[Symbols.worker].query(this[Symbols.queries].completeJobs, [results.passed]);
                await this[Symbols.worker].query(this[Symbols.queries].errorJobs, [results.failed]);
                await this[Symbols.worker].query('COMMIT');

                // If we had errors, notify the retry queue
                if (results.failed.length) {
                    await this[Symbols.worker].query(this[Symbols.queries].notifyRetryQueue);
                }
            }

            if (didWork) {
                this.emit('drain');
            }

            this[Symbols.subscriberResolver]();
        })();
    }

    async retry(fn) {

        if (this[Symbols.retrier]) {
            throw new Error('A retry handler has already been added to this queue');
        }

        Object.defineProperty(this, Symbols.retrier, {
            value: internals.wrapSubscriber(fn, this.timeout)
        });

        await new Promise(async (resolve) => {

            this[Symbols.retryWorker].once('connect', () => {

                return resolve();
            });

            await this[Symbols.retryWorker].connect();
        });

        await this[Symbols.retryWorker].query(this[Symbols.queries].listenRetries);
        this[Symbols.retrierMutex] = new Promise((resolve) => {

            this[Symbols.retrierResolver] = resolve;
        });

        this[Symbols.retryWorker].on('notification', (msg) => {

            this[Symbols.retry]();
        });

        this.emit('retrierReady');
        this[Symbols.retry]();
    }

    async [Symbols.retry]() {

        await this[Symbols.retryWorkerMutex];
        this[Symbols.retryWorkerMutex] = (async () => {

            this[Symbols.retrierMutex] = new Promise((resolve) => {

                this[Symbols.retrierResolver] = resolve;
            });

            let didWork = false;
            while (!this[Symbols.stopped]) {
                await this[Symbols.retryWorker].query('BEGIN');
                const nextJobs = await this[Symbols.retryWorker].query(this[Symbols.queries].findRetries);

                if (nextJobs.rowCount === 0) {
                    await this[Symbols.retryWorker].query('ROLLBACK');
                    break;
                }

                const timeout = nextJobs.rows[0].retry_at - Date.now();
                if (timeout > 0) {
                    await this[Symbols.retryWorker].query('ROLLBACK');
                    this[Symbols.retryWorkTimer] = setTimeout(() => {

                        this[Symbols.retry]();
                    }, timeout);
                    break;
                }

                didWork = true;
                const jobs = await this[Symbols.retryWorker].query(this[Symbols.queries].lockRetries, [nextJobs.rows.map((row) => row.id)]);
                const results = await internals.gatherResults(this[Symbols.retrier], jobs.rows.map((row) => Object.assign({}, row)));

                await this[Symbols.retryWorker].query(this[Symbols.queries].completeJobs, [results.passed]);
                await this[Symbols.retryWorker].query(this[Symbols.queries].resetJobs, [results.reset]);
                await this[Symbols.retryWorker].query(this[Symbols.queries].errorJobs, [results.failed]);
                await this[Symbols.retryWorker].query('COMMIT');
            }

            if (didWork) {
                this.emit('drainRetries');
            }

            this[Symbols.retrierResolver]();
        })();
    }

    async end() {

        this[Symbols.stopped] = true;

        await this[Symbols.subscriberMutex];
        await this[Symbols.workerMutex];
        await this[Symbols.retrierMutex];
        await this[Symbols.retryWorkerMutex];

        clearTimeout(this[Symbols.workTimer]);
        clearTimeout(this[Symbols.retryWorkTimer]);

        if (this.healthcheckPort) {
            await new Promise((resolve) => {

                this[Symbols.healthcheck].close(resolve);
            });
        }

        if (this[Symbols.client]._connected) {
            await this[Symbols.client].end();
        }

        if (this[Symbols.worker]._connected) {
            await this[Symbols.worker].end();
        }

        if (this[Symbols.retryWorker]._connected) {
            await this[Symbols.retryWorker].end();
        }

        this.emit('end');
    }
}

module.exports = Porker;
<commit_msg>only wait for things we have to wait for<commit_after>'use strict';

const EventEmitter = require('events').EventEmitter;

const Http = require('http');
const Pg = require('pg');
const Util = require('util');

const Sql = require('./sql');
const Symbols = require('./symbols');

const internals = {};
internals.timeout = Util.promisify(setTimeout);

// we wrap the user supplied function to guarantee
// that it does not throw and returns a boolean
// representing if it failed (or timed out)
internals.wrapSubscriber = (fn, timeout) => {

    return (job) => {

        return Promise.race([
            (async () => {

                try {
                    await fn(job);
                    return false;
                }
                catch (err) {
                    return true;
                }
            })(),
            (async () => {

                await internals.timeout(timeout);
                return true;
            })()
        ]);
    };
};

internals.gatherResults = async (fn, jobs) => {

    // run subscribe fn in parallel for each row
    const results = await Promise.all(jobs.map(async (job) => {

        return {
            id: job.id,
            repeat: job.repeat_every,
            failed: await fn(Object.assign({}, job))
        };
    }));

    // gather results so we can do bulk actions
    return results.reduce((acc, result) => {

        if (result.failed) {
            acc.failed.push(result.id);
        }
        else if (result.repeat) {
            acc.reset.push(result.id);
        }
        else {
            acc.passed.push(result.id);
        }

        return acc;
    }, { passed: [], reset: [], failed: [] });
};

class Porker extends EventEmitter {
    constructor({ connection, queue, errorThreshold = 1, retryDelay = '5 minutes', timeout = 15000, concurrency = 1, healthcheckPort = null } = {}) {

        super();

        if (!queue) {
            throw new Error('Missing required parameter: queue');
        }

        this.queue = queue;
        this.errorThreshold = errorThreshold;
        this.retryDelay = retryDelay;
        this.timeout = timeout;
        this.concurrency = concurrency;
        this.healthcheckPort = healthcheckPort;

        Object.defineProperties(this, {
            [Symbols.client]: {
                value: new Pg.Client(connection),
                writable: true
            },
            [Symbols.worker]: {
                value: new Pg.Client(connection),
                writable: true
            },
            [Symbols.workerMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.subscriberMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.subscriberResolver]: {
                value: () => {},
                writable: true
            },
            [Symbols.retryWorker]: {
                value: new Pg.Client(connection),
                writable: true
            },
            [Symbols.retryWorkerMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.retrierMutex]: {
                value: Promise.resolve(),
                writable: true
            },
            [Symbols.retrierResolver]: {
                value: () => {},
                writable: true
            },
            [Symbols.queries]: {
                value: Sql.queries(this)
            },
            [Symbols.healthcheck]: {
                value: Http.createServer((req, res) => {

                    // $lab:coverage:off$
                    const connected = this[Symbols.client].readyForQuery &&
                                      (this[Symbols.subscriber] ? this[Symbols.worker].readyForQuery : true) &&
                                      (this[Symbols.retrier] ? this[Symbols.retryWorker].readyForQuery : true);
                    // $lab:coverage:on$

                    res.writeHead(connected ? 200 : 400);
                    return res.end();
                })
            }
        });

        if (this.healthcheckPort) {
            this[Symbols.healthcheck].listen(this.healthcheckPort);
        }
    }

    connect() {

        if (!this[Symbols.connectMutex]) {
            Object.defineProperty(this, Symbols.connectMutex, {
                value: new Promise(async (resolve) => {

                    this[Symbols.client].once('connect', () => {

                        this.emit('ready');
                        return resolve();
                    });

                    await this[Symbols.client].connect();
                })
            });
        }

        return this[Symbols.connectMutex];
    }

    async create() {

        await this.connect();

        return this[Symbols.client].query(this[Symbols.queries].createTable);
    }

    async drop() {

        await this.connect();

        return this[Symbols.client].query(this[Symbols.queries].dropTable);
    }

    async publish(jobs, { priority = 0, repeat } = {}) {

        await this.connect();

        const list = [].concat(jobs);
        const res = await this[Symbols.client].query(this[Symbols.queries].insertJobs(list), [priority, repeat, ...list]);
        await this[Symbols.client].query(this[Symbols.queries].notifyQueue);

        return res.rows.map((row) => {

            return row.id;
        });
    }

    async unpublish(jobs) {

        await this.connect();

        return this[Symbols.client].query(this[Symbols.queries].completeJobs, [[].concat(jobs)]);
    }

    async subscribe(fn) {

        if (this[Symbols.subscriber]) {
            throw new Error('A subscriber has already been added to this queue');
        }

        Object.defineProperty(this, Symbols.subscriber, {
            value: internals.wrapSubscriber(fn, this.timeout)
        });

        await new Promise(async (resolve) => {

            this[Symbols.worker].once('connect', () => {

                return resolve();
            });

            await this[Symbols.worker].connect();
        });

        await this[Symbols.worker].query(this[Symbols.queries].listenPublishes);
        this[Symbols.subscriberMutex] = new Promise((resolve) => {

            this[Symbols.subscriberResolver] = resolve;
        });

        this[Symbols.worker].on('notification', (msg) => {

            this[Symbols.work]();
        });

        this.emit('subscriberReady');
        this[Symbols.work]();
    }

    async [Symbols.work]() {

        await this[Symbols.workerMutex];
        this[Symbols.workerMutex] = (async () => {

            this[Symbols.subscriberMutex] = new Promise((resolve) => {

                this[Symbols.subscriberResolver] = resolve;
            });

            let didWork = false;
            while (!this[Symbols.stopped]) {
                await this[Symbols.worker].query('BEGIN');
                const nextJobs = await this[Symbols.worker].query(this[Symbols.queries].findNextRun);

                if (nextJobs.rowCount === 0) {
                    await this[Symbols.worker].query('ROLLBACK');
                    break;
                }

                // Delay before next job should be run
                const timeout = nextJobs.rows[0].next_run - Date.now();
                if (timeout > 0) {
                    await this[Symbols.worker].query('ROLLBACK');
                    this[Symbols.workTimer] = setTimeout(() => {

                        this[Symbols.work]();
                    }, timeout);
                    break;
                }

                // We have a recurring job, so go ahead and schedule that
                const future = nextJobs.rows.find((row) => row.future_run);
                if (future) {
                    this[Symbols.workTimer] = setTimeout(() => {

                        this[Symbols.work]();
                    }, future.future_run - Date.now());
                }

                didWork = true;
                const jobs = await this[Symbols.worker].query(this[Symbols.queries].lockJobs, [nextJobs.rows.map((row) => row.id)]);
                const results = await internals.gatherResults(this[Symbols.subscriber], jobs.rows.map((row) => Object.assign({}, row)));

                await this[Symbols.worker].query(this[Symbols.queries].completeJobs, [results.passed]);
                await this[Symbols.worker].query(this[Symbols.queries].errorJobs, [results.failed]);
                await this[Symbols.worker].query('COMMIT');

                // If we had errors, notify the retry queue
                if (results.failed.length) {
                    await this[Symbols.worker].query(this[Symbols.queries].notifyRetryQueue);
                }
            }

            if (didWork) {
                this.emit('drain');
            }

            this[Symbols.subscriberResolver]();
        })();
    }

    async retry(fn) {

        if (this[Symbols.retrier]) {
            throw new Error('A retry handler has already been added to this queue');
        }

        Object.defineProperty(this, Symbols.retrier, {
            value: internals.wrapSubscriber(fn, this.timeout)
        });

        await new Promise(async (resolve) => {

            this[Symbols.retryWorker].once('connect', () => {

                return resolve();
            });

            await this[Symbols.retryWorker].connect();
        });

        await this[Symbols.retryWorker].query(this[Symbols.queries].listenRetries);
        this[Symbols.retrierMutex] = new Promise((resolve) => {

            this[Symbols.retrierResolver] = resolve;
        });

        this[Symbols.retryWorker].on('notification', (msg) => {

            this[Symbols.retry]();
        });

        this.emit('retrierReady');
        this[Symbols.retry]();
    }

    async [Symbols.retry]() {

        await this[Symbols.retryWorkerMutex];
        this[Symbols.retryWorkerMutex] = (async () => {

            this[Symbols.retrierMutex] = new Promise((resolve) => {

                this[Symbols.retrierResolver] = resolve;
            });

            let didWork = false;
            while (!this[Symbols.stopped]) {
                await this[Symbols.retryWorker].query('BEGIN');
                const nextJobs = await this[Symbols.retryWorker].query(this[Symbols.queries].findRetries);

                if (nextJobs.rowCount === 0) {
                    await this[Symbols.retryWorker].query('ROLLBACK');
                    break;
                }

                const timeout = nextJobs.rows[0].retry_at - Date.now();
                if (timeout > 0) {
                    await this[Symbols.retryWorker].query('ROLLBACK');
                    this[Symbols.retryWorkTimer] = setTimeout(() => {

                        this[Symbols.retry]();
                    }, timeout);
                    break;
                }

                didWork = true;
                const jobs = await this[Symbols.retryWorker].query(this[Symbols.queries].lockRetries, [nextJobs.rows.map((row) => row.id)]);
                const results = await internals.gatherResults(this[Symbols.retrier], jobs.rows.map((row) => Object.assign({}, row)));

                await this[Symbols.retryWorker].query(this[Symbols.queries].completeJobs, [results.passed]);
                await this[Symbols.retryWorker].query(this[Symbols.queries].resetJobs, [results.reset]);
                await this[Symbols.retryWorker].query(this[Symbols.queries].errorJobs, [results.failed]);
                await this[Symbols.retryWorker].query('COMMIT');
            }

            if (didWork) {
                this.emit('drainRetries');
            }

            this[Symbols.retrierResolver]();
        })();
    }

    async end() {

        this[Symbols.stopped] = true;

        if (this.healthcheckPort) {
            await new Promise((resolve) => {

                this[Symbols.healthcheck].close(resolve);
            });
        }

        if (this[Symbols.client]._connected) {
            await this[Symbols.client].end();
        }

        if (this[Symbols.worker]._connected) {
            await this[Symbols.subscriberMutex];
            await this[Symbols.workerMutex];
            clearTimeout(this[Symbols.workTimer]);

            await this[Symbols.worker].end();
        }

        if (this[Symbols.retryWorker]._connected) {
            await this[Symbols.retrierMutex];
            await this[Symbols.retryWorkerMutex];
            clearTimeout(this[Symbols.retryWorkTimer]);

            await this[Symbols.retryWorker].end();
        }

        this.emit('end');
    }
}

module.exports = Porker;
<|endoftext|>


<commit_before>function Simulation( renderer, width, height ) {

	this.width = width;
	this.height = height;
	this.renderer = renderer;
	this.targetPos = 0;

	this.data = new Float32Array( this.width * this.height * 4 );

//	var geometry = new THREE.IcosahedronGeometry( 2, 3 );
//	var geometry = new THREE.TorusKnotGeometry( 2, .6, 100, 16 );
//	var geometry = new THREE.BoxGeometry( 1,1,1 );

//	var m = new THREE.Mesh( geometry, new THREE.MeshNormalMaterial () );
//	scene.add( m );

//	var points = THREE.GeometryUtils.randomPointsInGeometry( geometry, this.width * this.height );

	/*
	var r = 2;
	for( var i = 0, l = this.width * this.height; i < l; i ++ ) {

		this.data[ i * 4 ] = ( .5 - Math.random() ) * r;
		this.data[ i * 4 + 1 ] = ( .5 - Math.random() ) * r;
		this.data[ i * 4 + 2 ] = ( .5 - Math.random() ) * r;
		this.data[ i * 4 + 3 ] = Math.random() * 100; // frames life

	}*/

	var r = 1;
	for( var i = 0, l = this.width * this.height; i < l; i ++ ) {

		var phi = Math.random() * 2 * Math.PI;
		var costheta = Math.random() * 2 -1;
		var theta = Math.acos( costheta );
		r = .85 + .15 * Math.random();

		this.data[ i * 4 ] = r * Math.sin( theta) * Math.cos( phi );
		this.data[ i * 4 + 1 ] = r * Math.sin( theta) * Math.sin( phi );
		this.data[ i * 4 + 2 ] = r * Math.cos( theta );
		this.data[ i * 4 + 3 ] = Math.random() * 100; // frames life

	}

/*	points.forEach( function( p, i ) {

		this.data[ i * 4 ]     = p.x;
		this.data[ i * 4 + 1 ] = p.y;
		this.data[ i * 4 + 2 ] = p.z;
		this.data[ i * 4 + 3 ] = Math.random() * 100; // frames life

	}.bind( this ) );*/

	var floatType = isMobile.apple.device ? THREE.HalfFloatType : THREE.FloatType;

	this.texture = new THREE.DataTexture( this.data, this.width, this.height, THREE.RGBAFormat, THREE.FloatType );
	this.texture.minFilter = THREE.NearestFilter;
	this.texture.magFilter = THREE.NearestFilter;
	this.texture.needsUpdate = true;

	this.rtTexturePos = new THREE.WebGLRenderTarget( this.width, this.height, {
		wrapS: THREE.RepeatWrapping,
		wrapT: THREE.RepeatWrapping,
		minFilter: THREE.NearestFilter,
		magFilter: THREE.NearestFilter,
		format: THREE.RGBAFormat,
		type: floatType,
		stencilBuffer: false
	});

	this.targets = [ this.rtTexturePos, this.rtTexturePos.clone() ];

	this.simulationShader = new THREE.ShaderMaterial({

		uniforms: {
			active: { type: 'f', value: 1 },
			width: { type: "f", value: this.width },
			height: { type: "f", value: this.height },
			oPositions: { type: "t", value: this.texture },
			tPositions: { type: "t", value: null },
			timer: { type: "f", value: 0 },
			delta: { type: "f", value: 0 },
			speed: { type: "f", value: .5 },
			reset: { type: 'f', value: 0 },
			offset: { type: 'v3', value: new THREE.Vector3( 0, 0, 0 ) },
			genScale: { type: 'f', value: 1 },
			factor: { type: 'f', value: .5 },
			evolution: { type: 'f', value: .5 },
			inverseModelViewMatrix: { type: 'm4', value: new THREE.Matrix4() },
			radius: { type: 'f', value: 2 }
		},

		vertexShader: document.getElementById('texture_vertex_simulation_shader').textContent,
		fragmentShader:  document.getElementById('texture_fragment_simulation_shader').textContent,
		side: THREE.DoubleSide

	});

	this.simulationShader.uniforms.tPositions.value = this.texture;

	this.rtScene = new THREE.Scene();
	this.rtCamera = new THREE.OrthographicCamera( -this.width / 2, this.width / 2, -this.height / 2, this.height / 2, -500, 1000 );
	this.rtQuad = new THREE.Mesh(
		new THREE.PlaneBufferGeometry( this.width, this.height ),
		this.simulationShader
	);
	this.rtScene.add( this.rtQuad );

	this.renderer.render( this.rtScene, this.rtCamera, this.rtTexturePos );

	this.plane = new THREE.Mesh( new THREE.PlaneGeometry( 64, 64 ), new THREE.MeshBasicMaterial( { map: this.rtTexturePos, side: THREE.DoubleSide } ) );
	//scene.add( this.plane );

}

Simulation.prototype.render = function( time, delta ) {

	this.simulationShader.uniforms.timer.value = time;
	this.simulationShader.uniforms.delta.value = delta;

	this.simulationShader.uniforms.tPositions.value = this.targets[ this.targetPos ];
	this.targetPos = 1 - this.targetPos;
	this.renderer.render( this.rtScene, this.rtCamera, this.targets[ this.targetPos ] );

}
<commit_msg>optimisation<commit_after>function Simulation( renderer, width, height ) {

	this.width = width;
	this.height = height;
	this.renderer = renderer;
	this.targetPos = 0;

	this.data = new Float32Array( this.width * this.height * 4 );

//	var geometry = new THREE.IcosahedronGeometry( 2, 3 );
//	var geometry = new THREE.TorusKnotGeometry( 2, .6, 100, 16 );
//	var geometry = new THREE.BoxGeometry( 1,1,1 );

//	var m = new THREE.Mesh( geometry, new THREE.MeshNormalMaterial () );
//	scene.add( m );

//	var points = THREE.GeometryUtils.randomPointsInGeometry( geometry, this.width * this.height );

	/*
	var r = 2;
	for( var i = 0, l = this.width * this.height; i < l; i ++ ) {

		this.data[ i * 4 ] = ( .5 - Math.random() ) * r;
		this.data[ i * 4 + 1 ] = ( .5 - Math.random() ) * r;
		this.data[ i * 4 + 2 ] = ( .5 - Math.random() ) * r;
		this.data[ i * 4 + 3 ] = Math.random() * 100; // frames life

	}*/

	var r = 1;
	for( var i = 0, l = this.width * this.height; i < l; i ++ ) {

		var phi = Math.random() * 2 * Math.PI;
		var costheta = Math.random() * 2 -1;
		var theta = Math.acos( costheta );
		r = .85 + .15 * Math.random();

		this.data[ i * 4 ] = r * Math.sin( theta) * Math.cos( phi );
		this.data[ i * 4 + 1 ] = r * Math.sin( theta) * Math.sin( phi );
		this.data[ i * 4 + 2 ] = r * Math.cos( theta );
		this.data[ i * 4 + 3 ] = Math.random() * 100; // frames life

	}

/*	points.forEach( function( p, i ) {

		this.data[ i * 4 ]     = p.x;
		this.data[ i * 4 + 1 ] = p.y;
		this.data[ i * 4 + 2 ] = p.z;
		this.data[ i * 4 + 3 ] = Math.random() * 100; // frames life

	}.bind( this ) );*/

	var floatType = isMobile.apple.device ? THREE.HalfFloatType : THREE.FloatType;

	this.texture = new THREE.DataTexture( this.data, this.width, this.height, THREE.RGBAFormat, THREE.FloatType );
	this.texture.minFilter = THREE.NearestFilter;
	this.texture.magFilter = THREE.NearestFilter;
	this.texture.needsUpdate = true;

	this.rtTexturePos = new THREE.WebGLRenderTarget( this.width, this.height, {
		wrapS: THREE.ClampToEdgeWrapping,
		wrapT: THREE.ClampToEdgeWrapping,
		minFilter: THREE.NearestFilter,
		magFilter: THREE.NearestFilter,
		format: THREE.RGBAFormat,
		type: floatType,
		stencilBuffer: false,
		depthBuffer: false,
		generateMipmaps: false
	});

	this.targets = [ this.rtTexturePos, this.rtTexturePos.clone() ];

	this.simulationShader = new THREE.ShaderMaterial({

		uniforms: {
			active: { type: 'f', value: 1 },
			width: { type: "f", value: this.width },
			height: { type: "f", value: this.height },
			oPositions: { type: "t", value: this.texture },
			tPositions: { type: "t", value: null },
			timer: { type: "f", value: 0 },
			delta: { type: "f", value: 0 },
			speed: { type: "f", value: .5 },
			reset: { type: 'f', value: 0 },
			offset: { type: 'v3', value: new THREE.Vector3( 0, 0, 0 ) },
			genScale: { type: 'f', value: 1 },
			factor: { type: 'f', value: .5 },
			evolution: { type: 'f', value: .5 },
			inverseModelViewMatrix: { type: 'm4', value: new THREE.Matrix4() },
			radius: { type: 'f', value: 2 }
		},

		vertexShader: document.getElementById('texture_vertex_simulation_shader').textContent,
		fragmentShader:  document.getElementById('texture_fragment_simulation_shader').textContent,
		side: THREE.DoubleSide

	});

	this.simulationShader.uniforms.tPositions.value = this.texture;

	this.rtScene = new THREE.Scene();
	this.rtCamera = new THREE.OrthographicCamera( -this.width / 2, this.width / 2, -this.height / 2, this.height / 2, -500, 1000 );
	this.rtQuad = new THREE.Mesh(
		new THREE.PlaneBufferGeometry( this.width, this.height ),
		this.simulationShader
	);
	this.rtScene.add( this.rtQuad );

	this.renderer.render( this.rtScene, this.rtCamera, this.rtTexturePos );

	this.plane = new THREE.Mesh( new THREE.PlaneGeometry( 64, 64 ), new THREE.MeshBasicMaterial( { map: this.rtTexturePos, side: THREE.DoubleSide } ) );
	//scene.add( this.plane );

}

Simulation.prototype.render = function( time, delta ) {

	this.simulationShader.uniforms.timer.value = time;
	this.simulationShader.uniforms.delta.value = delta;

	this.simulationShader.uniforms.tPositions.value = this.targets[ this.targetPos ];
	this.targetPos = 1 - this.targetPos;
	this.renderer.render( this.rtScene, this.rtCamera, this.targets[ this.targetPos ] );

}
<|endoftext|>