<commit_before>var inherits = require('util').inherits;

module.exports = function (HAPnode, config, functions) {
    var Accessory = HAPnode.Accessory;
    var Service = HAPnode.Service;
    var Characteristic = HAPnode.Characteristic;
    var uuid = HAPnode.uuid;
    var debug = HAPnode.debug;

    var module = {};

    module.newDevice = function (device, tempDisplayUnit) {
        var temperatureDisplayUnit = tempDisplayUnit;
        var Thermostat = {

            veraIsUsingFahrenheit: function(){
                return this.getTemperatureDisplayUnits() == Characteristic.TemperatureDisplayUnits.FAHRENHEIT
            },

            fahrenheitToCelsius: function(temperature) {
                return (temperature - 32) / 1.8;
            },

            celsiusToFahrenheit: function(temperature) {
                return (temperature * 1.8) + 32;
            },

            getHVACState: function(){
                var hvacstate, temperature, heat, cool;
                debug("Making hvacstate request for device %s", device.name);
                hvacstate = functions.getVariable(device.id, 'hvacstate');
                temperature = parseInt(functions.getVariable(device.id, 'temperature'));
                heat = parseInt(functions.getVariable(device.id, 'heat'));
                cool = parseInt(functions.getVariable(device.id, 'cool'));
                debug("HVAC State returned as: ", hvacstate);
                if (hvacstate){
                  switch(hvacstate){
                      case "Idle":
                      case "PendingHeat":
                      case "PendingCool":
                      case "FanOnly":
                      case "Vent":
                          return Characteristic.CurrentHeatingCoolingState.OFF;
                          break;
                      case "Heating":
                          return Characteristic.CurrentHeatingCoolingState.HEAT;
                          break;
                      case "Cooling":
                          return Characteristic.CurrentHeatingCoolingState.COOL;
                          break;
                      default:
                          null;
                  }
                }else{
                    debug("WARNING: NEST ")
                    mode = functions.getVariable(device.id, 'mode');
                    switch(mode){
                        case "Off":
                        case "InDeadBand":
                        case "Idle":
                        case "eco": // Nest Plugin

                          return Characteristic.CurrentHeatingCoolingState.OFF;
                          break;
                        case "HeatOn":
                        case "AuxHeatOn":
                        case "EconomyHeatOn":
                        case "EmergencyHeatOn":
                        case "EnergySavingsHeating":
                        case "BuildingProtection":
                            if (heat<temperature){
                              return Characteristic.CurrentHeatingCoolingState.OFF;
                            }else{
                              return Characteristic.CurrentHeatingCoolingState.HEAT;
                            }
                            break;
                        case "CoolOn":
                        case "AuxCoolOn":
                        case "EconomyCoolOn":
                            if (cool>temperature){
                              return Characteristic.CurrentHeatingCoolingState.OFF;
                            }else{
                              return Characteristic.CurrentHeatingCoolingState.COOL;
                            }
                            break;
                        default:
                            null;
                  }
                }
            },

            getMode: function(){
                mode = functions.getVariable(device.id, 'mode');
                switch(mode){
                    case "Off":
                    case "eco": // Nest Plugin
                        return Characteristic.TargetHeatingCoolingState.OFF;
                        break;
                    case "HeatOn":
                    case "AuxHeatOn":
                    case "EconomyHeatOn":
                    case "EmergencyHeatOn":
                    case "BuildingProtection":
                        return Characteristic.TargetHeatingCoolingState.HEAT;
                        break;
                    case "CoolOn":
                    case "AuxCoolOn":
                    case "EconomyCoolOn":
                        return Characteristic.TargetHeatingCoolingState.COOL;
                        break;
                    case "AutoChangeOver":
                    case "EnergySavingsMode":
                        return Characteristic.TargetHeatingCoolingState.AUTO;
                        break;
                    default:
                        null;
              }
            },

            setMode: function(value){
                var serviceId = 'urn:upnp-org:serviceId:HVAC_UserOperatingMode1';
                var action_name = 'SetModeTarget';
                var action_param = 'NewModeTarget';
                var params = {
                  action: action_name,
                  serviceId: serviceId,
                  DeviceNum: device.id
                }
                var vera_value;

                switch(value){
                    case Characteristic.TargetHeatingCoolingState.OFF:
                        vera_value = "Off";
                        break;
                    case Characteristic.TargetHeatingCoolingState.HEAT:
                        vera_value = "HeatOn";
                        break;
                    case Characteristic.TargetHeatingCoolingState.COOL:
                        vera_value = "CoolOn";
                        break;
                    case Characteristic.TargetHeatingCoolingState.AUTO:
                        vera_value = "AutoChangeOver";
                        break;
                }
                params[action_param] = vera_value
                return functions.executeAction(params);
            },

            getCurrentTemperature: function(){
                debug("Making temperature request for device %s", device.name);
                temperature = parseFloat(functions.getVariable(device.id, 'temperature'));
                debug("[Before Conversion] Current Temperature for Thermostat #%s is %s", device.name, temperature);
                if (this.veraIsUsingFahrenheit()){
                    temperature = this.fahrenheitToCelsius(temperature);
                }
                temperature = Math.round( temperature * 1e1 ) / 1e1;
                debug("Current Temperature for Thermostat #%s is %s", device.name, temperature);
                // return Math.max(0, temperature);
                return temperature;
            },

            getTargetTemperature: function(){
                debug("Making setpoint request for device %s", device.name);
                setpoint = parseInt(functions.getVariable(device.id, 'setpoint'));
                debug("[Before Conversion] Current Temperature for Thermostat #%s is %s", device.name, setpoint);
                if (this.veraIsUsingFahrenheit()){
                    setpoint = this.fahrenheitToCelsius(setpoint);
                }
                //setpoint = parseInt(setpoint);
                debug("Current Temperature for Thermostat #%s is %s", device.name, setpoint);
                return setpoint;
            },

            setTargetTemperature: function(value){
                var services, action_name, action_param, mode;
                mode = Thermostat.getMode();
                switch(mode){
                  case 1:
                    services = ['urn:upnp-org:serviceId:TemperatureSetpoint1_Heat'];
                    break;
                  case 2:
                    services = ['urn:upnp-org:serviceId:TemperatureSetpoint1_Cool'];
                    break;
                  case 3:
                    services = ['urn:upnp-org:serviceId:TemperatureSetpoint1_Heat','urn:upnp-org:serviceId:TemperatureSetpoint1_Cool'];
                    break;
                }
                if (this.veraIsUsingFahrenheit()){
                    value = Math.round(this.celsiusToFahrenheit(value));
                };

                action_name = 'SetCurrentSetpoint';
                requests = services.map(function(service){
                  functions.executeAction({
                    action: 'SetCurrentSetpoint',
                    serviceId: service,
                    DeviceNum: device.id,
                    NewCurrentSetpoint: value
                  }).then(function(response){

                  }).catch(function(response){
                    console.log("response was:", typeof(response));
                    // Dirty Hack For WWN Plugin. Uses wrong serviceId
                    if(typeof(response) === 'string' && response.match(/ERROR/)){
                      functions.executeAction({
                        action: 'SetCurrentSetpoint',
                        serviceId: service.match(/(^.+)_/)[1],
                        DeviceNum: device.id,
                        NewCurrentSetpoint: value
                      });
                    }
                  })
                });
                debug("[After Conversion] Set Target Temperature for Thermostat #%s is %s", device.name, value);
                //
                return Promise.all(requests);
            },

            getTemperatureDisplayUnits: function(){
                if (temperatureDisplayUnit == "C"){
                    return Characteristic.TemperatureDisplayUnits.CELSIUS
                }else{
                    return Characteristic.TemperatureDisplayUnits.FAHRENHEIT
                }
            },

            getBatteryLevel: function () {
                var level = parseFloat(functions.getVariable(device.id, 'batterylevel'));
                debug("current battery level: %s", level);
                return level;
            },

            getIsLowBattery: function () {
                var level = this.getBatteryLevel();
                debug("Checking low battery for level %s: ", level);
                if (level && level < 20) {
                    return Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
                }
                return Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
            },

            identify: function () {
                debug("Identity of the thermostat is %s", device.name);
            }
        };

        var thermostatUUID = uuid.generate('device:thermostat:' + config.cardinality + ':' + device.id);
        var thermostat = new Accessory(device.name, thermostatUUID);
        thermostat.username = functions.genMac('device:' + config.cardinality + ':' + device.id);
        thermostat.pincode = config.pincode;
        thermostat.deviceid = device.id;

        thermostat.on('identify', function (paired, callback) {
            Thermostat.identify();
            callback(); // success
        });

        var service = new Service.Thermostat(device.name);

        service.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
            .on('get', function (callback) {
                debug('Getting current heating/cooling state for %s', device.name);
                callback(null, Thermostat.getHVACState());
            });

        service.getCharacteristic(Characteristic.TargetHeatingCoolingState)
            .on('get', function (callback) {
                debug('Getting target heating/cooling value for %s', device.name);
                callback(null, Thermostat.getMode());
            });

        service.getCharacteristic(Characteristic.TargetHeatingCoolingState)
            .on('set', function (value, callback) {
                debug('Setting target heating/cooling value for %s', device.name);
                Thermostat.setMode(value).then(function(val){
                    // return our current value
                    callback(null, val);
                });
            });

        service
          .getCharacteristic(Characteristic.CurrentTemperature)
          .setProps({
              minValue: -100,
              maxValue: 100
          })
          .on('get', function (callback) {
              debug('Getting current temp. value for %s', device.name);
              callback(null, Thermostat.getCurrentTemperature());
          });

        service
          .getCharacteristic(Characteristic.TargetTemperature)
          .setProps({
              minValue: -100,
              maxValue: 100
          })
          .on('get', function (callback) {
              debug('Getting target temp value for %s', device.name);
              callback(null, Thermostat.getTargetTemperature());
          });

        service.getCharacteristic(Characteristic.TargetTemperature)
            .on('set', function (value, callback) {
                debug('Setting target temp value for %s to %s', device.name, value);
                Thermostat.setTargetTemperature(value).then(function(val){
                    // return our current value
                    callback(null, val);
                });
            });

        service.getCharacteristic(Characteristic.TemperatureDisplayUnits)
            .on('get', function (callback) {
                unit = Thermostat.getTemperatureDisplayUnits();
                debug('Temperature display unit for %s is %s', device.name, unit);
                callback(null, unit);
            });

        service.getCharacteristic(Characteristic.TemperatureDisplayUnits)
            .on('set', function (value, callback) {
                debug('Setting temp display units value for %s', device.name);
                    // return our current value
                    callback(null, value);
            });

        thermostat.addService(service);

        var batteryService = new Service.BatteryService(device.name);
        batteryService.getCharacteristic(Characteristic.BatteryLevel)
            .on('get', function (callback) {
          		callback(null, Thermostat.getBatteryLevel());
            });

        batteryService.getCharacteristic(Characteristic.StatusLowBattery)
            .on('get', function (callback) {
		callback(null, Thermostat.getIsLowBattery());
            });

        thermostat.addService(batteryService);

        return thermostat;
    };

    return module;
};
<commit_msg>cleanup<commit_after>var inherits = require('util').inherits;

module.exports = function (HAPnode, config, functions) {
    var Accessory = HAPnode.Accessory;
    var Service = HAPnode.Service;
    var Characteristic = HAPnode.Characteristic;
    var uuid = HAPnode.uuid;
    var debug = HAPnode.debug;

    var module = {};

    module.newDevice = function (device, tempDisplayUnit) {
        var temperatureDisplayUnit = tempDisplayUnit;
        var Thermostat = {

            veraIsUsingFahrenheit: function(){
                return this.getTemperatureDisplayUnits() == Characteristic.TemperatureDisplayUnits.FAHRENHEIT
            },

            fahrenheitToCelsius: function(temperature) {
                return (temperature - 32) / 1.8;
            },

            celsiusToFahrenheit: function(temperature) {
                return (temperature * 1.8) + 32;
            },

            getHVACState: function(){
                var hvacstate, temperature, heat, cool;
                debug("Making hvacstate request for device %s", device.name);
                hvacstate = functions.getVariable(device.id, 'hvacstate');
                temperature = parseInt(functions.getVariable(device.id, 'temperature'));
                heat = parseInt(functions.getVariable(device.id, 'heat'));
                cool = parseInt(functions.getVariable(device.id, 'cool'));
                debug("HVAC State returned as: ", hvacstate);
                if (hvacstate){
                  switch(hvacstate){
                      case "Idle":
                      case "PendingHeat":
                      case "PendingCool":
                      case "FanOnly":
                      case "Vent":
                          return Characteristic.CurrentHeatingCoolingState.OFF;
                          break;
                      case "Heating":
                          return Characteristic.CurrentHeatingCoolingState.HEAT;
                          break;
                      case "Cooling":
                          return Characteristic.CurrentHeatingCoolingState.COOL;
                          break;
                      default:
                          null;
                  }
                }else{
                    debug("WARNING: NEST ")
                    mode = functions.getVariable(device.id, 'mode');
                    switch(mode){
                        case "Off":
                        case "InDeadBand":
                        case "Idle":
                        case "eco": // Nest Plugin

                          return Characteristic.CurrentHeatingCoolingState.OFF;
                          break;
                        case "HeatOn":
                        case "AuxHeatOn":
                        case "EconomyHeatOn":
                        case "EmergencyHeatOn":
                        case "EnergySavingsHeating":
                        case "BuildingProtection":
                            if (heat<temperature){
                              return Characteristic.CurrentHeatingCoolingState.OFF;
                            }else{
                              return Characteristic.CurrentHeatingCoolingState.HEAT;
                            }
                            break;
                        case "CoolOn":
                        case "AuxCoolOn":
                        case "EconomyCoolOn":
                            if (cool>temperature){
                              return Characteristic.CurrentHeatingCoolingState.OFF;
                            }else{
                              return Characteristic.CurrentHeatingCoolingState.COOL;
                            }
                            break;
                        default:
                            null;
                  }
                }
            },

            getMode: function(){
                mode = functions.getVariable(device.id, 'mode');
                switch(mode){
                    case "Off":
                    case "eco": // Nest Plugin
                        return Characteristic.TargetHeatingCoolingState.OFF;
                        break;
                    case "HeatOn":
                    case "AuxHeatOn":
                    case "EconomyHeatOn":
                    case "EmergencyHeatOn":
                    case "BuildingProtection":
                        return Characteristic.TargetHeatingCoolingState.HEAT;
                        break;
                    case "CoolOn":
                    case "AuxCoolOn":
                    case "EconomyCoolOn":
                        return Characteristic.TargetHeatingCoolingState.COOL;
                        break;
                    case "AutoChangeOver":
                    case "EnergySavingsMode":
                        return Characteristic.TargetHeatingCoolingState.AUTO;
                        break;
                    default:
                        null;
              }
            },

            setMode: function(value){
                var serviceId = 'urn:upnp-org:serviceId:HVAC_UserOperatingMode1';
                var action_name = 'SetModeTarget';
                var action_param = 'NewModeTarget';
                var params = {
                  action: action_name,
                  serviceId: serviceId,
                  DeviceNum: device.id
                }
                var vera_value;

                switch(value){
                    case Characteristic.TargetHeatingCoolingState.OFF:
                        vera_value = "Off";
                        break;
                    case Characteristic.TargetHeatingCoolingState.HEAT:
                        vera_value = "HeatOn";
                        break;
                    case Characteristic.TargetHeatingCoolingState.COOL:
                        vera_value = "CoolOn";
                        break;
                    case Characteristic.TargetHeatingCoolingState.AUTO:
                        vera_value = "AutoChangeOver";
                        break;
                }
                params[action_param] = vera_value
                return functions.executeAction(params);
            },

            getCurrentTemperature: function(){
                debug("Making temperature request for device %s", device.name);
                temperature = parseFloat(functions.getVariable(device.id, 'temperature'));
                debug("[Before Conversion] Current Temperature for Thermostat #%s is %s", device.name, temperature);
                if (this.veraIsUsingFahrenheit()){
                    temperature = this.fahrenheitToCelsius(temperature);
                }
                temperature = Math.round( temperature * 1e1 ) / 1e1;
                debug("Current Temperature for Thermostat #%s is %s", device.name, temperature);
                return temperature;
            },

            getTargetTemperature: function(){
                debug("Making setpoint request for device %s", device.name);
                setpoint = parseInt(functions.getVariable(device.id, 'setpoint'));
                debug("[Before Conversion] Current Temperature for Thermostat #%s is %s", device.name, setpoint);
                if (this.veraIsUsingFahrenheit()){
                    setpoint = this.fahrenheitToCelsius(setpoint);
                }
                //setpoint = parseInt(setpoint);
                debug("Current Temperature for Thermostat #%s is %s", device.name, setpoint);
                return setpoint;
            },

            setTargetTemperature: function(value){
                var services, action_name, action_param, mode;
                mode = Thermostat.getMode();
                switch(mode){
                  case 1:
                    services = ['urn:upnp-org:serviceId:TemperatureSetpoint1_Heat'];
                    break;
                  case 2:
                    services = ['urn:upnp-org:serviceId:TemperatureSetpoint1_Cool'];
                    break;
                  case 3:
                    services = ['urn:upnp-org:serviceId:TemperatureSetpoint1_Heat','urn:upnp-org:serviceId:TemperatureSetpoint1_Cool'];
                    break;
                }
                if (this.veraIsUsingFahrenheit()){
                    value = Math.round(this.celsiusToFahrenheit(value));
                };

                action_name = 'SetCurrentSetpoint';
                requests = services.map(function(service){
                  functions.executeAction({
                    action: 'SetCurrentSetpoint',
                    serviceId: service,
                    DeviceNum: device.id,
                    NewCurrentSetpoint: value
                  }).then(function(response){

                  }).catch(function(response){
                    console.log("response was:", typeof(response));
                    // Dirty Hack For WWN Plugin. Uses wrong serviceId
                    if(typeof(response) === 'string' && response.match(/ERROR/)){
                      functions.executeAction({
                        action: 'SetCurrentSetpoint',
                        serviceId: service.match(/(^.+)_/)[1],
                        DeviceNum: device.id,
                        NewCurrentSetpoint: value
                      });
                    }
                  })
                });
                debug("[After Conversion] Set Target Temperature for Thermostat #%s is %s", device.name, value);
                //
                return Promise.all(requests);
            },

            getTemperatureDisplayUnits: function(){
                if (temperatureDisplayUnit == "C"){
                    return Characteristic.TemperatureDisplayUnits.CELSIUS
                }else{
                    return Characteristic.TemperatureDisplayUnits.FAHRENHEIT
                }
            },

            getBatteryLevel: function () {
                var level = parseFloat(functions.getVariable(device.id, 'batterylevel'));
                debug("current battery level: %s", level);
                return level;
            },

            getIsLowBattery: function () {
                var level = this.getBatteryLevel();
                debug("Checking low battery for level %s: ", level);
                if (level && level < 20) {
                    return Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW;
                }
                return Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
            },

            identify: function () {
                debug("Identity of the thermostat is %s", device.name);
            }
        };

        var thermostatUUID = uuid.generate('device:thermostat:' + config.cardinality + ':' + device.id);
        var thermostat = new Accessory(device.name, thermostatUUID);
        thermostat.username = functions.genMac('device:' + config.cardinality + ':' + device.id);
        thermostat.pincode = config.pincode;
        thermostat.deviceid = device.id;

        thermostat.on('identify', function (paired, callback) {
            Thermostat.identify();
            callback(); // success
        });

        var service = new Service.Thermostat(device.name);

        service.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
            .on('get', function (callback) {
                debug('Getting current heating/cooling state for %s', device.name);
                callback(null, Thermostat.getHVACState());
            });

        service.getCharacteristic(Characteristic.TargetHeatingCoolingState)
            .on('get', function (callback) {
                debug('Getting target heating/cooling value for %s', device.name);
                callback(null, Thermostat.getMode());
            });

        service.getCharacteristic(Characteristic.TargetHeatingCoolingState)
            .on('set', function (value, callback) {
                debug('Setting target heating/cooling value for %s', device.name);
                Thermostat.setMode(value).then(function(val){
                    // return our current value
                    callback(null, val);
                });
            });

        service
          .getCharacteristic(Characteristic.CurrentTemperature)
          .setProps({
              minValue: -100,
              maxValue: 100
          })
          .on('get', function (callback) {
              debug('Getting current temp. value for %s', device.name);
              callback(null, Thermostat.getCurrentTemperature());
          });

        service
          .getCharacteristic(Characteristic.TargetTemperature)
          .setProps({
              minValue: -100,
              maxValue: 100
          })
          .on('get', function (callback) {
              debug('Getting target temp value for %s', device.name);
              callback(null, Thermostat.getTargetTemperature());
          });

        service.getCharacteristic(Characteristic.TargetTemperature)
            .on('set', function (value, callback) {
                debug('Setting target temp value for %s to %s', device.name, value);
                Thermostat.setTargetTemperature(value).then(function(val){
                    // return our current value
                    callback(null, val);
                });
            });

        service.getCharacteristic(Characteristic.TemperatureDisplayUnits)
            .on('get', function (callback) {
                unit = Thermostat.getTemperatureDisplayUnits();
                debug('Temperature display unit for %s is %s', device.name, unit);
                callback(null, unit);
            });

        service.getCharacteristic(Characteristic.TemperatureDisplayUnits)
            .on('set', function (value, callback) {
                debug('Setting temp display units value for %s', device.name);
                    // return our current value
                    callback(null, value);
            });

        thermostat.addService(service);

        var batteryService = new Service.BatteryService(device.name);
        batteryService.getCharacteristic(Characteristic.BatteryLevel)
            .on('get', function (callback) {
          		callback(null, Thermostat.getBatteryLevel());
            });

        batteryService.getCharacteristic(Characteristic.StatusLowBattery)
            .on('get', function (callback) {
		callback(null, Thermostat.getIsLowBattery());
            });

        thermostat.addService(batteryService);

        return thermostat;
    };

    return module;
};
<|endoftext|>


<commit_before>var winston = require('winston'),
	_ = require('underscore')._,
	EventEmitter = require('events').EventEmitter,
	redis_lib = require('redis')
	express = require('express'),
	http = require('http'),
	passport = require('passport'),
	GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
	sockjs_lib = require('sockjs');

var logger;

exports.UnhangoutServer = function() {

}

exports.UnhangoutServer.prototype = {
	options: null,
	running: false,
	express: null,
	http: null,
	sockjs: null,
	inited: false,
	redis: null,

	init: function(options) {
		if(_.isUndefined(options)) {
			options = {};
		}

		this.options = _.defaults(options, {"level":"debug", "transport":"console", "HOST":"localhost", "PORT":7777,
			"REDIS_HOST":"localhost", "REDIS_PORT":6379, "SESSION_SECRET":"fake secret", "REDIS_DB":0});
		var transports = [];
		switch(this.options.transport) {
			case "console":
				transports.push(new (winston.transports.Console)({
						timestamp: true,
						json: false,
						level: this.options.level
					}));
				break;
			case "file":
				transports.push(new (winston.transports.File)({
							filename: 'server.log',
							timestamp: true,
							json: false,
							level: this.options.level
						}));
				break;
		}

		logger = new (winston.Logger)({transports:transports, levels:winston.config.syslog.levels});

		if(this.options.transport=="console") logger.cli();

		if(!("GOOGLE_CLIENT_ID" in this.options)) {
			logger.error("Missing GOOGLE_CLIENT_ID in options.")
			this.emit("error", "Missing GOOGLE_CLIENT_ID in options.");
			return;
		}

		if(!("GOOGLE_CLIENT_SECRET" in this.options)) {
			logger.error("Missing GOOGLE_CLIENT_SECRET in options.")
			this.emit("error", "Missing GOOGLE_CLIENT_SECRET in options.");
			return;
		}

		this.redis = redis_lib.createClient(parseInt(this.options.REDIS_PORT), this.options.REDIS_HOST);
		this.redis.on("connect", _.bind(function(err) {
			if(err) {
				logger.error("Error connecting to redis: " + err);
				this.emit("error", "Error connecting to redis: " + err, err);
				return;
			}

			this.inited = true;
			logger.info("UnhangoutServer initialized.");
			this.redis.select(this.options.REDIS_DB, _.bind(function(err, res) {
				if(err) this.emit("error", err);
				this.emit("inited");
			}, this));
		}, this));
	},

	start: function() {
		if(!this.inited) {
			logger.error("Attempted to start, but server is not initialized.");
			this.emit("error", "Attempted to start, but server is not initialized.");
			return;
		}

		logger.log("info", "Starting UnhangoutServer on %s:%d", this.options.HOST, this.options.PORT);

		this.express = express();
		this.http = http.createServer(this.express);
		this.sockjs = sockjs_lib.createServer({
			"log":function(severity, message) {
				logger.log("debug", severity + ": " + message);
			},
			"disconnect_delay": this.options.disconnect_delay
		});

		logger.info("sockjs server created");

		this.sockjs.installHandlers(this.http, {prefix:'/sock'});

		logger.info("\tsock thandlers installed");

		passport.use(new GoogleStrategy({
			clientID: this.options.GOOGLE_CLIENT_ID,
			clientSecret: this.options.GOOGLE_CLIENT_SECRET,
			callbackURL: "http://" + this.options.HOST + ":" + this.options.PORT + "/auth/google/callback"
		}, function(accessToken, refreshToken, profile, done) {

			logger.info("callback!");
			logger.info(JSON.stringify(profile));
			logger.info(JSON.stringify(accessToken));
			logger.info(JSON.stringify(refreshToken));

			return done(null, profile);
			// User.findOrCreate({ googleId: profile.id }, function(err, user) {
			// 	return done(err, user);
			// })
		}));

		passport.serializeUser(_.bind(function(user, done) {
			// We store the google profile object as a string, since it seems to be more than
			// one level deep. We'll store our own additions to it as separate keys
			this.redis.hset("user:" + user.id, "google-profile", JSON.stringify(user), _.bind(function(err, res) {
				if(err) {done(err, user);}
				else {

					// This is mostly just for testing purposes. Could drop this without any negative
					// effects.
					this.redis.hincrby("user:" + user.id, "serialization-count", 1, function(err, res) {
						if(err) {done(true, user);}
						else {
							done(err, user);
						}
					});
				}
			}, this));
		}, this));

		passport.deserializeUser(_.bind(function(obj, done) {
			this.redis.hgetall("user:" + obj.id, _.bind(function(err, res) {
				if(err) {done(err, obj);}
				else {
					// this little dance is to extract the google part of the profile (which is a string)
					// and then merge it in with our additions to the profile.
					var user = JSON.parse(res["google-profile"]);
					delete res["google-profile"];
					_.extend(user, res);
					logger.debug("deserialized user: " + JSON.stringify(user));
					done(null, user);
				}
			}, this));
		}, this));

		this.express.engine('.ejs', require('ejs').__express);
		this.express.set('views', __dirname + '/../views');
		this.express.set('view engine', 'html');

		this.express.use(express.cookieParser());
		this.express.use(express.bodyParser());
		this.express.use(express.session({ secret: this.options.SESSION_SECRET}));
		this.express.use(passport.initialize());
		this.express.use(passport.session());
		this.express.use("/public", express.static(__dirname + "/../public"));

		this.express.get("/", function(req, res) {
			res.render('index.ejs', {user:req.user});
		});

		this.express.get('/login', function(req, res) {
			res.render('login', {user:req.user});
		});

		this.express.get("/auth/google", passport.authenticate('google', { scope: ['https://www.googleapis.com/auth/userinfo.profile',
		                                            'https://www.googleapis.com/auth/userinfo.email']}),
			function(req, res) {
				logger.warning("Auth request function called. This is unexpected! We expect this to get routed to google instead.");
			}
		);

		this.express.get("/auth/google/callback", passport.authenticate('google', {failureRedirect: '/login'}),
			function(req, res) {
				res.redirect("/");
			}
		);

		this.express.get("/logout", function(req, res) {
			req.logout();
			res.redirect("/");
		});

		this.http.listen(this.options.PORT);
		logger.info("http server listening");

		this.emit("started");
		this.running = true;
	},

	stop: function() {
		if(!this.running) {
			logger.warning("Tried to stop a server that was not running.");
			this.emit("error", "Tried to stop a server that was not running.");
			return;
		}

		logger.info("Stopping UnhangoutServer!");

		// TODO gracefully disconnect all users

		this.http.close();

		this.http.on("close", _.bind(function() {
			this.running = false;
			this.emit("stopped");
		}, this));
	},

	destroy: function() {
		this.express = null;
		this.sockjs = null;
		this.http = null;

		logger.info("destroyed");
		this.emit("destroyed");
	}
}

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) { return next(); }
  res.redirect('/login');
}

// Mix in the node events structures so we have on/emit available on the server.
// This is helpful for testing and various other sorts of indirection.
_.extend(exports.UnhangoutServer.prototype, EventEmitter.prototype);<commit_msg>deserializeUser now returns a backbone user object.<commit_after>var winston = require('winston'),
	_ = require('underscore')._,
	EventEmitter = require('events').EventEmitter,
	redis_lib = require('redis')
	express = require('express'),
	http = require('http'),
	passport = require('passport'),
	GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
	sockjs_lib = require('sockjs');

var models = require('./../public/js/models.js');

var logger;

exports.UnhangoutServer = function() {

}

exports.UnhangoutServer.prototype = {
	options: null,
	running: false,
	express: null,
	http: null,
	sockjs: null,
	inited: false,
	redis: null,

	init: function(options) {
		if(_.isUndefined(options)) {
			options = {};
		}

		this.options = _.defaults(options, {"level":"debug", "transport":"console", "HOST":"localhost", "PORT":7777,
			"REDIS_HOST":"localhost", "REDIS_PORT":6379, "SESSION_SECRET":"fake secret", "REDIS_DB":0});
		var transports = [];
		switch(this.options.transport) {
			case "console":
				transports.push(new (winston.transports.Console)({
						timestamp: true,
						json: false,
						level: this.options.level
					}));
				break;
			case "file":
				transports.push(new (winston.transports.File)({
							filename: 'server.log',
							timestamp: true,
							json: false,
							level: this.options.level
						}));
				break;
		}

		logger = new (winston.Logger)({transports:transports, levels:winston.config.syslog.levels});

		if(this.options.transport=="console") logger.cli();

		if(!("GOOGLE_CLIENT_ID" in this.options)) {
			logger.error("Missing GOOGLE_CLIENT_ID in options.")
			this.emit("error", "Missing GOOGLE_CLIENT_ID in options.");
			return;
		}

		if(!("GOOGLE_CLIENT_SECRET" in this.options)) {
			logger.error("Missing GOOGLE_CLIENT_SECRET in options.")
			this.emit("error", "Missing GOOGLE_CLIENT_SECRET in options.");
			return;
		}

		this.redis = redis_lib.createClient(parseInt(this.options.REDIS_PORT), this.options.REDIS_HOST);
		this.redis.on("connect", _.bind(function(err) {
			if(err) {
				logger.error("Error connecting to redis: " + err);
				this.emit("error", "Error connecting to redis: " + err, err);
				return;
			}

			this.inited = true;
			logger.info("UnhangoutServer initialized.");
			this.redis.select(this.options.REDIS_DB, _.bind(function(err, res) {
				if(err) this.emit("error", err);
				this.emit("inited");
			}, this));
		}, this));
	},

	start: function() {
		if(!this.inited) {
			logger.error("Attempted to start, but server is not initialized.");
			this.emit("error", "Attempted to start, but server is not initialized.");
			return;
		}

		logger.log("info", "Starting UnhangoutServer on %s:%d", this.options.HOST, this.options.PORT);

		this.express = express();
		this.http = http.createServer(this.express);
		this.sockjs = sockjs_lib.createServer({
			"log":function(severity, message) {
				logger.log("debug", severity + ": " + message);
			},
			"disconnect_delay": this.options.disconnect_delay
		});

		logger.info("sockjs server created");

		this.sockjs.installHandlers(this.http, {prefix:'/sock'});

		logger.info("\tsock thandlers installed");

		passport.use(new GoogleStrategy({
			clientID: this.options.GOOGLE_CLIENT_ID,
			clientSecret: this.options.GOOGLE_CLIENT_SECRET,
			callbackURL: "http://" + this.options.HOST + ":" + this.options.PORT + "/auth/google/callback"
		}, function(accessToken, refreshToken, profile, done) {

			logger.info("callback!");
			logger.info(JSON.stringify(profile));
			logger.info(JSON.stringify(accessToken));
			logger.info(JSON.stringify(refreshToken));

			return done(null, profile);
			// User.findOrCreate({ googleId: profile.id }, function(err, user) {
			// 	return done(err, user);
			// })
		}));

		passport.serializeUser(_.bind(function(user, done) {
			// We store the google profile object as a string, since it seems to be more than
			// one level deep. We'll store our own additions to it as separate keys
			this.redis.hset("user:" + user.id, "google-profile", JSON.stringify(user), _.bind(function(err, res) {
				if(err) {done(err, user);}
				else {

					// This is mostly just for testing purposes. Could drop this without any negative
					// effects.
					this.redis.hincrby("user:" + user.id, "serialization-count", 1, function(err, res) {
						if(err) {done(true, user);}
						else {
							done(err, user);
						}
					});
				}
			}, this));
		}, this));

		passport.deserializeUser(_.bind(function(obj, done) {
			this.redis.hgetall("user:" + obj.id, _.bind(function(err, res) {
				if(err) {done(err, obj);}
				else {
					// this little dance is to extract the google part of the profile (which is a string)
					// and then merge it in with our additions to the profile.
					var userAttrs = JSON.parse(res["google-profile"]);
					delete res["google-profile"];
					_.extend(userAttrs, res);

					var user = new models.User(userAttrs);

					logger.debug("deserialized user: " + JSON.stringify(user.toJSON()));
					done(null, user);
				}
			}, this));
		}, this));

		this.express.engine('.ejs', require('ejs').__express);
		this.express.set('views', __dirname + '/../views');
		this.express.set('view engine', 'html');

		this.express.use(express.cookieParser());
		this.express.use(express.bodyParser());
		this.express.use(express.session({ secret: this.options.SESSION_SECRET}));
		this.express.use(passport.initialize());
		this.express.use(passport.session());
		this.express.use("/public", express.static(__dirname + "/../public"));

		this.express.get("/", function(req, res) {
			res.render('index.ejs', {user:req.user});
		});

		this.express.get('/login', function(req, res) {
			res.render('login', {user:req.user});
		});

		this.express.get("/auth/google", passport.authenticate('google', { scope: ['https://www.googleapis.com/auth/userinfo.profile',
		                                            'https://www.googleapis.com/auth/userinfo.email']}),
			function(req, res) {
				logger.warning("Auth request function called. This is unexpected! We expect this to get routed to google instead.");
			}
		);

		this.express.get("/auth/google/callback", passport.authenticate('google', {failureRedirect: '/login'}),
			function(req, res) {
				res.redirect("/");
			}
		);

		this.express.get("/logout", function(req, res) {
			req.logout();
			res.redirect("/");
		});

		this.http.listen(this.options.PORT);
		logger.info("http server listening");

		this.emit("started");
		this.running = true;
	},

	stop: function() {
		if(!this.running) {
			logger.warning("Tried to stop a server that was not running.");
			this.emit("error", "Tried to stop a server that was not running.");
			return;
		}

		logger.info("Stopping UnhangoutServer!");

		// TODO gracefully disconnect all users

		this.http.close();

		this.http.on("close", _.bind(function() {
			this.running = false;
			this.emit("stopped");
		}, this));
	},

	destroy: function() {
		this.express = null;
		this.sockjs = null;
		this.http = null;

		logger.info("destroyed");
		this.emit("destroyed");
	}
}

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) { return next(); }
  res.redirect('/login');
}

// Mix in the node events structures so we have on/emit available on the server.
// This is helpful for testing and various other sorts of indirection.
_.extend(exports.UnhangoutServer.prototype, EventEmitter.prototype);<|endoftext|>


<commit_before>;(function(Darkroom, fabric, window) {
  'use strict';

  var _this = null,
      root  = null;

  var requestFileSystem = (window.webkitRequestFileSystem || window.requestFileSystem);

  var helpers = {

    getOptions: function(img) {

      var OPTS = [
        'lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY',
        'lockUniScaling', 'hasControls', 'hasBorders', 'selectable', 'evented'
      ];

      return OPTS.reduce(

        function(acc, opt) {

          acc[opt] = img.get('name');
          return acc;
        },
        {}
      );
    }
  };

  var eventListeners = {

    dragover: function(event) { event.preventDefault(); },

    drop: function(event) {

      var currentImage = _this.image,
          darkroom     = _this.darkroom,
          options      = _this.options,
          canvas       = _this.canvas;

      event.preventDefault();

      Array.prototype.forEach.call(event.dataTransfer.files, function(file) {

        root.getFile(file.name, {create: true, exclusive: false}, function(fileEntry) {

          if (fileEntry.isFile && file.type.match('^image/.*') && file.size <= options.maxFileSize) {

            fabric.Image.fromURL(window.URL.createObjectURL(file), function(newImage) {

              var height = Math.min(Math.max(options.minHeight, newImage.height), options.maxHeight),
                  width  = Math.min(Math.max(options.minWidth, newImage.width),   options.maxWidth);

              canvas.remove(currentImage);
              canvas.setHeight(height);
              canvas.setWidth(width);

              newImage
                .set(helpers.getOptions(currentImage))
                .set({height: height, width: width});

              canvas.add(newImage);

              darkroom.image = newImage;
              currentImage   = newImage;
            });
          }
        });
      });
    }
  };

  Darkroom.plugins['dragndrop'] = Darkroom.Plugin.extend({
    defaults: {
      maxFileSize: (5 * 1024 * 1024),
      maxWidth:    800,
      maxHeight:   600,
      minHeight:   300,
      minWidth:    400,
    },
    initialize: function initDarkroomDragnDropPlugin() {

      _this = this;

      var container = this.darkroom.container,
          options   = this.options;

      this.darkroom = this.darkroom,
      this.canvas   = this.darkroom.canvas,
      this.image    = this.darkroom.image;

      container.addEventListener('dragover', eventListeners.dragover);

      requestFileSystem(TEMPORARY, options.maxFileSize, function(fs) {

        root = fs.root;
        container.addEventListener('drop', eventListeners.drop);
      });
    }
  });
})(Darkroom, fabric, window);
<commit_msg>refactor<commit_after>;(function(Darkroom, fabric, window) {
  'use strict';

  var _this = null,
      root  = null;

  var requestFileSystem = (window.webkitRequestFileSystem || window.requestFileSystem);

  var helpers = {

    getOptions: function(img) {

      var OPTS = [
        'lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY',
        'lockUniScaling', 'hasControls', 'hasBorders', 'selectable', 'evented'
      ];

      return OPTS.reduce(

        function(acc, opt) {

          acc[opt] = img.get('name');
          return acc;
        },
        {}
      );
    }
  };

  var eventListeners = {

    dragover: function(event) { event.preventDefault(); },

    drop: function(event) {

      var currentImage = _this.image,
          darkroom     = _this.darkroom,
          options      = _this.options,
          canvas       = _this.canvas;

      event.preventDefault();

      Array.prototype.forEach.call(event.dataTransfer.files, function(file) {

        root.getFile(file.name, {create: true, exclusive: false}, function(fileEntry) {

          if (fileEntry.isFile && file.type.match('^image/.*') && file.size <= options.maxFileSize) {

            fabric.Image.fromURL(window.URL.createObjectURL(file), function(newImage) {

              var height = Math.min(Math.max(options.minHeight, newImage.height), options.maxHeight),
                  width  = Math.min(Math.max(options.minWidth, newImage.width),   options.maxWidth);

              canvas.remove(currentImage);
              canvas.setHeight(height);
              canvas.setWidth(width);

              newImage
                .set(helpers.getOptions(currentImage))
                .set({height: height, width: width});

              canvas.add(newImage);

              darkroom.image = newImage;
              currentImage   = newImage;
            });
          }
        });
      });
    }
  };

  Darkroom.plugins['dragndrop'] = Darkroom.Plugin.extend({
    defaults: {
      maxFileSize: (5 * 1024 * 1024),
      maxWidth:    800,
      maxHeight:   600,
      minHeight:   300,
      minWidth:    400,
    },
    initialize: function initDarkroomDragnDropPlugin() {

      _this = this;

      var container = this.darkroom.container,
          options   = this.options;

      this.darkroom = this.darkroom,
      this.canvas   = this.darkroom.canvas,
      this.image    = this.darkroom.image;

      container.addEventListener('dragover', eventListeners.dragover);

      requestFileSystem(TEMPORARY, options.maxFileSize, function(fs) {

        root = fs.root;
        container.addEventListener('drop', eventListeners.drop);
      });
    }
  });
})(Darkroom, fabric, window);
<|endoftext|>


<commit_before>//Render a simple vertical check box group, where the buttons all have the same sizes
//TODO: not ready for use in simulations, it will need further development & discussion first.
define( function( require ) {
  "use strict";

  var Path = require( 'SCENERY/nodes/Path' );
  var CheckBox = require( 'SUN/CheckBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Shape = require( 'KITE/Shape' );
  var inherit = require( 'PHET_CORE/inherit' );

  /**
   * Main constructor.
   *
   * @param items  an array of {content, property}
   * @param options
   * @constructor
   */
  function VerticalCheckBoxGroup( items, options ) {
    options = options || {};
    var padding = options.padding ? options.padding : 8;

    var width = 0;
    for ( var i = 0; i < items.length; i++ ) {
      width = Math.max( width, items[i].content.width );
    }

    var children = [];
    for ( i = 0; i < items.length; i++ ) {

      //Add an invisible strut to each content to make the widths match
      var content = new Path( {shape: Shape.rect( 0, 0, width + padding, 0 ), children: [items[i].content], renderer: 'svg'} );
      children.push( new CheckBox( content, items[i].property, {label: items[i].label} ) );
    }

    options.children = children;
    options.renderer = 'svg';
    VBox.call( this, options );
  }

  inherit( VBox, VerticalCheckBoxGroup );

  return VerticalCheckBoxGroup;
} );<commit_msg>space checkboxes now that they don't have their own buttons, see #8<commit_after>//Render a simple vertical check box group, where the buttons all have the same sizes
//TODO: not ready for use in simulations, it will need further development & discussion first.
define( function( require ) {
  "use strict";

  var Path = require( 'SCENERY/nodes/Path' );
  var CheckBox = require( 'SUN/CheckBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Shape = require( 'KITE/Shape' );
  var inherit = require( 'PHET_CORE/inherit' );

  /**
   * Main constructor.
   *
   * @param items  an array of {content, property}
   * @param options
   * @constructor
   */
  function VerticalCheckBoxGroup( items, options ) {
    options = _.extend( { spacing: 3 }, options );
    var padding = options.padding ? options.padding : 8;

    var width = 0;
    for ( var i = 0; i < items.length; i++ ) {
      width = Math.max( width, items[i].content.width );
    }

    var children = [];
    for ( i = 0; i < items.length; i++ ) {

      //Add an invisible strut to each content to make the widths match
      var content = new Path( {shape: Shape.rect( 0, 0, width + padding, 0 ), children: [items[i].content], renderer: 'svg'} );
      children.push( new CheckBox( content, items[i].property, {label: items[i].label} ) );
    }

    options.children = children;
    options.renderer = 'svg';
    VBox.call( this, options );
  }

  inherit( VBox, VerticalCheckBoxGroup );

  return VerticalCheckBoxGroup;
} );<|endoftext|>


<commit_before>"use strict";

var System = require('./system.js');

const millisecondsPerMinute = 60000;
const millisecondsPerSecond = 1000;

class Engines extends System {
  constructor(data) {
    super(data);
    this.speed = { max: 10, current: 0, cruising: 6 };
    this.heat = {
      current: 10, target: 10, delta: 0, secondsUntilOverheat: null,
      max: 100, powered: 10, cruising: 50,
      minutesAtMaxSpeed: 5, minutesToCoolDown: 10
    }
  }

  getState() {
    var state = super.getState();
    state.speed = this.speed;
    state.heat = {
      current: Math.round(this.heat.current),
      max: this.heat.max,
      powered: this.heat.powered,
      cruising: this.heat.cruising
    };
    return state;
  }

  setCurrentSpeed(newSpeed) {
    if (!super.hasEnoughPower()) {
      return;
    }
    if (typeof newSpeed === "number") {
      this.speed.current = Math.round(Math.max(Math.min(newSpeed, this.speed.max), 0));
      this.calculateHeatValuesForCurrentSpeed();
    }
  }

  calculateHeatValuesForCurrentSpeed() {
    this.calculateTargetHeat();
    this.calculateHeatDelta();
    this.calculateSecondsUntilOverheat();
  }

  calculateTargetHeat() {
    if (this.speed.current > 0) {
      if (this.speed.current > this.speed.cruising) {
        this.heat.target = this.heat.max
      } else {
        this.heat.target = this.heat.cruising
      }
    } else {
      if (super.hasEnoughPower()) {
        this.heat.target =  this.heat.powered;
      } else {
        this.heat.target = 0;
      }
    }
  }

  calculateHeatDelta() {
    if (this.heat.target > this.heat.current) {
      this.calculateHeatDeltaForCurrentSpeed();
    } else {
      this.calculateHeatDeltaForCooldown();
    }
  }

  calculateHeatDeltaForCurrentSpeed() {
    var currentSpeed = Math.max(this.speed.current, 1);
    var minutesAtCurrentSpeed = this.heat.minutesAtMaxSpeed * (this.speed.max / currentSpeed);
    var heatPerMinute = (this.heat.max - this.heat.powered) / minutesAtCurrentSpeed;
    this.heat.delta = heatPerMinute / millisecondsPerMinute;
  }

  calculateHeatDeltaForCooldown() {
    var coolingPerMinute = this.heat.max / this.heat.minutesToCoolDown;
    this.heat.delta = -1 * coolingPerMinute / millisecondsPerMinute;
  }

  calculateSecondsUntilOverheat() {
    var remainingHeat = this.heat.max - this.heat.current;
    if (this.heat.target == this.heat.max && this.heat.delta > 0 && remainingHeat > 0) {
      var millisecondsUntilOverheat = remainingHeat / this.heat.delta;
      this.heat.secondsUntilOverheat = Math.floor(millisecondsUntilOverheat / millisecondsPerSecond);
      return;
    }
    this.heat.secondsUntilOverheat = null;
  }

  setEventBus(emitter) {
    super.setEventBus(emitter);
    var self = this;
    this.eventBus.on('pulse', function (millisecondsSinceLastPulse) {
      self.onPulse(millisecondsSinceLastPulse);
    });
  }

  onPulse(millisecondsSinceLastPulse) {
    var newHeat = this.heat.current + (this.heat.delta * millisecondsSinceLastPulse);
    // TODO: stop at the target heat
    this.heat.current = Math.max(Math.min(newHeat, this.heat.max), 0);
  }
}

module.exports = Engines;
<commit_msg>Fix heat so it stops at the target<commit_after>"use strict";

var System = require('./system.js');

const millisecondsPerMinute = 60000;
const millisecondsPerSecond = 1000;

class Engines extends System {
  constructor(data) {
    super(data);
    this.speed = { max: 10, current: 0, cruising: 6 };
    this.heat = {
      current: 10, target: 10, delta: 0, secondsUntilOverheat: null,
      max: 100, powered: 10, cruising: 50,
      minutesAtMaxSpeed: 5, minutesToCoolDown: 10
    }
  }

  getState() {
    var state = super.getState();
    state.speed = this.speed;
    state.heat = {
      current: Math.round(this.heat.current),
      max: this.heat.max,
      powered: this.heat.powered,
      cruising: this.heat.cruising
    };
    return state;
  }

  setCurrentSpeed(newSpeed) {
    if (!super.hasEnoughPower()) {
      return;
    }
    if (typeof newSpeed === "number") {
      this.speed.current = Math.round(Math.max(Math.min(newSpeed, this.speed.max), 0));
      this.calculateHeatValuesForCurrentSpeed();
    }
  }

  calculateHeatValuesForCurrentSpeed() {
    this.calculateTargetHeat();
    this.calculateHeatDelta();
    this.calculateSecondsUntilOverheat();
  }

  calculateTargetHeat() {
    if (this.speed.current > 0) {
      if (this.speed.current > this.speed.cruising) {
        this.heat.target = this.heat.max
      } else {
        this.heat.target = this.heat.cruising
      }
    } else {
      if (super.hasEnoughPower()) {
        this.heat.target =  this.heat.powered;
      } else {
        this.heat.target = 0;
      }
    }
  }

  calculateHeatDelta() {
    if (this.heat.target > this.heat.current) {
      this.calculateHeatDeltaForCurrentSpeed();
    } else {
      this.calculateHeatDeltaForCooldown();
    }
  }

  calculateHeatDeltaForCurrentSpeed() {
    var currentSpeed = Math.max(this.speed.current, 1);
    var minutesAtCurrentSpeed = this.heat.minutesAtMaxSpeed * (this.speed.max / currentSpeed);
    var heatPerMinute = (this.heat.max - this.heat.powered) / minutesAtCurrentSpeed;
    this.heat.delta = heatPerMinute / millisecondsPerMinute;
  }

  calculateHeatDeltaForCooldown() {
    var coolingPerMinute = this.heat.max / this.heat.minutesToCoolDown;
    this.heat.delta = -1 * coolingPerMinute / millisecondsPerMinute;
  }

  calculateSecondsUntilOverheat() {
    var remainingHeat = this.heat.max - this.heat.current;
    if (this.heat.target == this.heat.max && this.heat.delta > 0 && remainingHeat > 0) {
      var millisecondsUntilOverheat = remainingHeat / this.heat.delta;
      this.heat.secondsUntilOverheat = Math.floor(millisecondsUntilOverheat / millisecondsPerSecond);
      return;
    }
    this.heat.secondsUntilOverheat = null;
  }

  setEventBus(emitter) {
    super.setEventBus(emitter);
    var self = this;
    this.eventBus.on('pulse', function (millisecondsSinceLastPulse) {
      self.onPulse(millisecondsSinceLastPulse);
    });
  }

  onPulse(millisecondsSinceLastPulse) {
    var newHeat = this.heat.current + (this.heat.delta * millisecondsSinceLastPulse);
    // TODO: stop at the target heat
    if (this.heat.delta < 0 && newHeat < this.heat.target) {
      newHeat = this.heat.target;
    }
    if (this.heat.delta > 0 && newHeat > this.heat.target) {
      newHeat = this.heat.target;
    }
    this.heat.current = Math.max(Math.min(newHeat, this.heat.max), 0);
  }
}

module.exports = Engines;
<|endoftext|>


<commit_before>// Copyright 2013-2018, University of Colorado Boulder

/**
 * A vertical group of checkboxes.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  var Checkbox = require( 'SUN/Checkbox' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  /**
   * Main constructor.
   *
   * @param items  an array of {content, property, indent, [tandem]}
   * @param {Object} [options]
   * @constructor
   */
  function VerticalCheckboxGroup( items, options ) {

    options = _.extend( {
      spacing: 10, // vertical spacing
      padding: 8, //TODO what is this? It looks like it's added to the right of the checkbox. Shouldn't this be an x-margin, added to left and right?
      checkboxColor: 'black',
      align: 'left',
      boxWidth: 21,
      tandem: Tandem.optional
    }, options );

    // compute max width of the items
    var maxWidth = 0;
    for ( var i = 0; i < items.length; i++ ) {
      maxWidth = Math.max( maxWidth, items[ i ].content.width );
    }

    // process each item
    var children = items.map( function( item ) {
      var offset = item.indent || 0;

      //Attach each item to an invisible strut to make the widths match.
      var content = new Path( Shape.rect( 0, 0, maxWidth + options.padding - offset, 0 ), { children: [ item.content ] } );
      var checkbox = new Checkbox( content, item.property, {
        checkboxColor: options.checkboxColor,
        boxWidth: options.boxWidth,
        tandem: item.tandem || Tandem.optional
      } );
      checkbox.mouseArea = checkbox.touchArea = Shape.bounds( checkbox.bounds.dilatedXY( 5, options.spacing / 2 ) );
      if ( item.indent ) {
        return new HBox( {
          children: [ new Rectangle( 0, 0, item.indent, 1 ), checkbox ]
        } );
      }
      else {
        return new HBox( { children: [ checkbox ] } );
      }
    } );

    options.children = children; //TODO bad form, if options.children was already set, then this will blow it away
    VBox.call( this, options );
  }

  sun.register( 'VerticalCheckboxGroup', VerticalCheckboxGroup );

  return inherit( VBox, VerticalCheckboxGroup );
} );<commit_msg>cleanup/rewrite VerticalCheckboxGroup, #344<commit_after>// Copyright 2013-2018, University of Colorado Boulder

/**
 * Convenience type for creating a group of AquaRadioButtons with vertical orientation.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Checkbox = require( 'SUN/Checkbox' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var HStrut = require( 'SCENERY/nodes/HStrut' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  /**
   * @param {Object[]} items - Each item describes a checkbox, and is an object with these properties:
   *    node: Node, // label for the button
   *    property: Property.<boolean>, // Property associated with the button
   *    indent: number, // how much to indent each check box from the left edge
   *    [tandemName: Tandem] // optional tandem for PhET-iO
   * @param {Object} [options]
   * @constructor
   */
  function VerticalCheckboxGroup( items, options ) {

    options = _.extend( {

      // dilation of pointer areas, y dimension is computed
      touchAreaXDilation: 5,
      mouseAreaXDilation: 5,

      //TODO #344 this is the total of left and right margins, replace with xMargin?
      padding: 8,

      //TODO #344 these are passed to CheckBox, replace with checkboxOptions: {...}
      boxWidth: 21,
      checkboxColor: 'black',

      // supertype options
      spacing: 10, // vertical spacing
      align: 'left',
      tandem: Tandem.optional
    }, options );

    // Verify that the client hasn't set options that we will be overwriting.
    assert && assert( !options.children, 'VerticalCheckboxGroup sets children' );

    //TODO #344 there's a bug here, indent for each item is not considered
    // Determine the max item width
    var maxWidth = 0;
    for ( var i = 0; i < items.length; i++ ) {
      maxWidth = Math.max( maxWidth, items[ i ].content.width );
    }

    //TODO #344 this for loop looks very much like VerticalAquaRadioButtonGroup, something to factor out?
    // Create a checkbox for each item
    options.children = [];
    for ( i = 0; i < items.length; i++ ) {

      var item = items[ i ];
      var indent = item.indent || 0;

      //TODO #344 item.content is item.node in VerticalAquaRadioButtonGroup
      // Content for the checkbox. Add an invisible strut, so that buttons have uniform width.
      var content = new Node( {
        children: [ new HStrut( maxWidth + options.padding - indent ), item.content ]
      } );

      var checkbox = new Checkbox( content, item.property, {
        checkboxColor: options.checkboxColor,
        boxWidth: options.boxWidth,
        tandem: item.tandem || Tandem.optional
        //TODO #344 VerticalAquaRadioButtonGroup has accessibleLabel, why not here?
        //TODO #344 VerticalAquaRadioButtonGroup has a11yNameAttribute, why not here?
      } );

      // set pointer areas, y dimensions are computed
      var yDilation = options.spacing / 2;
      checkbox.mouseArea = checkbox.localBounds.dilatedXY( options.mouseAreaXDilation, yDilation );
      checkbox.touchArea = checkbox.localBounds.dilatedXY( options.touchAreaXDilation, yDilation );

      //TODO #344 indent feature is missing from VerticalAquaRadioButtonGroup, should it be added?
      if ( item.indent ) {

        // indent the checkbox from the left edge using a strut
        options.children.push( new HBox( {
          children: [ new HStrut( item.indent ), checkbox ]
        } ) );
      }
      else {
        options.children.push( checkbox );
      }
    }

    VBox.call( this, options );
  }

  sun.register( 'VerticalCheckboxGroup', VerticalCheckboxGroup );

  return inherit( VBox, VerticalCheckboxGroup );
} );<|endoftext|>


<commit_before>var files = {
	"webgl": [
		"webgl_animation_cloth",
		"webgl_animation_scene",
		"webgl_animation_skinning_blending",
		"webgl_animation_skinning_morph",
		"webgl_camera",
		"webgl_camera_logarithmicdepthbuffer",
		"webgl_decals",
		"webgl_effects_anaglyph",
		"webgl_effects_cardboard",
		"webgl_effects_parallaxbarrier",
		"webgl_effects_peppersghost",
		"webgl_effects_stereo",
		"webgl_exporter_obj",
		"webgl_geometries",
		"webgl_geometries2",
		"webgl_geometry_colors",
		"webgl_geometry_colors_blender",
		"webgl_geometry_colors_lookuptable",
		"webgl_geometry_convex",
		"webgl_geometry_cube",
		"webgl_geometry_dynamic",
		"webgl_geometry_extrude_shapes",
		"webgl_geometry_extrude_shapes2",
		"webgl_geometry_extrude_splines",
		"webgl_geometry_hierarchy",
		"webgl_geometry_hierarchy2",
		"webgl_geometry_large_mesh",
		"webgl_geometry_minecraft",
		"webgl_geometry_minecraft_ao",
		"webgl_geometry_normals",
		"webgl_geometry_nurbs",
		"webgl_geometry_shapes",
		"webgl_geometry_spline_editor",
		"webgl_geometry_teapot",
		"webgl_geometry_terrain",
		"webgl_geometry_terrain_fog",
		"webgl_geometry_terrain_raycast",
		"webgl_geometry_text",
		"webgl_geometry_text_earcut",
		"webgl_geometry_text_pnltri",
		"webgl_gpgpu_birds",
		"webgl_gpu_particle_system",
		"webgl_hdr",
		"webgl_helpers",
		"webgl_interactive_buffergeometry",
		"webgl_interactive_cubes",
		"webgl_interactive_cubes_gpu",
		"webgl_interactive_instances_gpu",
		"webgl_interactive_cubes_ortho",
		"webgl_interactive_draggablecubes",
		"webgl_interactive_lines",
		"webgl_interactive_points",
		"webgl_interactive_raycasting_points",
		"webgl_interactive_voxelpainter",
		"webgl_kinect",
		"webgl_lensflares",
		"webgl_lights_hemisphere",
		"webgl_lights_pointlights",
		"webgl_lights_pointlights2",
		"webgl_lines_colors",
		"webgl_lines_cubes",
		"webgl_lines_dashed",
		"webgl_lines_sphere",
		"webgl_lines_splines",
		"webgl_loader_3mf",
		"webgl_loader_amf",
		"webgl_loader_assimp2json",
		"webgl_loader_awd",
		"webgl_loader_babylon",
		"webgl_loader_collada",
		"webgl_loader_collada_keyframe",
		"webgl_loader_collada_kinematics",
		"webgl_loader_collada_skinning",
		"webgl_loader_ctm",
		"webgl_loader_ctm_materials",
		"webgl_loader_fbx",
		"webgl_loader_gltf",
		"webgl_loader_json_blender",
		"webgl_loader_json_claraio",
		"webgl_loader_json_objconverter",
		"webgl_loader_md2",
		"webgl_loader_md2_control",
		"webgl_loader_mmd",
		"webgl_loader_mmd_pose",
		"webgl_loader_mmd_audio",
		"webgl_loader_msgpack",
		"webgl_loader_obj",
		"webgl_loader_obj_mtl",
		"webgl_loader_pcd",
		"webgl_loader_pdb",
		"webgl_loader_ply",
		"webgl_loader_sea3d",
		"webgl_loader_sea3d_hierarchy",
		"webgl_loader_sea3d_keyframe",
		"webgl_loader_sea3d_morph",
		"webgl_loader_sea3d_skinning",
		"webgl_loader_sea3d_sound",
		"webgl_loader_scene",
		"webgl_loader_stl",
		"webgl_loader_utf8",
		"webgl_loader_vrml",
		"webgl_loader_vtk",
		"webgl_lod",
		"webgl_marchingcubes",
		"webgl_materials",
		"webgl_materials_blending",
		"webgl_materials_blending_custom",
		"webgl_materials_bumpmap",
		"webgl_materials_bumpmap_skin",
		"webgl_materials_cars",
		"webgl_materials_cubemap",
		"webgl_materials_cubemap_balls_reflection",
		"webgl_materials_cubemap_balls_refraction",
		"webgl_materials_cubemap_dynamic",
		"webgl_materials_cubemap_dynamic2",
		"webgl_materials_cubemap_escher",
		"webgl_materials_cubemap_refraction",
		"webgl_materials_displacementmap",
		"webgl_materials_envmaps",
		"webgl_materials_grass",
		"webgl_materials_lightmap",
		"webgl_materials_nodes",
		"webgl_materials_normalmap",
		"webgl_materials_parallaxmap",
		"webgl_materials_shaders_fresnel",
		"webgl_materials_skin",
		"webgl_materials_standard",
		"webgl_materials_texture_anisotropy",
		"webgl_materials_texture_compressed",
		"webgl_materials_texture_filters",
		"webgl_materials_texture_hdr",
		"webgl_materials_texture_manualmipmap",
		"webgl_materials_texture_pvrtc",
		"webgl_materials_texture_tga",
		"webgl_materials_variations_basic",
		"webgl_materials_variations_lambert",
		"webgl_materials_variations_phong",
		"webgl_materials_variations_standard",
		"webgl_materials_variations_standard2",
		"webgl_materials_video",
		"webgl_materials_wireframe",
		"webgl_mirror",
		"webgl_modifier_subdivision",
		"webgl_modifier_tessellation",
		"webgl_morphnormals",
		"webgl_morphtargets",
		"webgl_morphtargets_horse",
		"webgl_morphtargets_human",
		"webgl_multiple_canvases_circle",
		"webgl_multiple_canvases_complex",
		"webgl_multiple_canvases_grid",
		"webgl_multiple_elements",
		"webgl_multiple_elements_text",
		"webgl_multiple_renderers",
		"webgl_multiple_views",
		"webgl_nearestneighbour",
		"webgl_objects_update",
		"webgl_octree",
		"webgl_octree_raycasting",
		"webgl_panorama_equirectangular",
		"webgl_particles_general",
		"webgl_performance",
		"webgl_performance_doublesided",
		"webgl_performance_static",
		"webgl_points_billboards",
		"webgl_points_billboards_colors",
		"webgl_points_dynamic",
		"webgl_points_random",
		"webgl_points_sprites",
		"webgl_postprocessing",
		"webgl_postprocessing_advanced",
		"webgl_postprocessing_crossfade",
		"webgl_postprocessing_dof",
		"webgl_postprocessing_dof2",
		"webgl_postprocessing_glitch",
		"webgl_postprocessing_godrays",
		"webgl_postprocessing_masking",
		"webgl_postprocessing_nodes",
		"webgl_postprocessing_ssao",
		"webgl_raycast_texture",
		"webgl_read_float_buffer",
		"webgl_rtt",
		"webgl_sandbox",
		"webgl_shader",
		"webgl_shader_lava",
		"webgl_shader2",
		"webgl_shaders_ocean",
		"webgl_shaders_ocean2",
		"webgl_shaders_sky",
		"webgl_shaders_tonemapping",
		"webgl_shaders_vector",
		"webgl_shading_physical",
		"webgl_shadowmap",
		"webgl_shadowmap_performance",
		"webgl_shadowmap_pointlight",
		"webgl_shadowmap_viewer",
		"webgl_shadowmesh",
		"webgl_skinning_simple",
		"webgl_sprites",
		"webgl_terrain_dynamic",
		"webgl_test_memory",
		"webgl_test_memory2",
		"webgl_trails",
		"webgl_video_panorama_equirectangular"
	],
	"webgl / advanced": [
		"webgl_buffergeometry",
		"webgl_buffergeometry_constructed_from_geometry",
		"webgl_buffergeometry_custom_attributes_particles",
		"webgl_buffergeometry_drawcalls",
		"webgl_buffergeometry_instancing",
		"webgl_buffergeometry_instancing_billboards",
		"webgl_buffergeometry_instancing_dynamic",
		"webgl_buffergeometry_instancing_interleaved_dynamic",
		"webgl_buffergeometry_lines",
		"webgl_buffergeometry_lines_indexed",
		"webgl_buffergeometry_points",
		"webgl_buffergeometry_rawshader",
		"webgl_buffergeometry_selective_draw",
		"webgl_buffergeometry_uint",
		"webgl_custom_attributes",
		"webgl_custom_attributes_lines",
		"webgl_custom_attributes_points",
		"webgl_custom_attributes_points2",
		"webgl_custom_attributes_points3",
		"webgl_raymarching_reflect"
	],
	"webvr": [
		"webvr_cubes",
		"webvr_video",
		"webvr_stereo_pano"
	],
	"css3d": [
		"css3d_molecules",
		"css3d_panorama",
		"css3d_panorama_deviceorientation",
		"css3d_periodictable",
		"css3d_sandbox",
		"css3d_sprites",
		"css3d_youtube"
	],
	"css3d stereo": [
		"css3dstereo_periodictable",
	],
	"misc": [
		"misc_animation_authoring",
		"misc_animation_keys",
		"misc_controls_deviceorientation",
		"misc_controls_fly",
		"misc_controls_orbit",
		"misc_controls_pointerlock",
		"misc_controls_trackball",
		"misc_controls_transform",
		"misc_fps",
		"misc_lights_test",
		"misc_lookat",
		"misc_sound",
		"misc_ubiquity_test",
		"misc_ubiquity_test2",
		"misc_uv_tests"
	],
	"canvas": [
		"canvas_ascii_effect",
		"canvas_camera_orthographic",
		"canvas_camera_orthographic2",
		"canvas_geometry_birds",
		"canvas_geometry_cube",
		"canvas_geometry_earth",
		"canvas_geometry_hierarchy",
		"canvas_geometry_nurbs",
		"canvas_geometry_panorama",
		"canvas_geometry_panorama_fisheye",
		"canvas_geometry_shapes",
		"canvas_geometry_terrain",
		"canvas_geometry_text",
		"canvas_interactive_cubes",
		"canvas_interactive_cubes_tween",
		"canvas_interactive_particles",
		"canvas_interactive_voxelpainter",
		"canvas_lights_pointlights",
		"canvas_lines",
		"canvas_lines_colors",
		"canvas_lines_colors_2d",
		"canvas_lines_dashed",
		"canvas_lines_sphere",
		"canvas_materials",
		"canvas_materials_normal",
		"canvas_materials_reflection",
		"canvas_materials_video",
		"canvas_morphtargets_horse",
		"canvas_particles_floor",
		"canvas_particles_random",
		"canvas_particles_sprites",
		"canvas_particles_waves",
		"canvas_performance",
		"canvas_sandbox"
	],
	"raytracing": [
		"raytracing_sandbox"
	],
	"software": [
		"software_geometry_earth",
		"software_sandbox"
	],
	"svg": [
		"svg_sandbox"
	]
};
<commit_msg>Examples: Added smaa example to list.<commit_after>var files = {
	"webgl": [
		"webgl_animation_cloth",
		"webgl_animation_scene",
		"webgl_animation_skinning_blending",
		"webgl_animation_skinning_morph",
		"webgl_camera",
		"webgl_camera_logarithmicdepthbuffer",
		"webgl_decals",
		"webgl_effects_anaglyph",
		"webgl_effects_cardboard",
		"webgl_effects_parallaxbarrier",
		"webgl_effects_peppersghost",
		"webgl_effects_stereo",
		"webgl_exporter_obj",
		"webgl_geometries",
		"webgl_geometries2",
		"webgl_geometry_colors",
		"webgl_geometry_colors_blender",
		"webgl_geometry_colors_lookuptable",
		"webgl_geometry_convex",
		"webgl_geometry_cube",
		"webgl_geometry_dynamic",
		"webgl_geometry_extrude_shapes",
		"webgl_geometry_extrude_shapes2",
		"webgl_geometry_extrude_splines",
		"webgl_geometry_hierarchy",
		"webgl_geometry_hierarchy2",
		"webgl_geometry_large_mesh",
		"webgl_geometry_minecraft",
		"webgl_geometry_minecraft_ao",
		"webgl_geometry_normals",
		"webgl_geometry_nurbs",
		"webgl_geometry_shapes",
		"webgl_geometry_spline_editor",
		"webgl_geometry_teapot",
		"webgl_geometry_terrain",
		"webgl_geometry_terrain_fog",
		"webgl_geometry_terrain_raycast",
		"webgl_geometry_text",
		"webgl_geometry_text_earcut",
		"webgl_geometry_text_pnltri",
		"webgl_gpgpu_birds",
		"webgl_gpu_particle_system",
		"webgl_hdr",
		"webgl_helpers",
		"webgl_interactive_buffergeometry",
		"webgl_interactive_cubes",
		"webgl_interactive_cubes_gpu",
		"webgl_interactive_instances_gpu",
		"webgl_interactive_cubes_ortho",
		"webgl_interactive_draggablecubes",
		"webgl_interactive_lines",
		"webgl_interactive_points",
		"webgl_interactive_raycasting_points",
		"webgl_interactive_voxelpainter",
		"webgl_kinect",
		"webgl_lensflares",
		"webgl_lights_hemisphere",
		"webgl_lights_pointlights",
		"webgl_lights_pointlights2",
		"webgl_lines_colors",
		"webgl_lines_cubes",
		"webgl_lines_dashed",
		"webgl_lines_sphere",
		"webgl_lines_splines",
		"webgl_loader_3mf",
		"webgl_loader_amf",
		"webgl_loader_assimp2json",
		"webgl_loader_awd",
		"webgl_loader_babylon",
		"webgl_loader_collada",
		"webgl_loader_collada_keyframe",
		"webgl_loader_collada_kinematics",
		"webgl_loader_collada_skinning",
		"webgl_loader_ctm",
		"webgl_loader_ctm_materials",
		"webgl_loader_fbx",
		"webgl_loader_gltf",
		"webgl_loader_json_blender",
		"webgl_loader_json_claraio",
		"webgl_loader_json_objconverter",
		"webgl_loader_md2",
		"webgl_loader_md2_control",
		"webgl_loader_mmd",
		"webgl_loader_mmd_pose",
		"webgl_loader_mmd_audio",
		"webgl_loader_msgpack",
		"webgl_loader_obj",
		"webgl_loader_obj_mtl",
		"webgl_loader_pcd",
		"webgl_loader_pdb",
		"webgl_loader_ply",
		"webgl_loader_sea3d",
		"webgl_loader_sea3d_hierarchy",
		"webgl_loader_sea3d_keyframe",
		"webgl_loader_sea3d_morph",
		"webgl_loader_sea3d_skinning",
		"webgl_loader_sea3d_sound",
		"webgl_loader_scene",
		"webgl_loader_stl",
		"webgl_loader_utf8",
		"webgl_loader_vrml",
		"webgl_loader_vtk",
		"webgl_lod",
		"webgl_marchingcubes",
		"webgl_materials",
		"webgl_materials_blending",
		"webgl_materials_blending_custom",
		"webgl_materials_bumpmap",
		"webgl_materials_bumpmap_skin",
		"webgl_materials_cars",
		"webgl_materials_cubemap",
		"webgl_materials_cubemap_balls_reflection",
		"webgl_materials_cubemap_balls_refraction",
		"webgl_materials_cubemap_dynamic",
		"webgl_materials_cubemap_dynamic2",
		"webgl_materials_cubemap_escher",
		"webgl_materials_cubemap_refraction",
		"webgl_materials_displacementmap",
		"webgl_materials_envmaps",
		"webgl_materials_grass",
		"webgl_materials_lightmap",
		"webgl_materials_nodes",
		"webgl_materials_normalmap",
		"webgl_materials_parallaxmap",
		"webgl_materials_shaders_fresnel",
		"webgl_materials_skin",
		"webgl_materials_standard",
		"webgl_materials_texture_anisotropy",
		"webgl_materials_texture_compressed",
		"webgl_materials_texture_filters",
		"webgl_materials_texture_hdr",
		"webgl_materials_texture_manualmipmap",
		"webgl_materials_texture_pvrtc",
		"webgl_materials_texture_tga",
		"webgl_materials_variations_basic",
		"webgl_materials_variations_lambert",
		"webgl_materials_variations_phong",
		"webgl_materials_variations_standard",
		"webgl_materials_variations_standard2",
		"webgl_materials_video",
		"webgl_materials_wireframe",
		"webgl_mirror",
		"webgl_modifier_subdivision",
		"webgl_modifier_tessellation",
		"webgl_morphnormals",
		"webgl_morphtargets",
		"webgl_morphtargets_horse",
		"webgl_morphtargets_human",
		"webgl_multiple_canvases_circle",
		"webgl_multiple_canvases_complex",
		"webgl_multiple_canvases_grid",
		"webgl_multiple_elements",
		"webgl_multiple_elements_text",
		"webgl_multiple_renderers",
		"webgl_multiple_views",
		"webgl_nearestneighbour",
		"webgl_objects_update",
		"webgl_octree",
		"webgl_octree_raycasting",
		"webgl_panorama_equirectangular",
		"webgl_particles_general",
		"webgl_performance",
		"webgl_performance_doublesided",
		"webgl_performance_static",
		"webgl_points_billboards",
		"webgl_points_billboards_colors",
		"webgl_points_dynamic",
		"webgl_points_random",
		"webgl_points_sprites",
		"webgl_postprocessing",
		"webgl_postprocessing_advanced",
		"webgl_postprocessing_crossfade",
		"webgl_postprocessing_dof",
		"webgl_postprocessing_dof2",
		"webgl_postprocessing_glitch",
		"webgl_postprocessing_godrays",
		"webgl_postprocessing_masking",
		"webgl_postprocessing_nodes",
		"webgl_postprocessing_smaa",
		"webgl_postprocessing_ssao",
		"webgl_raycast_texture",
		"webgl_read_float_buffer",
		"webgl_rtt",
		"webgl_sandbox",
		"webgl_shader",
		"webgl_shader_lava",
		"webgl_shader2",
		"webgl_shaders_ocean",
		"webgl_shaders_ocean2",
		"webgl_shaders_sky",
		"webgl_shaders_tonemapping",
		"webgl_shaders_vector",
		"webgl_shading_physical",
		"webgl_shadowmap",
		"webgl_shadowmap_performance",
		"webgl_shadowmap_pointlight",
		"webgl_shadowmap_viewer",
		"webgl_shadowmesh",
		"webgl_skinning_simple",
		"webgl_sprites",
		"webgl_terrain_dynamic",
		"webgl_test_memory",
		"webgl_test_memory2",
		"webgl_trails",
		"webgl_video_panorama_equirectangular"
	],
	"webgl / advanced": [
		"webgl_buffergeometry",
		"webgl_buffergeometry_constructed_from_geometry",
		"webgl_buffergeometry_custom_attributes_particles",
		"webgl_buffergeometry_drawcalls",
		"webgl_buffergeometry_instancing",
		"webgl_buffergeometry_instancing_billboards",
		"webgl_buffergeometry_instancing_dynamic",
		"webgl_buffergeometry_instancing_interleaved_dynamic",
		"webgl_buffergeometry_lines",
		"webgl_buffergeometry_lines_indexed",
		"webgl_buffergeometry_points",
		"webgl_buffergeometry_rawshader",
		"webgl_buffergeometry_selective_draw",
		"webgl_buffergeometry_uint",
		"webgl_custom_attributes",
		"webgl_custom_attributes_lines",
		"webgl_custom_attributes_points",
		"webgl_custom_attributes_points2",
		"webgl_custom_attributes_points3",
		"webgl_raymarching_reflect"
	],
	"webvr": [
		"webvr_cubes",
		"webvr_video",
		"webvr_stereo_pano"
	],
	"css3d": [
		"css3d_molecules",
		"css3d_panorama",
		"css3d_panorama_deviceorientation",
		"css3d_periodictable",
		"css3d_sandbox",
		"css3d_sprites",
		"css3d_youtube"
	],
	"css3d stereo": [
		"css3dstereo_periodictable",
	],
	"misc": [
		"misc_animation_authoring",
		"misc_animation_keys",
		"misc_controls_deviceorientation",
		"misc_controls_fly",
		"misc_controls_orbit",
		"misc_controls_pointerlock",
		"misc_controls_trackball",
		"misc_controls_transform",
		"misc_fps",
		"misc_lights_test",
		"misc_lookat",
		"misc_sound",
		"misc_ubiquity_test",
		"misc_ubiquity_test2",
		"misc_uv_tests"
	],
	"canvas": [
		"canvas_ascii_effect",
		"canvas_camera_orthographic",
		"canvas_camera_orthographic2",
		"canvas_geometry_birds",
		"canvas_geometry_cube",
		"canvas_geometry_earth",
		"canvas_geometry_hierarchy",
		"canvas_geometry_nurbs",
		"canvas_geometry_panorama",
		"canvas_geometry_panorama_fisheye",
		"canvas_geometry_shapes",
		"canvas_geometry_terrain",
		"canvas_geometry_text",
		"canvas_interactive_cubes",
		"canvas_interactive_cubes_tween",
		"canvas_interactive_particles",
		"canvas_interactive_voxelpainter",
		"canvas_lights_pointlights",
		"canvas_lines",
		"canvas_lines_colors",
		"canvas_lines_colors_2d",
		"canvas_lines_dashed",
		"canvas_lines_sphere",
		"canvas_materials",
		"canvas_materials_normal",
		"canvas_materials_reflection",
		"canvas_materials_video",
		"canvas_morphtargets_horse",
		"canvas_particles_floor",
		"canvas_particles_random",
		"canvas_particles_sprites",
		"canvas_particles_waves",
		"canvas_performance",
		"canvas_sandbox"
	],
	"raytracing": [
		"raytracing_sandbox"
	],
	"software": [
		"software_geometry_earth",
		"software_sandbox"
	],
	"svg": [
		"svg_sandbox"
	]
};
<|endoftext|>


<commit_before>var TIMEOUT = 36000;
var CLEAR_INTERVAL = 5000;
var CACHE_TIME = CLEAR_INTERVAL * 2;
var MAX_LENGTH = 512;
var MIN_LENGTH = 432;
var COUNT = 100;
var count = 0;
var ids = [];
var data = {};
var proxy, binded, timeout, interval;

function disable() {
	proxy.removeListener('request', handleRequest);
	ids = [];
	data = {};
	interval && clearInterval(interval);
	interval = null;
	binded = false;
}

function enable() {
	!binded && proxy.on('request', handleRequest);

	binded = true;
	clearTimeout(timeout);
	timeout = setTimeout(disable, TIMEOUT);
	if (!interval) {
		interval = setInterval(clearCache, CLEAR_INTERVAL);
	}
}

/**
 * 如果超过最大缓存数，清理如下请求数据：
 * 1. 已经请求结束且结束时间超过10秒
 * 2. 请求#1前面的未结束且未被ui读取过的请求
 */
function clearCache() {
	var len = ids.length;
	if (len <= MAX_LENGTH) {
		return;
	}

	var index = -1; //已经完成，且缓存超过10s的最后一个请求
	var now = Date.now();
	for (var i = len - 1; i >= 0; i--) {
		var curData = data[ids[i]];
		if (curData.endTime && now - curData.endTime > TIMEOUT) {
			index = i;
			break;
		}
	}

	if (index < 0) {
		return;
	}

	var _ids = [];
	var end;
	++index;
	for (var i = 0; i < index; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData.read && (!curData.endTime || now - curData.endTime < CACHE_TIME
				|| now - curData.startTime < TIMEOUT)) {
			_ids.push(id);
		} else {
			delete data[id];
			if (--len <= MIN_LENGTH) {
				_ids.push.apply(_ids, ids.slice(i + 1, index));
				break;
			}
		}
	}
	ids = _ids.concat(ids.slice(index));
}

function getIds(startTime, count) {
	var len = ids.length;
	if (!len) {
		return [];
	}

	startTime = ((startTime || Date.now() - 6000) + '').split('-');
	count = Math.min(count || COUNT, len);

	startTime[0] = parseInt(startTime[0], 10) || 0;
	startTime[1] = parseInt(startTime[1], 10) || 0;

	if (compareId(ids[0], startTime)) {

		return ids.slice(0, count);
	}

	var end = len - 1;
	if (!end || !compareId(ids[end], startTime)) {

		return  [];
	}

	var index = getIndex(startTime, 0, end);
	return ids.slice(index, index + count);
}

function compareId(curId, refId) {
	curId = curId.split('-');
	return curId[0] > refId[0] || (curId[0] == refId[0] && curId[1] > refId[1]);
}

function getIndex(startTime, start, end) {
	if (end - start <= 1) {
		return compareId(ids[start], startTime) ? start : end;
	}

	var mid = Math.floor((start + end) / 2);
	var id = ids[mid];
	return compareId(id, startTime) ? getIndex(startTime, start, mid) : getIndex(startTime, mid + 1, end);
}

function getList(ids) {
	var result = [];
	for (var i = 0, len = ids && ids.length; i < len; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData) {
			curData.read = true;
			result[i] = curData;
		}
	}

	return result;
}

function handleRequest(req) {
	var curData = req.data;
	var startTime = curData.startTime;
	var id = curData.id = startTime + '-' + ++count;
	data[id] = curData;
	ids.push(id);
}

module.exports = function init(_proxy) {
	proxy = _proxy;
	/**
	 * options: {
	 * 		startTime: timestamp || timestamp + '-' + count
	 * 		count: 获取新数据的数量
	 * 		ids: 请未结束的id列表
	 * }
	 *
	 * @param options
	 */
	proxy.getData = function get(options) {
		enable();
		options = options || {};
		var data = {};
		var newIds = options.startTime == -1 ? [] : getIds(options.startTime, options.count);
		var list = getList(newIds).concat(getList(options.ids));
		for (var i = 0, len = list.length; i < len; i++) {
			var item = list[i];
			if (item) {
				data[item.id] = item;
			}
		}

		return {
			ids: options.ids || [],
			newIds: newIds,
			data: data
		};
	};
};<commit_msg>count<commit_after>var TIMEOUT = 36000;
var CLEAR_INTERVAL = 5000;
var CACHE_TIME = CLEAR_INTERVAL * 2;
var MAX_LENGTH = 512;
var MIN_LENGTH = 432;
var COUNT = 80;
var count = 0;
var ids = [];
var data = {};
var proxy, binded, timeout, interval;

function disable() {
	proxy.removeListener('request', handleRequest);
	ids = [];
	data = {};
	interval && clearInterval(interval);
	interval = null;
	binded = false;
}

function enable() {
	!binded && proxy.on('request', handleRequest);

	binded = true;
	clearTimeout(timeout);
	timeout = setTimeout(disable, TIMEOUT);
	if (!interval) {
		interval = setInterval(clearCache, CLEAR_INTERVAL);
	}
}

/**
 * 如果超过最大缓存数，清理如下请求数据：
 * 1. 已经请求结束且结束时间超过10秒
 * 2. 请求#1前面的未结束且未被ui读取过的请求
 */
function clearCache() {
	var len = ids.length;
	if (len <= MAX_LENGTH) {
		return;
	}

	var index = -1; //已经完成，且缓存超过10s的最后一个请求
	var now = Date.now();
	for (var i = len - 1; i >= 0; i--) {
		var curData = data[ids[i]];
		if (curData.endTime && now - curData.endTime > TIMEOUT) {
			index = i;
			break;
		}
	}

	if (index < 0) {
		return;
	}

	var _ids = [];
	var end;
	++index;
	for (var i = 0; i < index; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData.read && (!curData.endTime || now - curData.endTime < CACHE_TIME
				|| now - curData.startTime < TIMEOUT)) {
			_ids.push(id);
		} else {
			delete data[id];
			if (--len <= MIN_LENGTH) {
				_ids.push.apply(_ids, ids.slice(i + 1, index));
				break;
			}
		}
	}
	ids = _ids.concat(ids.slice(index));
}

function getIds(startTime, count) {
	var len = ids.length;
	if (!len) {
		return [];
	}

	startTime = ((startTime || Date.now() - 6000) + '').split('-');
	count = Math.min(count || COUNT, len);

	startTime[0] = parseInt(startTime[0], 10) || 0;
	startTime[1] = parseInt(startTime[1], 10) || 0;

	if (compareId(ids[0], startTime)) {

		return ids.slice(0, count);
	}

	var end = len - 1;
	if (!end || !compareId(ids[end], startTime)) {

		return  [];
	}

	var index = getIndex(startTime, 0, end);
	return ids.slice(index, index + count);
}

function compareId(curId, refId) {
	curId = curId.split('-');
	return curId[0] > refId[0] || (curId[0] == refId[0] && curId[1] > refId[1]);
}

function getIndex(startTime, start, end) {
	if (end - start <= 1) {
		return compareId(ids[start], startTime) ? start : end;
	}

	var mid = Math.floor((start + end) / 2);
	var id = ids[mid];
	return compareId(id, startTime) ? getIndex(startTime, start, mid) : getIndex(startTime, mid + 1, end);
}

function getList(ids) {
	var result = [];
	for (var i = 0, len = ids && ids.length; i < len; i++) {
		var id = ids[i];
		var curData = data[id];
		if (curData) {
			curData.read = true;
			result[i] = curData;
		}
	}

	return result;
}

function handleRequest(req) {
	var curData = req.data;
	var startTime = curData.startTime;
	var id = curData.id = startTime + '-' + ++count;
	data[id] = curData;
	ids.push(id);
}

module.exports = function init(_proxy) {
	proxy = _proxy;
	/**
	 * options: {
	 * 		startTime: timestamp || timestamp + '-' + count
	 * 		count: 获取新数据的数量
	 * 		ids: 请未结束的id列表
	 * }
	 *
	 * @param options
	 */
	proxy.getData = function get(options) {
		enable();
		options = options || {};
		var data = {};
		var newIds = options.startTime == -1 ? [] : getIds(options.startTime, options.count);
		var list = getList(newIds).concat(getList(options.ids));
		for (var i = 0, len = list.length; i < len; i++) {
			var item = list[i];
			if (item) {
				data[item.id] = item;
			}
		}

		return {
			ids: options.ids || [],
			newIds: newIds,
			data: data
		};
	};
};<|endoftext|>


<commit_before>// Setup: Author creates contract with 4k ether
var DATA_FEED = 'cryptsyfeed'
var DATA_INDEX = 'USD/ETH'
var AUTHOR = 'myWallet'

// if enough to run
if (tx.value < 200 * block.basefee) {
    return //("Insufficient fee")
}
// if not initialized
if (!contract.storage[1000]) {
    // must send 1000 ether
    if (tx.value < 1000) {
        return //("Insufficient value")
    }
    // set as initialized
    contract.storage[1000] = 1
    // store the initial investment in USD, minus a fee, via the exchange rate held at block.contract_storage(DATA_FEED)[DATA_INDEX]
    // D is the contract address for the data feed, I is the datastore index for the latest ethereum-USD exchange rate
    contract.storage[1001] = tx.value * 0.998 * block.contract_storage(DATA_FEED)[DATA_INDEX]
    // store the date (in seconds) at which the contract matures (1 month)
    contract.storage[1002] = block.timestamp + 30 * 86400
    // store the investor wallet address
    contract.storage[1003] = tx.sender
} else {
    // convert initial investment back into ether at current exchange rate
    var ethervalue = contract.storage[1001] / block.contract_storage(DATA_FEED)[DATA_INDEX]
    // if ether value is over 5k, send all to investor (at anytime)
    // or if time expired, send value to investor and remainder to contract author (A)
    if (ethervalue >= 5000) {
        mktx(contract.storage[1003], 5000, 0, 0)
    } else if (block.timestamp > contract.storage[1002]) {
        mktx(contract.storage[1003], ethervalue, 0, 0)
        mktx(AUTHOR, (5000 - ethervalue), 0, 0)
    }
}<commit_msg>Clarified hedge example<commit_after>// Setup: Author creates contract with 4k ether
var DATA_FEED = 'cryptsyfeed'
var DATA_INDEX = 'USD/ETH'
var AUTHOR = 'myWallet'

// if not initialized
if (!contract.storage['contract_initialized']) {
    // must send 1000 ether
    if (tx.value < 1000) {
        return // Insufficient value
    }
    // set as initialized
    contract.storage['contract_initialized'] = true
    // store the initial investment in USD, minus a fee, via the exchange rate held on the datafeed
    // D is the contract address for the data feed, I is the datastore index for the latest ethereum-USD exchange rate
    contract.storage['initial_investment_usd'] = tx.value * 0.998 * getCurrentUsdEthRate()
    // store the date (in seconds) at which the contract matures (1 month)
    contract.storage['contract_mature_date'] = block.timestamp + 30 * 86400
    // store the investor wallet address
    contract.storage['investor_wallet_addr'] = tx.sender
} else {
    // convert initial investment back into ether at current exchange rate
    var ethervalue = contract.storage['initial_investment_usd'] / getCurrentUsdEthRate()
    // if ether value is over 5k, send all to investor (at anytime)
    // or if time expired, send value to investor and remainder to contract author (A)
    if (ethervalue >= 5000) {
        mktx(contract.storage['investor_wallet_addr'], 5000, 0, 0)
    } else if (block.timestamp > contract.storage['contract_mature_date']) {
        mktx(contract.storage['investor_wallet_addr'], ethervalue, 0, 0)
        mktx(AUTHOR, (5000 - ethervalue), 0, 0)
    }
}

function getCurrentUsdEthRate() {
    return block.contract_storage(DATA_FEED)[DATA_INDEX]
}<|endoftext|>