<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
'use strict';

var captureHost = require('../util.js').captureHost;
var errors = require('../errors.js');
var globalTimers = require('timers');
var isEmptyArray = require('../util.js').isEmptyArray;
var mergeJoinResponses = require('./join-response-merge.js');
var numOrDefault = require('../util.js').numOrDefault;
var safeParse = require('../util.js').safeParse;
var TypedError = require('error/typed');

var JoinAbortedError = TypedError({
    type: 'ringpop.join-aborted',
    message: 'Join aborted because `{reason}`',
    reason: null
});

var JoinDurationExceededError = TypedError({
    type: 'ringpop.join-duration-exceeded',
    message: 'Join duration of `{duration}` exceeded max `{max}`',
    duration: null,
    max: null
});

var JOIN_SIZE = 3;
var JOIN_TIMEOUT = 1000;
var PARALLELISM_FACTOR = 2;

function isSingleNodeCluster(ringpop) {
    return Array.isArray(ringpop.bootstrapHosts) &&
        ringpop.bootstrapHosts.length === 1 &&
        ringpop.bootstrapHosts[0] === ringpop.hostPort;
}

// Note that this function mutates the array passed in.
function takeNode(hosts) {
    var index = Math.floor(Math.random() * hosts.length);
    var host = hosts[index];
    hosts.splice(index, 1);
    return host;
}

function Joiner(opts) {
    opts = opts || {};

    if (!opts.ringpop) {
        throw errors.OptionRequiredError({
            context: 'ringpop',
            option: 'ringpop'
        });
    }

    if (isEmptyArray(opts.ringpop.bootstrapHosts)) {
        throw errors.InvalidOptionError({
            option: 'ringpop',
            reason: '`bootstrapHosts` is expected to be an array of size 1 or more'
        });
    }

    this.ringpop = opts.ringpop;
    this.timers = opts.timers || globalTimers;
    this.host = captureHost(this.ringpop.hostPort);
    this.joinTimeout = numOrDefault(opts.joinTimeout, JOIN_TIMEOUT);

    // This is used as a multiple of the required nodes left
    // to join to satisfy `joinSize`. Additional parallelism
    // can be applied in order for `joinSize` to be satisified
    // faster.
    this.parallelismFactor = numOrDefault(opts.parallelismFactor,
        PARALLELISM_FACTOR);

    // Potential nodes are nodes in the ringpop bootstrap
    // list that can be joined. Upon instantiation, this step
    // simply filters out a node from attempting to join itself.
    this.potentialNodes = this.collectPotentialNodes();
    this.preferredNodes = null;
    this.nonPreferredNodes = null;

    // We either join the number of nodes defined by `joinSize`
    // or limit it to the number of `potentialNodes`. After all,
    // we can't join more than there are to join in the first place.
    var joinSize = numOrDefault(opts.joinSize, JOIN_SIZE);
    this.joinSize = Math.min(joinSize, this.potentialNodes.length);

    // A round is defined as a complete cycle through all
    // potential join targets. Once a round is completed,
    // we start all over again. A full-cycle should be pretty
    // darned rare. We will try and try to join other nodes
    // until `joinSize` is reached or `maxJoinDuration` is
    // exceeded.
    this.roundPotentialNodes = null;
    this.roundPreferredNodes = null;

    // Changes received by other nodes will be aggregated and
    // applied once the join process is complete.
    this.joinResponses = [];

    this.joinDelay = this.ringpop.config.get('joinDelayMin');
    this.joinRetries = 0;
}

// Potential nodes are those that are not this instance of ringpop.
Joiner.prototype.collectPotentialNodes = function collectPotentialNodes(nodesJoined) {
    nodesJoined = nodesJoined || [];

    var self = this;
    return this.ringpop.bootstrapHosts.filter(function filterHost(hostPort) {
        return self.ringpop.hostPort !== hostPort && nodesJoined.indexOf(hostPort) === -1;
    });
};

// Preferred nodes are those that are not on the same host as this instance of ringpop.
Joiner.prototype.collectPreferredNodes = function collectPreferredNodes() {
    var self = this;
    return this.potentialNodes.filter(function filterHost(hostPort) {
        return self.host !== captureHost(hostPort);
    });
};

// Non-preferred nodes are everyone else.
Joiner.prototype.collectNonPreferredNodes = function collectNonPreferredNodes() {
    var self = this;

    if (isEmptyArray(this.preferredNodes)) {
        return this.potentialNodes;
    } else {
        return this.potentialNodes.filter(function filterHost(hostPort) {
            return self.preferredNodes.indexOf(hostPort) === -1;
        });
    }
};

Joiner.prototype.init = function init(nodesJoined) {
    // TODO The "collect" operations are fairly inefficient. This
    // can be improved by indexing by host/port values.
    this.potentialNodes = this.collectPotentialNodes(nodesJoined);
    this.preferredNodes = this.collectPreferredNodes();
    this.nonPreferredNodes = this.collectNonPreferredNodes();

    // Note that these are copies. During the process of joining
    // the "round"-prefixed collections are mutated.
    this.roundPotentialNodes = this.potentialNodes.slice(0);
    this.roundPreferredNodes = this.preferredNodes.slice(0);
    this.roundNonPreferredNodes = this.nonPreferredNodes.slice(0);
};

Joiner.prototype.join = function join(callback) {
    var self = this;

    if (this.ringpop.destroyed) {
        process.nextTick(function onTick() {
            callback(JoinAbortedError({
                reason: 'joiner was destroyed'
            }));
        });
        return;
    }

    // No need to go through the join process if you're the only one in the cluster.
    if (isSingleNodeCluster(this.ringpop)) {
        this.ringpop.logger.info('ringpop received a single node cluster join', {
            local: this.ringpop.whoami()
        });

        process.nextTick(function onTick() {
            callback();
        });
        return;
    }

    var nodesJoined = [];
    var numGroups = 0;
    var numJoined = 0;
    var numFailed = 0;
    var startTime = Date.now();
    var calledBack = false;

    function onJoin(err, nodes) {
        if (calledBack) {
            return;
        }

        if (self.ringpop.destroyed) {
            calledBack = true;
            callback(JoinAbortedError({
                reason: 'joiner was destroyed',
            }));
            return;
        }

        if (err) {
            calledBack = true;
            callback(err);
            return;
        }

        nodesJoined = nodesJoined.concat(nodes.successes);
        numJoined += nodes.successes.length;
        numFailed += nodes.failures.length;
        numGroups++;

        if (numJoined >= self.joinSize) {
            var joinTime = Date.now() - startTime;

            var updates = mergeJoinResponses(self.ringpop, self.joinResponses);

            // Update membership only once, when join is complete and successful.
            self.ringpop.membership.update(updates);

            // No need to keep this data hanging around.
            self.joinResponses = null;

            self.ringpop.stat('timing', 'join', joinTime);
            self.ringpop.stat('increment', 'join.complete');
            self.ringpop.logger.debug('ringpop join complete', {
                local: self.ringpop.whoami(),
                joinSize: self.joinSize,
                joinTime: joinTime,
                numJoined: numJoined,
                numGroups: numGroups,
                numFailed: numFailed
            });

            calledBack = true;
            callback(null, nodesJoined);
        } else {
            var joinDuration = Date.now() - startTime;
            var maxJoinDuration = self.ringpop.config.get('maxJoinDuration');
            if (joinDuration > maxJoinDuration) {
                self.ringpop.logger.warn('ringpop max join duration exceeded', {
                    local: self.ringpop.whoami(),
                    joinDuration: joinDuration,
                    maxJoinDuration: maxJoinDuration,
                    numJoined: numJoined,
                    numFailed: numFailed,
                    startTime: startTime
                });

                calledBack = true;
                callback(JoinDurationExceededError({
                    joinDuration: joinDuration,
                    maxJoinDuration: maxJoinDuration
                }));
                return;
            }

            // Compute a join delay that grows exponentially with
            // each join retry that is performed. Add a randomized
            // fuzz to the delay that is between 1x and 1.5x its
            // value.
            var oldJoinDelay = self.joinDelay;
            var delayMax = self.ringpop.config.get('joinDelayMax');
            var delayMin = self.ringpop.config.get('joinDelayMin');
            var newDelay = delayMin * Math.pow(2, self.joinRetries);
            var withFuzz = Math.floor(Math.random() *
                ((newDelay * 1.5) - newDelay)) + newDelay;
            self.joinDelay = Math.min(delayMax, withFuzz);

            // Determine if the join delay has exceeded the maximum
            // delay and send out a warning letting developers know
            // that Ringpop is having trouble.
            if (self.ringpop.config.get('joinTroubleErrorEnabled') &&
                oldJoinDelay < delayMax &&
                self.joinDelay >= delayMax) {
                var errorMsg = 'ringpop joiner reached max retry delay. ' +
                    'this is a strong indication that ringpop is having ' +
                    'trouble joining a cluster and could be due to a ' +
                    'misconfiguration of your environment. ringpop will ' +
                    'continue to join up to the max join duration.';
                self.ringpop.logger.error(errorMsg, {
                    local: self.ringpop.whoami(),
                    retriesSoFar: self.joinRetries,
                    joinDelayMax: delayMax,
                    maxJoinDuration: maxJoinDuration
                });
            }

            self.ringpop.logger.info('ringpop joiner not yet complete; will attempt retry after delay', {
                local: self.ringpop.whoami(),
                retriesSoFar: self.joinRetries,
                delay: self.joinDelay,
                maxJoinDuration: maxJoinDuration,
                timeJoiningSoFar: Date.now() - startTime,
                delayWithoutFuzz: newDelay,
                delayWithFuzz: self.joinDelay,
                joinSize: self.joinSize,
                numNodesJoined: numJoined,
                numNodesFailed: numFailed,
                numNodesLeft: self.joinSize - numJoined
            });
            // Attempt to retry the join after applying the delay backoff.
            self.timers.setTimeout(function onTimeout() {
                self.joinRetries++;
                self.joinGroup(nodesJoined, onJoin);
            }, self.joinDelay);
        }
    }

    this.joinGroup(nodesJoined, onJoin);
};

Joiner.prototype.joinGroup = function joinGroup(totalNodesJoined, callback) {
    var self = this;
    var group = this.selectGroup(totalNodesJoined);

    this.ringpop.logger.debug('ringpop selected join group', {
        local: self.ringpop.whoami(),
        group: group,
        numNodes: group.length
    });

    var nodesJoined = [];
    var nodesFailed = [];
    var numNodesLeft = this.joinSize - totalNodesJoined.length;
    var calledBack = false;
    var startTime = Date.now();

    function onJoin(err, node) {
        if (calledBack) {
            return;
        }

        if (err) {
            nodesFailed.push(node);
        } else {
            nodesJoined.push(node);
        }

        var numCompleted = nodesJoined.length + nodesFailed.length;

        // Finished when either all joins have completed or enough to satisfy
        // the join requirements as defined by `joinSize`.
        if (nodesJoined.length >= numNodesLeft || numCompleted >= group.length) {
            self.ringpop.logger.debug('ringpop join group complete', {
                local: self.ringpop.whoami(),
                groupSize: group.length,
                joinSize: self.joinSize,
                joinTime: Date.now() - startTime,
                numFailures: nodesFailed.length,
                numSuccesses: nodesJoined.length,
                numNodesLeft: numNodesLeft,
                failures: nodesFailed,
                successes: nodesJoined
            });

            calledBack = true;
            callback(null, {
                successes: nodesJoined,
                failures: nodesFailed
            });
        }
    }

    for (var i = 0; i < group.length; i++) {
        this.joinNode(group[i], onJoin);
    }
};

Joiner.prototype.joinNode = function joinNode(node, callback) {
    var self = this;
    var joinOpts = {
        host: node,
        timeout: this.joinTimeout,
        serviceName: 'ringpop',
        hasNoParent: true,
        retryLimit: 1,
        trace: false,
        headers: {
            'as': 'raw',
            'cn': 'ringpop'
        }
    };
    var joinBody = JSON.stringify({
        app: this.ringpop.app,
        source: this.ringpop.whoami(),
        incarnationNumber: this.ringpop.membership.localMember.incarnationNumber
    });

    self.ringpop.channel
        .waitForIdentified({
            host: joinOpts.host
        }, onIdentified);

    function onIdentified(err) {
        if (err) {
            callback(err);
        } else {
            self.ringpop.channel
                .request(joinOpts)
                .send('/protocol/join', null, joinBody, function onSend(err, res, arg2, arg3) {
                    if (!err && !res.ok) {
                        err = new Error(String(arg3));
                    }

                    if (err) {
                        return callback(err, node);
                    }

                    var bodyObj = safeParse(arg3.toString());

                    // Verify that `joinResponses` is not null. It is set
                    // to null upon completion of the join process. There may,
                    // however, be in-flight /protocol/join requests that have
                    // yet to complete.
                    if (bodyObj && self.joinResponses !== null) {
                        self.joinResponses.push({
                            checksum: bodyObj.membershipChecksum,
                            members: bodyObj.membership
                        });
                    }

                    callback(null, node);
                });
        }
    }
};

Joiner.prototype.selectGroup = function selectGroup(nodesJoined) {
    nodesJoined = nodesJoined || [];

    var self = this;

    // If fully exhausted or first round, initialize this rounds' nodes.
    if (isEmptyArray(this.roundPreferredNodes) && isEmptyArray(this.roundNonPreferredNodes)) {
        this.init(nodesJoined);
    }

    var preferredNodes = this.roundPreferredNodes;
    var nonPreferredNodes = this.roundNonPreferredNodes;
    var numNodesLeft = self.joinSize - nodesJoined.length;
    var group = [];

    function continueSelect() {
        var numNodesSelected = group.length;
        if (numNodesSelected === numNodesLeft * self.parallelismFactor) {
            return false;
        }

        var numNodesAvailable = preferredNodes.length + nonPreferredNodes.length;
        if (numNodesAvailable === 0) {
            return false;
        }

        return true;
    }

    while (continueSelect()) {
        if (preferredNodes.length > 0) {
            group.push(takeNode(preferredNodes));
        } else if (nonPreferredNodes.length > 0) {
            group.push(takeNode(nonPreferredNodes));
        }
    }

    return group;
};

function createJoiner(opts) {
    return new Joiner(opts);
}

function joinCluster(opts, callback) {
    var joiner = createJoiner(opts);
    joiner.join(callback);
    return joiner;
}

module.exports = {
    createJoiner: createJoiner,
    joinCluster: joinCluster
};
<commit_msg>Changed join log line to debug from info<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
'use strict';

var captureHost = require('../util.js').captureHost;
var errors = require('../errors.js');
var globalTimers = require('timers');
var isEmptyArray = require('../util.js').isEmptyArray;
var mergeJoinResponses = require('./join-response-merge.js');
var numOrDefault = require('../util.js').numOrDefault;
var safeParse = require('../util.js').safeParse;
var TypedError = require('error/typed');

var JoinAbortedError = TypedError({
    type: 'ringpop.join-aborted',
    message: 'Join aborted because `{reason}`',
    reason: null
});

var JoinDurationExceededError = TypedError({
    type: 'ringpop.join-duration-exceeded',
    message: 'Join duration of `{duration}` exceeded max `{max}`',
    duration: null,
    max: null
});

var JOIN_SIZE = 3;
var JOIN_TIMEOUT = 1000;
var PARALLELISM_FACTOR = 2;

function isSingleNodeCluster(ringpop) {
    return Array.isArray(ringpop.bootstrapHosts) &&
        ringpop.bootstrapHosts.length === 1 &&
        ringpop.bootstrapHosts[0] === ringpop.hostPort;
}

// Note that this function mutates the array passed in.
function takeNode(hosts) {
    var index = Math.floor(Math.random() * hosts.length);
    var host = hosts[index];
    hosts.splice(index, 1);
    return host;
}

function Joiner(opts) {
    opts = opts || {};

    if (!opts.ringpop) {
        throw errors.OptionRequiredError({
            context: 'ringpop',
            option: 'ringpop'
        });
    }

    if (isEmptyArray(opts.ringpop.bootstrapHosts)) {
        throw errors.InvalidOptionError({
            option: 'ringpop',
            reason: '`bootstrapHosts` is expected to be an array of size 1 or more'
        });
    }

    this.ringpop = opts.ringpop;
    this.timers = opts.timers || globalTimers;
    this.host = captureHost(this.ringpop.hostPort);
    this.joinTimeout = numOrDefault(opts.joinTimeout, JOIN_TIMEOUT);

    // This is used as a multiple of the required nodes left
    // to join to satisfy `joinSize`. Additional parallelism
    // can be applied in order for `joinSize` to be satisified
    // faster.
    this.parallelismFactor = numOrDefault(opts.parallelismFactor,
        PARALLELISM_FACTOR);

    // Potential nodes are nodes in the ringpop bootstrap
    // list that can be joined. Upon instantiation, this step
    // simply filters out a node from attempting to join itself.
    this.potentialNodes = this.collectPotentialNodes();
    this.preferredNodes = null;
    this.nonPreferredNodes = null;

    // We either join the number of nodes defined by `joinSize`
    // or limit it to the number of `potentialNodes`. After all,
    // we can't join more than there are to join in the first place.
    var joinSize = numOrDefault(opts.joinSize, JOIN_SIZE);
    this.joinSize = Math.min(joinSize, this.potentialNodes.length);

    // A round is defined as a complete cycle through all
    // potential join targets. Once a round is completed,
    // we start all over again. A full-cycle should be pretty
    // darned rare. We will try and try to join other nodes
    // until `joinSize` is reached or `maxJoinDuration` is
    // exceeded.
    this.roundPotentialNodes = null;
    this.roundPreferredNodes = null;

    // Changes received by other nodes will be aggregated and
    // applied once the join process is complete.
    this.joinResponses = [];

    this.joinDelay = this.ringpop.config.get('joinDelayMin');
    this.joinRetries = 0;
}

// Potential nodes are those that are not this instance of ringpop.
Joiner.prototype.collectPotentialNodes = function collectPotentialNodes(nodesJoined) {
    nodesJoined = nodesJoined || [];

    var self = this;
    return this.ringpop.bootstrapHosts.filter(function filterHost(hostPort) {
        return self.ringpop.hostPort !== hostPort && nodesJoined.indexOf(hostPort) === -1;
    });
};

// Preferred nodes are those that are not on the same host as this instance of ringpop.
Joiner.prototype.collectPreferredNodes = function collectPreferredNodes() {
    var self = this;
    return this.potentialNodes.filter(function filterHost(hostPort) {
        return self.host !== captureHost(hostPort);
    });
};

// Non-preferred nodes are everyone else.
Joiner.prototype.collectNonPreferredNodes = function collectNonPreferredNodes() {
    var self = this;

    if (isEmptyArray(this.preferredNodes)) {
        return this.potentialNodes;
    } else {
        return this.potentialNodes.filter(function filterHost(hostPort) {
            return self.preferredNodes.indexOf(hostPort) === -1;
        });
    }
};

Joiner.prototype.init = function init(nodesJoined) {
    // TODO The "collect" operations are fairly inefficient. This
    // can be improved by indexing by host/port values.
    this.potentialNodes = this.collectPotentialNodes(nodesJoined);
    this.preferredNodes = this.collectPreferredNodes();
    this.nonPreferredNodes = this.collectNonPreferredNodes();

    // Note that these are copies. During the process of joining
    // the "round"-prefixed collections are mutated.
    this.roundPotentialNodes = this.potentialNodes.slice(0);
    this.roundPreferredNodes = this.preferredNodes.slice(0);
    this.roundNonPreferredNodes = this.nonPreferredNodes.slice(0);
};

Joiner.prototype.join = function join(callback) {
    var self = this;

    if (this.ringpop.destroyed) {
        process.nextTick(function onTick() {
            callback(JoinAbortedError({
                reason: 'joiner was destroyed'
            }));
        });
        return;
    }

    // No need to go through the join process if you're the only one in the cluster.
    if (isSingleNodeCluster(this.ringpop)) {
        this.ringpop.logger.info('ringpop received a single node cluster join', {
            local: this.ringpop.whoami()
        });

        process.nextTick(function onTick() {
            callback();
        });
        return;
    }

    var nodesJoined = [];
    var numGroups = 0;
    var numJoined = 0;
    var numFailed = 0;
    var startTime = Date.now();
    var calledBack = false;

    function onJoin(err, nodes) {
        if (calledBack) {
            return;
        }

        if (self.ringpop.destroyed) {
            calledBack = true;
            callback(JoinAbortedError({
                reason: 'joiner was destroyed',
            }));
            return;
        }

        if (err) {
            calledBack = true;
            callback(err);
            return;
        }

        nodesJoined = nodesJoined.concat(nodes.successes);
        numJoined += nodes.successes.length;
        numFailed += nodes.failures.length;
        numGroups++;

        if (numJoined >= self.joinSize) {
            var joinTime = Date.now() - startTime;

            var updates = mergeJoinResponses(self.ringpop, self.joinResponses);

            // Update membership only once, when join is complete and successful.
            self.ringpop.membership.update(updates);

            // No need to keep this data hanging around.
            self.joinResponses = null;

            self.ringpop.stat('timing', 'join', joinTime);
            self.ringpop.stat('increment', 'join.complete');
            self.ringpop.logger.debug('ringpop join complete', {
                local: self.ringpop.whoami(),
                joinSize: self.joinSize,
                joinTime: joinTime,
                numJoined: numJoined,
                numGroups: numGroups,
                numFailed: numFailed
            });

            calledBack = true;
            callback(null, nodesJoined);
        } else {
            var joinDuration = Date.now() - startTime;
            var maxJoinDuration = self.ringpop.config.get('maxJoinDuration');
            if (joinDuration > maxJoinDuration) {
                self.ringpop.logger.warn('ringpop max join duration exceeded', {
                    local: self.ringpop.whoami(),
                    joinDuration: joinDuration,
                    maxJoinDuration: maxJoinDuration,
                    numJoined: numJoined,
                    numFailed: numFailed,
                    startTime: startTime
                });

                calledBack = true;
                callback(JoinDurationExceededError({
                    joinDuration: joinDuration,
                    maxJoinDuration: maxJoinDuration
                }));
                return;
            }

            // Compute a join delay that grows exponentially with
            // each join retry that is performed. Add a randomized
            // fuzz to the delay that is between 1x and 1.5x its
            // value.
            var oldJoinDelay = self.joinDelay;
            var delayMax = self.ringpop.config.get('joinDelayMax');
            var delayMin = self.ringpop.config.get('joinDelayMin');
            var newDelay = delayMin * Math.pow(2, self.joinRetries);
            var withFuzz = Math.floor(Math.random() *
                ((newDelay * 1.5) - newDelay)) + newDelay;
            self.joinDelay = Math.min(delayMax, withFuzz);

            // Determine if the join delay has exceeded the maximum
            // delay and send out a warning letting developers know
            // that Ringpop is having trouble.
            if (self.ringpop.config.get('joinTroubleErrorEnabled') &&
                oldJoinDelay < delayMax &&
                self.joinDelay >= delayMax) {
                var errorMsg = 'ringpop joiner reached max retry delay. ' +
                    'this is a strong indication that ringpop is having ' +
                    'trouble joining a cluster and could be due to a ' +
                    'misconfiguration of your environment. ringpop will ' +
                    'continue to join up to the max join duration.';
                self.ringpop.logger.error(errorMsg, {
                    local: self.ringpop.whoami(),
                    retriesSoFar: self.joinRetries,
                    joinDelayMax: delayMax,
                    maxJoinDuration: maxJoinDuration
                });
            }

            self.ringpop.logger.debug('ringpop joiner not yet complete; will attempt retry after delay', {
                local: self.ringpop.whoami(),
                retriesSoFar: self.joinRetries,
                delay: self.joinDelay,
                maxJoinDuration: maxJoinDuration,
                timeJoiningSoFar: Date.now() - startTime,
                delayWithoutFuzz: newDelay,
                delayWithFuzz: self.joinDelay,
                joinSize: self.joinSize,
                numNodesJoined: numJoined,
                numNodesFailed: numFailed,
                numNodesLeft: self.joinSize - numJoined
            });
            // Attempt to retry the join after applying the delay backoff.
            self.timers.setTimeout(function onTimeout() {
                self.joinRetries++;
                self.joinGroup(nodesJoined, onJoin);
            }, self.joinDelay);
        }
    }

    this.joinGroup(nodesJoined, onJoin);
};

Joiner.prototype.joinGroup = function joinGroup(totalNodesJoined, callback) {
    var self = this;
    var group = this.selectGroup(totalNodesJoined);

    this.ringpop.logger.debug('ringpop selected join group', {
        local: self.ringpop.whoami(),
        group: group,
        numNodes: group.length
    });

    var nodesJoined = [];
    var nodesFailed = [];
    var numNodesLeft = this.joinSize - totalNodesJoined.length;
    var calledBack = false;
    var startTime = Date.now();

    function onJoin(err, node) {
        if (calledBack) {
            return;
        }

        if (err) {
            nodesFailed.push(node);
        } else {
            nodesJoined.push(node);
        }

        var numCompleted = nodesJoined.length + nodesFailed.length;

        // Finished when either all joins have completed or enough to satisfy
        // the join requirements as defined by `joinSize`.
        if (nodesJoined.length >= numNodesLeft || numCompleted >= group.length) {
            self.ringpop.logger.debug('ringpop join group complete', {
                local: self.ringpop.whoami(),
                groupSize: group.length,
                joinSize: self.joinSize,
                joinTime: Date.now() - startTime,
                numFailures: nodesFailed.length,
                numSuccesses: nodesJoined.length,
                numNodesLeft: numNodesLeft,
                failures: nodesFailed,
                successes: nodesJoined
            });

            calledBack = true;
            callback(null, {
                successes: nodesJoined,
                failures: nodesFailed
            });
        }
    }

    for (var i = 0; i < group.length; i++) {
        this.joinNode(group[i], onJoin);
    }
};

Joiner.prototype.joinNode = function joinNode(node, callback) {
    var self = this;
    var joinOpts = {
        host: node,
        timeout: this.joinTimeout,
        serviceName: 'ringpop',
        hasNoParent: true,
        retryLimit: 1,
        trace: false,
        headers: {
            'as': 'raw',
            'cn': 'ringpop'
        }
    };
    var joinBody = JSON.stringify({
        app: this.ringpop.app,
        source: this.ringpop.whoami(),
        incarnationNumber: this.ringpop.membership.localMember.incarnationNumber
    });

    self.ringpop.channel
        .waitForIdentified({
            host: joinOpts.host
        }, onIdentified);

    function onIdentified(err) {
        if (err) {
            callback(err);
        } else {
            self.ringpop.channel
                .request(joinOpts)
                .send('/protocol/join', null, joinBody, function onSend(err, res, arg2, arg3) {
                    if (!err && !res.ok) {
                        err = new Error(String(arg3));
                    }

                    if (err) {
                        return callback(err, node);
                    }

                    var bodyObj = safeParse(arg3.toString());

                    // Verify that `joinResponses` is not null. It is set
                    // to null upon completion of the join process. There may,
                    // however, be in-flight /protocol/join requests that have
                    // yet to complete.
                    if (bodyObj && self.joinResponses !== null) {
                        self.joinResponses.push({
                            checksum: bodyObj.membershipChecksum,
                            members: bodyObj.membership
                        });
                    }

                    callback(null, node);
                });
        }
    }
};

Joiner.prototype.selectGroup = function selectGroup(nodesJoined) {
    nodesJoined = nodesJoined || [];

    var self = this;

    // If fully exhausted or first round, initialize this rounds' nodes.
    if (isEmptyArray(this.roundPreferredNodes) && isEmptyArray(this.roundNonPreferredNodes)) {
        this.init(nodesJoined);
    }

    var preferredNodes = this.roundPreferredNodes;
    var nonPreferredNodes = this.roundNonPreferredNodes;
    var numNodesLeft = self.joinSize - nodesJoined.length;
    var group = [];

    function continueSelect() {
        var numNodesSelected = group.length;
        if (numNodesSelected === numNodesLeft * self.parallelismFactor) {
            return false;
        }

        var numNodesAvailable = preferredNodes.length + nonPreferredNodes.length;
        if (numNodesAvailable === 0) {
            return false;
        }

        return true;
    }

    while (continueSelect()) {
        if (preferredNodes.length > 0) {
            group.push(takeNode(preferredNodes));
        } else if (nonPreferredNodes.length > 0) {
            group.push(takeNode(nonPreferredNodes));
        }
    }

    return group;
};

function createJoiner(opts) {
    return new Joiner(opts);
}

function joinCluster(opts, callback) {
    var joiner = createJoiner(opts);
    joiner.join(callback);
    return joiner;
}

module.exports = {
    createJoiner: createJoiner,
    joinCluster: joinCluster
};
<|endoftext|>


<commit_before>(function () {
    angular.module("services").factory("ModalService", ModalService);

    ModalService.$inject = ['$uibModal'];

    function ModalService($uibModal) {
        var service = {
            modal: null
        };

        service.dismiss = function (reason) {
            service.modal.dismiss(reason);
            service.modal = null;
        };

        service.close = function (reason) {
            service.modal.close(reason);
            service.modal = null;
        };

        service.openScientillaDocumentForm = function (document, researchEntity) {
            var scopeVars = {
                document: document,
                researchEntity: researchEntity
            };

            service.modal = openModal(
                    '<scientilla-document-form\
                        document="vm.document"\
                        research-entity="vm.researchEntity"\
                        on-failure="vm.onFailure"\
                        on-submit="vm.onSubmit" \
                        close-fn="vm.onClose" \
                    ></scientilla-document-form>',
                    scopeVars
                    );

            return service.modal.result;
        };

        service.openScientillaDocumentDetails = function (document){
            var scopeVars = {
                document: document
            };

            service.modal = openModal(
                '<scientilla-document-details\
                    document="vm.document"\
                ></scientilla-document-details>',
                scopeVars,
                {size: 'lg'}
            );

            return service.modal.result;
        };

        service.openScientillaUserForm = function (user) {

            var scopeVars = {
                user: user
            };

            service.modal = openModal(
                    '<scientilla-user-form\
                        user="vm.user"\
                        on-failure="vm.onFailure"\
                        on-submit="vm.onSubmit"\
                    ></scientilla-user-form>',
                    scopeVars
                    );

            return service.modal.result;
        };

        service.openScientillaTagForm = function (document) {

            var scopeVars = {
                document: document
            };

            service.modal = openModal(
                '<scientilla-tag-form\
                    document="vm.document"\
                    on-submit="vm.onSubmit" \
                    close-fn="vm.onClose" \
                 ></scientilla-tag-form>',
                scopeVars
            );

            return service.modal.result;
        };

        service.openScientillaGroupForm = function (group) {

            var scopeVars = {
                group: group
            };

            service.modal = openModal(
                '<scientilla-group-form\
                    group="vm.group"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit"\
                 ></scientilla-group-form>',
                scopeVars
            );

            return service.modal.result;
        };

        service.openDocumentAffiliationForm = function(document) {
            var scopeVars = {
                document: document
            };

            service.modal = openModal(
                '<scientilla-document-affiliations\
                    document="vm.document"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit"\
                ></scientilla-document-affiliations>',
                scopeVars,
                {size: 'lg'}
            );

            return service.modal.result;
        };

        service.openDocumentVerificationForm = function(document, verificationFn) {

            var scopeVars = {
                document: document,
                verificationFn: verificationFn
            };

            service.modal = openModal(
                '<scientilla-document-verification-form\
                    document="vm.document"\
                    verification-fn="vm.verificationFn"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit"\
                ></scientilla-document-verification-form>',
                scopeVars,
                {size: 'lg'}
            );

            return service.modal.result;
        };

        service.multipleChoiceConfirm = function(title, message, buttonLabels) {
            buttonLabels = buttonLabels || [];
            var ret = new Promise(function (resolve, reject) {
                var scope = {
                    title: title,
                    message: message,
                    cancel: function () {
                        this.onClose();
                        reject();
                    },
                    ok: function (i) {
                        this.onSubmit();
                        resolve(i);
                    },
                    buttonLabels: buttonLabels
                };
                service.modal = openModal('\

\

\

{{vm.title}}
\

{{vm.message}}
\

\

' +
                            scope.buttonLabels.map(function(b, i) { return '<scientilla-button ng-click="vm.ok('+i+')">'+b+'</scientilla-button>';}).join('') +
                            '<scientilla-button ng-click="vm.cancel()" type="cancel">Cancel</scientilla-button>\

',
                        scope);

                service.modal.result.catch(function () {
                    reject();
                });
            });

            return ret;
        };

        service.confirm = function (title, message) {
            return service.multipleChoiceConfirm(title, message, ['Ok']);
        };

        return service;

        // private
        function openModal(template, scope, args) {
            var callbacks = getDefaultCallbacks();

            _.defaults(scope, callbacks);

            var controller = function () {
                _.assign(this, scope);
            };

            return $uibModal.open(
                _.defaults({
                    animation: true,
                    template: template,
                    controller: controller,
                    controllerAs: 'vm'
                }, args)
            );
        }

        function getDefaultCallbacks() {
            var callbacks = {
                onFailure: function(){},
                onSubmit: service.close,
                onClose: service.close
            };

            return callbacks;
        }
    }

}());
<commit_msg>Enlarged scientilla document form, fixed identation.<commit_after>(function () {
    angular.module("services").factory("ModalService", ModalService);

    ModalService.$inject = ['$uibModal'];

    function ModalService($uibModal) {
        var service = {
            modal: null
        };

        service.dismiss = function (reason) {
            service.modal.dismiss(reason);
            service.modal = null;
        };

        service.close = function (reason) {
            service.modal.close(reason);
            service.modal = null;
        };

        service.openScientillaDocumentForm = function (document, researchEntity) {
            var scopeVars = {
                document: document,
                researchEntity: researchEntity
            };

            service.modal = openModal(
                '<scientilla-document-form\
                    document="vm.document"\
                    research-entity="vm.researchEntity"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit" \
                    close-fn="vm.onClose" \
                ></scientilla-document-form>',
                scopeVars,
                {size: 'lg'}
            );

            return service.modal.result;
        };

        service.openScientillaDocumentDetails = function (document) {
            var scopeVars = {
                document: document
            };

            service.modal = openModal(
                '<scientilla-document-details\
                    document="vm.document"\
                ></scientilla-document-details>',
                scopeVars,
                {size: 'lg'}
            );

            return service.modal.result;
        };

        service.openScientillaUserForm = function (user) {

            var scopeVars = {
                user: user
            };

            service.modal = openModal(
                '<scientilla-user-form\
                    user="vm.user"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit"\
                ></scientilla-user-form>',
                scopeVars
            );

            return service.modal.result;
        };

        service.openScientillaTagForm = function (document) {

            var scopeVars = {
                document: document
            };

            service.modal = openModal(
                '<scientilla-tag-form\
                    document="vm.document"\
                    on-submit="vm.onSubmit" \
                    close-fn="vm.onClose" \
                 ></scientilla-tag-form>',
                scopeVars
            );

            return service.modal.result;
        };

        service.openScientillaGroupForm = function (group) {

            var scopeVars = {
                group: group
            };

            service.modal = openModal(
                '<scientilla-group-form\
                    group="vm.group"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit"\
                 ></scientilla-group-form>',
                scopeVars
            );

            return service.modal.result;
        };

        service.openDocumentAffiliationForm = function (document) {
            var scopeVars = {
                document: document
            };

            service.modal = openModal(
                '<scientilla-document-affiliations\
                    document="vm.document"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit"\
                ></scientilla-document-affiliations>',
                scopeVars,
                {size: 'lg'}
            );

            return service.modal.result;
        };

        service.openDocumentVerificationForm = function (document, verificationFn) {

            var scopeVars = {
                document: document,
                verificationFn: verificationFn
            };

            service.modal = openModal(
                '<scientilla-document-verification-form\
                    document="vm.document"\
                    verification-fn="vm.verificationFn"\
                    on-failure="vm.onFailure"\
                    on-submit="vm.onSubmit"\
                ></scientilla-document-verification-form>',
                scopeVars,
                {size: 'lg'}
            );

            return service.modal.result;
        };

        service.multipleChoiceConfirm = function (title, message, buttonLabels) {
            buttonLabels = buttonLabels || [];
            var ret = new Promise(function (resolve, reject) {
                var scope = {
                    title: title,
                    message: message,
                    cancel: function () {
                        this.onClose();
                        reject();
                    },
                    ok: function (i) {
                        this.onSubmit();
                        resolve(i);
                    },
                    buttonLabels: buttonLabels
                };
                service.modal = openModal('\

\

\

{{vm.title}}
\

{{vm.message}}
\

\

' +
                    scope.buttonLabels.map(function (b, i) {
                        return '<scientilla-button ng-click="vm.ok(' + i + ')">' + b + '</scientilla-button>';
                    }).join('') +
                    '<scientilla-button ng-click="vm.cancel()" type="cancel">Cancel</scientilla-button>\

',
                    scope);

                service.modal.result.catch(function () {
                    reject();
                });
            });

            return ret;
        };

        service.confirm = function (title, message) {
            return service.multipleChoiceConfirm(title, message, ['Ok']);
        };

        return service;

        // private
        function openModal(template, scope, args) {
            var callbacks = getDefaultCallbacks();

            _.defaults(scope, callbacks);

            var controller = function () {
                _.assign(this, scope);
            };

            return $uibModal.open(
                _.defaults({
                    animation: true,
                    template: template,
                    controller: controller,
                    controllerAs: 'vm'
                }, args)
            );
        }

        function getDefaultCallbacks() {
            var callbacks = {
                onFailure: _.noop,
                onSubmit: service.close,
                onClose: service.close
            };

            return callbacks;
        }
    }

}());
<|endoftext|>


<commit_before>'use strict';

var mongodb = require('mongodb');

/**
 * @constructor
 * @param {Api} api
 * @param {Object} options
 */
var DataSource = module.exports = function (api, options) {
    this.log = api.log.child({'component': 'flora-mongodb'});
    this.connections = {};
    this.baseUrl = 'mongodb://' + options.server.host + ':' + (options.server.port ? options.server.port : 27017) + '/';
};

DataSource.prototype.prepare = function () {};

/**
 * @param {Object} request
 * @param {Function} callback
 */
DataSource.prototype.process = function (request, callback) {
    var query = (request.filter) ? buildQuery(request.filter) : {};
    var options = {};

    if (request.attributes) {
        options.fields = {};
        request.attributes.forEach(function (a) {
            options.fields[a] = 1;
        });
    }

    if (request.limit) {
        options.limit = request.limit;
    }

    if (request.order) {
        options.sort = request.order.map(function(o) {
            var attribute = o.attribute; // FIXME: field name mapping
            var direction = (o.direction === 'desc' ? -1 : 1);
            return [attribute, direction];
        });
    }

    this.getConnection(request.database, function (err, db) {
        if (err) return callback(err, null);
        db.collection(request.collection, function onCollection(err2, collection) {
            if (err2) return callback(err, null);
            collection.find(query, options).toArray(function (err3, docs) {
                if (err3) return callback(err3, null);
                callback(null, {
                    totalCount: null,
                    data: docs
                });
            });
        });
    });
};

/**
 * @param {Function} callback
 */
DataSource.prototype.close = function (callback) {
    // TODO: implement
    if (callback) callback();
};

/**
 * @param {string} database
 * @param {Function} callback
 * @private
 */
DataSource.prototype.getConnection = function (database, callback) {
    if (this.connections[database]) return callback(null, this.connections[database]);

    var self = this;
    var url = this.baseUrl + database + '/';

    this.log.trace('connecting to MongoDB ' + database + ' on ' + url);

    mongodb.MongoClient.connect(url, function(err, db) {
        if (err) return callback(err, null);
        // TODO: implement authentication, reconnect, etc.
        self.connections[database] = db;
        callback(null, db);
    });
};

/**
 * @param {Object} filter
 * @returns {Object}
 */
function buildQuery(filter) {
    var or = filter.map(function (andFilters) {
        var and = [];
        andFilters.forEach(function (condition) {
            if (condition.operator === 'equal') {
                var c = {};
                if (typeof condition.value === 'object') {
                    // TODO: check if it really is an array
                    // TODO: field name mapping
                    // TODO: type casting?
                    c[condition.attribute] = {'$in': condition.value};
                } else {
                    c[condition.attribute] = condition.value;
                }
                and.push(c);
            }
        });
        return (and.length === 1) ? and[0] : {'$and': and};
    });
    return (or.length === 1) ? or[0] : {'$or': or};
}
<commit_msg>Use the right error object in case when db.collection fails<commit_after>'use strict';

var mongodb = require('mongodb');

/**
 * @constructor
 * @param {Api} api
 * @param {Object} options
 */
var DataSource = module.exports = function (api, options) {
    this.log = api.log.child({'component': 'flora-mongodb'});
    this.connections = {};
    this.baseUrl = 'mongodb://' + options.server.host + ':' + (options.server.port ? options.server.port : 27017) + '/';
};

DataSource.prototype.prepare = function () {};

/**
 * @param {Object} request
 * @param {Function} callback
 */
DataSource.prototype.process = function (request, callback) {
    var query = (request.filter) ? buildQuery(request.filter) : {};
    var options = {};

    if (request.attributes) {
        options.fields = {};
        request.attributes.forEach(function (a) {
            options.fields[a] = 1;
        });
    }

    if (request.limit) {
        options.limit = request.limit;
    }

    if (request.order) {
        options.sort = request.order.map(function(o) {
            var attribute = o.attribute; // FIXME: field name mapping
            var direction = (o.direction === 'desc' ? -1 : 1);
            return [attribute, direction];
        });
    }

    this.getConnection(request.database, function (err, db) {
        if (err) return callback(err, null);
        db.collection(request.collection, function onCollection(err2, collection) {
            if (err2) return callback(err2, null);
            collection.find(query, options).toArray(function (err3, docs) {
                if (err3) return callback(err3, null);
                callback(null, {
                    totalCount: null,
                    data: docs
                });
            });
        });
    });
};

/**
 * @param {Function} callback
 */
DataSource.prototype.close = function (callback) {
    // TODO: implement
        if (callback) callback();
};

/**
 * @param {string} database
 * @param {Function} callback
 * @private
 */
DataSource.prototype.getConnection = function (database, callback) {
    if (this.connections[database]) return callback(null, this.connections[database]);

    var self = this;
    var url = this.baseUrl + database + '/';

    this.log.trace('connecting to MongoDB ' + database + ' on ' + url);

    mongodb.MongoClient.connect(url, function(err, db) {
        if (err) return callback(err, null);
        // TODO: implement authentication, reconnect, etc.
        self.connections[database] = db;
        callback(null, db);
    });
};

/**
 * @param {Object} filter
 * @returns {Object}
 */
function buildQuery(filter) {
    var or = filter.map(function (andFilters) {
        var and = [];
        andFilters.forEach(function (condition) {
            if (condition.operator === 'equal') {
                var c = {};
                if (typeof condition.value === 'object') {
                    // TODO: check if it really is an array
                    // TODO: field name mapping
                    // TODO: type casting?
                    c[condition.attribute] = {'$in': condition.value};
                } else {
                    c[condition.attribute] = condition.value;
                }
                and.push(c);
            }
        });
        return (and.length === 1) ? and[0] : {'$and': and};
    });
    return (or.length === 1) ? or[0] : {'$or': or};
}
<|endoftext|>


<commit_before>//post widget

// var scripts = document.getElementsByTagName('script');
// console.table(scripts)
// for (var i = 0, l = scripts.length; i < l; i++) {
// 	if (scripts[i].src.indexOf('redditjs.com/post.js') > -1) {
// 		alert(scripts[i].getAttribute('data-id'));
// 		break;
// 	}
// }

(function() {
	"use strict"

	var postUrl;
	var width;
	var height;
	var postSortOrder;
	var script;

	initScript(script)

	function initScript(script) {
		script = document.getElementById('rjs_post');
		if (script) {

			postUrl = script.getAttribute('data-url') || window.location.href
			width = script.getAttribute('data-width') || 500
			height = script.getAttribute('data-height') || 350
			postSortOrder = script.getAttribute('postSortOrder') || 'mostComments'

			var embedUrl = "http://localhost:8002/embed?url=" + postUrl + '&width=' + width + '&height=' + height + '&postSortOrder=' + postSortOrder

			var iframeWrapper = document.createElement("div");
			iframeWrapper.style.width = '100%'

			var ifrm = document.createElement("IFRAME");
			ifrm.setAttribute("src", embedUrl);
			//start out invis and expand after loaded
			ifrm.style.height = '0px'
			ifrm.style.width = '0px'
			ifrm.style.margin = '0 auto'
			ifrm.style.display = 'block'

			iframeWrapper.appendChild(ifrm)
			//script.appendChild(iframeWrapper)
			script.parentNode.insertBefore(iframeWrapper, script.nextSibling);

			setupMessenger(ifrm)

		}
	}

	function setupMessenger(ifrm) {
		var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
		var eventer = window[eventMethod];
		var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";

		// Listen to message from child window
		eventer(messageEvent, function(e) {

			if (typeof e === 'undefined' && typeof e.data === 'undefined') {
				//error checking
				return;
			}

			ifrm.style.border = '5px #f0f0f0 solid'
			ifrm.style.resize = 'both';
			ifrm.style.overflow = 'auto';

			if (typeof e.data.newWidth != null) {
				var newHeight = e.data.newHeight || height
				setHeight(ifrm, newHeight)
			}

			if (typeof e.data.newHeight != null) {
				var newWidth = e.data.newWidth || width
				setWidth(ifrm, newWidth)
			}

		}, false);
	}

	function setHeight(ifrm, newHeight) {
		ifrm.height = newHeight
		//ifrm.css('height', newHeight)
		ifrm.style.height = newHeight + "px"

	}

	function setWidth(ifrm, newWidth) {
		ifrm.width = newWidth
		//ifrm.css('width', newWidth)
		ifrm.style.width = newWidth + "px"
	}

}());<commit_msg>self invoking and loading from the invoking embeded script<commit_after>//post widget
(function() {
	"use strict";

	//var script = document.getElementById('rjs_post');

	var scripts = document.getElementsByTagName('script');
	var script = scripts[scripts.length - 1]; //the script that loaded this file

	(function redditJSInit(script) {

		var postUrl;
		var width;
		var height;
		var postSortOrder;
		var script;

		initScript(script)

		function initScript(script) {

			if (script) {

				postUrl = script.getAttribute('data-url') || window.location.href
				width = script.getAttribute('data-width') || 500
				height = script.getAttribute('data-height') || 350
				postSortOrder = script.getAttribute('postSortOrder') || 'mostComments'

				var embedUrl = "http://localhost:8002/embed?url=" + postUrl + '&width=' + width + '&height=' + height + '&postSortOrder=' + postSortOrder

				var iframeWrapper = document.createElement("div");
				iframeWrapper.style.width = '100%'

				var ifrm = document.createElement("IFRAME");
				ifrm.setAttribute("src", embedUrl);
				//start out invis and expand after loaded
				ifrm.style.height = '0px'
				ifrm.style.width = '0px'
				ifrm.style.margin = '0 auto'
				ifrm.style.display = 'block'

				iframeWrapper.appendChild(ifrm)
				//script.appendChild(iframeWrapper)
				script.parentNode.insertBefore(iframeWrapper, script.nextSibling);

				setupMessenger(ifrm)

			}
		}

		function setupMessenger(ifrm) {
			var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
			var eventer = window[eventMethod];
			var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";

			// Listen to message from child window
			eventer(messageEvent, function(e) {

				if (typeof e === 'undefined' && typeof e.data === 'undefined') {
					//error checking
					return;
				}

				ifrm.style.border = '5px #f0f0f0 solid'
				ifrm.style.resize = 'both';
				ifrm.style.overflow = 'auto';

				if (typeof e.data.newWidth != null) {
					var newHeight = e.data.newHeight || height
					setHeight(ifrm, newHeight)
				}

				if (typeof e.data.newHeight != null) {
					var newWidth = e.data.newWidth || width
					setWidth(ifrm, newWidth)
				}

			}, false);
		}

		function setHeight(ifrm, newHeight) {
			ifrm.height = newHeight
			//ifrm.css('height', newHeight)
			ifrm.style.height = newHeight + "px"

		}

		function setWidth(ifrm, newWidth) {
			ifrm.width = newWidth
			//ifrm.css('width', newWidth)
			ifrm.style.width = newWidth + "px"
		}

	})(script);

}());<|endoftext|>


<commit_before>import Abilities from '../abilities/Abilities';
import GameEvents from '../events/GameEvents';
import Moves from '../entities/creatures/moves/Moves';
import Effects from '../effects/Effects';
import Weapons from '../entities/weapons/Weapons';
import DamageTypes from '../entities/DamageTypes';

import Animation from './animations/Animation';
import AnimationGroup from './animations/AnimationGroup';
import FloatingTextAnimation from './animations/FloatingTextAnimation';
import ProjectileAnimation from './animations/ProjectileAnimation';
import SingleFrameAnimation from './animations/SingleFrameAnimation';
import TransitionAnimation from './animations/TransitionAnimation';

const PIXI = require('pixi.js');
const TextureCache = (PIXI.utils.TextureCache);
const Sprite = PIXI.Sprite;

const DAMAGE_COLORS = {
    [DamageTypes.MELEE_PHYSICAL]: 'darkred',
    [DamageTypes.RANGED_PHYSICAL]: 'darkred',
    [DamageTypes.FIRE]: 'orange',
    [DamageTypes.COLD]: 'darkblue',
    [DamageTypes.ELECTRICAL]: 'yellow',
    [DamageTypes.ENERGY]: 'white',
    [DamageTypes.POISON]: 'emerald'
};

const DAMAGE_OUTLINE_COLORS = {
    [DamageTypes.MELEE_PHYSICAL]: 'pink',
    [DamageTypes.RANGED_PHYSICAL]: 'pink',
    [DamageTypes.FIRE]: 'darkred',
    [DamageTypes.COLD]: 'skyblue',
    [DamageTypes.ELECTRICAL]: 'orange',
    [DamageTypes.ENERGY]: 'yellow',
    [DamageTypes.POISON]: 'darkgreen'
};

// const MS_PER_TICK = 1;
// const PROJECTILE_SPEED = 10; // Tiles per second

const TILE_WIDTH = 50; // TODO: De-dupe

const Easings = {
    linear: t => t,
    easeIn: (t) => t*t
};

const buildSprite = (textureName) => {
    const sprite = new Sprite(TextureCache[textureName]);
    sprite.width = TILE_WIDTH;
    sprite.height = TILE_WIDTH;
    sprite.anchor.x = sprite.anchor.y = .5;
    return sprite;
};

export default class DefaultPixiAnimationPack {
    getAnimation(sharedData, pixiDungeonView, gameEvent) {
        const dungeon = sharedData.getDungeon();
        const stage = pixiDungeonView.getStage();
        let cumulativeTime = 0;
        if(gameEvent instanceof GameEvents.PositionChangeEvent) {
            const cause = gameEvent.getCause();
            if(cause instanceof Moves.MovementMove || cause instanceof Effects.KnockbackEffect) {
                const creature = gameEvent.getCreature();
                const duration = creature.getSpeed() * 60 / 1000;
                const from = gameEvent.getFromCoords();
                const to = gameEvent.getToCoords();
                const dx = to.x - from.x;
                const dy = to.y - from.y;
                const group = () => pixiDungeonView.getEntityById(creature.getId());
                const animations = [
                    new TransitionAnimation(duration, {
                        group,
                        properties: {
                            x: {start: -(dx * pixiDungeonView.getTileWidth()), end: 0},
                            y: {start: -(dy * pixiDungeonView.getTileWidth()), end: 0}
                        },
                        onStart: () => {
                            pixiDungeonView.moveCreatureGroup(creature, to.x, to.y);
                            [gameEvent.getFromCoords(), gameEvent.getToCoords()].forEach(({x, y}) =>
                                pixiDungeonView.getTileGroup(x, y).update()
                            );
                        }
                    }),
                ];
                if(creature.getFaction() === 'Player') {
                    const viewportAnimation = new Animation();
                    viewportAnimation.advance = () => {
                        const {x, y} = group();
                        const tileWidth = pixiDungeonView.getTileWidth();
                        pixiDungeonView.moveViewport(to.x, to.y, x % tileWidth, y % tileWidth);
                    };
                    animations.push(viewportAnimation);
                }
                return new AnimationGroup(animations);
            } else {
                // TODO: Extract slide animation to an animation factory.
                // Use a fast slide for Leap ability
                pixiDungeonView.updateCreatureLocations();
                pixiDungeonView.moveViewport();
                [gameEvent.getFromCoords(), gameEvent.getToCoords()].forEach(({x, y}) =>
                    pixiDungeonView.getTileGroup(x, y).update()
                );
            }
        } else if(gameEvent instanceof GameEvents.AttackEvent) {
            const weapon = gameEvent.getWeapon();
            if(weapon.isMelee()) {
                // TODO: Slash animation
            } else {
                let spriteName;
                if(weapon instanceof Weapons.Shortbow || weapon instanceof Weapons.Longbow) {
                    spriteName = 'Arrow';
                } else {
                    spriteName = 'Bullet';
                }
                return new ProjectileAnimation(15, {
                    pixiDungeonView,
                    sprite: buildSprite(spriteName),
                    dungeon,
                    attacker: gameEvent.getAttacker(),
                    target: gameEvent.getTarget(),
                });
            }
        } else if(gameEvent instanceof GameEvents.AbilityEvent) {
            const ability = gameEvent.getAbility();
            const creature = gameEvent.getCreature();
            const tile = gameEvent.getTile();
            if(ability instanceof Abilities.Fireball) {
                const EXPAND_FRAMES = 25;
                const FADE_FRAMES = 15;
                const MAX_SCALE = 2.5;
                const sprite = new Sprite(TextureCache['Fireball']);
                sprite.x = (tile.getX() + .5) * TILE_WIDTH;
                sprite.y = (tile.getY() + .5) * TILE_WIDTH;
                sprite.width = TILE_WIDTH;
                sprite.height = TILE_WIDTH;
                sprite.anchor.x = sprite.anchor.y = .5;
                sprite.scale = 0;
                return new Animation(EXPAND_FRAMES + FADE_FRAMES, {
                    onStart: () => stage.addChild(sprite),
                    onEnd: () => stage.removeChild(sprite),
                    advance: (delta, cumulativeTime) => {
                        if(cumulativeTime < EXPAND_FRAMES) {
                            const scale = MAX_SCALE * Easings.easeIn(cumulativeTime / EXPAND_FRAMES);
                            sprite.scale.set(scale, scale);
                        } else {
                            sprite.scale.set(MAX_SCALE, MAX_SCALE);
                            sprite.alpha = 1 - (cumulativeTime - EXPAND_FRAMES) / FADE_FRAMES;
                        }
                    }
                });
            } else if(ability instanceof Abilities.ForceDart) {
                return new ProjectileAnimation(15, {
                    pixiDungeonView,
                    sprite: buildSprite('ForceDart'),
                    dungeon,
                    attacker: creature,
                    target: tile.getCreature(),
                });
            }
        } else if(gameEvent instanceof GameEvents.DeathEvent) {
            const creature = gameEvent.getCreature();
            const sprite = pixiDungeonView.getEntityById(creature.getId());
            return new Animation(20, {
                onEnd: () => sprite.parent.removeChild(sprite),
                advance: (delta, cumulativeTime, proportion) => {
                    sprite.alpha = 1 - Easings.linear(proportion);
                }
            });
        } else if(gameEvent instanceof GameEvents.ZeroDamageEvent) {
            const creature = gameEvent.getCreature();
            const cause = gameEvent.getCause();
            const tile = dungeon.getTile(creature);
            const x = tile.getX();
            const y = tile.getY();

            if((cause instanceof Weapons.FrostDagger || cause instanceof Weapons.LightningRod)) {
                return;
            }

            const text = new PIXI.Text('Blocked', {
                fontFamily: 'Arial',
                fontSize: 10,
                fill: 'white',
                stroke: 'black',
                strokeThickness: 2
            });
            text.x = (x + .1) * TILE_WIDTH;
            const startY = (y + .5) * TILE_WIDTH;
            const endY = y * TILE_WIDTH;
            text.y = startY;
            pixiDungeonView.addParticle(text);

            return new Animation(40, {
                onStart: () => pixiDungeonView.addParticle(text),
                onEnd: () => text.parent.removeChild(text),
                advance: (delta, cumulativeTime, proportion) => {
                    text.y = (endY - startY) * Easings.linear(proportion) + startY;
                }
            });
        } else if(gameEvent instanceof GameEvents.HitpointsEvent) {
            const amount = gameEvent.getAmount();
            const damageType = gameEvent.getDamageType();
            const creature = gameEvent.getCreature();
            const cause = gameEvent.getCause();
            const tile = dungeon.getTile(creature);
            const x = tile.getX();
            const y = tile.getY();

            const hpAnimation = new Animation(0);
            hpAnimation.onStart = () => pixiDungeonView.setCreatureHpBarWidth(
                creature.getId(),
                Math.max(0, creature.getCurrentHP()) / creature.getBaseHP()
            );
            hpAnimation.advance = () => {};

            const animations = [hpAnimation];

            if(amount < 0) {
                const isShifted = (cause instanceof Weapons.FrostDagger || cause instanceof Weapons.LightningRod) &&
                        (damageType === DamageTypes.MELEE_PHYSICAL || damageType === DamageTypes.RANGED_PHYSICAL);
                animations.push(new FloatingTextAnimation(pixiDungeonView, new PIXI.Text(amount, {
                    fontFamily: 'Arial',
                    fontSize: 20,
                    fill: DAMAGE_COLORS[damageType],
                    stroke: DAMAGE_OUTLINE_COLORS[damageType],
                    strokeThickness: 2
                }), {
                    x,
                    y,
                    xOffset: isShifted ? .4 : 0
                }));
            }

            return new AnimationGroup(animations);
        } else if(gameEvent instanceof GameEvents.VisibilityChangeEvent) {
            const newlyHiddenTileCoords = gameEvent.getNewlyHiddenTileCoords();
            const newlyVisibleTileCoords = gameEvent.getNewlyVisibleTileCoords();
            const animation = new AnimationGroup(
                newlyHiddenTileCoords.map(({x, y}) => new TransitionAnimation(20, {
                    group: pixiDungeonView.getTileGroup(x, y),
                    properties: {alpha: {start: 1, end: .5}}
                })).concat(newlyVisibleTileCoords.map(({x, y}) => {
                    const tileGroup = pixiDungeonView.getTileGroup(x, y);
                    return new TransitionAnimation(20, {
                        group: tileGroup,
                        properties: {alpha: {start: () => tileGroup.alpha, end: 1}}
                    });
                }))
            );
            animation.onStart = () => {
                // TODO: Make this stateless. Don't require VisibilityChangeEvent. Look at movement-type Events
                newlyHiddenTileCoords.forEach(({x, y}) => {
                    pixiDungeonView.getItemContainer(x, y).visible = false;
                    pixiDungeonView.getCreatureContainer(x, y).visible = false;
                });
                newlyVisibleTileCoords.forEach(({x, y}) => {
                    pixiDungeonView.getItemContainer(x, y).visible = true;
                    pixiDungeonView.getCreatureContainer(x, y).visible = true;
                });
            };
            return animation;
        } else if(gameEvent instanceof GameEvents.HumanToMoveEvent) {
            return new AnimationGroup(
                dungeon.getCreatures().map(creature => {
                    const speedAnimation = new Animation(0);
                    speedAnimation.onStart = () => pixiDungeonView.setCreatureSpeedBarWidth(
                        creature.getId(),
                        creature.getTimeToNextMove() / creature.getSpeed()
                    );
                    speedAnimation.advance = () => {};
                    return speedAnimation;
                })
            );
        } else if(gameEvent instanceof GameEvents.SpawnEvent) {
            // TODO: Also place creature from here rather than handling it entirely in the PixiDungeonView
            const creature = gameEvent.getCreature();

            return new SingleFrameAnimation(() => {
                pixiDungeonView.moveCreatureGroup(creature, gameEvent.getX(), gameEvent.getY());
                pixiDungeonView.setCreatureHpBarWidth(
                    creature.getId(),
                    Math.max(0, creature.getCurrentHP()) / creature.getBaseHP()
                );
            });
        } else if(gameEvent instanceof GameEvents.TakeItemEvent) {
            return new SingleFrameAnimation(() => {
                pixiDungeonView.removeEntityById(gameEvent.getItem().getId());
            });
        } else if(gameEvent instanceof GameEvents.ItemDropEvent) {
            const tile = gameEvent.getTile();
            const item = gameEvent.getItem();
            return new SingleFrameAnimation(() => {
                pixiDungeonView.moveItemGroup(item, tile.getX(), tile.getY());
            });
        } else if(gameEvent instanceof GameEvents.BuffAppliedEvent) {
            const creature = gameEvent.getCreature();
            const buff = gameEvent.getBuff();
            return new SingleFrameAnimation(() => {
                pixiDungeonView.addStatusIcon(creature.getId(), buff.getId(), buff.toString());
            });
        } else if(gameEvent instanceof GameEvents.BuffEndedEvent) {
            const creature = gameEvent.getCreature();
            const buff = gameEvent.getBuff();
            return new SingleFrameAnimation(() => {
                pixiDungeonView.removeStatusIcon(creature.getId(), buff.getId());
            });
        }
    }
}<commit_msg>Dead code removal<commit_after>import Abilities from '../abilities/Abilities';
import GameEvents from '../events/GameEvents';
import Moves from '../entities/creatures/moves/Moves';
import Effects from '../effects/Effects';
import Weapons from '../entities/weapons/Weapons';
import DamageTypes from '../entities/DamageTypes';

import Animation from './animations/Animation';
import AnimationGroup from './animations/AnimationGroup';
import FloatingTextAnimation from './animations/FloatingTextAnimation';
import ProjectileAnimation from './animations/ProjectileAnimation';
import SingleFrameAnimation from './animations/SingleFrameAnimation';
import TransitionAnimation from './animations/TransitionAnimation';

const PIXI = require('pixi.js');
const TextureCache = (PIXI.utils.TextureCache);
const Sprite = PIXI.Sprite;

const DAMAGE_COLORS = {
    [DamageTypes.MELEE_PHYSICAL]: 'darkred',
    [DamageTypes.RANGED_PHYSICAL]: 'darkred',
    [DamageTypes.FIRE]: 'orange',
    [DamageTypes.COLD]: 'darkblue',
    [DamageTypes.ELECTRICAL]: 'yellow',
    [DamageTypes.ENERGY]: 'white',
    [DamageTypes.POISON]: 'emerald'
};

const DAMAGE_OUTLINE_COLORS = {
    [DamageTypes.MELEE_PHYSICAL]: 'pink',
    [DamageTypes.RANGED_PHYSICAL]: 'pink',
    [DamageTypes.FIRE]: 'darkred',
    [DamageTypes.COLD]: 'skyblue',
    [DamageTypes.ELECTRICAL]: 'orange',
    [DamageTypes.ENERGY]: 'yellow',
    [DamageTypes.POISON]: 'darkgreen'
};

// const MS_PER_TICK = 1;
// const PROJECTILE_SPEED = 10; // Tiles per second

const TILE_WIDTH = 50; // TODO: De-dupe

const Easings = {
    linear: t => t,
    easeIn: (t) => t*t
};

const buildSprite = (textureName) => {
    const sprite = new Sprite(TextureCache[textureName]);
    sprite.width = TILE_WIDTH;
    sprite.height = TILE_WIDTH;
    sprite.anchor.x = sprite.anchor.y = .5;
    return sprite;
};

export default class DefaultPixiAnimationPack {
    getAnimation(sharedData, pixiDungeonView, gameEvent) {
        const dungeon = sharedData.getDungeon();
        const stage = pixiDungeonView.getStage();
        if(gameEvent instanceof GameEvents.PositionChangeEvent) {
            const cause = gameEvent.getCause();
            if(cause instanceof Moves.MovementMove || cause instanceof Effects.KnockbackEffect) {
                const creature = gameEvent.getCreature();
                const duration = creature.getSpeed() * 60 / 1000;
                const from = gameEvent.getFromCoords();
                const to = gameEvent.getToCoords();
                const dx = to.x - from.x;
                const dy = to.y - from.y;
                const group = () => pixiDungeonView.getEntityById(creature.getId());
                const animations = [
                    new TransitionAnimation(duration, {
                        group,
                        properties: {
                            x: {start: -(dx * pixiDungeonView.getTileWidth()), end: 0},
                            y: {start: -(dy * pixiDungeonView.getTileWidth()), end: 0}
                        },
                        onStart: () => {
                            pixiDungeonView.moveCreatureGroup(creature, to.x, to.y);
                            [gameEvent.getFromCoords(), gameEvent.getToCoords()].forEach(({x, y}) =>
                                pixiDungeonView.getTileGroup(x, y).update()
                            );
                        }
                    }),
                ];
                if(creature.getFaction() === 'Player') {
                    const viewportAnimation = new Animation();
                    viewportAnimation.advance = () => {
                        const {x, y} = group();
                        const tileWidth = pixiDungeonView.getTileWidth();
                        pixiDungeonView.moveViewport(to.x, to.y, x % tileWidth, y % tileWidth);
                    };
                    animations.push(viewportAnimation);
                }
                return new AnimationGroup(animations);
            } else {
                // TODO: Extract slide animation to an animation factory.
                // Use a fast slide for Leap ability
                pixiDungeonView.updateCreatureLocations();
                pixiDungeonView.moveViewport();
                [gameEvent.getFromCoords(), gameEvent.getToCoords()].forEach(({x, y}) =>
                    pixiDungeonView.getTileGroup(x, y).update()
                );
            }
        } else if(gameEvent instanceof GameEvents.AttackEvent) {
            const weapon = gameEvent.getWeapon();
            if(weapon.isMelee()) {
                // TODO: Slash animation
            } else {
                let spriteName;
                if(weapon instanceof Weapons.Shortbow || weapon instanceof Weapons.Longbow) {
                    spriteName = 'Arrow';
                } else {
                    spriteName = 'Bullet';
                }
                return new ProjectileAnimation(15, {
                    pixiDungeonView,
                    sprite: buildSprite(spriteName),
                    dungeon,
                    attacker: gameEvent.getAttacker(),
                    target: gameEvent.getTarget(),
                });
            }
        } else if(gameEvent instanceof GameEvents.AbilityEvent) {
            const ability = gameEvent.getAbility();
            const creature = gameEvent.getCreature();
            const tile = gameEvent.getTile();
            if(ability instanceof Abilities.Fireball) {
                const EXPAND_FRAMES = 25;
                const FADE_FRAMES = 15;
                const MAX_SCALE = 2.5;
                const sprite = new Sprite(TextureCache['Fireball']);
                sprite.x = (tile.getX() + .5) * TILE_WIDTH;
                sprite.y = (tile.getY() + .5) * TILE_WIDTH;
                sprite.width = TILE_WIDTH;
                sprite.height = TILE_WIDTH;
                sprite.anchor.x = sprite.anchor.y = .5;
                sprite.scale = 0;
                return new Animation(EXPAND_FRAMES + FADE_FRAMES, {
                    onStart: () => stage.addChild(sprite),
                    onEnd: () => stage.removeChild(sprite),
                    advance: (delta, cumulativeTime) => {
                        if(cumulativeTime < EXPAND_FRAMES) {
                            const scale = MAX_SCALE * Easings.easeIn(cumulativeTime / EXPAND_FRAMES);
                            sprite.scale.set(scale, scale);
                        } else {
                            sprite.scale.set(MAX_SCALE, MAX_SCALE);
                            sprite.alpha = 1 - (cumulativeTime - EXPAND_FRAMES) / FADE_FRAMES;
                        }
                    }
                });
            } else if(ability instanceof Abilities.ForceDart) {
                return new ProjectileAnimation(15, {
                    pixiDungeonView,
                    sprite: buildSprite('ForceDart'),
                    dungeon,
                    attacker: creature,
                    target: tile.getCreature(),
                });
            }
        } else if(gameEvent instanceof GameEvents.DeathEvent) {
            const creature = gameEvent.getCreature();
            const sprite = pixiDungeonView.getEntityById(creature.getId());
            return new Animation(20, {
                onEnd: () => sprite.parent.removeChild(sprite),
                advance: (delta, cumulativeTime, proportion) => {
                    sprite.alpha = 1 - Easings.linear(proportion);
                }
            });
        } else if(gameEvent instanceof GameEvents.ZeroDamageEvent) {
            const creature = gameEvent.getCreature();
            const cause = gameEvent.getCause();
            const tile = dungeon.getTile(creature);
            const x = tile.getX();
            const y = tile.getY();

            if((cause instanceof Weapons.FrostDagger || cause instanceof Weapons.LightningRod)) {
                return;
            }

            const text = new PIXI.Text('Blocked', {
                fontFamily: 'Arial',
                fontSize: 10,
                fill: 'white',
                stroke: 'black',
                strokeThickness: 2
            });
            text.x = (x + .1) * TILE_WIDTH;
            const startY = (y + .5) * TILE_WIDTH;
            const endY = y * TILE_WIDTH;
            text.y = startY;
            pixiDungeonView.addParticle(text);

            return new Animation(40, {
                onStart: () => pixiDungeonView.addParticle(text),
                onEnd: () => text.parent.removeChild(text),
                advance: (delta, cumulativeTime, proportion) => {
                    text.y = (endY - startY) * Easings.linear(proportion) + startY;
                }
            });
        } else if(gameEvent instanceof GameEvents.HitpointsEvent) {
            const amount = gameEvent.getAmount();
            const damageType = gameEvent.getDamageType();
            const creature = gameEvent.getCreature();
            const cause = gameEvent.getCause();
            const tile = dungeon.getTile(creature);
            const x = tile.getX();
            const y = tile.getY();

            const hpAnimation = new Animation(0);
            hpAnimation.onStart = () => pixiDungeonView.setCreatureHpBarWidth(
                creature.getId(),
                Math.max(0, creature.getCurrentHP()) / creature.getBaseHP()
            );
            hpAnimation.advance = () => {};

            const animations = [hpAnimation];

            if(amount < 0) {
                const isShifted = (cause instanceof Weapons.FrostDagger || cause instanceof Weapons.LightningRod) &&
                        (damageType === DamageTypes.MELEE_PHYSICAL || damageType === DamageTypes.RANGED_PHYSICAL);
                animations.push(new FloatingTextAnimation(pixiDungeonView, new PIXI.Text(amount, {
                    fontFamily: 'Arial',
                    fontSize: 20,
                    fill: DAMAGE_COLORS[damageType],
                    stroke: DAMAGE_OUTLINE_COLORS[damageType],
                    strokeThickness: 2
                }), {
                    x,
                    y,
                    xOffset: isShifted ? .4 : 0
                }));
            }

            return new AnimationGroup(animations);
        } else if(gameEvent instanceof GameEvents.VisibilityChangeEvent) {
            const newlyHiddenTileCoords = gameEvent.getNewlyHiddenTileCoords();
            const newlyVisibleTileCoords = gameEvent.getNewlyVisibleTileCoords();
            const animation = new AnimationGroup(
                newlyHiddenTileCoords.map(({x, y}) => new TransitionAnimation(20, {
                    group: pixiDungeonView.getTileGroup(x, y),
                    properties: {alpha: {start: 1, end: .5}}
                })).concat(newlyVisibleTileCoords.map(({x, y}) => {
                    const tileGroup = pixiDungeonView.getTileGroup(x, y);
                    return new TransitionAnimation(20, {
                        group: tileGroup,
                        properties: {alpha: {start: () => tileGroup.alpha, end: 1}}
                    });
                }))
            );
            animation.onStart = () => {
                // TODO: Make this stateless. Don't require VisibilityChangeEvent. Look at movement-type Events
                newlyHiddenTileCoords.forEach(({x, y}) => {
                    pixiDungeonView.getItemContainer(x, y).visible = false;
                    pixiDungeonView.getCreatureContainer(x, y).visible = false;
                });
                newlyVisibleTileCoords.forEach(({x, y}) => {
                    pixiDungeonView.getItemContainer(x, y).visible = true;
                    pixiDungeonView.getCreatureContainer(x, y).visible = true;
                });
            };
            return animation;
        } else if(gameEvent instanceof GameEvents.HumanToMoveEvent) {
            return new AnimationGroup(
                dungeon.getCreatures().map(creature => {
                    const speedAnimation = new Animation(0);
                    speedAnimation.onStart = () => pixiDungeonView.setCreatureSpeedBarWidth(
                        creature.getId(),
                        creature.getTimeToNextMove() / creature.getSpeed()
                    );
                    speedAnimation.advance = () => {};
                    return speedAnimation;
                })
            );
        } else if(gameEvent instanceof GameEvents.SpawnEvent) {
            // TODO: Also place creature from here rather than handling it entirely in the PixiDungeonView
            const creature = gameEvent.getCreature();

            return new SingleFrameAnimation(() => {
                pixiDungeonView.moveCreatureGroup(creature, gameEvent.getX(), gameEvent.getY());
                pixiDungeonView.setCreatureHpBarWidth(
                    creature.getId(),
                    Math.max(0, creature.getCurrentHP()) / creature.getBaseHP()
                );
            });
        } else if(gameEvent instanceof GameEvents.TakeItemEvent) {
            return new SingleFrameAnimation(() => {
                pixiDungeonView.removeEntityById(gameEvent.getItem().getId());
            });
        } else if(gameEvent instanceof GameEvents.ItemDropEvent) {
            const tile = gameEvent.getTile();
            const item = gameEvent.getItem();
            return new SingleFrameAnimation(() => {
                pixiDungeonView.moveItemGroup(item, tile.getX(), tile.getY());
            });
        } else if(gameEvent instanceof GameEvents.BuffAppliedEvent) {
            const creature = gameEvent.getCreature();
            const buff = gameEvent.getBuff();
            return new SingleFrameAnimation(() => {
                pixiDungeonView.addStatusIcon(creature.getId(), buff.getId(), buff.toString());
            });
        } else if(gameEvent instanceof GameEvents.BuffEndedEvent) {
            const creature = gameEvent.getCreature();
            const buff = gameEvent.getBuff();
            return new SingleFrameAnimation(() => {
                pixiDungeonView.removeStatusIcon(creature.getId(), buff.getId());
            });
        }
    }
}<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var sys = require('sys');
var net = require('net');
var NUL = '\0';

var chars = Buffer('RSKZQCTD','utf8');
var UTF8 = {
  R: chars[0],
  S: chars[1],
  K: chars[2],
  Z: chars[3],
  Q: chars[4],
  C: chars[5],
  T: chars[6],
  D: chars[7]
};

var Client = function(config) {
  EventEmitter.call(this);
  config = config || {};
  this.user = config.user;
  this.database = config.database;
  this.port = config.port || 5432;
  this.queryQueue = [];
};
sys.inherits(Client, EventEmitter);

Client.prototype.connect = function() {
  var con = net.createConnection(this.port);
  var self = this;
  con.on('connect', function() {
    var data = ['user',self.user,'database', self.database,NUL].join(NUL);
    var dataBuffer = Buffer(data);
    var fullBuffer = Buffer(8 + dataBuffer.length);
    fullBuffer[0] = fullBuffer.length >>> 24;
    fullBuffer[1] = fullBuffer.length >>> 16;
    fullBuffer[2] = fullBuffer.length >>> 8;
    fullBuffer[3] = fullBuffer.length >>> 0;
    fullBuffer[4] = 0;
    fullBuffer[5] = 3;
    fullBuffer[6] = 0;
    fullBuffer[7] = 0;
    fullBuffer.write(data,8);
    con.write(fullBuffer);
  });
  con.on('data', function(data) {
    var parser = new Parser(data);
    var result = parser.parse();
    result.forEach(function(msg) {
      self.emit('message', msg);
      self.emit(msg.name, msg);
    });
  });
  this.con = con;
  this.on('ReadyForQuery', function() {
    self.readyForQuery = true;
    self.pulseQueryQueue();
  });
  this.on('message', function(msg) {
    console.log(msg.name);
  });
};

Client.prototype.query = function(queryText) {
  this.queryQueue.push(new Query({
    text: queryText
  }));
  this.pulseQueryQueue();
};

Client.prototype.pulseQueryQueue = function() {
  if(!this.readyForQuery) {
    return;
  }
  var query = this.queryQueue.shift();
  if(!query) {
    return;
  }
  var txt = query.text + "\0"
  var queryTextBuffer = Buffer(txt);
  var len = queryTextBuffer.length+4;
  var messageBuffer = Buffer(queryTextBuffer.length + 5);
  messageBuffer[0] = UTF8.Q;
  messageBuffer[1] = len >>> 24;
  messageBuffer[2] = len >>> 16;
  messageBuffer[3] = len >>> 8;
  messageBuffer[4] = len >>> 0;
  messageBuffer.write(txt,5);
  this.con.write(messageBuffer);
  this.readyForQuery = false;
};

var Query = function(config) {
  this.text = config.text;
};

var Parser = function(buffer) {
  this.offset = 0;
  this.buffer = buffer;
};

var p = Parser.prototype;

p.parse = function() {
  var messages = [];
  var message = this.parseMessage();
  while(message) {
    messages.push(message);
    message = this.parseMessage();
  }
  return messages;
};
p.parseMessage =  function() {
  if(this.buffer.length == this.offset) {
    return false;
  }
  var messageID = this.buffer[this.offset];
  switch(messageID) {
  case UTF8.R:
    return this.parseR();
  case UTF8.S:
    return this.parseS();
  case UTF8.K:
    return this.parseK();
  case UTF8.Z:
    return this.parseZ();
  case UTF8.C:
    return this.parseC();
  case UTF8.T:
    return this.parseT();
  case UTF8.D:
    return this.parseD();
  default:
    throw new Error("Unsupported message ID: " + Buffer([messageID]).toString('utf8') + " (" + messageID.toString(16) + ")");
  }
};

p.parseR = function() {
  var type = this.buffer[this.offset++];
  var length = this.parseLength();
  if(length == 8) {
    this.offset += 4;
    return {
      name: 'AuthenticationOk',
      id: 'R',
      length: length
    }
  }p
  throw new Error("Unknown AuthenticatinOk message type");
};

p.parseS = function(buffer) {
  var msg = this.parseStart('ParameterStatus');
  msg.parameterName = this.parseCString();
  msg.parameterValue = this.parseCString();
  return msg;
};

p.parseK = function() {
  var msg = this.parseStart('BackendKeyData');
  msg.processID = this.readInt32();
  msg.secretKey = this.readInt32();
  return msg;
};

p.parseC = function() {
  var msg = this.parseStart('CommandComplete');
  msg.text = this.parseCString();
  return msg;
};

//parses common start of message packets
p.parseStart = function(name) {
  return {
    name: name,
    id: this.readChar(),
    length: this.readInt32()
  }
};

p.readChar = function() {
  return Buffer([this.buffer[this.offset++]]).toString('utf8');
};

p.parseZ = function() {
  var msg = this.parseStart('ReadyForQuery');
  msg.status = this.readChar();
  return msg;
};

p.parseT = function() {
  var msg = this.parseStart('RowDescription');
  msg.fieldCount = this.readInt16();
  var fields = [];
  for(var i = 0; i < msg.fieldCount; i++){
    fields[i] = this.parseField();
  }
  msg.fields = fields;
  return msg;
};

p.parseField = function() {
  var row = {
    name: this.parseCString(),
    tableID: this.readInt32(),
    columnID: this.readInt16(),
    dataType: this.readInt32(),
    dataTypeSize: this.readInt16(),
    dataTypeModifier: this.readInt32(),
    format: this.readInt16() == 0 ? 'text' : 'binary'
  };
  return row;
};

p.parseD = function() {
  var msg = this.parseStart('DataRow');
  msg.fieldCount = this.readInt16();
};

p.readInt32 = function() {
  var buffer = this.buffer;
  return ((buffer[this.offset++] << 24) +
          (buffer[this.offset++] << 16) +
          (buffer[this.offset++] << 8) +
          buffer[this.offset++]);
};

p.readInt16 = function() {
  return ((this.buffer[this.offset++] << 8) + (this.buffer[this.offset++] << 0));
};

p.parseLength =  function() {
  return this.readInt32();
};

p.parseCString = function(buffer) {
  var start = this.offset;
  while(this.buffer[this.offset++]) { };
  return this.buffer.toString('utf8',start, this.offset - 1);
};

module.exports = {
  Client: Client,
  Parser: Parser
};
<commit_msg>code cleanup<commit_after>var EventEmitter = require('events').EventEmitter;
var sys = require('sys');
var net = require('net');

var NUL = '\0';
var chars = ['R','S','K','Z','Q','C','T','D'];
var charBuff = Buffer(chars.join(''),'utf8');
var UTF8 = {};
for(var i = 0; i < charBuff.length; i++){
  var char = chars[i];
  UTF8[char] = charBuff[i];
};

var Client = function(config) {
  EventEmitter.call(this);
  config = config || {};
  this.user = config.user;
  this.database = config.database;
  this.port = config.port || 5432;
  this.queryQueue = [];
};

sys.inherits(Client, EventEmitter);

Client.prototype.connect = function() {
  var con = net.createConnection(this.port);
  var self = this;
  con.on('connect', function() {
    var data = ['user',self.user,'database', self.database,NUL].join(NUL);
    var dataBuffer = Buffer(data);
    var fullBuffer = Buffer(8 + dataBuffer.length);
    fullBuffer[0] = fullBuffer.length >>> 24;
    fullBuffer[1] = fullBuffer.length >>> 16;
    fullBuffer[2] = fullBuffer.length >>> 8;
    fullBuffer[3] = fullBuffer.length >>> 0;
    fullBuffer[4] = 0;
    fullBuffer[5] = 3;
    fullBuffer[6] = 0;
    fullBuffer[7] = 0;
    fullBuffer.write(data,8);
    con.write(fullBuffer);
  });
  con.on('data', function(data) {
    var parser = new Parser(data);
    var result = parser.parse();
    result.forEach(function(msg) {
      self.emit('message', msg);
      self.emit(msg.name, msg);
    });
  });
  this.con = con;
  this.on('ReadyForQuery', function() {
    self.readyForQuery = true;
    self.pulseQueryQueue();
  });
  this.on('message', function(msg) {
    console.log(msg.name);
  });
};

Client.prototype.query = function(queryText) {
  this.queryQueue.push(new Query({
    text: queryText
  }));
  this.pulseQueryQueue();
};

Client.prototype.pulseQueryQueue = function() {
  if(!this.readyForQuery) {
    return;
  }
  var query = this.queryQueue.shift();
  if(!query) {
    return;
  }
  var txt = query.text + "\0"
  var queryTextBuffer = Buffer(txt);
  var len = queryTextBuffer.length+4;
  var messageBuffer = Buffer(queryTextBuffer.length + 5);
  messageBuffer[0] = UTF8.Q;
  messageBuffer[1] = len >>> 24;
  messageBuffer[2] = len >>> 16;
  messageBuffer[3] = len >>> 8;
  messageBuffer[4] = len >>> 0;
  messageBuffer.write(txt,5);
  this.con.write(messageBuffer);
  this.readyForQuery = false;
};

var Query = function(config) {
  this.text = config.text;
};

var Parser = function(buffer) {
  this.offset = 0;
  this.buffer = buffer;
};

var p = Parser.prototype;

p.parse = function() {
  var messages = [];
  var message = this.parseMessage();
  while(message) {
    messages.push(message);
    message = this.parseMessage();
  }
  return messages;
};

p.parseMessage =  function() {
  if(this.buffer.length == this.offset) {
    return false;
  }
  var messageID = this.buffer[this.offset];
  switch(messageID) {
  case UTF8.R:
    return this.parseR();
  case UTF8.S:
    return this.parseS();
  case UTF8.K:
    return this.parseK();
  case UTF8.Z:
    return this.parseZ();
  case UTF8.C:
    return this.parseC();
  case UTF8.T:
    return this.parseT();
  case UTF8.D:
    return this.parseD();
  default:
    throw new Error("Unsupported message ID: " + Buffer([messageID]).toString('utf8') + " (" + messageID.toString(16) + ")");
  }
};

p.parseR = function() {
  var type = this.buffer[this.offset++];
  var length = this.parseLength();
  if(length == 8) {
    this.offset += 4;
    return {
      name: 'AuthenticationOk',
      id: 'R',
      length: length
    }
  }p
  throw new Error("Unknown AuthenticatinOk message type");
};

p.parseS = function(buffer) {
  var msg = this.parseStart('ParameterStatus');
  msg.parameterName = this.parseCString();
  msg.parameterValue = this.parseCString();
  return msg;
};

p.parseK = function() {
  var msg = this.parseStart('BackendKeyData');
  msg.processID = this.readInt32();
  msg.secretKey = this.readInt32();
  return msg;
};

p.parseC = function() {
  var msg = this.parseStart('CommandComplete');
  msg.text = this.parseCString();
  return msg;
};

//parses common start of message packets
p.parseStart = function(name) {
  return {
    name: name,
    id: this.readChar(),
    length: this.readInt32()
  }
};

p.readChar = function() {
  return Buffer([this.buffer[this.offset++]]).toString('utf8');
};

p.parseZ = function() {
  var msg = this.parseStart('ReadyForQuery');
  msg.status = this.readChar();
  return msg;
};

p.parseT = function() {
  var msg = this.parseStart('RowDescription');
  msg.fieldCount = this.readInt16();
  var fields = [];
  for(var i = 0; i < msg.fieldCount; i++){
    fields[i] = this.parseField();
  }
  msg.fields = fields;
  return msg;
};

p.parseField = function() {
  var row = {
    name: this.parseCString(),
    tableID: this.readInt32(),
    columnID: this.readInt16(),
    dataType: this.readInt32(),
    dataTypeSize: this.readInt16(),
    dataTypeModifier: this.readInt32(),
    format: this.readInt16() == 0 ? 'text' : 'binary'
  };
  return row;
};

p.parseD = function() {
  var msg = this.parseStart('DataRow');
  msg.fieldCount = this.readInt16();
};

p.readInt32 = function() {
  var buffer = this.buffer;
  return ((buffer[this.offset++] << 24) +
          (buffer[this.offset++] << 16) +
          (buffer[this.offset++] << 8) +
          buffer[this.offset++]);
};

p.readInt16 = function() {
  return ((this.buffer[this.offset++] << 8) + (this.buffer[this.offset++] << 0));
};

p.parseLength =  function() {
  return this.readInt32();
};

p.parseCString = function(buffer) {
  var start = this.offset;
  while(this.buffer[this.offset++]) { };
  return this.buffer.toString('utf8',start, this.offset - 1);
};

module.exports = {
  Client: Client,
  Parser: Parser
};
<|endoftext|>