Parse

File Parse coupling.js

This tree is parsed live from the source file.

Classes

  • {{ item.name }}

    • {{ key }}

Not Classes

{{ getTree() }}

Comments

{{ getTreeComments() }}

Source

            /*
---
title: Coupling
---

Bind the values of a point with another point.
Provide a relative offset object.

    coupling = new Coupling()
    coupling.couple(a, b, { x: 10, y: 10})

Both points are reative to their coupled position:

    // Dragging either point will result in constant coupling.
    dragging.add(a, b)
 */
class Coupling {

    constructor() {
        this.pairs = new Set
    }

    couple(a, b, offset, keys=['x', 'y', 'radius', 'rotation']) {
        /*
        Couple two points, binding the x,y,radius,rotation
        Apply a relative offset to update the values between binding changes

        when a side is manipulated, the _other_ side is updated.
        If B is the altered value, the relativeOffset is applied inversly.

        Apply a function to the relative offset key to capture live change.s
        */

        if(offset == undefined) {
            // {x: 0, y: 0, radius: 0, rotation: 0}
            offset = {}
            keys.map(k=>offset[k]=0)
        }

        const copy = (origin, cacheObj) => {
            /* Copy a dictionaries keys from B into A.
                copy(from, to)
            */
            for(let key in offset) {
                cacheObj[key] = origin[key]
            }
            return cacheObj
        }

        /* an item to install into the future tests*/
        this.pairs.add({
            a, b, offset
            , aCache: copy(a, { dirty: true })
            , bCache: copy(b, { dirty: true })
        })
    }

    step(){
        /* Perform a single _step_ of the coupling test,
        if either side of the connection has changed, bubble the update
        to its sibling - through the relative conversion. */


        const perform = function(changes,
                                primary, primaryCache,
                                secondary, secondaryCache,
                                func
                            ){
            /* The perform function is called by the each pair _twice_, for
            both sides of a coupling, providing a list of keys (`changes`) to
            assign updates.

            This is called only if a value stores in the cache differs to
            the current value on a point.
             */
            changes.forEach((key, i) => {
                /* Install the current primary value into its cache.
                and apply the value to the second entity, also applying
                its new value to its cache,
                */
                const av = primary[key]
                primaryCache[key] = av
                secondaryCache[key] = secondary[key] = func(key, av)
            });
        }

        /* A Test of a single item. */
        const eachFunc = item => {

            let a = item.a
                , b = item.b
                , offset = item.offset
                , aCache = item.aCache
                , bCache = item.bCache
                ;

            perform(this.hasChanged(a, aCache)
                    , a, aCache
                    , b, bCache
                    , (key, value) => {
                            return value + offset[key];
                        }
                    )

            perform(this.hasChanged(b, bCache)
                    , b, bCache
                    , a, aCache
                    , (key, value) => {
                            return value + -offset[key];
                        }
                    )
        }

        this.pairs.forEach(eachFunc)
    }

    hasChanged(a, aCache) {
        /* Given an object and its _cache_, test each key in the _cache_
        and return a Set of changed keys

            hasChanged({foo: 20, bar: 10}, {foo: 10, bar: 10})
            ['foo']

        The return keys are values that do not match _a_.
        If the cache is flagged _dirty_, all keys are returned.

            hasChanged({foo: 20, bar:10}, {foo: 20, bar: 10, dirty: true})
            ['foo', 'bar']

        If no changes are detected `undefined` is returned.

            if(this.hasChanged({}, {})) {
                // is not called.
            }
        */
        let res = new Set;
        if(aCache.dirty) { return Object.keys(aCache) };
        for(let key in aCache) {
            if(a[key] != aCache[key]) {
                res.add(key)
            }
        }

        return (res.size == 0)? undefined: res;
    }

}


class LockedCoupling {

    constructor() {
        this.pairs = new Set
    }

    couple(a, b, offset, keys=undefined) {
        /*
        Couple two points, binding the x,y,radius,rotation
        Apply a relative offset to update the values between binding changes

        when a side is manipulated, the _other_ side is updated.
        If B is the altered value, the relativeOffset is applied inversly.

        Apply a function to the relative offset key to capture live change.s
        */

        if(keys == undefined) {
            //['x', 'y', 'radius', 'rotation']
            keys = Object.keys(offset)
        }

        if(offset == undefined) {
            // {x: 0, y: 0, radius: 0, rotation: 0}
            offset = {}
            keys.map(k=>offset[k]=0)
        }

        const copy = (origin, cacheObj) => {
            /* Copy a dictionaries keys from B into A.
                copy(from, to)
            */
            for(let key of keys) {
                cacheObj[key] = origin[key]
            }
            return cacheObj
        }

        /* an item to install into the future tests*/
        this.pairs.add({
            a, b, offset, keys
            , aCache: copy(a, { dirty: true })
            , bCache: copy(b, { dirty: true })
        })
    }

    step(){

        const perform = function(changes,
                                primary, primaryCache,
                                secondary, secondaryCache,
                                func
                            ){
            changes.forEach((key, i) => {
                /* Install the current primary value into its cache.
                and apply the value to the second entity, also applying
                its new value to its cache,
                */
                const av = primary[key]
                primaryCache[key] = av
                secondaryCache[key] = secondary[key] = func(key, av)
            });
        }
        /* A Test of an item */
        const eachFunc = item => {

            let a = item.a
                , b = item.b
                , offset = item.offset
                , aCache = item.aCache
                , bCache = item.bCache
                ;
            perform(this.hasChanged(a, aCache)
                    , a, aCache
                    , b, bCache
                    , (key, value) => value + offset[key]
                    )

            perform(this.hasChanged(b, bCache)
                    , b, bCache
                    , a, aCache
                    , (key, value) => value + -offset[key]
                    )
        }

        this.pairs.forEach(eachFunc)
    }

    hasChanged(a, aCache) {
        /* Given an object and its _cache_, test each key in the _cache_
        and return a Set of changed keys

            hasChanged({foo: 20, bar: 10}, {foo: 10, bar: 10})
            ['foo']

        The return keys are values that do not match _a_.
        If the cache is flagged _dirty_, all keys are returned.

            hasChanged({foo: 20, bar:10}, {foo: 20, bar: 10, dirty: true})
            ['foo', 'bar']

        If no changes are detected `undefined` is returned.

            if(this.hasChanged({}, {})) {
                // is not called.
            }
        */
        let res = new Set;
        if(aCache.dirty || isNaN(aCache.dirty)) {

            /* Here we flag _zero_ rather than delete or undefined
            to denote that dirty is false without extra steps. */
            aCache.dirty = 0
            return Object.keys(aCache)
        };

        for(let key in aCache) {
            if(a[key] != aCache[key]) {
                res.add(key)
            }
        }

        return (res.size == 0)? undefined: res;
    }

}
copy