mediamonks/seng-signals

View on GitHub
src/lib/SignalAbstract.ts

Summary

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

/**
 * Signal provides method for managing queues of a single event listener and dispatching it.
 **/

import "ts-helpers";
import {SignalConnection} from "./SignalConnection";

export class Task
{
    public fn:Function;
    public next:Task|null = null;

    constructor(fn:Function)
    {
        this.fn = fn;
    }
}

/**
 *
 * @class SignalAbstract
 */
export class SignalAbstract
{
    public static DISPATCHING_SENTINEL:SignalConnection = new SignalConnection(null, null);

    private _head:SignalConnection|null;
    private _deferredTasks:Task|null = null;

    constructor(listener?:Function)
    {
        this._head = listener ? new SignalConnection(this, listener) : null;
    }

    /**
     *
     * @returns {boolean}
     */
    public hasListeners():boolean
    {
        return this._head != null;
    }

    /**
     * removes All connections from signal
     */
    public disconnectAll():this
    {
        if(this.hasListeners())
        {
            if(this.dispatching())
            {
                this.defer(() => this._disconnectAll());
            } else {
                this._disconnectAll();
            }
        }

        return this;
    }

    protected _disconnectAll():this
    {
        if(this.hasListeners())
        {
            while(this._head)
            {
                this.disconnect(this._head);
            }
        }

        return this;
    }

    /**
     *
     * @param {Function} listener
     * @param {boolean} prioritize
     * @returns {SignalConnection}
     */

    public connect(listener:Function, prioritize:boolean = false):SignalConnection
    {
        var conn:SignalConnection = new SignalConnection(this, listener);

        if(this.dispatching())
        {
            this.defer(() => this.listAdd(conn, prioritize));
        }
        else
        {
            this.listAdd(conn, prioritize);
        }

        return conn;
    }

    /**
     *
     * @param {SignalConnection} conn
     */
    public disconnect(conn:SignalConnection):void
    {

        if(this._head == SignalAbstract.DISPATCHING_SENTINEL)
        {
            this.defer(() => this.listRemove(conn));
        }
        else
        {
            this.listRemove(conn);
        }
    }

    /**
     *
     * @param fn
     */
    protected defer(fn:() => void):void
    {
        var tail:Task|null = null;
        var p = this._deferredTasks;

        while(p != null)
        {
            tail = p;
            p = p.next;
        }

        var task:Task = new Task(fn);
        if(tail != null)
        {
            tail.next = task;
        }
        else
        {
            this._deferredTasks = task;
        }
    }

    protected willEmit():SignalConnection|null
    {
        var snapshot = this._head;
        this._head = SignalAbstract.DISPATCHING_SENTINEL;
        return snapshot;
    }

    protected didEmit(head:SignalConnection|null):void
    {
        var snapshot = this._deferredTasks;

        this._head = head;
        this._deferredTasks = null;

        while(snapshot != null)
        {
            snapshot.fn();
            snapshot = snapshot.next;
        }
    }

    /**
     * Is true when the signal is in its dispatching cicle
     * @returns {boolean}
     */
    public dispatching():boolean
    {
        return this._head == SignalAbstract.DISPATCHING_SENTINEL;
    }

    protected listAdd(conn:SignalConnection, prioritize:boolean):void
    {
        if(prioritize)
        {
            conn._next = this._head;
            this._head = conn;
        }
        else
        {
            var tail:SignalConnection|null = null;
            var p:SignalConnection|null = <SignalConnection> this._head;

            while(p != null)
            {
                tail = p;
                p = p._next;
            }

            if(tail != null)
            {
                tail._next = conn;
            }
            else
            {
                this._head = conn;
            }
        }
    }

    /**
     * Removes listener
     * @param conn
     */
    protected listRemove(conn:SignalConnection):void
    {
        var prev:SignalConnection|null = null;
        var p:SignalConnection|null = this._head;

        while(p != null)
        {
            if(p == conn)
            {
                var next = p._next;
                if(prev == null)
                {
                    this._head = next;
                }
                else
                {
                    prev._next = next;
                }
                return;
            }
            prev = p;
            p = p._next;
        }
    }
}