<commit_before>/**
 * @fileoverview This file defines the Exclusion service. This service manages
 * the exclusion between layers.
 */
goog.provide('app.ExclusionManager');

goog.require('app');
goog.require('app.BlankLayer');
goog.require('app.Notify');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('ngeo.BackgroundLayerMgr');
goog.require('ol.Object');

/**
 * @param {ngeo.BackgroundLayerMgr} ngeoBackgroundLayerMgr Background layer
 *     manager.
 * @param {app.BlankLayer} appBlankLayer Blank layer service.
 * @param {app.Notify} appNotify Notify service.
 * @constructor
 * @ngInject
 */
app.ExclusionManager = function(ngeoBackgroundLayerMgr, appBlankLayer,
    appNotify) {

  /**
   * @type {ngeo.BackgroundLayerMgr}
   * @private
   */
  this.backgroundLayerMgr_ = ngeoBackgroundLayerMgr;

  /**
   * @type {app.BlankLayer}
   * @private
   */
  this.blankLayer_ = appBlankLayer;

  /**
   * @type {app.Notify}
   * @private
   */
  this.notify_ = appNotify;
};

/**
 * @param {string} one The first list of exclusions.
 * @param {string} two The second list of exclusions.
 * @return {boolean} Whether the array intersect or not.
 * @private
 */
app.ExclusionManager.prototype.intersects_ = function(one, two) {
  var arr1 = /** @type {Array} */ (JSON.parse(one));
  var arr2 = /** @type {Array} */ (JSON.parse(two));
  var concat = arr1.concat(arr2);
  goog.array.sort(concat);
  var i;
  var len = concat.length;
  for (i = 1; i < len; i++) {
    if (concat[i] == concat[i - 1]) {
      return true;
    }
  }
  return false;
};

/**
 * @param {ol.Map} map The OpenLayers Map.
 * @param {ol.layer.Layer} layer1 The layer which was added or for which the
 *     opacity property has changed.
 * @private
 */
app.ExclusionManager.prototype.checkForLayerExclusion_ = function(map, layer1) {
  var opacity = layer1.getOpacity();
  // don't do anything if layer is not displayed on map
  if (opacity === 0) {
    return;
  }

  if (!goog.isDef(layer1.get('metadata'))) {
    return;
  }
  var exclusion1 = layer1.get('metadata')['exclusion'];

  if (!goog.isDef(exclusion1)) {
    return;
  }

  var layers = map.getLayers().getArray();
  var len = layers.length;
  var i;
  var layer2;
  var msg;

  var layersToHide = [];
  for (i = 0; i < len; i++) {
    layer2 = layers[i];
    if (layer2 == layer1 || !goog.isDef(layer2.get('metadata')) ||
        !goog.isDef(layer2.get('metadata')['exclusion'])) {
      continue;
    }

    // check exclusion with current baselayer
    var exclusion2 = layer2.get('metadata')['exclusion'];
    opacity = layer2.getOpacity();
    if (this.intersects_(exclusion1, exclusion2) && opacity > 0) {
      // layer to exclude is not the current base layer
      if (i !== 0) {
        layersToHide.push(layer2.get('label'));
        layer2.setOpacity(0);
      } else {
        this.backgroundLayerMgr_.set(map, this.blankLayer_);
        msg = 'The background layer has been deactivated because the ' +
            'layer ' + layer1.get('label') +
            ' cannot be displayed on top of it';
        this.notify_(msg);
      }
    }
  }
  if (layersToHide.length) {
    msg = 'The layer(s) ' + layersToHide.join(',') +
        ' have been deactivated because they cannot be displayed while ' +
        ' the layer ' + layer1.get('label') +
        ' is displayed';
    this.notify_(msg);
  }
};

/**
 * @param {ol.Map} map The OpenLayers map.
 */
app.ExclusionManager.prototype.init = function(map) {
  var layerOpacityListenerKeys = {};

  // listen on layers being added to the map
  // base layers switch should fire the event as well
  goog.events.listen(map.getLayers(), ol.CollectionEventType.ADD,
      /**
       * @param {ol.CollectionEvent} e Collection event.
       */
      function(e) {
        var layer = /** @type {ol.layer.Layer} */ (e.element);
        this.checkForLayerExclusion_(map, layer);

        // listen on opacity change
        var key = goog.events.listen(layer,
            ol.Object.getChangeEventType(ol.layer.LayerProperty.OPACITY),
            function(e) {
              this.checkForLayerExclusion_(map, layer);
            }, undefined, this);
        layerOpacityListenerKeys[goog.getUid(layer)] = key;
      }, undefined, this);

  // remove any listener on opacity change when layer is removed from map
  goog.events.listen(map.getLayers(), ol.CollectionEventType.REMOVE,
      /**
       * @param {ol.CollectionEvent} e Collection event.
       */
      function(e) {
        var layer = /** @type {ol.layer.Layer} */ (e.element);
        goog.asserts.assert(goog.getUid(layer) in layerOpacityListenerKeys);
        goog.events.unlistenByKey(layerOpacityListenerKeys[goog.getUid(layer)]);
      }, undefined, this);
};

app.module.service('appExclusionManager', app.ExclusionManager);
<commit_msg>Exclusions messages can now be translated<commit_after>/**
 * @fileoverview This file defines the Exclusion service. This service manages
 * the exclusion between layers.
 */
goog.provide('app.ExclusionManager');

goog.require('app');
goog.require('app.BlankLayer');
goog.require('app.Notify');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('ngeo.BackgroundLayerMgr');
goog.require('ol.Object');

/**
 * @param {angularGettext.Catalog} gettextCatalog Gettext catalog.
 * @param {ngeo.BackgroundLayerMgr} ngeoBackgroundLayerMgr Background layer
 *     manager.
 * @param {app.BlankLayer} appBlankLayer Blank layer service.
 * @param {app.Notify} appNotify Notify service.
 * @constructor
 * @ngInject
 */
app.ExclusionManager = function(gettextCatalog, ngeoBackgroundLayerMgr,
    appBlankLayer, appNotify) {

  /**
   * @type {ngeo.BackgroundLayerMgr}
   * @private
   */
  this.backgroundLayerMgr_ = ngeoBackgroundLayerMgr;

  /**
   * @type {app.BlankLayer}
   * @private
   */
  this.blankLayer_ = appBlankLayer;

  /**
   * @type {app.Notify}
   * @private
   */
  this.notify_ = appNotify;

  /**
   * @type {function(string, Object<string, *>): string}
   * @private
   */
  this.translate_ = goog.bind(gettextCatalog.getString, gettextCatalog);

  /**
   * @type {string}
   * @private
   */
  this.exclusionMessage1_ = 'Background has been deactivated because the ' +
      'layer {{layer}} cannot be displayed on top of it.';

  /**
   * @type {string}
   * @private
   */
  this.exclusionMessage2_ = 'The layer(s) {{layersToHide}} have been ' +
      'deactivated because they cannot be displayed while the layer ' +
      '{{layer}} is displayed';
};

/**
 * @param {string} one The first list of exclusions.
 * @param {string} two The second list of exclusions.
 * @return {boolean} Whether the array intersect or not.
 * @private
 */
app.ExclusionManager.prototype.intersects_ = function(one, two) {
  var arr1 = /** @type {Array} */ (JSON.parse(one));
  var arr2 = /** @type {Array} */ (JSON.parse(two));
  var concat = arr1.concat(arr2);
  goog.array.sort(concat);
  var i;
  var len = concat.length;
  for (i = 1; i < len; i++) {
    if (concat[i] == concat[i - 1]) {
      return true;
    }
  }
  return false;
};

/**
 * @param {ol.Map} map The OpenLayers Map.
 * @param {ol.layer.Layer} layer1 The layer which was added or for which the
 *     opacity property has changed.
 * @private
 */
app.ExclusionManager.prototype.checkForLayerExclusion_ = function(map, layer1) {
  var opacity = layer1.getOpacity();
  // don't do anything if layer is not displayed on map
  if (opacity === 0) {
    return;
  }

  if (!goog.isDef(layer1.get('metadata'))) {
    return;
  }
  var exclusion1 = layer1.get('metadata')['exclusion'];

  if (!goog.isDef(exclusion1)) {
    return;
  }

  var layers = map.getLayers().getArray();
  var len = layers.length;
  var i;
  var layer2;
  var msg;

  var layersToHide = [];
  for (i = 0; i < len; i++) {
    layer2 = layers[i];
    if (layer2 == layer1 || !goog.isDef(layer2.get('metadata')) ||
        !goog.isDef(layer2.get('metadata')['exclusion'])) {
      continue;
    }

    // check exclusion with current baselayer
    var exclusion2 = layer2.get('metadata')['exclusion'];
    opacity = layer2.getOpacity();
    if (this.intersects_(exclusion1, exclusion2) && opacity > 0) {
      // layer to exclude is not the current base layer
      if (i !== 0) {
        layersToHide.push(layer2.get('label'));
        layer2.setOpacity(0);
      } else {
        this.backgroundLayerMgr_.set(map, this.blankLayer_);
        msg = this.translate_(this.exclusionMessage1_, {
          'layer': '' + layer1.get('label') + ''
        });
        this.notify_(msg);
      }
    }
  }
  if (layersToHide.length) {
    msg = this.translate_(this.exclusionMessage2_, {
      'layersToHide': '' + layersToHide.join(', ') + '',
      'layer': '' + layer1.get('label') + ''
    });
    this.notify_(msg);
  }
};

/**
 * @param {ol.Map} map The OpenLayers map.
 */
app.ExclusionManager.prototype.init = function(map) {
  var layerOpacityListenerKeys = {};

  // listen on layers being added to the map
  // base layers switch should fire the event as well
  goog.events.listen(map.getLayers(), ol.CollectionEventType.ADD,
      /**
       * @param {ol.CollectionEvent} e Collection event.
       */
      function(e) {
        var layer = /** @type {ol.layer.Layer} */ (e.element);
        this.checkForLayerExclusion_(map, layer);

        // listen on opacity change
        var key = goog.events.listen(layer,
            ol.Object.getChangeEventType(ol.layer.LayerProperty.OPACITY),
            function(e) {
              this.checkForLayerExclusion_(map, layer);
            }, undefined, this);
        layerOpacityListenerKeys[goog.getUid(layer)] = key;
      }, undefined, this);

  // remove any listener on opacity change when layer is removed from map
  goog.events.listen(map.getLayers(), ol.CollectionEventType.REMOVE,
      /**
       * @param {ol.CollectionEvent} e Collection event.
       */
      function(e) {
        var layer = /** @type {ol.layer.Layer} */ (e.element);
        goog.asserts.assert(goog.getUid(layer) in layerOpacityListenerKeys);
        goog.events.unlistenByKey(layerOpacityListenerKeys[goog.getUid(layer)]);
      }, undefined, this);
};

app.module.service('appExclusionManager', app.ExclusionManager);
<|endoftext|>


<commit_before>module.exports = {
	"Text": PropertyModel,
	"Number": PropertyModel
}

function PropertyModel(value) {
	this._value = value
}

PropertyModel.prototype.observe = function(callback) {
	var info = _getObservationInfo(this)
	fin.observe(info.id, info.chain, function(mutation) {
		callback(mutation.value, mutation.op)
	})
}

var _getObservationInfo = function(propertyModel) {
	var propertyNameChain = []

	while(propertyModel._parent) {
		propertyNameChain.push(propertyModel._propertyID)
		propertyModel = propertyModel._parent
	}
	return { id:propertyModel._id, chain:propertyNameChain }
}
<commit_msg>Start fleshing out the property models in earnest. We'll need .observe and .on to begin with, and correct behavior for Text/Number vs List/Set<commit_after>var fin = require('../client')

module.exports = {
	"Text": PropertyModel,
	"Number": PropertyModel,
	"List": PropertyModel
}

/* Property model types (Text/Number, List/Set)
 **********************************************/
function PropertyModel(value) { this._value = value }
PropertyModel.prototype = {
	observe: _modelObserve,
	on: _modelOn
}

function _modelObserve(callback) {
	_observe(this, function(mutation) {
		callback(mutation.args, mutation.op)
	})
}

function _modelOn(mutationType, callback) {
	_observe(this, function(mutation) {
		if (mutation.op != mutationType) { return }
		// TODO if this is a List/Set model, and the type "of" is a CustomModel,
		// then instantiate that model and pass it as the argument to callback
		callback(mutation.args)
	})
}

/* Util function. All callbacks get called
 * in the context of the model passed in
 *****************************************/
var _observe = function(propertyModel, callback) {
	var info = _getObservationInfo(propertyModel)
	fin.observe(info.id, info.chain, function(mutation) {
		callback.call(propertyModel, mutation)
	})
}

var _getObservationInfo = function(propertyModel) {
	var propertyNameChain = []

	while(propertyModel._parent) {
		propertyNameChain.push(propertyModel._propertyID)
		propertyModel = propertyModel._parent
	}
	return { id:propertyModel._id, chain:propertyNameChain }
}
<|endoftext|>


<commit_before>'use strict'

let io = null

let connectionInfo

const MIN_WAIT = 100
let wait = MIN_WAIT

let states = []

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        if (connectionInfo) {
            connectionInfo.visible = false
        }
    };

    io.onmessage = (msg) => {
        states.push(JSON.parse(msg.data))
    }

    io.onclose = () => {
        if (connectionInfo) {
            connectionInfo.text = 'Reconnecting...'
            connectionInfo.visible = true
        }
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(data)
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const app = new Application(
    1000, 1000, {
        backgroundColor: 0xFFFFFF,
        autoResize: true,
        antialias: true,
        resolution: 1,
    },
)

const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
connect(addr)
document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

document.addEventListener("keydown", (event) => {
    send('keydown:' + event.key)
})
document.addEventListener("keyup", (event) => {
    send('keyup:' + event.key)
})

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = 1000
    background.height = 1000
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.8
    connectionInfo.anchor.set(0.5)
    connectionInfo.y = 30
    connectionInfo.x = 120
    app.stage.addChild(connectionInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    app.ticker.add(gameLoop)
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    states.splice(0, index)
    let interpolatedState = getInterpolatedState(states[0], states[1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime() {
    const now = new Date().getTime()
    const INTERPOLATION_DELTA = 100
    return now - INTERPOLATION_DELTA
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from
    state.vel.x += (to.vel.x - from.vel.x) * fraction
    state.vel.y += (to.vel.y - from.vel.y) * fraction
    state.pos.x += (to.pos.x - from.pos.x) * fraction
    state.pos.y += (to.pos.y - from.pos.y) * fraction
    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.pos.x
    ball.y = state.pos.y
}<commit_msg>Client: Make interpolation time a bit higher to smoothen animation<commit_after>'use strict'

let io = null

let connectionInfo

const MIN_WAIT = 100
let wait = MIN_WAIT

let states = []

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        if (connectionInfo) {
            connectionInfo.visible = false
        }
    };

    io.onmessage = (msg) => {
        states.push(JSON.parse(msg.data))
    }

    io.onclose = () => {
        if (connectionInfo) {
            connectionInfo.text = 'Reconnecting...'
            connectionInfo.visible = true
        }
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(data)
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const app = new Application(
    1000, 1000, {
        backgroundColor: 0xFFFFFF,
        autoResize: true,
        antialias: true,
        resolution: 1,
    },
)

const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
connect(addr)
document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

document.addEventListener("keydown", (event) => {
    send('keydown:' + event.key)
})
document.addEventListener("keyup", (event) => {
    send('keyup:' + event.key)
})

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = 1000
    background.height = 1000
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.8
    connectionInfo.anchor.set(0.5)
    connectionInfo.y = 30
    connectionInfo.x = 120
    app.stage.addChild(connectionInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    app.ticker.add(gameLoop)
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    states.splice(0, index)
    let interpolatedState = getInterpolatedState(states[0], states[1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime() {
    const now = new Date().getTime()
    const INTERPOLATION_DELTA = 300
    return now - INTERPOLATION_DELTA
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from
    state.vel.x += (to.vel.x - from.vel.x) * fraction
    state.vel.y += (to.vel.y - from.vel.y) * fraction
    state.pos.x += (to.pos.x - from.pos.x) * fraction
    state.pos.y += (to.pos.y - from.pos.y) * fraction
    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.pos.x
    ball.y = state.pos.y
}<|endoftext|>


<commit_before>const render_tab_bar = require('../templates/tab_bar.hbs');
const rendered_markdown = require('./rendered_markdown');

function get_sub_count(current_stream) {
    const sub_count = current_stream.subscriber_count;
    return sub_count;
}

function get_formatted_sub_count(current_stream) {
    let sub_count = get_sub_count(current_stream);
    if (sub_count >= 1000) {
        // parseInt() is used to floor the value of division to an integer
        sub_count = parseInt(sub_count / 1000, 10) + "k";
    }
    return sub_count;
}

function make_tab_data(filter) {
    const tab_data = {};
    if (filter === undefined) {
        return {
            title: 'All messages',
            icon: 'home',
        };
    }
    tab_data.title = filter.get_title();
    tab_data.icon = filter.get_icon();
    if (tab_data.icon === 'hashtag' || tab_data.icon === 'lock') {
        const stream = filter.operands("stream")[0];
        const current_stream  = stream_data.get_sub_by_name(stream);
        if (current_stream) {
            tab_data.rendered_narrow_description = current_stream.rendered_description;
            tab_data.sub_count = get_sub_count(current_stream);
            tab_data.formatted_sub_count = get_formatted_sub_count(current_stream);
            tab_data.stream_settings_link = "#streams/" + current_stream.stream_id + "/" + current_stream.name;
        } else {
            tab_data.title = 'Unknown Stream';
            tab_data.sub_count = '0';
            tab_data.formatted_sub_count = '0';
            tab_data.rendered_narrow_description = "This stream does not exist or is private.";
        }
    }
    return tab_data;
}

exports.colorize_tab_bar = function () {
    const filter = narrow_state.filter();
    if (filter === undefined || !filter.has_operator('stream')) {return;}
    const color_for_stream = stream_data.get_color(filter.operands("stream")[0]);
    const stream_light = colorspace.getHexColor(colorspace.getDecimalColor(color_for_stream));
    $("#tab_list .fa-hashtag").css('color', stream_light);
    $("#tab_list .fa-lock").css('color', stream_light);
};

function append_and_display_title_area(tab_bar_data) {
    const tab_bar = $("#tab_bar");
    tab_bar.empty();
    const rendered = render_tab_bar(tab_bar_data);
    tab_bar.append(rendered);
    if (tab_bar_data.stream_settings_link) {
        exports.colorize_tab_bar();
    }
    tab_bar.removeClass('notdisplayed');
    const content = tab_bar.find('span.rendered_markdown');
    if (content) {
        // Update syntax like stream names, emojis, mentions, timestamps.
        rendered_markdown.update_elements(content);
    }
}

function bind_title_area_handlers() {
    $("#tab_list span:nth-last-child(2)").on("click", function (e) {
        if (document.getSelection().type === "Range") {
            // Allow copy/paste to work normally without interference.
            return;
        }

        // Let links behave normally, ie, do nothing if
        if ($(e.target).closest("a").length === 0) {
            exports.open_search_bar_and_close_narrow_description();
            search.initiate_search();
            e.preventDefault();
            e.stopPropagation();
        }
    });

    const color = $(".search_closed").css("color");
    const night_mode_color = $(".nightmode .closed_icon").css("color");

    // make sure that hover plays nicely with whether search is being
    // opened or not.
    $(".narrow_description > a").hover(function () {
        if (night_mode_color) {
            $(".search_closed").css("color", night_mode_color);
        } else {
            $(".search_closed").css("color", color);
        }
    }, function () {
        $(".search_closed").css("color", "");
    });
}

function build_tab_bar(filter) {
    // This makes sure we don't waste time appending tab_bar on a template where it's never used
    if (filter && !filter.is_common_narrow()) {
        exports.open_search_bar_and_close_narrow_description();
    } else {
        const tab_bar_data = make_tab_data(filter);
        append_and_display_title_area(tab_bar_data);
        bind_title_area_handlers();
        exports.close_search_bar_and_open_narrow_description();
    }
}

exports.exit_search = function () {
    const filter = narrow_state.filter();
    if (!filter || filter.is_common_narrow()) {
        // for common narrows, we change the UI (and don't redirect)
        exports.close_search_bar_and_open_narrow_description();

        // reset searchbox text
        const search_string = narrow_state.search_string();
        // This does not need to be conditional like the corresponding
        // function call in narrow.activate because search filters are
        // not common narrows
        if (search_string !== "") {
            $("#search_query").val(search_string + " ");
        }
    } else {
        // for "searching narrows", we redirect
        window.location.replace(filter.generate_redirect_url());
    }
};

exports.initialize = function () {
    exports.render_title_area();

    // register searchbar click handler
    $('#search_exit').on("click", function (e) {
        tab_bar.exit_search();
        e.preventDefault();
        e.stopPropagation();
    });
};

exports.render_title_area = function () {
    // TODO: Implement rerendering for stream privacy or subscriber
    // count changes. We simply need to call this function in the
    // appropriate places.
    const filter = narrow_state.filter();
    build_tab_bar(filter);
};

exports.open_search_bar_and_close_narrow_description = function () {
    $(".navbar-search").addClass("expanded");
    $("#tab_list").addClass("hidden");
};

exports.close_search_bar_and_open_narrow_description = function () {
    const filter = narrow_state.filter();
    if (!(filter && !filter.is_common_narrow())) {
        $(".navbar-search").removeClass("expanded");
        $("#tab_list").removeClass("hidden");
    }
};

window.tab_bar = exports;
<commit_msg>navbar: Fix search icon click event.<commit_after>const render_tab_bar = require('../templates/tab_bar.hbs');
const rendered_markdown = require('./rendered_markdown');

function get_sub_count(current_stream) {
    const sub_count = current_stream.subscriber_count;
    return sub_count;
}

function get_formatted_sub_count(current_stream) {
    let sub_count = get_sub_count(current_stream);
    if (sub_count >= 1000) {
        // parseInt() is used to floor the value of division to an integer
        sub_count = parseInt(sub_count / 1000, 10) + "k";
    }
    return sub_count;
}

function make_tab_data(filter) {
    const tab_data = {};
    if (filter === undefined) {
        return {
            title: 'All messages',
            icon: 'home',
        };
    }
    tab_data.title = filter.get_title();
    tab_data.icon = filter.get_icon();
    if (tab_data.icon === 'hashtag' || tab_data.icon === 'lock') {
        const stream = filter.operands("stream")[0];
        const current_stream  = stream_data.get_sub_by_name(stream);
        if (current_stream) {
            tab_data.rendered_narrow_description = current_stream.rendered_description;
            tab_data.sub_count = get_sub_count(current_stream);
            tab_data.formatted_sub_count = get_formatted_sub_count(current_stream);
            tab_data.stream_settings_link = "#streams/" + current_stream.stream_id + "/" + current_stream.name;
        } else {
            tab_data.title = 'Unknown Stream';
            tab_data.sub_count = '0';
            tab_data.formatted_sub_count = '0';
            tab_data.rendered_narrow_description = "This stream does not exist or is private.";
        }
    }
    return tab_data;
}

exports.colorize_tab_bar = function () {
    const filter = narrow_state.filter();
    if (filter === undefined || !filter.has_operator('stream')) {return;}
    const color_for_stream = stream_data.get_color(filter.operands("stream")[0]);
    const stream_light = colorspace.getHexColor(colorspace.getDecimalColor(color_for_stream));
    $("#tab_list .fa-hashtag").css('color', stream_light);
    $("#tab_list .fa-lock").css('color', stream_light);
};

function append_and_display_title_area(tab_bar_data) {
    const tab_bar = $("#tab_bar");
    tab_bar.empty();
    const rendered = render_tab_bar(tab_bar_data);
    tab_bar.append(rendered);
    if (tab_bar_data.stream_settings_link) {
        exports.colorize_tab_bar();
    }
    tab_bar.removeClass('notdisplayed');
    const content = tab_bar.find('span.rendered_markdown');
    if (content) {
        // Update syntax like stream names, emojis, mentions, timestamps.
        rendered_markdown.update_elements(content);
    }
}

function bind_title_area_handlers() {
    $(".search_closed").on("click", function (e) {
        exports.open_search_bar_and_close_narrow_description();
        search.initiate_search();
        e.preventDefault();
        e.stopPropagation();
    });

    $("#tab_list span:nth-last-child(2)").on("click", function (e) {
        if (document.getSelection().type === "Range") {
            // Allow copy/paste to work normally without interference.
            return;
        }

        // Let links behave normally, ie, do nothing if
        if ($(e.target).closest("a").length === 0) {
            exports.open_search_bar_and_close_narrow_description();
            search.initiate_search();
            e.preventDefault();
            e.stopPropagation();
        }
    });

    const color = $(".search_closed").css("color");
    const night_mode_color = $(".nightmode .closed_icon").css("color");

    // make sure that hover plays nicely with whether search is being
    // opened or not.
    $(".narrow_description > a").hover(function () {
        if (night_mode_color) {
            $(".search_closed").css("color", night_mode_color);
        } else {
            $(".search_closed").css("color", color);
        }
    }, function () {
        $(".search_closed").css("color", "");
    });
}

function build_tab_bar(filter) {
    // This makes sure we don't waste time appending tab_bar on a template where it's never used
    if (filter && !filter.is_common_narrow()) {
        exports.open_search_bar_and_close_narrow_description();
    } else {
        const tab_bar_data = make_tab_data(filter);
        append_and_display_title_area(tab_bar_data);
        bind_title_area_handlers();
        exports.close_search_bar_and_open_narrow_description();
    }
}

exports.exit_search = function () {
    const filter = narrow_state.filter();
    if (!filter || filter.is_common_narrow()) {
        // for common narrows, we change the UI (and don't redirect)
        exports.close_search_bar_and_open_narrow_description();

        // reset searchbox text
        const search_string = narrow_state.search_string();
        // This does not need to be conditional like the corresponding
        // function call in narrow.activate because search filters are
        // not common narrows
        if (search_string !== "") {
            $("#search_query").val(search_string + " ");
        }
    } else {
        // for "searching narrows", we redirect
        window.location.replace(filter.generate_redirect_url());
    }
};

exports.initialize = function () {
    exports.render_title_area();

    // register searchbar click handler
    $('#search_exit').on("click", function (e) {
        tab_bar.exit_search();
        e.preventDefault();
        e.stopPropagation();
    });
};

exports.render_title_area = function () {
    // TODO: Implement rerendering for stream privacy or subscriber
    // count changes. We simply need to call this function in the
    // appropriate places.
    const filter = narrow_state.filter();
    build_tab_bar(filter);
};

exports.open_search_bar_and_close_narrow_description = function () {
    $(".navbar-search").addClass("expanded");
    $("#tab_list").addClass("hidden");
};

exports.close_search_bar_and_open_narrow_description = function () {
    const filter = narrow_state.filter();
    if (!(filter && !filter.is_common_narrow())) {
        $(".navbar-search").removeClass("expanded");
        $("#tab_list").removeClass("hidden");
    }
};

window.tab_bar = exports;
<|endoftext|>


<commit_before>'use strict'

global.DEFAULT_PRIORITY = global.PRIORITIES_DEFAULT || 6
const MAX_PRIORITY = 16
const MAX_PID = 9999999
const WALL = 9

class Scheduler {
  constructor () {
    if (!Memory.qos.scheduler) {
      Memory.qos.scheduler = {}
    }
    this.memory = Memory.qos.scheduler

    this.processCache = {}
    if (!this.memory.processes) {
      this.memory.processes = {
        'index': {},
        'running': false,
        'completed': [],
        'queues': {}
      }
    }
  }

  shift () {
    // Promote processes that did not run.
    for (let x = 0; x <= MAX_PRIORITY; x++) {
      // If we're at the lowest priority merge it with the next priority rather than replacing it, so no pids are lost.
      if (x === 0 || x === WALL) {
        if (!this.memory.processes.queues[x]) {
          this.memory.processes.queues[x] = []
        }
        if (this.memory.processes.queues[x + 1]) {
          this.memory.processes.queues[x] = this.memory.processes.queues[x].concat(this.memory.processes.queues[x + 1])
        }
        continue
      }

      // Don't merge priorities from above the wall to below it.
      if ((x + 1) === WALL) {
        continue
      }

      // If the last tick did not hit the wall then do not promote "above the wall" processes.
      if (x >= WALL && this.memory.processes.hitwall) {
        break
      }

      // Replace the current priority queue with the one above it, or reset this one if there is none.
      if (this.memory.processes.queues[x + 1]) {
        this.memory.processes.queues[x] = this.memory.processes.queues[x + 1]
        this.memory.processes.queues[x + 1] = []
      } else {
        this.memory.processes.queues[x] = []
      }
    }

    // Add processes that did run back into the system, including any "running" scripts that never completed
    if (this.memory.processes.running) {
      this.memory.processes.completed.push(this.memory.processes.running)
      this.memory.processes.running = false
    }

    // Randomize order of completed processes before reinserting them to
    // * prevent error prone combinations (such as two really high processes running back to back) from recurring,
    // * keep specific processes from being favored by the scheduler.
    const completed = _.shuffle(_.uniq(this.memory.processes.completed))
    let pid
    for (pid of completed) {
      // If process is dead do not merge it back into the queue system.
      if (!this.memory.processes.index[pid]) {
        continue
      }
      try {
        const priority = this.getPriorityForPid(pid)
        this.memory.processes.queues[priority].push(pid)
      } catch (err) {
        delete this.memory.processes.index[pid]
        Logger.log(err, LOG_ERROR)
      }
    }
    this.memory.processes.hitwall = false
    this.memory.processes.completed = []
  }

  getNextProcess () {
    // Reset any "running" pids
    if (this.memory.processes.running) {
      this.memory.processes.completed.push(this.memory.processes.running)
      this.memory.processes.running = false
    }

    // Iterate through the queues until a pid is found.
    let x
    for (x = 0; x <= MAX_PRIORITY; x++) {
      if (x >= WALL) {
        this.memory.processes.hitwall = true
      }
      if (!this.memory.processes.queues[x] || this.memory.processes.queues[x].length <= 0) {
        continue
      }

      this.memory.processes.running = this.memory.processes.queues[x].shift()

      // Don't run this pid twice in a single tick.
      if (this.memory.processes.completed.includes(this.memory.processes.running)) {
        continue
      }

      // If process doesn't exist anymore don't use it.
      if (!this.memory.processes.index[this.memory.processes.running]) {
        continue
      }

      // If process has a parent and the parent has died kill the child process.
      if (this.memory.processes.index[this.memory.processes.running].p) {
        if (!this.isPidActive(this.memory.processes.index[this.memory.processes.running].p)) {
          this.kill(this.memory.processes.running)
          continue
        }
      }

      return this.getProcessForPid(this.memory.processes.running)
    }

    // Nothing was found
    return false
  }

  launchProcess (name, data = {}, parent = false) {
    const pid = this.getNextPid()
    this.memory.processes.index[pid] = {
      n: name,
      d: data,
      p: parent
    }
    const priority = this.getPriorityForPid(pid)
    if (!this.memory.processes.queues[priority]) {
      this.memory.processes.queues[priority] = []
    }
    this.memory.processes.queues[priority].push(pid)
    return pid
  }

  getNextPid () {
    if (!this.memory.lastPid) {
      this.memory.lastPid = 0
    }
    while (true) {
      this.memory.lastPid++
      if (this.memory.lastPid > MAX_PID) {
        this.memory.lastPid = 0
      }
      if (this.memory.processes.index[this.memory.lastPid]) {
        continue
      }
      return this.memory.lastPid
    }
  }

  isPidActive (pid) {
    return !!this.memory.processes.index[pid]
  }

  kill (pid) {
    if (this.memory.processes.index[pid]) {
      delete this.memory.processes.index[pid]
    }
  }

  sleep (pid, ticks) {
    if ((typeof ticks) == 'number' && this.memory.processes.index[pid]) {
      const priority = this.getPriorityForPid(pid)
      // Remove process from execution queue
      delete this.memory.processes.queues[priority][pid]

      // Create new sleep array if necessary
      if (!this.memory.processes.sleep) {
        this.memory.processes.sleep = []
      }
      // Add process to sleep list or update its unsleep time
      this.memory.processes.sleep[pid] = ticks
    }
  }

  unsleep (pid) {
    if (this.memory.processes.index[pid]) {
      const priority = this.getPriorityForPid(pid)
      // Push the process back to the execution queue
      this.memory.processes.queues[priority].push(pid)
      // and remove it from sleep list
      delete this.memory.processes.sleep[pid]
    }
  }

  getProcessCount () {
    return Object.keys(this.memory.processes.index).length
  }

  getCompletedProcessCount () {
    return this.memory.processes.completed.length
  }

  getPriorityForPid (pid) {
    const program = this.getProcessForPid(pid)
    if (!program.getPriority) {
      return DEFAULT_PRIORITY
    }
    const priority = program.getPriority()
    return priority < MAX_PRIORITY ? priority : MAX_PRIORITY
  }

  getProcessForPid (pid) {
    if (!this.processCache[pid]) {
      const ProgramClass = this.getProgramClass(this.memory.processes.index[pid].n)
      this.processCache[pid] = new ProgramClass(pid,
        this.memory.processes.index[pid].n,
        this.memory.processes.index[pid].d,
        this.memory.processes.index[pid].p
      )
    }
    return this.processCache[pid]
  }

  getProgramClass (program) {
    return require(`programs_${program}`)
  }
}

module.exports = Scheduler
<commit_msg>Updating of sleep queue in Scheduler constructor<commit_after>'use strict'

global.DEFAULT_PRIORITY = global.PRIORITIES_DEFAULT || 6
const MAX_PRIORITY = 16
const MAX_PID = 9999999
const WALL = 9

class Scheduler {
  constructor () {
    if (!Memory.qos.scheduler) {
      Memory.qos.scheduler = {}
    }
    this.memory = Memory.qos.scheduler

    this.processCache = {}
    if (!this.memory.processes) {
      this.memory.processes = {
        'index': {},
        'running': false,
        'completed': [],
        'queues': {}
      }
    }

    // Update sleep timers and resume right processes
    this.memory.processes.sleep = this.memory.processes.sleep.forEach(function(ticks, pid) {
      this.memory.processes.sleep[pid] -= 1
      if (this.memory.processes.sleep[pid] == 0)
        this.unsleep(pid)
    });

  }

  shift () {
    // Promote processes that did not run.
    for (let x = 0; x <= MAX_PRIORITY; x++) {
      // If we're at the lowest priority merge it with the next priority rather than replacing it, so no pids are lost.
      if (x === 0 || x === WALL) {
        if (!this.memory.processes.queues[x]) {
          this.memory.processes.queues[x] = []
        }
        if (this.memory.processes.queues[x + 1]) {
          this.memory.processes.queues[x] = this.memory.processes.queues[x].concat(this.memory.processes.queues[x + 1])
        }
        continue
      }

      // Don't merge priorities from above the wall to below it.
      if ((x + 1) === WALL) {
        continue
      }

      // If the last tick did not hit the wall then do not promote "above the wall" processes.
      if (x >= WALL && this.memory.processes.hitwall) {
        break
      }

      // Replace the current priority queue with the one above it, or reset this one if there is none.
      if (this.memory.processes.queues[x + 1]) {
        this.memory.processes.queues[x] = this.memory.processes.queues[x + 1]
        this.memory.processes.queues[x + 1] = []
      } else {
        this.memory.processes.queues[x] = []
      }
    }

    // Add processes that did run back into the system, including any "running" scripts that never completed
    if (this.memory.processes.running) {
      this.memory.processes.completed.push(this.memory.processes.running)
      this.memory.processes.running = false
    }

    // Randomize order of completed processes before reinserting them to
    // * prevent error prone combinations (such as two really high processes running back to back) from recurring,
    // * keep specific processes from being favored by the scheduler.
    const completed = _.shuffle(_.uniq(this.memory.processes.completed))
    let pid
    for (pid of completed) {
      // If process is dead do not merge it back into the queue system.
      if (!this.memory.processes.index[pid]) {
        continue
      }
      try {
        const priority = this.getPriorityForPid(pid)
        this.memory.processes.queues[priority].push(pid)
      } catch (err) {
        delete this.memory.processes.index[pid]
        Logger.log(err, LOG_ERROR)
      }
    }
    this.memory.processes.hitwall = false
    this.memory.processes.completed = []
  }

  getNextProcess () {
    // Reset any "running" pids
    if (this.memory.processes.running) {
      this.memory.processes.completed.push(this.memory.processes.running)
      this.memory.processes.running = false
    }

    // Iterate through the queues until a pid is found.
    let x
    for (x = 0; x <= MAX_PRIORITY; x++) {
      if (x >= WALL) {
        this.memory.processes.hitwall = true
      }
      if (!this.memory.processes.queues[x] || this.memory.processes.queues[x].length <= 0) {
        continue
      }

      this.memory.processes.running = this.memory.processes.queues[x].shift()

      // Don't run this pid twice in a single tick.
      if (this.memory.processes.completed.includes(this.memory.processes.running)) {
        continue
      }

      // If process doesn't exist anymore don't use it.
      if (!this.memory.processes.index[this.memory.processes.running]) {
        continue
      }

      // If process has a parent and the parent has died kill the child process.
      if (this.memory.processes.index[this.memory.processes.running].p) {
        if (!this.isPidActive(this.memory.processes.index[this.memory.processes.running].p)) {
          this.kill(this.memory.processes.running)
          continue
        }
      }

      return this.getProcessForPid(this.memory.processes.running)
    }

    // Nothing was found
    return false
  }

  launchProcess (name, data = {}, parent = false) {
    const pid = this.getNextPid()
    this.memory.processes.index[pid] = {
      n: name,
      d: data,
      p: parent
    }
    const priority = this.getPriorityForPid(pid)
    if (!this.memory.processes.queues[priority]) {
      this.memory.processes.queues[priority] = []
    }
    this.memory.processes.queues[priority].push(pid)
    return pid
  }

  getNextPid () {
    if (!this.memory.lastPid) {
      this.memory.lastPid = 0
    }
    while (true) {
      this.memory.lastPid++
      if (this.memory.lastPid > MAX_PID) {
        this.memory.lastPid = 0
      }
      if (this.memory.processes.index[this.memory.lastPid]) {
        continue
      }
      return this.memory.lastPid
    }
  }

  isPidActive (pid) {
    return !!this.memory.processes.index[pid]
  }

  kill (pid) {
    if (this.memory.processes.index[pid]) {
      delete this.memory.processes.index[pid]
    }
  }

  sleep (pid, ticks) {
    if ((typeof ticks) == 'number' && this.memory.processes.index[pid]) {
      const priority = this.getPriorityForPid(pid)
      // Remove process from execution queue
      delete this.memory.processes.queues[priority][pid]

      // Create new sleep array if necessary
      if (!this.memory.processes.sleep) {
        this.memory.processes.sleep = []
      }
      // Add process to sleep list or update its unsleep time
      this.memory.processes.sleep[pid] = ticks
    }
  }

  unsleep (pid) {
    if (this.memory.processes.index[pid]) {
      const priority = this.getPriorityForPid(pid)
      // Push the process back to the execution queue
      this.memory.processes.queues[priority].push(pid)
      // and remove it from sleep list
      delete this.memory.processes.sleep[pid]
    }
  }

  getProcessCount () {
    return Object.keys(this.memory.processes.index).length
  }

  getCompletedProcessCount () {
    return this.memory.processes.completed.length
  }

  getPriorityForPid (pid) {
    const program = this.getProcessForPid(pid)
    if (!program.getPriority) {
      return DEFAULT_PRIORITY
    }
    const priority = program.getPriority()
    return priority < MAX_PRIORITY ? priority : MAX_PRIORITY
  }

  getProcessForPid (pid) {
    if (!this.processCache[pid]) {
      const ProgramClass = this.getProgramClass(this.memory.processes.index[pid].n)
      this.processCache[pid] = new ProgramClass(pid,
        this.memory.processes.index[pid].n,
        this.memory.processes.index[pid].d,
        this.memory.processes.index[pid].p
      )
    }
    return this.processCache[pid]
  }

  getProgramClass (program) {
    return require(`programs_${program}`)
  }
}

module.exports = Scheduler
<|endoftext|>


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

exports.add_routes = function(webapp, velbus, config) {

function reply_to_get(req, res, next) {
	var addr = parseInt( req.params[0], 16 );
	var field = req.params[1];
	if( field != undefined ) {
		field = field.split('/');
		field.shift(); // remove empty first element
	} else {
		field = [];
	}

	// TODO: optimize based on field: we don't always need both requests

	// Set up listener for the answer
	var timeout;

	var total_msg = {};
	var send_answer = function() {
		clearTimeout(timeout);

		delete total_msg.data;

		for( var i = 0; i < field.length; i++ ) {
			if( ! total_msg.hasOwnProperty( field[i] ) ) {
				res.send("Unknown property", 404);
				return;
			}
			total_msg = total_msg[ field[i] ];
		}

		if( typeof total_msg == "string" ) {
			res.send(total_msg, {'Content-Type': 'text/plain'} );
		} else {
			res.send(total_msg);
		}
	};

	var waiting_for = { 'tss': 1, 'st': 1 };

	var save_tss = function(msg) {
		waiting_for['tss'] = 0;
		delete msg['current temperature']; // We want the precise one from sensorTemp
		for (var attr in msg) {
			total_msg[attr] = msg[attr];
		}
		if( waiting_for['st'] == 0 ) {
			// We're the last one
			send_answer();
		}
	};

	var save_st = function(msg) {
		waiting_for['st'] = 0;
		for (var attr in msg) {
			total_msg[attr] = msg[attr];
		}
		if( waiting_for['tss'] == 0 ) {
			// We're the last one
			send_answer();
		}
	};

	velbus.once('temp sensor status ' + addr, save_tss);
	velbus.once('sensor temperature ' + addr, save_st);

	timeout = setTimeout(function() {
			velbus.removeListener('temp sensor status ' + addr, save_tss);
			velbus.removeListener('sensor temperature ' + addr, save_st);
			res.send("Timeout", 500);
		}, config.webapp.timeout);

	// Now send the request
	velbus.send_message(3, addr, 0, "\xfa\x00" ); // status
	velbus.send_message(3, addr, 0, "\xe5\x00" ); // high precision temp
}

webapp.get(/\/control\/temp\/([0-9a-fA-F]{2})((?:\/(?:[a-zA-Z]|%20)+)*)\/?$/, reply_to_get);

}
<commit_msg>Optimized temperature readout<commit_after>var util = require('util');

exports.add_routes = function(webapp, velbus, config) {

function reply_to_get(req, res, next) {
	var addr = parseInt( req.params[0], 16 );
	var field = req.params[1];
	if( field != undefined ) {
		field = field.split('/');
		field.shift(); // remove empty first element
	} else {
		field = [];
	}

	var need_cmd = {
		'temperature sensor status': 1,
		'sensor temperature': 1
	};
	if( field.length > 0 ) {
		// We might be able to optimize what we need
		if( field[0] == 'current temperature' ||
		    field[0] == 'minimum temperature' ||
		    field[0] == 'maximum temperature' ) {
			// we only need sensorTemp
			need_cmd['temperature sensor status'] = 0;
		} else {
			// we only need temperature sensor status
			need_cmd['sensor temperature'] = 0;
		}
	}

	// Set up listener for the answer
	var timeout;

	var total_msg = {};
	var send_answer = function() {
		// Are we still waiting for someone?
		var waiting = 0;
		for( var i in need_cmd ) { waiting += need_cmd[i]; }
		if( waiting > 0 ) {
			return; // And wait for the next one to call us
		}

		clearTimeout(timeout);

		delete total_msg.data;

		// Descend into requested property
		for( var i = 0; i < field.length; i++ ) {
			if( ! total_msg.hasOwnProperty( field[i] ) ) {
				res.send("Unknown property", 404);
				return;
			}
			total_msg = total_msg[ field[i] ];
		}

		if( typeof total_msg == "object" ) {
			res.send(total_msg);
		} else {
			res.send('' + total_msg, {'Content-Type': 'text/plain'} );
		}
	};

	var save_tss = function(msg) {
		need_cmd['temperature sensor status'] = 0;
		delete msg['current temperature']; // We want the precise one from sensorTemp
		for (var attr in msg) {
			total_msg[attr] = msg[attr];
		}
		send_answer();
	};

	var save_st = function(msg) {
		need_cmd['sensor temperature'] = 0;
		for (var attr in msg) {
			total_msg[attr] = msg[attr];
		}
		send_answer();
	};

	if( need_cmd['temperature sensor status'] ) {
		velbus.once('temp sensor status ' + addr, save_tss);
	}
	if( need_cmd['sensor temperature'] ) {
		velbus.once('sensor temperature ' + addr, save_st);
	}

	timeout = setTimeout(function() {
			velbus.removeListener('temp sensor status ' + addr, save_tss);
			velbus.removeListener('sensor temperature ' + addr, save_st);
			res.send("Timeout", 500);
		}, config.webapp.timeout);

	// Now send the request
	if( need_cmd['temperature sensor status'] ) {
		velbus.send_message(3, addr, 0, "\xfa\x00" );
	}
	if( need_cmd['sensor temperature'] ) {
		velbus.send_message(3, addr, 0, "\xe5\x00" ); // high precision temp
	}
}

webapp.get(/\/control\/temp\/([0-9a-fA-F]{2})((?:\/(?:[a-zA-Z]|%20)+)*)\/?$/, reply_to_get);

}
<|endoftext|>


<commit_before>function getUrl(properties, layer) {
    return window.SETTINGS.urls.detail.replace(new RegExp('modelname', 'g'), data.modelname)
        .replace('0', properties.pk);
}

function ChangeColors(e, data) {
    if (data.modelname != 'report')
        return;
    var map = data.map;
    var dt = MapEntity.mainDatatable;

    // For each report status
    var status_ids_and_colors = JSON.parse($('#status_ids_and_colors').text());
    for (var status in status_ids_and_colors) {
        status_id = status_ids_and_colors[status]["id"]
        status_color = status_ids_and_colors[status]["color"]
        // Use this status' color...
        L.Util.extend(
            window.SETTINGS.map.styles["report-" + status_id] = { 'weight': 5, 'color': status_color, 'opacity': 0.9, fillOpacity: 0.9 },
        );
        // ... in creating layer with reports that have this status
        var layer = new L.ObjectsLayer(null, {
            modelname: "report",
            objectUrl: getUrl,
            style: { color: status_color },
            onEachFeature: function (geojson, layer) {
                if (geojson.properties.name) layer.bindLabel(geojson.properties.name);
            }
        });
        layer.load("/api/report/report.geojson?_status_id=" + status_id)
        map.addLayer(layer)

        // Sync layer to map to preserve classic layer functionnalities
        var mapsync = new L.MapListSync(dt,
            map,
            layer, {
            filter: {
                form: $('#mainfilter'),
                submitbutton: $('#filter'),
                resetbutton: $('#reset'),
                bboxfield: $('#id_bbox'),
            }
        });
        mapsync.on('reloaded', function (data) {
            MapEntity.history.saveListInfo({
                model: data.modelname,
                nb: data.nbrecords
            });
            layer.fire('info', { info: (data.nbrecords + ' ' + tr("results")) });
        });

    }
}

function ChangeColor(e, data) {
    if (data.modelname != 'report')
        return;
    var map = data.map;
    map.eachLayer(function (layer) {
        if (layer.options['modelname'] === "report") {
            layer.setStyle({ color: $("#report_color").text() })
        }
    }
    )
}

function InitReportStatusLegend(e, data) {
    if (data.modelname != 'report')
        return;
    var map = data.map;

    var legend = L.control({ position: 'bottomleft' });
    legend.onAdd = function (map) {

        var div = L.DomUtil.create('div', 'legend-statuses');
        header = ['' + tr("Statuses") + '']
        inner = []
        // For each report status
        var status_ids_and_colors = JSON.parse($('#status_ids_and_colors').text());
        for (var status in status_ids_and_colors) {
            status_label = status_ids_and_colors[status]["label"]
            status_color = status_ids_and_colors[status]["color"]
            status_id = status_ids_and_colors[status]["identifier"]
            div.innerHTML +=
                inner.push(
                    ' ' + status_label
                );
        }
        div.innerHTML = header
        div.innerHTML += inner.join('
');
        return div;
    };
    legend.addTo(map);

    $(".legend-statuses")[0].style.display = 'none'; // init as hidden, use selector in controls overlay to display

    var LegendLayer = L.Class.extend({
        onAdd: function (map) {
            $(".legend-statuses").toggle();
        },
        onRemove: function (map) {
            $(".legend-statuses").toggle();
        },
    });
    control = new LegendLayer()
    map.layerscontrol.addOverlay(control, tr("Legend"));
    map.addLayer(control); //init as visible
}

$(window).on('entity:map:detail', ChangeColor);
$(window).on('entity:map:list', ChangeColors);
$(window).on('entity:map:list', InitReportStatusLegend);
<commit_msg>Remove yellow reports layer<commit_after>function getUrl(properties, layer) {
    return window.SETTINGS.urls.detail.replace(new RegExp('modelname', 'g'), data.modelname)
        .replace('0', properties.pk);
}

function ChangeColors(e, data) {
    if (data.modelname != 'report')
        return;
    var map = data.map;
    var dt = MapEntity.mainDatatable;
    map.eachLayer(function (layer) {
        if (layer.options != 'undefined' && layer.options.modelname === 'report') {
            map.removeLayer(layer);
        }
    });
    // For each report status
    var status_ids_and_colors = JSON.parse($('#status_ids_and_colors').text());
    for (var status in status_ids_and_colors) {
        status_id = status_ids_and_colors[status]["id"]
        status_color = status_ids_and_colors[status]["color"]
        // Use this status' color...
        L.Util.extend(
            window.SETTINGS.map.styles["report-" + status_id] = { 'weight': 5, 'color': status_color, 'opacity': 0.9, fillOpacity: 0.9 },
        );
        // ... in creating layer with reports that have this status
        var layer = new L.ObjectsLayer(null, {
            modelname: "report",
            objectUrl: getUrl,
            style: { color: status_color },
            onEachFeature: function (geojson, layer) {
                if (geojson.properties.name) layer.bindLabel(geojson.properties.name);
            }
        });
        layer.load("/api/report/report.geojson?_status_id=" + status_id)
        map.addLayer(layer)

        // Sync layer to map to preserve classic layer functionnalities
        var mapsync = new L.MapListSync(dt,
            map,
            layer, {
            filter: {
                form: $('#mainfilter'),
                submitbutton: $('#filter'),
                resetbutton: $('#reset'),
                bboxfield: $('#id_bbox'),
            }
        });
        mapsync.on('reloaded', function (data) {
            MapEntity.history.saveListInfo({
                model: data.modelname,
                nb: data.nbrecords
            });
            layer.fire('info', { info: (data.nbrecords + ' ' + tr("results")) });
        });

    }
}

function ChangeColor(e, data) {
    if (data.modelname != 'report')
        return;
    var map = data.map;
    map.eachLayer(function (layer) {
        if (layer.options['modelname'] === "report") {
            layer.setStyle({ color: $("#report_color").text() })
        }
    }
    )
}

function InitReportStatusLegend(e, data) {
    if (data.modelname != 'report')
        return;
    var map = data.map;

    var legend = L.control({ position: 'bottomleft' });
    legend.onAdd = function (map) {

        var div = L.DomUtil.create('div', 'legend-statuses');
        header = ['' + tr("Statuses") + '']
        inner = []
        // For each report status
        var status_ids_and_colors = JSON.parse($('#status_ids_and_colors').text());
        for (var status in status_ids_and_colors) {
            status_label = status_ids_and_colors[status]["label"]
            status_color = status_ids_and_colors[status]["color"]
            status_id = status_ids_and_colors[status]["identifier"]
            div.innerHTML +=
                inner.push(
                    ' ' + status_label
                );
        }
        div.innerHTML = header
        div.innerHTML += inner.join('
');
        return div;
    };
    legend.addTo(map);

    $(".legend-statuses")[0].style.display = 'none'; // init as hidden, use selector in controls overlay to display

    var LegendLayer = L.Class.extend({
        onAdd: function (map) {
            $(".legend-statuses").toggle();
        },
        onRemove: function (map) {
            $(".legend-statuses").toggle();
        },
    });
    control = new LegendLayer()
    map.layerscontrol.addOverlay(control, tr("Legend"));
    map.addLayer(control); //init as visible
}

$(window).on('entity:map:detail', ChangeColor);
$(window).on('entity:map:list', ChangeColors);
$(window).on('entity:map:list', InitReportStatusLegend);
<|endoftext|>


<commit_before>'use strict'

let io = null

let connectionInfo

const MIN_WAIT = 100
let wait = MIN_WAIT

let states = []

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        if (connectionInfo) {
            connectionInfo.visible = false
        }
    };

    io.onmessage = (msg) => {
        states.push(JSON.parse(msg.data, (key, value) => value === "" ? 0 : value))
        const state = states[states.length - 1]
        while (players.length < state.players.length) {
            spawnPlayer()
        }

        while (players.length > state.players.length) {
            removePlayer()
        }
    }

    io.onclose = () => {
        if (connectionInfo) {
            connectionInfo.text = 'Reconnecting...'
            connectionInfo.visible = true
        }
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const app = new Application(
    1000, 1000, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
connect(addr)
document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball
let players = []
let texturesReady = false
function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = 1000
    background.height = 1000
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = 40
    app.stage.addChild(connectionInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    texturesReady = true
    app.ticker.add(gameLoop)
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    states.splice(0, index)
    let interpolatedState = getInterpolatedState(states[0], states[1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime() {
    const now = new Date().getTime()
    const INTERPOLATION_DELTA = 100
    return now - INTERPOLATION_DELTA
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from

    state.ball.vel.x += (to.ball.vel.x - from.ball.vel.x) * fraction
    state.ball.vel.y += (to.ball.vel.y - from.ball.vel.y) * fraction
    state.ball.pos.x += (to.ball.pos.x - from.ball.pos.x) * fraction
    state.ball.pos.y += (to.ball.pos.y - from.ball.pos.y) * fraction

    for (let i = 0; i < players.length; i++) {
        if (i >= state.players.length || i >= to.players.length)
            continue;
        state.players[i].acc.x += (to.players[i].acc.x - from.players[i].acc.x) * fraction
        state.players[i].acc.y += (to.players[i].acc.y - from.players[i].acc.y) * fraction
        state.players[i].vel.x += (to.players[i].vel.x - from.players[i].vel.x) * fraction
        state.players[i].vel.y += (to.players[i].vel.y - from.players[i].vel.y) * fraction
        state.players[i].pos.x += (to.players[i].pos.x - from.players[i].pos.x) * fraction
        state.players[i].pos.y += (to.players[i].pos.y - from.players[i].pos.y) * fraction
    }

    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.ball.pos.x
    ball.y = state.ball.pos.y

    addBlur(ball, state.ball.vel)
    for (let i = 0; i < players.length; i++) {
        if (i >= state.players.length)
            continue;
        players[i].x = state.players[i].pos.x
        players[i].y = state.players[i].pos.y
        addBlur(players[i], state.players[i].vel)
    }
}

function addBlur(obj, vel) {
    const maxVel = Math.max(vel.x, vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        obj.filters = [blurFilter]
    }
}

function spawnPlayer() {
    if (!texturesReady)
        return
    const player = new Sprite(resources.pong.textures['fancy-paddle-green.png'])
    player.anchor.set(0.5)
    app.stage.addChild(player)
    players.push(player)
}

function removePlayer() {
    if (!app)
        return
    const player = players[players.length - 1]
    app.stage.removeChild(player)
    players.pop()
}<commit_msg>Client: Fix crashes<commit_after>'use strict'

let io = null
let connectionInfo
let states = []
function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        connectionInfo.visible = false
    };

    io.onmessage = (msg) => {
        states.push(JSON.parse(msg.data, (key, value) => value === "" ? 0 : value))
        const state = states[states.length - 1]
        const currPlayers = {}
        for (let player of state.players) {
            currPlayers[player.id] = player
        }
        const lastIds = Object.keys(players)
        const currIds = Object.keys(currPlayers)
        for (let id of currIds)
            if (lastIds.indexOf(id) === -1)
                spawnPlayer(id)
        for (let id of lastIds)
            if (currIds.indexOf(id) === -1)
                removePlayer(id)
    }

    io.onclose = () => {
        connectionInfo.text = 'Reconnecting...'
        connectionInfo.visible = true
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

const MIN_WAIT = 100
let wait = MIN_WAIT
function resetWait() {
    wait = MIN_WAIT
}
function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const app = new Application(
    1000, 1000, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball
let players = {}
function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = 1000
    background.height = 1000
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = 40
    app.stage.addChild(connectionInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
    connect(addr)
    app.ticker.add(gameLoop)
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    states.splice(0, index)
    let interpolatedState = getInterpolatedState(states[0], states[1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime() {
    const now = new Date().getTime()
    const INTERPOLATION_DELTA = 100
    return now - INTERPOLATION_DELTA
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from

    state.ball.vel.x += (to.ball.vel.x - from.ball.vel.x) * fraction
    state.ball.vel.y += (to.ball.vel.y - from.ball.vel.y) * fraction
    state.ball.pos.x += (to.ball.pos.x - from.ball.pos.x) * fraction
    state.ball.pos.y += (to.ball.pos.y - from.ball.pos.y) * fraction

    for (let id of Object.keys(state.players)) {
        if (!to.players[id] || !from.players[id])
            continue
        state.players[id].vel.x += (to.players[id].vel.x - from.players[id].vel.x) * fraction
        state.players[id].vel.y += (to.players[id].vel.y - from.players[id].vel.y) * fraction
        state.players[id].pos.x += (to.players[id].pos.x - from.players[id].pos.x) * fraction
        state.players[id].pos.y += (to.players[id].pos.y - from.players[id].pos.y) * fraction
    }

    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.ball.pos.x
    ball.y = state.ball.pos.y

    addBlur(ball, state.ball.vel)
    for (let id of Object.keys(players)) {
        if (!state.players[id])
            continue;
        players[id].x = state.players[id].pos.x
        players[id].y = state.players[id].pos.y
        addBlur(players[id], state.players[id].vel)
    }
}

function addBlur(obj, vel) {
    const maxVel = Math.max(vel.x, vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        obj.filters = [blurFilter]
    }
}

function spawnPlayer(id) {
    const player = new Sprite(resources.pong.textures['fancy-paddle-green.png'])
    player.anchor.set(0.5)
    app.stage.addChild(player)
    players[id] = player
}

function removePlayer(id) {
    const player = players[id]
    app.stage.removeChild(player)
    delete players[id]
}<|endoftext|>


<commit_before>'use strict'

let io = null
let connectionInfo
let pingInfo
let states = []

let ping

function setPingInfo(ping) {
    pingInfo.text = 'Ping: ' + ping
    let fill
    if (ping < 100)
        fill = 0x57fc20
    else if (ping < 200)
        fill = 0xfc9520
    else
        fill = 0xef1c2a
    pingInfo.style.fill = fill
}

class Ping {
    constructor() {
        this.average = 0
        this.pings = [0]
        setInterval(() => this.calcAverage(), 1000)
    }

    add(origTimestamp) {
        const ping = Date.now() - origTimestamp
        if (ping < 0)
            console.error('Ping cannot be negative, received timestamp from future')
        this.pings.push(ping)
        const buffer = 200
        if (this.pings.length > buffer)
            this.pings.splice(0, this.pings.length - buffer)
    }

    calcAverage() {
        if (!this.pings)
            return
        const sum = this.pings.reduce((a, b) => a + b, 0)
        this.average = (sum / this.pings.length) | 0
        setPingInfo(this.average)
    }
}

function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        connectionInfo.visible = false
    };

    io.onmessage = (msg) => {
        const state = JSON.parse(msg.data, (key, value) => value === "" ? 0 : value)
        ping.add(state.timestamp)
        states.push(state)
        // Todo: Exchange for real queue library
        setTimeout(states.shift, 100)
        const lastIds = Object.keys(players)
        const currIds = Object.keys(state.players)
        // Todo: Optimize this algorithm
        for (let id of currIds)
            if (lastIds.indexOf(id) === -1)
                spawnPlayer(id)
        for (let id of lastIds)
            if (currIds.indexOf(id) === -1)
                removePlayer(id)
    }

    io.onclose = () => {
        connectionInfo.text = 'Reconnecting...'
        connectionInfo.visible = true
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

const MIN_WAIT = 100
let wait = MIN_WAIT

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const GAME_WIDTH = 1000
const GAME_HEIGHT = 1000
const app = new Application(
    screen.availWidth, screen.availHeight, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball
let players = {}

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = GAME_WIDTH
    background.height = GAME_HEIGHT
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = GAME_WIDTH - 200
    app.stage.addChild(connectionInfo)

    pingInfo = new PIXI.Text('Ping: Calculating...')
    pingInfo.style.fill = 0xe3e3ed
    pingInfo.style.dropShadow = true
    pingInfo.style.dropShadowAlpha = 0.7
    pingInfo.y = 30
    pingInfo.x = 40
    app.stage.addChild(pingInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    resize()
    window.addEventListener('resize', resize);

    const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
    ping = new Ping()
    connect(addr)
    app.ticker.add(gameLoop)
}

function resize() {
    let ratio = window.innerWidth / GAME_WIDTH
    if (GAME_HEIGHT * ratio > window.innerHeight)
        ratio = window.innerHeight / GAME_HEIGHT
    app.width = app.stage.width = GAME_WIDTH * ratio
    app.height = app.stage.height = GAME_HEIGHT * ratio
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime()
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    let interpolatedState = getInterpolatedState(states[index], states[index + 1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime() {
    const now = new Date().getTime()
    const minDelay = 100
    const maxDelay = 300
    const delay = Math.max(minDelay, Math.min(maxDelay, ping.average))
    return now - delay
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from

    state.ball.vel.x += (to.ball.vel.x - from.ball.vel.x) * fraction
    state.ball.vel.y += (to.ball.vel.y - from.ball.vel.y) * fraction
    state.ball.pos.x += (to.ball.pos.x - from.ball.pos.x) * fraction
    state.ball.pos.y += (to.ball.pos.y - from.ball.pos.y) * fraction

    for (let id of Object.keys(state.players)) {
        if (!to.players[id] || !from.players[id])
            continue
        state.players[id].vel.x += (to.players[id].vel.x - from.players[id].vel.x) * fraction
        state.players[id].vel.y += (to.players[id].vel.y - from.players[id].vel.y) * fraction
        state.players[id].pos.x += (to.players[id].pos.x - from.players[id].pos.x) * fraction
        state.players[id].pos.y += (to.players[id].pos.y - from.players[id].pos.y) * fraction
    }

    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.ball.pos.x
    ball.y = state.ball.pos.y

    addBlur(ball, state.ball.vel)
    for (let id of Object.keys(players)) {
        if (!state.players[id])
            continue;
        players[id].x = state.players[id].pos.x
        players[id].y = state.players[id].pos.y
        addBlur(players[id], state.players[id].vel)
    }
}

function addBlur(obj, vel) {
    const maxVel = Math.max(vel.x, vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        obj.filters = [blurFilter]
    }
}

function spawnPlayer(id) {
    const player = new Sprite(resources.pong.textures['fancy-paddle-green.png'])
    player.anchor.set(0.5)
    app.stage.addChild(player)
    players[id] = player
}

function removePlayer(id) {
    const player = players[id]
    app.stage.removeChild(player)
    delete players[id]
}<commit_msg>Web: Update to new protocol<commit_after>'use strict'

let io = null
let connectionInfo
let pingInfo
let states = []

let ping

function setPingInfo(ping) {
    pingInfo.text = 'Ping: ' + ping
    let fill
    if (ping < 100)
        fill = 0x57fc20
    else if (ping < 200)
        fill = 0xfc9520
    else
        fill = 0xef1c2a
    pingInfo.style.fill = fill
}

function connect(address) {
    io = new WebSocket(address)
    io.onopen = () => {
        resetWait()
        connectionInfo.visible = false
    };

    io.onmessage = (msg) => {
        const state = JSON.parse(msg.data, (key, value) => value === "" ? 0 : value)
        state.timestamp = Date.now()
        states.push(state)
        // Todo: Exchange for real queue library
        setTimeout(states.shift, 100)
        const lastIds = Object.keys(players)
        const currIds = Object.keys(state.players)
        // Todo: Optimize this algorithm
        for (let id of currIds)
            if (lastIds.indexOf(id) === -1)
                spawnPlayer(id)
        for (let id of lastIds)
            if (currIds.indexOf(id) === -1)
                removePlayer(id)
    }

    io.onclose = () => {
        connectionInfo.text = 'Reconnecting...'
        connectionInfo.visible = true
        io = null
        setTimeout(() => {
            incrementWait()
            connect(address);
        }, wait)
    };

    io.onerror = (err) => {
        console.error('Socket encountered error: ', err.message, 'Closing socket')
        io.close()
        io = null
    };
}

const MIN_WAIT = 100
let wait = MIN_WAIT

function resetWait() {
    wait = MIN_WAIT
}

function incrementWait() {
    const MAX_WAIT = 2000
    if (wait < MAX_WAIT)
        wait *= 1.25
    else
        wait = MAX_WAIT
}

function send(data) {
    if (io && io.readyState === 1) {
        console.log('sending data: ', data)
        io.send(JSON.stringify(data))
    }
}

const Application = PIXI.Application,
    loader = PIXI.loader,
    resources = PIXI.loader.resources,
    Sprite = PIXI.Sprite

const GAME_WIDTH = 1000
const GAME_HEIGHT = 1000
const app = new Application(
    screen.availWidth, screen.availHeight, {
        backgroundColor: 0xFFFFFF,
        antialias: true,
    },
)

document.body.appendChild(app.view)

loader
    .add([{
        name: 'pong',
        url: 'assets/pong.json'
    }, ])
    .on('progress', loadProgressHandler)
    .load(setup)

let validKeys = ["ArrowUp", "ArrowDown"]
let keyPressed = {
    ArrowLeft: false,
    ArrowRight: false
}
document.addEventListener("keydown", (event) => {
    sendKeyWithVal(event.key, true)
})
document.addEventListener("keyup", (event) => {
    sendKeyWithVal(event.key, false)
})

function sendKeyWithVal(key, val) {
    if (validKeys.indexOf(key) > -1) {
        sendIfNew(key, val)
    }
}

let msgId = 0

function sendIfNew(key, val) {
    if (keyPressed[key] !== val) {
        keyPressed[key] = val
        let msg = {
            id: msgId,
            key: key,
            pressed: val
        }
        msgId++
        send(msg)
    }
}

function loadProgressHandler(loader, resource) {
    console.log('loading: ' + resource.name + ' (' + resource.url + ')')
    console.log('progress: ' + loader.progress + '%')
    if (resource.error)
        console.error(resource.error)
}

let ball
let players = {}

function setup() {
    const background = new Sprite(resources.pong.textures['fancy-court.png'])
    background.width = GAME_WIDTH
    background.height = GAME_HEIGHT
    app.stage.addChild(background)

    connectionInfo = new PIXI.Text('')
    connectionInfo.style.fill = 0xe3e3ed
    connectionInfo.style.dropShadow = true
    connectionInfo.style.dropShadowAlpha = 0.7
    connectionInfo.y = 30
    connectionInfo.x = GAME_WIDTH - 200
    app.stage.addChild(connectionInfo)

    pingInfo = new PIXI.Text('Ping: Calculating...')
    pingInfo.style.fill = 0xe3e3ed
    pingInfo.style.dropShadow = true
    pingInfo.style.dropShadowAlpha = 0.7
    pingInfo.y = 30
    pingInfo.x = 40
    app.stage.addChild(pingInfo)

    ball = new Sprite(resources.pong.textures['fancy-ball.png'])
    ball.anchor.set(0.5)
    app.stage.addChild(ball)

    resize()
    window.addEventListener('resize', resize);

    setInterval(() => setPingInfo(getOwnPing()))
    const addr = window.location.hostname === 'localhost' ? 'ws://localhost:8081' : 'wss://beta.jnferner.com/socket'
    connect(addr)
    app.ticker.add(gameLoop)
}

function getOwnPing() {
    return states.length > 0 ? states[states.length - 1].players[0] : 0
}

function resize() {
    let ratio = window.innerWidth / GAME_WIDTH
    if (GAME_HEIGHT * ratio > window.innerHeight)
        ratio = window.innerHeight / GAME_HEIGHT
    app.width = app.stage.width = GAME_WIDTH * ratio
    app.height = app.stage.height = GAME_HEIGHT * ratio
}

let state = connecting

function gameLoop(delta) {
    state()
}

function connecting() {
    connectionInfo.text = 'Connecting...'
    connectionInfo.visible = true

    const renderTime = getRenderTime(getOwnPing())
    const index = getIndexOfRenderState(states, renderTime)
    if (index >= 0) {
        connectionInfo.visible = false
        state = play
    }
}

function play() {
    render(states)
}

function render(states) {
    const renderTime = getRenderTime(getOwnPing())
    const index = getIndexOfRenderState(states, renderTime)
    if (index < 0)
        return
    let interpolatedState = getInterpolatedState(states[index], states[index + 1], renderTime)
    setWorld(interpolatedState)
}

function getRenderTime(ping) {
    const now = new Date().getTime()
    const minDelay = 100
    const maxDelay = 300
    const delay = Math.max(minDelay, Math.min(maxDelay, ping))
    return now - delay
}

function getIndexOfRenderState(states, renderTime) {
    const found = states.findIndex((state) => state.timestamp >= renderTime)
    return found - 1
}

function getInterpolatedState(from, to, renderTime) {
    const total = to.timestamp - from.timestamp
    const progress = renderTime - from.timestamp
    if (total === 0 || progress === 0)
        return from
    const fraction = progress / total
    let state = from

    state.ball.vel.x += (to.ball.vel.x - from.ball.vel.x) * fraction
    state.ball.vel.y += (to.ball.vel.y - from.ball.vel.y) * fraction
    state.ball.pos.x += (to.ball.pos.x - from.ball.pos.x) * fraction
    state.ball.pos.y += (to.ball.pos.y - from.ball.pos.y) * fraction

    for (let id of Object.keys(state.players)) {
        if (!to.players[id] || !from.players[id])
            continue
        state.players[id].vel.x += (to.players[id].vel.x - from.players[id].vel.x) * fraction
        state.players[id].vel.y += (to.players[id].vel.y - from.players[id].vel.y) * fraction
        state.players[id].pos.x += (to.players[id].pos.x - from.players[id].pos.x) * fraction
        state.players[id].pos.y += (to.players[id].pos.y - from.players[id].pos.y) * fraction
    }

    state.timestamp = renderTime
    return state
}

function setWorld(state) {
    ball.x = state.ball.pos.x
    ball.y = state.ball.pos.y

    addBlur(ball, state.ball.vel)
    for (let id of Object.keys(players)) {
        if (!state.players[id])
            continue;
        players[id].x = state.players[id].pos.x
        players[id].y = state.players[id].pos.y
        addBlur(players[id], state.players[id].vel)
    }
}

function addBlur(obj, vel) {
    const maxVel = Math.max(vel.x, vel.y)
    const strength = Math.pow(Math.atan(Math.pow((maxVel / 10), 1.5)), 2) - 0.2
    if (strength > 0) {
        const blurFilter = new PIXI.filters.BlurFilter(strength, 1, 1)
        obj.filters = [blurFilter]
    }
}

function spawnPlayer(id) {
    const player = new Sprite(resources.pong.textures['fancy-paddle-green.png'])
    player.anchor.set(0.5)
    app.stage.addChild(player)
    players[id] = player
}

function removePlayer(id) {
    const player = players[id]
    app.stage.removeChild(player)
    delete players[id]
}<|endoftext|>