<commit_before>import Thread from './utils/thread';
import Scene from './scene';
import Geo from './geo';

// Exports must appear outside a function, but will only be defined in main thread (below)
export var LeafletLayer;
export function leafletLayer(options) {
    return extendLeaflet(options);
}

function extendLeaflet(options) {

    // If LeafletLayer is already defined when this is called just return that immediately
    // e.g. if you call leafletLayer multiple times (which is valid)
    if (typeof LeafletLayer !== 'undefined') {
        return new LeafletLayer(options);
    }

    // Leaflet layer functionality is only defined in main thread
    if (Thread.is_main) {

        let L = options.leaflet || window.L;

        // Determine if we are extending the leaflet 0.7.x TileLayer class, or the newer
        // leaflet 1.x GridLayer class.
        let layerBaseClass = L.GridLayer ? L.GridLayer : L.TileLayer;
        let leafletVersion = layerBaseClass === L.GridLayer ? '1.x' : '0.7.x';
        let layerClassConfig = {};

        // If extending leaflet 0.7.x TileLayer, additional modifications are needed
        if (layerBaseClass === L.TileLayer) {
            layerClassConfig._addTile = function(){};
            layerClassConfig._removeTile = function(){};
            layerClassConfig._reset = function() {
                layerBaseClass.prototype._reset.apply(this, arguments);
                // re-add the canvas since base class `viewreset` event can remove it
                if (this.scene && this.scene.container && this.scene.canvas) {
                    this.scene.container.appendChild(this.scene.canvas);
                }
            };
        }

        // Define custom layer methods
        Object.assign(layerClassConfig, {

            initialize (options) {
                // Defaults
                options.showDebug = (!options.showDebug ? false : true);

                L.setOptions(this, options);
                this.createScene();
                this.hooks = {};
                this._updating_tangram = false;

                // Force leaflet zoom animations off
                this._zoomAnimated = false;
            },

            createScene () {
                this.scene = Scene.create(
                    this.options.scene,
                    {
                        numWorkers: this.options.numWorkers,
                        preUpdate: this.options.preUpdate,
                        postUpdate: this.options.postUpdate,
                        continuousZoom: (LeafletLayer.leafletVersion === '1.x'),
                        highDensityDisplay: this.options.highDensityDisplay,
                        logLevel: this.options.logLevel,
                        introspection: this.options.introspection,
                        // advanced option, app will have to manually called scene.update() per frame
                        disableRenderLoop: this.options.disableRenderLoop,
                        // advanced option, will require library to be served as same host as page
                        allowCrossDomainWorkers: this.options.allowCrossDomainWorkers
                    });
            },

            // Finish initializing scene and setup events when layer is added to map
            onAdd (map) {
                if (!this.scene) {
                    this.createScene();
                }

                layerBaseClass.prototype.onAdd.apply(this, arguments);

                this.hooks.resize = () => {
                    this._updating_tangram = true;
                    this.updateSize();
                    this._updating_tangram = false;
                };
                map.on('resize', this.hooks.resize);

                this.hooks.move = () => {
                    if (this._updating_tangram) {
                        return;
                    }

                    this._updating_tangram = true;
                    var view = map.getCenter();
                    view.zoom = Math.min(map.getZoom(), map.getMaxZoom() || Geo.default_view_max_zoom);

                    this.scene.view.setView(view);
                    this.scene.immediateRedraw();
                    this.reverseTransform();
                    this._updating_tangram = false;
                };
                map.on('move', this.hooks.move);

                this.hooks.zoomstart = () => {
                    if (this._updating_tangram) {
                        return;
                    }

                    this._updating_tangram = true;
                    this.scene.view.startZoom();
                    this._updating_tangram = false;
                };
                map.on('zoomstart', this.hooks.zoomstart);

                this.hooks.dragstart = () => {
                    this.scene.view.setPanning(true);
                };
                map.on('dragstart', this.hooks.dragstart);

                this.hooks.moveend = () => {
                    this.scene.view.setPanning(false);
                    this.scene.requestRedraw();
                };
                map.on('moveend', this.hooks.moveend);

                // Force leaflet zoom animations off
                map._zoomAnimated = false;

                // Modify default Leaflet behaviors
                this.modifyScrollWheelBehavior(map);
                this.modifyDoubleClickZoom(map);

                // Setup feature selection
                this.setupSelectionEventHandlers(map);
                this.setSelectionEvents(this.options.events);

                // Add GL canvas to layer container
                this.scene.container = this.getContainer();
                this.updateSize();

                // Initial view
                this.updateView();
                this.resizeOnFirstVisible();

                // Subscribe to tangram events
                this.scene.subscribe({
                    move: this.onTangramViewUpdate.bind(this)
                });

                // Use leaflet's existing event system as the callback mechanism
                this.scene.load(this.options.scene,
                    { config_path: this.options.sceneBasePath, blocking: false }).then(() => {

                    this._updating_tangram = true;

                    this.updateSize();
                    this.updateView();
                    this.reverseTransform();

                    this._updating_tangram = false;

                    this.fire('init');
                }).catch(error => {
                    this.fire('error', error);
                });
            },

            onRemove (map) {
                layerBaseClass.prototype.onRemove.apply(this, arguments);

                map.off('resize', this.hooks.resize);
                map.off('move', this.hooks.move);
                map.off('zoomstart', this.hooks.zoomstart);
                map.off('dragstart', this.hooks.dragstart);
                map.off('moveend', this.hooks.moveend);
                map.off('click', this.hooks.click);
                map.off('mousemove', this.hooks.mousemove);
                map.off('mouseout', this.hooks.mouseout);
                document.removeEventListener('visibilitychange', this.hooks.visibilitychange);
                this.hooks = {};

                if (this.scene) {
                    this.scene.destroy();
                    this.scene = null;
                }
            },

            createTile (coords) {
                var key = coords.x + '/' + coords.y + '/' + coords.z;
                var div = document.createElement('div');
                div.setAttribute('data-tile-key', key);
                div.style.width = '256px';
                div.style.height = '256px';

                if (this.options.showDebug) {
                    var debug_overlay = document.createElement('div');
                    debug_overlay.textContent = key;
                    debug_overlay.style.position = 'absolute';
                    debug_overlay.style.left = 0;
                    debug_overlay.style.top = 0;
                    debug_overlay.style.color = 'white';
                    debug_overlay.style.fontSize = '16px';
                    debug_overlay.style.textOutline = '1px #000000';
                    debug_overlay.style.padding = '8px';

                    div.appendChild(debug_overlay);
                    div.style.borderStyle = 'solid';
                    div.style.borderColor = 'white';
                    div.style.borderWidth = '1px';
                }

                return div;
            },

            // Modify leaflet's default scroll wheel behavior to render frames more frequently
            // (should generally lead to smoother scroll with Tangram frame re-render)
            modifyScrollWheelBehavior (map) {
                if (this.scene.view.continuous_zoom && map.scrollWheelZoom && this.options.modifyScrollWheel !== false) {
                    map.options.zoomSnap = 0;

                    const enabled = map.scrollWheelZoom.enabled();
                    map.scrollWheelZoom.disable();

                    // Chrome and Safari have smoother scroll-zoom without actively throttling the mouse wheel,
                    // while FF and Edge/IE do better with throttling.
                    // TODO: may be related to syncing differences with requestAnimationFrame loop, investigate further
                    if (L.Browser.chrome || L.Browser.safari) {
                        map.scrollWheelZoom._onWheelScroll = function (e) {
                            var delta = L.DomEvent.getWheelDelta(e);
                            this._delta += delta;
                            this._lastMousePos = this._map.mouseEventToContainerPoint(e);
                            this._performZoom();
                            L.DomEvent.stop(e);
                        };
                    }
                    else {
                        map.options.wheelDebounceTime = 20; // better default for FF and Edge/IE
                    }

                    map.scrollWheelZoom._performZoom = function () {
                        var map = this._map,
                            zoom = map.getZoom();

                        map._stop(); // stop panning and fly animations if any

                        var delta = this._delta / (this._map.options.wheelPxPerZoomLevel * 4);
                        this._delta = 0;

                        if ((zoom + delta) >= this._map.getMaxZoom()) {
                            delta = this._map.getMaxZoom() - zoom; // don't go past max zoom
                        }

                        if (!delta) { return; }

                        if (map.options.scrollWheelZoom === 'center') {
                            map.setZoom(zoom + delta);
                        } else {
                            map.setZoomAround(this._lastMousePos, zoom + delta);
                        }
                    };

                    if (enabled) {
                        map.scrollWheelZoom.enable();
                    }
                }
            },

            // Modify leaflet's default double-click zoom behavior, to match typical vector basemap products
            modifyDoubleClickZoom (map) {
                if (this.scene.view.continuous_zoom && map.doubleClickZoom && this.options.modifyDoubleClickZoom !== false) {

                    // Modified version of Leaflet's setZoomAround that doesn't trigger a moveEnd event
                    const setZoomAroundNoMoveEnd = function (map, latlng, zoom, options) {
                        var scale = map.getZoomScale(zoom),
                            viewHalf = map.getSize().divideBy(2),
                            containerPoint = latlng instanceof L.Point ? latlng : map.latLngToContainerPoint(latlng),

                            centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
                            newCenter = map.containerPointToLatLng(viewHalf.add(centerOffset));

                        return map._move(newCenter, zoom, { flyTo: true });
                    };

                    // Simplified version of Leaflet's flyTo, for short animations zooming around a point
                    const flyAround = function (map, targetCenter, targetZoom, options) {
                        options = options || {};
                        if (options.animate === false || !L.Browser.any3d) {
                            return map.setView(targetCenter, targetZoom, options);
                        }

                        map._stop();

                        var startZoom = map._zoom;

                        targetCenter = L.latLng(targetCenter);
                        targetZoom = targetZoom === undefined ? startZoom : targetZoom;
                        targetZoom = Math.min(targetZoom, map.getMaxZoom()); // don't go past max zoom

                        var from = map.project(map.getCenter(), startZoom),
                            to = map.project(targetCenter, startZoom);

                        var start = Date.now(),
                            duration = options.duration ? 1000 * options.duration : 75;

                        function frame() {
                            var t = (Date.now() - start) / duration;

                            if (t <= 1) {
                                // reuse internal flyTo frame to ensure these animations are canceled like others
                                map._flyToFrame = L.Util.requestAnimFrame(frame, map);

                                var center = from.add(to.subtract(from).multiplyBy(t));
                                center = [center.x, center.y];
                                center = Geo.metersToLatLng(center);
                                setZoomAroundNoMoveEnd(map, targetCenter, startZoom + (targetZoom - startZoom) * t);
                            } else {
                                setZoomAroundNoMoveEnd(map, targetCenter, targetZoom)
                                    ._moveEnd(true);
                            }
                        }

                        map._moveStart(true);

                        frame.call(map);
                        return map;
                    };

                    // Modify the double-click zoom handler to do a short zoom animation
                    const enabled = map.doubleClickZoom.enabled();
                    map.doubleClickZoom.disable();

                    map.doubleClickZoom._onDoubleClick = function (e) {
                        var map = this._map,
                            oldZoom = map.getZoom(),
                            delta = map.options.zoomDelta,
                            zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;

                        if (map.options.doubleClickZoom === 'center') {
                            flyAround(map, map.getCenter(), zoom);
                        } else {
                            flyAround(map, map.containerPointToLatLng(e.containerPoint), zoom);
                        }
                    };

                    if (enabled) {
                        map.doubleClickZoom.enable();
                    }
                }
            },

            updateView () {
                var view = this._map.getCenter();
                view.zoom = Math.min(this._map.getZoom(), this._map.getMaxZoom() || Geo.default_view_max_zoom);
                this.scene.view.setView(view);
            },

            updateSize () {
                var size = this._map.getSize();
                this.scene.resizeMap(size.x, size.y);
            },

            resizeOnFirstVisible () {
                let first_visibility = true;
                this.hooks.visibilitychange = () => {
                    if (first_visibility) {
                        first_visibility = false;
                        this.updateSize();
                    }
                };

                document.addEventListener('visibilitychange', this.hooks.visibilitychange);
            },

            onTangramViewUpdate () {
                if (!this._map || this._updating_tangram) {
                    return;
                }

                // View changed?
                let map_center = this._map.getCenter();
                let view_center = this.scene.view.center;
                if (map_center.lng === view_center.lng &&
                    map_center.lat === view_center.lat &&
                    this._map.getZoom() === this.scene.view.zoom) {
                    return;
                }

                this._updating_tangram = true;
                this._map.setView([this.scene.view.center.lat, this.scene.view.center.lng], this.scene.view.zoom, { animate: false });
                this.reverseTransform();
                this._updating_tangram = false;
            },

            render () {
                if (!this.scene) {
                    return;
                }
                this.scene.update();
            },

            // Reverse the CSS positioning Leaflet applies to the layer, since Tangram's WebGL canvas
            // is expected to be 'absolutely' positioned.
            reverseTransform () {
                if (!this._map || !this.scene || !this.scene.container) {
                    return;
                }

                var top_left = this._map.containerPointToLayerPoint([0, 0]);
                L.DomUtil.setPosition(this.scene.container, top_left);
            },

            // Tie Leaflet event handlers to Tangram feature selection
            setupSelectionEventHandlers (map) {
                this._selection_events = {};

                this.hooks.click = (event) => {
                    if (typeof this._selection_events.click === 'function') {
                        this.scene.getFeatureAt(event.containerPoint).
                            then(selection => {
                                let results = Object.assign({}, selection, { leaflet_event: event });
                                this._selection_events.click(results);
                            });
                    }
                };
                map.on('click', this.hooks.click);

                this.hooks.mousemove = (event) => {
                    if (typeof this._selection_events.hover === 'function') {
                        this.scene.getFeatureAt(event.containerPoint).
                            then(selection => {
                                let results = Object.assign({}, selection, { leaflet_event: event });
                                this._selection_events.hover(results);
                            });
                    }
                };
                map.on('mousemove', this.hooks.mousemove);

                this.hooks.mouseout = (event) => {
                    // When mouse leaves map, send an additional selection event to indicate no feature is selected
                    if (typeof this._selection_events.hover === 'function') {
                        this._selection_events.hover({ changed: true, leaflet_event: event });
                    }
                };
                map.on('mouseout', this.hooks.mouseout);
            },

            // Set user-defined handlers for feature selection events
            // Currently only one handler can be defined for each event type
            // Event types are: `click`, `hover` (leaflet `mousemove`)
            setSelectionEvents (events) {
                this._selection_events = Object.assign(this._selection_events, events);
            }

        });

        // Create the layer class
        LeafletLayer = layerBaseClass.extend(layerClassConfig);

        // Polyfill some 1.0 methods
        if (typeof LeafletLayer.remove !== 'function') {
            LeafletLayer.prototype.remove = function() {
                if (this._map) {
                    this._map.removeLayer(this);
                }
                this.fire('remove');
            };
        }

        LeafletLayer.layerBaseClass = layerBaseClass;
        LeafletLayer.leafletVersion = leafletVersion;

        return new LeafletLayer(options);
    }
}
<commit_msg>leaflet layer: fire viewreset event on zoom to keep other layers updated<commit_after>import Thread from './utils/thread';
import Scene from './scene';
import Geo from './geo';

// Exports must appear outside a function, but will only be defined in main thread (below)
export var LeafletLayer;
export function leafletLayer(options) {
    return extendLeaflet(options);
}

function extendLeaflet(options) {

    // If LeafletLayer is already defined when this is called just return that immediately
    // e.g. if you call leafletLayer multiple times (which is valid)
    if (typeof LeafletLayer !== 'undefined') {
        return new LeafletLayer(options);
    }

    // Leaflet layer functionality is only defined in main thread
    if (Thread.is_main) {

        let L = options.leaflet || window.L;

        // Determine if we are extending the leaflet 0.7.x TileLayer class, or the newer
        // leaflet 1.x GridLayer class.
        let layerBaseClass = L.GridLayer ? L.GridLayer : L.TileLayer;
        let leafletVersion = layerBaseClass === L.GridLayer ? '1.x' : '0.7.x';
        let layerClassConfig = {};
        let setZoomAroundNoMoveEnd; // alternate zoom function defined below

        // If extending leaflet 0.7.x TileLayer, additional modifications are needed
        if (layerBaseClass === L.TileLayer) {
            layerClassConfig._addTile = function(){};
            layerClassConfig._removeTile = function(){};
            layerClassConfig._reset = function() {
                layerBaseClass.prototype._reset.apply(this, arguments);
                // re-add the canvas since base class `viewreset` event can remove it
                if (this.scene && this.scene.container && this.scene.canvas) {
                    this.scene.container.appendChild(this.scene.canvas);
                }
            };
        }

        // Define custom layer methods
        Object.assign(layerClassConfig, {

            initialize (options) {
                // Defaults
                options.showDebug = (!options.showDebug ? false : true);

                L.setOptions(this, options);
                this.createScene();
                this.hooks = {};
                this._updating_tangram = false;
                this._zoomAnimated = false; // turn leaflet zoom animations off for this layer
            },

            createScene () {
                this.scene = Scene.create(
                    this.options.scene,
                    {
                        numWorkers: this.options.numWorkers,
                        preUpdate: this.options.preUpdate,
                        postUpdate: this.options.postUpdate,
                        continuousZoom: (LeafletLayer.leafletVersion === '1.x'),
                        highDensityDisplay: this.options.highDensityDisplay,
                        logLevel: this.options.logLevel,
                        introspection: this.options.introspection,
                        // advanced option, app will have to manually called scene.update() per frame
                        disableRenderLoop: this.options.disableRenderLoop,
                        // advanced option, will require library to be served as same host as page
                        allowCrossDomainWorkers: this.options.allowCrossDomainWorkers
                    });
            },

            // Finish initializing scene and setup events when layer is added to map
            onAdd (map) {
                if (!this.scene) {
                    this.createScene();
                }

                layerBaseClass.prototype.onAdd.apply(this, arguments);

                this.hooks.resize = () => {
                    this._updating_tangram = true;
                    this.updateSize();
                    this._updating_tangram = false;
                };
                map.on('resize', this.hooks.resize);

                this.hooks.move = () => {
                    if (this._updating_tangram) {
                        return;
                    }

                    this._updating_tangram = true;
                    var view = map.getCenter();
                    view.zoom = Math.min(map.getZoom(), map.getMaxZoom() || Geo.default_view_max_zoom);

                    this.scene.view.setView(view);
                    this.scene.immediateRedraw();
                    this.reverseTransform();
                    this._updating_tangram = false;
                };
                map.on('move', this.hooks.move);

                this.hooks.zoomstart = () => {
                    if (this._updating_tangram) {
                        return;
                    }

                    this._updating_tangram = true;
                    this.scene.view.startZoom();
                    this._updating_tangram = false;
                };
                map.on('zoomstart', this.hooks.zoomstart);

                this.hooks.dragstart = () => {
                    this.scene.view.setPanning(true);
                };
                map.on('dragstart', this.hooks.dragstart);

                this.hooks.moveend = () => {
                    this.scene.view.setPanning(false);
                    this.scene.requestRedraw();
                };
                map.on('moveend', this.hooks.moveend);

                // Modify default Leaflet behaviors
                this.modifyScrollWheelBehavior(map);
                this.modifyDoubleClickZoom(map);

                // Setup feature selection
                this.setupSelectionEventHandlers(map);
                this.setSelectionEvents(this.options.events);

                // Add GL canvas to layer container
                this.scene.container = this.getContainer();
                this.updateSize();

                // Initial view
                this.updateView();
                this.resizeOnFirstVisible();

                // Subscribe to tangram events
                this.scene.subscribe({
                    move: this.onTangramViewUpdate.bind(this)
                });

                // Use leaflet's existing event system as the callback mechanism
                this.scene.load(this.options.scene,
                    { config_path: this.options.sceneBasePath, blocking: false }).then(() => {

                    this._updating_tangram = true;

                    this.updateSize();
                    this.updateView();
                    this.reverseTransform();

                    this._updating_tangram = false;

                    this.fire('init');
                }).catch(error => {
                    this.fire('error', error);
                });
            },

            onRemove (map) {
                layerBaseClass.prototype.onRemove.apply(this, arguments);

                map.off('resize', this.hooks.resize);
                map.off('move', this.hooks.move);
                map.off('zoomstart', this.hooks.zoomstart);
                map.off('dragstart', this.hooks.dragstart);
                map.off('moveend', this.hooks.moveend);
                map.off('click', this.hooks.click);
                map.off('mousemove', this.hooks.mousemove);
                map.off('mouseout', this.hooks.mouseout);
                document.removeEventListener('visibilitychange', this.hooks.visibilitychange);
                this.hooks = {};

                if (this.scene) {
                    this.scene.destroy();
                    this.scene = null;
                }
            },

            createTile (coords) {
                var key = coords.x + '/' + coords.y + '/' + coords.z;
                var div = document.createElement('div');
                div.setAttribute('data-tile-key', key);
                div.style.width = '256px';
                div.style.height = '256px';

                if (this.options.showDebug) {
                    var debug_overlay = document.createElement('div');
                    debug_overlay.textContent = key;
                    debug_overlay.style.position = 'absolute';
                    debug_overlay.style.left = 0;
                    debug_overlay.style.top = 0;
                    debug_overlay.style.color = 'white';
                    debug_overlay.style.fontSize = '16px';
                    debug_overlay.style.textOutline = '1px #000000';
                    debug_overlay.style.padding = '8px';

                    div.appendChild(debug_overlay);
                    div.style.borderStyle = 'solid';
                    div.style.borderColor = 'white';
                    div.style.borderWidth = '1px';
                }

                return div;
            },

            // Modify leaflet's default scroll wheel behavior to render frames more frequently
            // (should generally lead to smoother scroll with Tangram frame re-render)
            modifyScrollWheelBehavior (map) {
                if (this.scene.view.continuous_zoom && map.scrollWheelZoom && this.options.modifyScrollWheel !== false) {
                    map.options.zoomSnap = 0;

                    const enabled = map.scrollWheelZoom.enabled();
                    map.scrollWheelZoom.disable();

                    // Chrome and Safari have smoother scroll-zoom without actively throttling the mouse wheel,
                    // while FF and Edge/IE do better with throttling.
                    // TODO: may be related to syncing differences with requestAnimationFrame loop, investigate further
                    if (L.Browser.chrome || L.Browser.safari) {
                        map.scrollWheelZoom._onWheelScroll = function (e) {
                            var delta = L.DomEvent.getWheelDelta(e);
                            this._delta += delta;
                            this._lastMousePos = this._map.mouseEventToContainerPoint(e);
                            this._performZoom();
                            L.DomEvent.stop(e);
                        };
                    }
                    else {
                        map.options.wheelDebounceTime = 20; // better default for FF and Edge/IE
                    }

                    map.scrollWheelZoom._performZoom = function () {
                        var map = this._map,
                            zoom = map.getZoom();

                        map._stop(); // stop panning and fly animations if any

                        var delta = this._delta / (this._map.options.wheelPxPerZoomLevel * 4);
                        this._delta = 0;

                        if ((zoom + delta) >= this._map.getMaxZoom()) {
                            delta = this._map.getMaxZoom() - zoom; // don't go past max zoom
                        }

                        if (!delta) { return; }

                        if (map.options.scrollWheelZoom === 'center') {
                            setZoomAroundNoMoveEnd(map, map.getCenter(), zoom + delta);
                        } else {
                            setZoomAroundNoMoveEnd(map, this._lastMousePos, zoom + delta);
                        }
                    };

                    if (enabled) {
                        map.scrollWheelZoom.enable();
                    }
                }
            },

            // Modify leaflet's default double-click zoom behavior, to match typical vector basemap products
            modifyDoubleClickZoom (map) {
                if (this.scene.view.continuous_zoom && map.doubleClickZoom && this.options.modifyDoubleClickZoom !== false) {

                    // Simplified version of Leaflet's flyTo, for short animations zooming around a point
                    const flyAround = function (map, targetCenter, targetZoom, options) {
                        options = options || {};
                        if (options.animate === false || !L.Browser.any3d) {
                            return map.setView(targetCenter, targetZoom, options);
                        }

                        map._stop();

                        var startZoom = map._zoom;

                        targetCenter = L.latLng(targetCenter);
                        targetZoom = targetZoom === undefined ? startZoom : targetZoom;
                        targetZoom = Math.min(targetZoom, map.getMaxZoom()); // don't go past max zoom

                        var from = map.project(map.getCenter(), startZoom),
                            to = map.project(targetCenter, startZoom);

                        var start = Date.now(),
                            duration = options.duration ? 1000 * options.duration : 75;

                        function frame() {
                            var t = (Date.now() - start) / duration;

                            if (t <= 1) {
                                // reuse internal flyTo frame to ensure these animations are canceled like others
                                map._flyToFrame = L.Util.requestAnimFrame(frame, map);

                                var center = from.add(to.subtract(from).multiplyBy(t));
                                center = [center.x, center.y];
                                center = Geo.metersToLatLng(center);
                                setZoomAroundNoMoveEnd(map, targetCenter, startZoom + (targetZoom - startZoom) * t);
                            } else {
                                setZoomAroundNoMoveEnd(map, targetCenter, targetZoom)
                                    ._moveEnd(true);
                            }
                        }

                        map._moveStart(true);

                        frame.call(map);
                        return map;
                    };

                    // Modify the double-click zoom handler to do a short zoom animation
                    const enabled = map.doubleClickZoom.enabled();
                    map.doubleClickZoom.disable();

                    map.doubleClickZoom._onDoubleClick = function (e) {
                        var map = this._map,
                            oldZoom = map.getZoom(),
                            delta = map.options.zoomDelta,
                            zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;

                        if (map.options.doubleClickZoom === 'center') {
                            flyAround(map, map.getCenter(), zoom);
                        } else {
                            flyAround(map, map.containerPointToLatLng(e.containerPoint), zoom);
                        }
                    };

                    if (enabled) {
                        map.doubleClickZoom.enable();
                    }
                }
            },

            updateView () {
                var view = this._map.getCenter();
                view.zoom = Math.min(this._map.getZoom(), this._map.getMaxZoom() || Geo.default_view_max_zoom);
                this.scene.view.setView(view);
            },

            updateSize () {
                var size = this._map.getSize();
                this.scene.resizeMap(size.x, size.y);
            },

            resizeOnFirstVisible () {
                let first_visibility = true;
                this.hooks.visibilitychange = () => {
                    if (first_visibility) {
                        first_visibility = false;
                        this.updateSize();
                    }
                };

                document.addEventListener('visibilitychange', this.hooks.visibilitychange);
            },

            onTangramViewUpdate () {
                if (!this._map || this._updating_tangram) {
                    return;
                }

                // View changed?
                let map_center = this._map.getCenter();
                let view_center = this.scene.view.center;
                if (map_center.lng === view_center.lng &&
                    map_center.lat === view_center.lat &&
                    this._map.getZoom() === this.scene.view.zoom) {
                    return;
                }

                this._updating_tangram = true;
                this._map.setView([this.scene.view.center.lat, this.scene.view.center.lng], this.scene.view.zoom, { animate: false });
                this.reverseTransform();
                this._updating_tangram = false;
            },

            render () {
                if (!this.scene) {
                    return;
                }
                this.scene.update();
            },

            // Reverse the CSS positioning Leaflet applies to the layer, since Tangram's WebGL canvas
            // is expected to be 'absolutely' positioned.
            reverseTransform () {
                if (!this._map || !this.scene || !this.scene.container) {
                    return;
                }

                var top_left = this._map.containerPointToLayerPoint([0, 0]);
                L.DomUtil.setPosition(this.scene.container, top_left);
            },

            // Tie Leaflet event handlers to Tangram feature selection
            setupSelectionEventHandlers (map) {
                this._selection_events = {};

                this.hooks.click = (event) => {
                    if (typeof this._selection_events.click === 'function') {
                        this.scene.getFeatureAt(event.containerPoint).
                            then(selection => {
                                let results = Object.assign({}, selection, { leaflet_event: event });
                                this._selection_events.click(results);
                            });
                    }
                };
                map.on('click', this.hooks.click);

                this.hooks.mousemove = (event) => {
                    if (typeof this._selection_events.hover === 'function') {
                        this.scene.getFeatureAt(event.containerPoint).
                            then(selection => {
                                let results = Object.assign({}, selection, { leaflet_event: event });
                                this._selection_events.hover(results);
                            });
                    }
                };
                map.on('mousemove', this.hooks.mousemove);

                this.hooks.mouseout = (event) => {
                    // When mouse leaves map, send an additional selection event to indicate no feature is selected
                    if (typeof this._selection_events.hover === 'function') {
                        this._selection_events.hover({ changed: true, leaflet_event: event });
                    }
                };
                map.on('mouseout', this.hooks.mouseout);
            },

            // Set user-defined handlers for feature selection events
            // Currently only one handler can be defined for each event type
            // Event types are: `click`, `hover` (leaflet `mousemove`)
            setSelectionEvents (events) {
                this._selection_events = Object.assign(this._selection_events, events);
            }

        });

        // Modified version of Leaflet's setZoomAround that doesn't trigger a moveEnd event
        setZoomAroundNoMoveEnd = function (map, latlng, zoom, options) {
            var scale = map.getZoomScale(zoom),
                viewHalf = map.getSize().divideBy(2),
                containerPoint = latlng instanceof L.Point ? latlng : map.latLngToContainerPoint(latlng),

                centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
                newCenter = map.containerPointToLatLng(viewHalf.add(centerOffset));

            var ret = map._move(newCenter, zoom, { flyTo: true });
            map.fire('viewreset');
            return ret;
        };

        // Create the layer class
        LeafletLayer = layerBaseClass.extend(layerClassConfig);

        // Polyfill some 1.0 methods
        if (typeof LeafletLayer.remove !== 'function') {
            LeafletLayer.prototype.remove = function() {
                if (this._map) {
                    this._map.removeLayer(this);
                }
                this.fire('remove');
            };
        }

        LeafletLayer.layerBaseClass = layerBaseClass;
        LeafletLayer.leafletVersion = leafletVersion;

        return new LeafletLayer(options);
    }
}
<|endoftext|>


<commit_before>/**
 * @fileOverview mixin for Backbone
 * @example Usage
 *
 * var Editable = {
 *     edit: function(){
 *         console.log('edit');
 *     }
 * };
 *
 * var Article = Backbone.Model.mix(Editable).extend({
 *     initialize: function(){
 *         this.edit();
 *     }
 * });
 *
 */
(function (Backbone) {
    /**
     * @class Backbone.Mixin
     * @constructor
     * @param {Object} [options]
     * @param {Array.<Object|Backbone.Mixin>} [options.dependencies]
     * @param {Object} proto
     */
    var Mixin = Backbone.Mixin = function (options, proto) {
        if (!proto) {
            proto = options;
            options = {};
        }

        /**
         * @public
         * @type {Object} mixing prototype
         */
        this.proto = proto;

        /**
         * @public
         * @type {Array.<Object|Backbone.Mixin>}
         */
        this.dependencies = options.dependencies || [];
    };

    /**
     * Creates a new class based on instance's constructor and mixes self to it. After that this class decorates the
     * instance.
     * @param {Object} instance
     * @returns {Object}
     */
    Mixin.prototype.decorate = function (instance) {
        var MixedClass = instance.constructor.mix(this);
        return MixedClass.decorate(instance);
    };

    var checkForSuper = /\b_super\b/;

    /**
     * @returns {Function}
     * @throws {Error} if mixin is not an instance of object
     */
    var mix = function (/**{Object|Backbone.Mixin} mixin1, ..*/) {
        var Class = this;

        _(arguments).forEach(function (mixin) {
            if (mixin instanceof Mixin) {
                Class = mix.apply(Class, mixin.dependencies);
                mixin = mixin.proto;
            }

            if (!_.isObject(mixin)) {
                throw new Error('Mixin must be an object');
            }

            if (mixin.initialize && !checkForSuper.test(mixin.initialize.toString())) {
                var init = mixin.initialize;
                mixin.initialize = function () {
                    this._super();
                    return init.apply(this, arguments);
                };
            }

            Class = Class.extend(mixin);
        });

        return Class;
    };

    Backbone.Model.mix = Backbone.Collection.mix = Backbone.Router.mix = Backbone.View.mix = Backbone.History.mix = mix;
})(Backbone);<commit_msg>PSD-11176 mixin static props<commit_after>/**
 * @fileOverview mixin for Backbone
 * @example Usage
 *
 * var Editable = {
 *     edit: function(){
 *         console.log('edit');
 *     }
 * };
 *
 * var Article = Backbone.Model.mix(Editable).extend({
 *     initialize: function(){
 *         this.edit();
 *     }
 * });
 *
 */
(function (Backbone) {
    /**
     * @class Backbone.Mixin
     * @constructor
     * @param {Object} [options]
     * @param {Array.<Object|Backbone.Mixin>} [options.dependencies]
     * @param {Object} proto
     * @param {Object} [staticProps]
     */
    var Mixin = Backbone.Mixin = function (options, proto, staticProps) {
        if (!proto) {
            proto = options;
            options = {};
        }

        /**
         * @public
         * @type {Object} mixing prototype
         */
        this.proto = proto;

        /**
         * @public
         * @type {Array.<Object|Backbone.Mixin>}
         */
        this.dependencies = options.dependencies || [];

        staticProps = staticProps || {};

        _.extend(this, staticProps);
    };

    /**
     * Creates a new class based on instance's constructor and mixes self to it. After that this class decorates the
     * instance.
     * @param {Object} instance
     * @returns {Object}
     */
    Mixin.prototype.decorate = function (instance) {
        var MixedClass = instance.constructor.mix(this);
        return MixedClass.decorate(instance);
    };

    var checkForSuper = /\b_super\b/;

    /**
     * @returns {Function}
     * @throws {Error} if mixin is not an instance of object
     */
    var mix = function (/**{Object|Backbone.Mixin} mixin1, ..*/) {
        var Class = this;

        _(arguments).forEach(function (mixin) {
            if (mixin instanceof Mixin) {
                Class = mix.apply(Class, mixin.dependencies);
                mixin = mixin.proto;
            }

            if (!_.isObject(mixin)) {
                throw new Error('Mixin must be an object');
            }

            if (mixin.initialize && !checkForSuper.test(mixin.initialize.toString())) {
                var init = mixin.initialize;
                mixin.initialize = function () {
                    this._super();
                    return init.apply(this, arguments);
                };
            }

            Class = Class.extend(mixin);
        });

        return Class;
    };

    Backbone.Model.mix = Backbone.Collection.mix = Backbone.Router.mix = Backbone.View.mix = Backbone.History.mix = mix;
})(Backbone);<|endoftext|>


<commit_before>/*
 * @license Hyphenopoly_Loader 2.2.0-devel - client side hyphenation
 *  ©2018  Mathias Nater, Zürich (mathiasnater at gmail dot com)
 *  https://github.com/mnater/Hyphenopoly
 *
 *  Released under the MIT license
 *  http://mnater.github.io/Hyphenopoly/LICENSE
 */

(function H9YL() {
    "use strict";
    const d = document;
    const H = Hyphenopoly;

    /**
     * Create Object without standard Object-prototype
     * @returns {Object} empty object
     */
    function empty() {
        return Object.create(null);
    }

    if (H.cacheFeatureTests && sessionStorage.getItem("Hyphenopoly_Loader")) {
        H.clientFeat = JSON.parse(sessionStorage.getItem("Hyphenopoly_Loader"));
    } else {
        H.clientFeat = {
            "langs": empty(),
            "polyfill": false,
            "wasm": null
        };
    }

    (function config() {
        // Set defaults for paths and setup
        if (H.paths) {
            if (!H.paths.patterndir) {
                H.paths.patterndir = "../Hyphenopoly/patterns/";
            }
            if (!H.paths.maindir) {
                H.paths.maindir = "../Hyphenopoly/";
            }
        } else {
            H.paths = {
                "maindir": "../Hyphenopoly/",
                "patterndir": "../Hyphenopoly/patterns/"
            };
        }
        if (H.setup) {
            if (!H.setup.classnames) {
                H.setup.classnames = {"hyphenate": {}};
            }
            if (!H.setup.timeout) {
                H.setup.timeout = 1000;
            }
        } else {
            H.setup = {
                "classnames": {"hyphenate": {}},
                "timeout": 1000
            };
        }
    }());

    const t = H.clientFeat;

    (function setupEvents() {
        // Events known to the system
        const definedEvents = empty();
        // Default events, execution deferred to Hyphenopoly.js
        const deferred = [];

        /*
         * Eegister for custom event handlers, where event is not yet defined
         * these events will be correctly registered in Hyphenopoly.js
         */
        const tempRegister = [];

        /**
         * Create Event Object
         * @param {string} name The Name of the event
         * @param {function} defFunc The default method of the event
         * @param {boolean} cancellable Is the default cancellable
         * @returns {undefined}
         */
        function define(name, defFunc, cancellable) {
            definedEvents[name] = {
                "cancellable": cancellable,
                "default": defFunc,
                "register": []
            };
        }

        define(
            "timeout",
            function def(e) {
                d.documentElement.style.visibility = "visible";
                window.console.info(
                    "Hyphenopolys 'FOUHC'-prevention timed out after %dms",
                    e.delay
                );
            },
            false
        );

        define(
            "error",
            function def(e) {
                window.console.error(e.msg);
            },
            true
        );

        define(
            "contentLoaded",
            function def(e) {
                deferred.push({
                    "data": e,
                    "name": "contentLoaded"
                });
            },
            false
        );

        define(
            "engineLoaded",
            function def(e) {
                deferred.push({
                    "data": e,
                    "name": "engineLoaded"
                });
            },
            false
        );

        define(
            "hpbLoaded",
            function def(e) {
                deferred.push({
                    "data": e,
                    "name": "hpbLoaded"
                });
            },
            false
        );

        /**
         * Dispatch error <name> with arguments <data>
         * @param {string} name The name of the event
         * @param {Object|undefined} data Data of the event
         * @returns {undefined}
         */
        function dispatch(name, data) {
            if (!data) {
                data = empty();
            }
            let defaultHasRun = false;
            definedEvents[name].register.forEach(function call(currentHandler) {
                let defaultPrevented = false;
                data.preventDefault = function preventDefault() {
                    if (definedEvents[name].cancellable) {
                        defaultPrevented = true;
                    }
                };
                currentHandler(data);
                if (!defaultPrevented &&
                    !defaultHasRun &&
                    definedEvents[name].default) {
                    definedEvents[name].default(data);
                    defaultHasRun = true;
                }
            });
            if (!defaultHasRun && definedEvents[name].default) {
                definedEvents[name].default(data);
            }
        }

        /**
         * Add EventListender <handler> to event <name>
         * @param {string} name The name of the event
         * @param {function} handler Function to register
         * @param {boolean} defer If the registration is deferred
         * @returns {undefined}
         */
        function addListener(name, handler, defer) {
            if (definedEvents[name]) {
                definedEvents[name].register.push(handler);
            } else if (defer) {
                tempRegister.push({
                    "handler": handler,
                    "name": name
                });
            } else {
                H.events.dispatch(
                    "error",
                    {"msg": `unknown Event "${name}" discarded`}
                );
            }
        }

        if (H.handleEvent) {
            Object.keys(H.handleEvent).forEach(function add(name) {
                addListener(name, H.handleEvent[name], true);
            });
        }

        H.events = empty();
        H.events.deferred = deferred;
        H.events.tempRegister = tempRegister;
        H.events.dispatch = dispatch;
        H.events.define = define;
        H.events.addListener = addListener;
    }());

    (function featureTestWasm() {
        /* eslint-disable max-len, no-magic-numbers, no-prototype-builtins */
        /**
         * Feature test for wasm
         * @returns {boolean} support
         */
        function runWasmTest() {
            /*
             * This is the original test, without webkit workaround
             * if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
             *     const module = new WebAssembly.Module(Uint8Array.from([0, 97, 115, 109, 1, 0, 0, 0]));
             *     if (WebAssembly.Module.prototype.isPrototypeOf(module)) {
             *         return WebAssembly.Instance.prototype.isPrototypeOf(new WebAssembly.Instance(module));
             *     }
             * }
             * return false;
             */

            // Wasm feature test with iOS bug detection (https://bugs.webkit.org/show_bug.cgi?id=181781)
            if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
                const module = new WebAssembly.Module(Uint8Array.from([0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127, 3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 16, 1, 14, 0, 32, 0, 65, 1, 54, 2, 0, 32, 0, 40, 2, 0, 11]));
                if (WebAssembly.Module.prototype.isPrototypeOf(module)) {
                    const inst = new WebAssembly.Instance(module);
                    return WebAssembly.Instance.prototype.isPrototypeOf(inst) && (inst.exports.test(4) !== 0);
                }
            }
            return false;
        }
        /* eslint-enable max-len, no-magic-numbers, no-prototype-builtins */
        if (t.wasm === null) {
            t.wasm = runWasmTest();
        }
    }());

    const scriptLoader = (function scriptLoader() {
        const loadedScripts = empty();

        /**
         * Load script by adding <script>-tag
         * @param {string} path Where the script is stored
         * @param {string} filename Filename of the script
         * @returns {undefined}
         */
        function loadScript(path, filename) {
            if (!loadedScripts[filename]) {
                const script = d.createElement("script");
                loadedScripts[filename] = true;
                script.src = path + filename;
                if (filename === "hyphenEngine.asm.js") {
                    script.addEventListener("load", function listener() {
                        H.events.dispatch("engineLoaded", {"msg": "asm"});
                    });
                }
                d.head.appendChild(script);
            }
        }
        return loadScript;
    }());

    const binLoader = (function binLoader() {
        const loadedBins = empty();

        /**
         * Get bin file using fetch
         * @param {string} path Where the script is stored
         * @param {string} fne Filename of the script with extension
         * @param {Object} msg Message
         * @returns {undefined}
         */
        function fetchBinary(path, fne, msg) {
            if (!loadedBins[fne]) {
                loadedBins[fne] = true;
                fetch(path + fne).then(
                    function resolve(response) {
                        if (response.ok) {
                            const name = fne.slice(0, fne.lastIndexOf("."));
                            if (name === "hyphenEngine") {
                                H.binaries[name] = response.arrayBuffer().then(
                                    function getModule(buf) {
                                        return new WebAssembly.Module(buf);
                                    }
                                );
                            } else {
                                H.binaries[name] = response.arrayBuffer();
                            }
                            H.events.dispatch(msg[0], {"msg": msg[1]});
                        }
                    }
                );
            }
        }

        /**
         * Get bin file using XHR
         * @param {string} path Where the script is stored
         * @param {string} fne Filename of the script with extension
         * @param {Object} msg Message
         * @returns {undefined}
         */
        function requestBinary(path, fne, msg) {
            if (!loadedBins[fne]) {
                loadedBins[fne] = true;
                const xhr = new XMLHttpRequest();
                xhr.open("GET", path + fne);
                xhr.onload = function onload() {
                    const name = fne.slice(0, fne.lastIndexOf("."));
                    H.binaries[name] = xhr.response;
                    H.events.dispatch(msg[0], {"msg": msg[1]});
                };
                xhr.responseType = "arraybuffer";
                xhr.send();
            }
        }

        return (t.wasm)
            ? fetchBinary
            : requestBinary;
    }());

    /**
     * Allocate memory for (w)asm
     * @param {string} lang Language
     * @returns {undefined}
     */
    function allocateMemory(lang) {
        let wasmPages = 0;
        switch (lang) {
        case "nl":
            wasmPages = 41;
            break;
        case "de":
            wasmPages = 75;
            break;
        case "nb-no":
            wasmPages = 92;
            break;
        case "hu":
            wasmPages = 207;
            break;
        default:
            wasmPages = 32;
        }
        if (!H.specMems) {
            H.specMems = empty();
        }
        if (t.wasm) {
            H.specMems[lang] = new WebAssembly.Memory({
                "initial": wasmPages,
                "maximum": 256
            });
        } else {
            /* eslint-disable no-bitwise */
            /**
             * Polyfill Math.log2
             * @param {number} x argument
             * @return {number} Log2(x)
             */
            Math.log2 = Math.log2 || function polyfillLog2(x) {
                return Math.log(x) * Math.LOG2E;
            };

            const asmPages = (2 << Math.floor(Math.log2(wasmPages))) * 65536;
            /* eslint-enable no-bitwise */
            H.specMems[lang] = new ArrayBuffer(asmPages);
        }
    }

    /**
     * Load all ressources for a required <lang>
     * @param {string} lang The language
     * @returns {undefined}
     */
    function loadRessources(lang) {
        if (!H.binaries) {
            H.binaries = empty();
        }
        scriptLoader(H.paths.maindir, "Hyphenopoly.js");
        if (t.wasm) {
            binLoader(
                H.paths.maindir,
                "hyphenEngine.wasm",
                ["engineLoaded", "wasm"]
            );
        } else {
            scriptLoader(H.paths.maindir, "hyphenEngine.asm.js");
        }
        binLoader(H.paths.patterndir, `${lang}.hpb`, ["hpbLoaded", lang]);
        allocateMemory(lang);
    }

    (function featureTestCSSHHyphenation() {
        const tester = (function tester() {
            let fakeBody = null;

            /**
             * Create and append div with CSS-hyphenated word
             * @param {string} lang Language
             * @returns {undefined}
             */
            function createTest(lang) {
                if (t.langs[lang]) {
                    return;
                }
                if (!fakeBody) {
                    fakeBody = d.createElement("body");
                }
                const testDiv = d.createElement("div");
                testDiv.lang = lang;
                testDiv.id = lang;
                testDiv.style.cssText = "visibility:hidden;-moz-hyphens:auto;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;width:48px;font-size:12px;line-height:12px;border:none;padding:0;word-wrap:normal";
                testDiv.appendChild(d.createTextNode(H.require[lang]));
                fakeBody.appendChild(testDiv);
            }

            /**
             * Append fakeBody with tests to target (document)
             * @param {Object} target Where to append fakeBody
             * @returns {Object|null} The body element or null, if no tests
             */
            function appendTests(target) {
                if (fakeBody) {
                    target.appendChild(fakeBody);
                    return fakeBody;
                }
                return null;
            }

            /**
             * Remove fakeBody
             * @returns {undefined}
             */
            function clearTests() {
                if (fakeBody) {
                    fakeBody.parentNode.removeChild(fakeBody);
                }
            }
            return {
                "appendTests": appendTests,
                "clearTests": clearTests,
                "createTest": createTest
            };
        }());

        /**
         * Checks if hyphens (ev.prefixed) is set to auto for the element.
         * @param {Object} el - the element
         * @returns {Boolean} result of the check
         */
        function checkCSSHyphensSupport(el) {
            const supp = el.style.hyphens === "auto" ||
                el.style.webkitHyphens === "auto" ||
                el.style.msHyphens === "auto" ||
                el.style["-moz-hyphens"] === "auto";
            return supp;
        }

        Object.keys(H.require).forEach(function doReqLangs(lang) {
            if (H.require[lang] === "FORCEHYPHENOPOLY") {
                t.polyfill = true;
                t.langs[lang] = "H9Y";
                loadRessources(lang);
            } else if (
                t.langs[lang] &&
                t.langs[lang] === "H9Y"
            ) {
                loadRessources(lang);
            } else {
                tester.createTest(lang);
            }
        });
        const testContainer = tester.appendTests(d.documentElement);
        if (testContainer !== null) {
            Object.keys(H.require).forEach(function checkReqLangs(lang) {
                if (H.require[lang] !== "FORCEHYPHENOPOLY") {
                    const el = d.getElementById(lang);
                    if (checkCSSHyphensSupport(el) && el.offsetHeight > 12) {
                        t.polyfill = t.polyfill || false;
                        t.langs[lang] = "CSS";
                    } else {
                        t.polyfill = true;
                        t.langs[lang] = "H9Y";
                        loadRessources(lang);
                    }
                }
            });
            tester.clearTests();
        }
    }());

    (function run() {
        if (t.polyfill) {
            d.documentElement.style.visibility = "hidden";

            H.setup.timeOutHandler = window.setTimeout(function timedOut() {
                d.documentElement.style.visibility = "visible";
                H.events.dispatch("timeout", {"delay": H.setup.timeout});
            }, H.setup.timeout);
            d.addEventListener(
                "DOMContentLoaded",
                function DCL() {
                    H.events.dispatch("contentLoaded", {"msg": ["contentLoaded"]});
                },
                {
                    "once": true,
                    "passive": true
                }
            );
        } else {
            window.Hyphenopoly = null;
        }
    }());

    if (H.cacheFeatureTests) {
        sessionStorage.setItem("Hyphenopoly_Loader", JSON.stringify(t));
    }
}());
<commit_msg>fix naming issue for minifier<commit_after>/*
 * @license Hyphenopoly_Loader 2.2.0-devel - client side hyphenation
 *  ©2018  Mathias Nater, Zürich (mathiasnater at gmail dot com)
 *  https://github.com/mnater/Hyphenopoly
 *
 *  Released under the MIT license
 *  http://mnater.github.io/Hyphenopoly/LICENSE
 */

(function H9YL() {
    "use strict";
    const d = document;
    const H = Hyphenopoly;

    /**
     * Create Object without standard Object-prototype
     * @returns {Object} empty object
     */
    function empty() {
        return Object.create(null);
    }

    if (H.cacheFeatureTests && sessionStorage.getItem("Hyphenopoly_Loader")) {
        H.clientFeat = JSON.parse(sessionStorage.getItem("Hyphenopoly_Loader"));
    } else {
        H.clientFeat = {
            "langs": empty(),
            "polyfill": false,
            "wasm": null
        };
    }

    (function config() {
        // Set defaults for paths and setup
        if (H.paths) {
            if (!H.paths.patterndir) {
                H.paths.patterndir = "../Hyphenopoly/patterns/";
            }
            if (!H.paths.maindir) {
                H.paths.maindir = "../Hyphenopoly/";
            }
        } else {
            H.paths = {
                "maindir": "../Hyphenopoly/",
                "patterndir": "../Hyphenopoly/patterns/"
            };
        }
        if (H.setup) {
            if (!H.setup.classnames) {
                H.setup.classnames = {"hyphenate": {}};
            }
            if (!H.setup.timeout) {
                H.setup.timeout = 1000;
            }
        } else {
            H.setup = {
                "classnames": {"hyphenate": {}},
                "timeout": 1000
            };
        }
    }());

    (function setupEvents() {
        // Events known to the system
        const definedEvents = empty();
        // Default events, execution deferred to Hyphenopoly.js
        const deferred = [];

        /*
         * Eegister for custom event handlers, where event is not yet defined
         * these events will be correctly registered in Hyphenopoly.js
         */
        const tempRegister = [];

        /**
         * Create Event Object
         * @param {string} name The Name of the event
         * @param {function} defFunc The default method of the event
         * @param {boolean} cancellable Is the default cancellable
         * @returns {undefined}
         */
        function define(name, defFunc, cancellable) {
            definedEvents[name] = {
                "cancellable": cancellable,
                "default": defFunc,
                "register": []
            };
        }

        define(
            "timeout",
            function def(e) {
                d.documentElement.style.visibility = "visible";
                window.console.info(
                    "Hyphenopolys 'FOUHC'-prevention timed out after %dms",
                    e.delay
                );
            },
            false
        );

        define(
            "error",
            function def(e) {
                window.console.error(e.msg);
            },
            true
        );

        define(
            "contentLoaded",
            function def(e) {
                deferred.push({
                    "data": e,
                    "name": "contentLoaded"
                });
            },
            false
        );

        define(
            "engineLoaded",
            function def(e) {
                deferred.push({
                    "data": e,
                    "name": "engineLoaded"
                });
            },
            false
        );

        define(
            "hpbLoaded",
            function def(e) {
                deferred.push({
                    "data": e,
                    "name": "hpbLoaded"
                });
            },
            false
        );

        /**
         * Dispatch error <name> with arguments <data>
         * @param {string} name The name of the event
         * @param {Object|undefined} data Data of the event
         * @returns {undefined}
         */
        function dispatch(name, data) {
            if (!data) {
                data = empty();
            }
            let defaultHasRun = false;
            definedEvents[name].register.forEach(function call(currentHandler) {
                let defaultPrevented = false;
                data.preventDefault = function preventDefault() {
                    if (definedEvents[name].cancellable) {
                        defaultPrevented = true;
                    }
                };
                currentHandler(data);
                if (!defaultPrevented &&
                    !defaultHasRun &&
                    definedEvents[name].default) {
                    definedEvents[name].default(data);
                    defaultHasRun = true;
                }
            });
            if (!defaultHasRun && definedEvents[name].default) {
                definedEvents[name].default(data);
            }
        }

        /**
         * Add EventListender <handler> to event <name>
         * @param {string} name The name of the event
         * @param {function} handler Function to register
         * @param {boolean} defer If the registration is deferred
         * @returns {undefined}
         */
        function addListener(name, handler, defer) {
            if (definedEvents[name]) {
                definedEvents[name].register.push(handler);
            } else if (defer) {
                tempRegister.push({
                    "handler": handler,
                    "name": name
                });
            } else {
                H.events.dispatch(
                    "error",
                    {"msg": `unknown Event "${name}" discarded`}
                );
            }
        }

        if (H.handleEvent) {
            Object.keys(H.handleEvent).forEach(function add(name) {
                addListener(name, H.handleEvent[name], true);
            });
        }

        H.events = empty();
        H.events.deferred = deferred;
        H.events.tempRegister = tempRegister;
        H.events.dispatch = dispatch;
        H.events.define = define;
        H.events.addListener = addListener;
    }());

    (function featureTestWasm() {
        /* eslint-disable max-len, no-magic-numbers, no-prototype-builtins */
        /**
         * Feature test for wasm
         * @returns {boolean} support
         */
        function runWasmTest() {
            /*
             * This is the original test, without webkit workaround
             * if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
             *     const module = new WebAssembly.Module(Uint8Array.from([0, 97, 115, 109, 1, 0, 0, 0]));
             *     if (WebAssembly.Module.prototype.isPrototypeOf(module)) {
             *         return WebAssembly.Instance.prototype.isPrototypeOf(new WebAssembly.Instance(module));
             *     }
             * }
             * return false;
             */

            // Wasm feature test with iOS bug detection (https://bugs.webkit.org/show_bug.cgi?id=181781)
            if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
                const module = new WebAssembly.Module(Uint8Array.from([0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127, 3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 16, 1, 14, 0, 32, 0, 65, 1, 54, 2, 0, 32, 0, 40, 2, 0, 11]));
                if (WebAssembly.Module.prototype.isPrototypeOf(module)) {
                    const inst = new WebAssembly.Instance(module);
                    return WebAssembly.Instance.prototype.isPrototypeOf(inst) && (inst.exports.test(4) !== 0);
                }
            }
            return false;
        }
        /* eslint-enable max-len, no-magic-numbers, no-prototype-builtins */
        if (H.clientFeat.wasm === null) {
            H.clientFeat.wasm = runWasmTest();
        }
    }());

    const scriptLoader = (function scriptLoader() {
        const loadedScripts = empty();

        /**
         * Load script by adding <script>-tag
         * @param {string} path Where the script is stored
         * @param {string} filename Filename of the script
         * @returns {undefined}
         */
        function loadScript(path, filename) {
            if (!loadedScripts[filename]) {
                const script = d.createElement("script");
                loadedScripts[filename] = true;
                script.src = path + filename;
                if (filename === "hyphenEngine.asm.js") {
                    script.addEventListener("load", function listener() {
                        H.events.dispatch("engineLoaded", {"msg": "asm"});
                    });
                }
                d.head.appendChild(script);
            }
        }
        return loadScript;
    }());

    const binLoader = (function binLoader() {
        const loadedBins = empty();

        /**
         * Get bin file using fetch
         * @param {string} path Where the script is stored
         * @param {string} fne Filename of the script with extension
         * @param {Object} msg Message
         * @returns {undefined}
         */
        function fetchBinary(path, fne, msg) {
            if (!loadedBins[fne]) {
                loadedBins[fne] = true;
                fetch(path + fne).then(
                    function resolve(response) {
                        if (response.ok) {
                            const name = fne.slice(0, fne.lastIndexOf("."));
                            if (name === "hyphenEngine") {
                                H.binaries[name] = response.arrayBuffer().then(
                                    function getModule(buf) {
                                        return new WebAssembly.Module(buf);
                                    }
                                );
                            } else {
                                H.binaries[name] = response.arrayBuffer();
                            }
                            H.events.dispatch(msg[0], {"msg": msg[1]});
                        }
                    }
                );
            }
        }

        /**
         * Get bin file using XHR
         * @param {string} path Where the script is stored
         * @param {string} fne Filename of the script with extension
         * @param {Object} msg Message
         * @returns {undefined}
         */
        function requestBinary(path, fne, msg) {
            if (!loadedBins[fne]) {
                loadedBins[fne] = true;
                const xhr = new XMLHttpRequest();
                xhr.open("GET", path + fne);
                xhr.onload = function onload() {
                    const name = fne.slice(0, fne.lastIndexOf("."));
                    H.binaries[name] = xhr.response;
                    H.events.dispatch(msg[0], {"msg": msg[1]});
                };
                xhr.responseType = "arraybuffer";
                xhr.send();
            }
        }

        return (H.clientFeat.wasm)
            ? fetchBinary
            : requestBinary;
    }());

    /**
     * Allocate memory for (w)asm
     * @param {string} lang Language
     * @returns {undefined}
     */
    function allocateMemory(lang) {
        let wasmPages = 0;
        switch (lang) {
        case "nl":
            wasmPages = 41;
            break;
        case "de":
            wasmPages = 75;
            break;
        case "nb-no":
            wasmPages = 92;
            break;
        case "hu":
            wasmPages = 207;
            break;
        default:
            wasmPages = 32;
        }
        if (!H.specMems) {
            H.specMems = empty();
        }
        if (H.clientFeat.wasm) {
            H.specMems[lang] = new WebAssembly.Memory({
                "initial": wasmPages,
                "maximum": 256
            });
        } else {
            /* eslint-disable no-bitwise */
            /**
             * Polyfill Math.log2
             * @param {number} x argument
             * @return {number} Log2(x)
             */
            Math.log2 = Math.log2 || function polyfillLog2(x) {
                return Math.log(x) * Math.LOG2E;
            };

            const asmPages = (2 << Math.floor(Math.log2(wasmPages))) * 65536;
            /* eslint-enable no-bitwise */
            H.specMems[lang] = new ArrayBuffer(asmPages);
        }
    }

    /**
     * Load all ressources for a required <lang>
     * @param {string} lang The language
     * @returns {undefined}
     */
    function loadRessources(lang) {
        if (!H.binaries) {
            H.binaries = empty();
        }
        scriptLoader(H.paths.maindir, "Hyphenopoly.js");
        if (H.clientFeat.wasm) {
            binLoader(
                H.paths.maindir,
                "hyphenEngine.wasm",
                ["engineLoaded", "wasm"]
            );
        } else {
            scriptLoader(H.paths.maindir, "hyphenEngine.asm.js");
        }
        binLoader(H.paths.patterndir, `${lang}.hpb`, ["hpbLoaded", lang]);
        allocateMemory(lang);
    }

    (function featureTestCSSHHyphenation() {
        const tester = (function tester() {
            let fakeBody = null;

            /**
             * Create and append div with CSS-hyphenated word
             * @param {string} lang Language
             * @returns {undefined}
             */
            function createTest(lang) {
                if (H.clientFeat.langs[lang]) {
                    return;
                }
                if (!fakeBody) {
                    fakeBody = d.createElement("body");
                }
                const testDiv = d.createElement("div");
                testDiv.lang = lang;
                testDiv.id = lang;
                testDiv.style.cssText = "visibility:hidden;-moz-hyphens:auto;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;width:48px;font-size:12px;line-height:12px;border:none;padding:0;word-wrap:normal";
                testDiv.appendChild(d.createTextNode(H.require[lang]));
                fakeBody.appendChild(testDiv);
            }

            /**
             * Append fakeBody with tests to target (document)
             * @param {Object} target Where to append fakeBody
             * @returns {Object|null} The body element or null, if no tests
             */
            function appendTests(target) {
                if (fakeBody) {
                    target.appendChild(fakeBody);
                    return fakeBody;
                }
                return null;
            }

            /**
             * Remove fakeBody
             * @returns {undefined}
             */
            function clearTests() {
                if (fakeBody) {
                    fakeBody.parentNode.removeChild(fakeBody);
                }
            }
            return {
                "appendTests": appendTests,
                "clearTests": clearTests,
                "createTest": createTest
            };
        }());

        /**
         * Checks if hyphens (ev.prefixed) is set to auto for the element.
         * @param {Object} el - the element
         * @returns {Boolean} result of the check
         */
        function checkCSSHyphensSupport(elm) {
            return (elm.style.hyphens === "auto" ||
                elm.style.webkitHyphens === "auto" ||
                elmm.style.msHyphens === "auto" ||
                elm.style["-moz-hyphens"] === "auto");
        }

        Object.keys(H.require).forEach(function doReqLangs(lang) {
            if (H.require[lang] === "FORCEHYPHENOPOLY") {
                H.clientFeat.polyfill = true;
                H.clientFeat.langs[lang] = "H9Y";
                loadRessources(lang);
            } else if (
                H.clientFeat.langs[lang] &&
                H.clientFeat.langs[lang] === "H9Y"
            ) {
                loadRessources(lang);
            } else {
                tester.createTest(lang);
            }
        });
        const testContainer = tester.appendTests(d.documentElement);
        if (testContainer !== null) {
            Object.keys(H.require).forEach(function checkReqLangs(lang) {
                if (H.require[lang] !== "FORCEHYPHENOPOLY") {
                    const el = d.getElementById(lang);
                    if (checkCSSHyphensSupport(el) && el.offsetHeight > 12) {
                        H.clientFeat.polyfill = H.clientFeat.polyfill || false;
                        H.clientFeat.langs[lang] = "CSS";
                    } else {
                        H.clientFeat.polyfill = true;
                        H.clientFeat.langs[lang] = "H9Y";
                        loadRessources(lang);
                    }
                }
            });
            tester.clearTests();
        }
    }());

    (function run() {
        if (H.clientFeat.polyfill) {
            d.documentElement.style.visibility = "hidden";

            H.setup.timeOutHandler = window.setTimeout(function timedOut() {
                d.documentElement.style.visibility = "visible";
                H.events.dispatch("timeout", {"delay": H.setup.timeout});
            }, H.setup.timeout);
            d.addEventListener(
                "DOMContentLoaded",
                function DCL() {
                    H.events.dispatch("contentLoaded", {"msg": ["contentLoaded"]});
                },
                {
                    "once": true,
                    "passive": true
                }
            );
        } else {
            window.Hyphenopoly = null;
        }
    }());

    if (H.cacheFeatureTests) {
        sessionStorage.setItem("Hyphenopoly_Loader", JSON.stringify(t));
    }
}());
<|endoftext|>


<commit_before>'use strict';

var Fiber = require('fibers')
  , IIPConnection = require('../../IIPConnection')
  , Process = require('../../Process')

Fiber.prototype.fbpProc = null;

var FiberRuntime = module.exports = function() {
  this._queue = [];
  this._count = null;
  this._tracing = false;
};

FiberRuntime.prototype.isTracing = function() {
  return this._tracing;
};

// TOOD Better description of parameter and maybe function name as well
FiberRuntime.prototype.pushToQueue = function(item) {
  this._queue.push(item);
};

FiberRuntime.prototype._close = function(proc) {
  if (this._tracing) {
    console.log(proc.name + ' closing');
  }
  proc.status = Process.Status.CLOSED;
  //console.log('cl' + count);
  this._count--;
  for (var i = 0; i < proc.outports.length; i++) {
    var conn = proc.outports[i][1].conn;
    if (conn.down.status == Process.Status.WAITING_TO_RECEIVE ||
        conn.down.status == Process.Status.NOT_INITIALIZED) {
      conn.down.status = Process.Status.READY_TO_EXECUTE;
      this._queue.push(conn.down);
    }
    conn.upstreamProcsUnclosed--;
    if ((conn.upstreamProcsUnclosed) <= 0) {
      conn.closed = true;
    }
  }

  for (var i = 0; i < proc.inports.length; i++) {
    var conn = proc.inports[i][1].conn;
    if (conn instanceof IIPConnection) {
      continue;
    }
    for (var j = 0; j < conn.up.length; j++) {
      if (conn.up[j].status == Process.Status.CLOSED) {
        this._queue.push(conn.up[j]);
      }
    }
  }
  if (proc.ownedIPs != 0) {
    console.log(proc.name + ' closed without disposing of all IPs');
  }
  if (this._tracing) {
    console.log(proc.name + ' closed');
  }
}

FiberRuntime.prototype.getCurrentProc = function() {
  return Fiber.current.fbpProc;
};

FiberRuntime.prototype.queueCallback = function(proc, result) {
  if (this._tracing) {
    console.log('queue ' + proc.name);
  }
  if (result != undefined) {
    proc.result = result;
  }
  this._queue.push(proc);
};

FiberRuntime.prototype.runAsyncCallback = function(cb) {
  var proc = this.getCurrentProc();
  proc.yielded = true;
  proc.cbpending = true;

  var self = this;

  cb(function () {
    proc.yielded = false;
    proc.cbpending = false;
    self.queueCallback(proc);
  });

  Fiber.yield();
};

FiberRuntime.prototype.run = function (processes, options, callback) {
  this._list = processes;
  this._count = this._list.length;

  this._tracing = global.tracing = Boolean(options.trace);

  var self = this;

  Fiber(function() {
    var startTime = Date.now();
    console.log('Start time: ' + startTime);

    self._actualRun.call(self);

    console.log('Elapsed time in secs: ' + (Date.now() - startTime));

    callback(null);
  }).run();
};

FiberRuntime.prototype._createFiber = function (process) {
  if (this._tracing) {
    console.log('creating new fiber for ' + process.name);
  }
  process.fiber = new Fiber(process.func.bind(null, this));
  process.fiber.fbpProc = process;
  process.status = Process.Status.ACTIVE;

  return process;
};

FiberRuntime.prototype._hasDeadLock = function () {
  for (var i = 0; i < this._list.length; i++) {
    if (this._list[i].cbpending || this._list[i].status == Process.Status.ACTIVE) {
      return false;
    }
  }
  return true;
};

FiberRuntime.prototype._genInitialQueue = function () {
  var self = this;
  this._queue = [];

  this._list.forEach(function (process) {
    var shallAdd = process.inports.every(function (inport) {
      return !(inport[1].conn instanceof IIPConnection);
    });

    if (shallAdd || true) {
      self._queue.push(process);
    }
  });
  return this._queue;
};

FiberRuntime.prototype._logProcessInfo = function (proc) {
  if (this._tracing) {
    console.log({name: proc.name, yielded: proc.yielded, cbpending: proc.cbpending});
  }
};

FiberRuntime.prototype._areUpConnsClosed = function (proc) {
  return proc.inports.every(function (inport) {
    if (inport[1].conn instanceof IIPConnection) {
      return true;
    }
    else if (!(inport[1].conn.closed) || inport[1].conn.usedslots > 0) {
      return false;
    }
    else {
      return true;
    }
  });
};

// Fibre running scheduler
FiberRuntime.prototype._actualRun = function (trace) {
  this._queue = this._genInitialQueue();

  while (true) {
    this._tick();

    if (this._count <= 0) {
      break;
    }

    if (this._hasDeadLock()) {
      console.log('Deadlock detected');
      for (var i = 0; i < this._list.length; i++) {
        console.log('- Process status: ' + this._list[i].getStatusString() + ' - ' + this._list[i].name);
      }
      throw 'DEADLOCK';
    }
    sleep(100);
  }
};

FiberRuntime.prototype._tick = function () {
  var x = this._queue.shift();
  while (x != undefined) {
    if (x.fiber == null) {
      x = this._createFiber(x);
    }

    if (x.status == Process.Status.DORMANT && this._areUpConnsClosed(x)) {
      this._close(x);
    }
    else if (x.status != Process.Status.CLOSED) {
      if (!x.cbpending) {
        x.fiber.run(x.result);
      }
      this._logProcessInfo(x);
      x.data = null;

      if (!x.yielded && !x.cbpending) {
        if (this._areUpConnsClosed(x)) {
          this._close(x);
        }
        else {
          x.status = Process.Status.DORMANT;
          this._queue.push(x);
          for (var i = 0; i < x.inports.length; i++) {
            var inport = x.inports[i];
            if (inport[1].conn instanceof IIPConnection) {
              inport[1].conn.closed = false;
            }
          }
        }
      }
    }

    if (this._tracing) {
      console.log("Shift new process, old one = " + x.name);
    }
    x = this._queue.shift();
    if (this._tracing && x) {
      console.log("New one: " + x.name);
    }
  }
};

function sleep(ms) {
  var fiber = Fiber.current;
  setTimeout(function() {
    fiber.run();
  }, ms);
  Fiber.yield();
}<commit_msg>FiberRuntime#runAsyncCallback should pass the result forth<commit_after>'use strict';

var Fiber = require('fibers')
  , IIPConnection = require('../../IIPConnection')
  , Process = require('../../Process')

Fiber.prototype.fbpProc = null;

var FiberRuntime = module.exports = function() {
  this._queue = [];
  this._count = null;
  this._tracing = false;
};

FiberRuntime.prototype.isTracing = function() {
  return this._tracing;
};

// TOOD Better description of parameter and maybe function name as well
FiberRuntime.prototype.pushToQueue = function(item) {
  this._queue.push(item);
};

FiberRuntime.prototype._close = function(proc) {
  if (this._tracing) {
    console.log(proc.name + ' closing');
  }
  proc.status = Process.Status.CLOSED;
  //console.log('cl' + count);
  this._count--;
  for (var i = 0; i < proc.outports.length; i++) {
    var conn = proc.outports[i][1].conn;
    if (conn.down.status == Process.Status.WAITING_TO_RECEIVE ||
        conn.down.status == Process.Status.NOT_INITIALIZED) {
      conn.down.status = Process.Status.READY_TO_EXECUTE;
      this._queue.push(conn.down);
    }
    conn.upstreamProcsUnclosed--;
    if ((conn.upstreamProcsUnclosed) <= 0) {
      conn.closed = true;
    }
  }

  for (var i = 0; i < proc.inports.length; i++) {
    var conn = proc.inports[i][1].conn;
    if (conn instanceof IIPConnection) {
      continue;
    }
    for (var j = 0; j < conn.up.length; j++) {
      if (conn.up[j].status == Process.Status.CLOSED) {
        this._queue.push(conn.up[j]);
      }
    }
  }
  if (proc.ownedIPs != 0) {
    console.log(proc.name + ' closed without disposing of all IPs');
  }
  if (this._tracing) {
    console.log(proc.name + ' closed');
  }
}

FiberRuntime.prototype.getCurrentProc = function() {
  return Fiber.current.fbpProc;
};

FiberRuntime.prototype.queueCallback = function(proc, result) {
  if (this._tracing) {
    console.log('queue ' + proc.name);
  }
  if (result != undefined) {
    proc.result = result;
  }
  this._queue.push(proc);
};

FiberRuntime.prototype.runAsyncCallback = function(cb) {
  var proc = this.getCurrentProc();
  proc.yielded = true;
  proc.cbpending = true;

  var self = this;

  cb(function (result) {
    proc.yielded = false;
    proc.cbpending = false;
    proc.result = result;
    self.queueCallback(proc);
  });

  return Fiber.yield();
};

FiberRuntime.prototype.run = function (processes, options, callback) {
  this._list = processes;
  this._count = this._list.length;

  this._tracing = global.tracing = Boolean(options.trace);

  var self = this;

  Fiber(function() {
    var startTime = Date.now();
    console.log('Start time: ' + startTime);

    self._actualRun.call(self);

    console.log('Elapsed time in secs: ' + (Date.now() - startTime));

    callback(null);
  }).run();
};

FiberRuntime.prototype._createFiber = function (process) {
  if (this._tracing) {
    console.log('creating new fiber for ' + process.name);
  }
  process.fiber = new Fiber(process.func.bind(null, this));
  process.fiber.fbpProc = process;
  process.status = Process.Status.ACTIVE;

  return process;
};

FiberRuntime.prototype._hasDeadLock = function () {
  for (var i = 0; i < this._list.length; i++) {
    if (this._list[i].cbpending || this._list[i].status == Process.Status.ACTIVE) {
      return false;
    }
  }
  return true;
};

FiberRuntime.prototype._genInitialQueue = function () {
  var self = this;
  this._queue = [];

  this._list.forEach(function (process) {
    var shallAdd = process.inports.every(function (inport) {
      return !(inport[1].conn instanceof IIPConnection);
    });

    if (shallAdd || true) {
      self._queue.push(process);
    }
  });
  return this._queue;
};

FiberRuntime.prototype._logProcessInfo = function (proc) {
  if (this._tracing) {
    console.log({name: proc.name, yielded: proc.yielded, cbpending: proc.cbpending});
  }
};

FiberRuntime.prototype._areUpConnsClosed = function (proc) {
  return proc.inports.every(function (inport) {
    if (inport[1].conn instanceof IIPConnection) {
      return true;
    }
    else if (!(inport[1].conn.closed) || inport[1].conn.usedslots > 0) {
      return false;
    }
    else {
      return true;
    }
  });
};

// Fibre running scheduler
FiberRuntime.prototype._actualRun = function (trace) {
  this._queue = this._genInitialQueue();

  while (true) {
    this._tick();

    if (this._count <= 0) {
      break;
    }

    if (this._hasDeadLock()) {
      console.log('Deadlock detected');
      for (var i = 0; i < this._list.length; i++) {
        console.log('- Process status: ' + this._list[i].getStatusString() + ' - ' + this._list[i].name);
      }
      throw 'DEADLOCK';
    }
    sleep(100);
  }
};

FiberRuntime.prototype._tick = function () {
  var x = this._queue.shift();
  while (x != undefined) {
    if (x.fiber == null) {
      x = this._createFiber(x);
    }

    if (x.status == Process.Status.DORMANT && this._areUpConnsClosed(x)) {
      this._close(x);
    }
    else if (x.status != Process.Status.CLOSED) {
      if (!x.cbpending) {
        x.fiber.run(x.result);
      }
      this._logProcessInfo(x);
      x.data = null;

      if (!x.yielded && !x.cbpending) {
        if (this._areUpConnsClosed(x)) {
          this._close(x);
        }
        else {
          x.status = Process.Status.DORMANT;
          this._queue.push(x);
          for (var i = 0; i < x.inports.length; i++) {
            var inport = x.inports[i];
            if (inport[1].conn instanceof IIPConnection) {
              inport[1].conn.closed = false;
            }
          }
        }
      }
    }

    if (this._tracing) {
      console.log("Shift new process, old one = " + x.name);
    }
    x = this._queue.shift();
    if (this._tracing && x) {
      console.log("New one: " + x.name);
    }
  }
};

function sleep(ms) {
  var fiber = Fiber.current;
  setTimeout(function() {
    fiber.run();
  }, ms);
  Fiber.yield();
}<|endoftext|>