Reduces number of calls to jQuery and coerces floats to ints before positioning.
authordsc <david.schoonover@gmail.com>
Mon, 28 Feb 2011 14:57:08 +0000 (06:57 -0800)
committerdsc <david.schoonover@gmail.com>
Mon, 28 Feb 2011 14:57:08 +0000 (06:57 -0800)
src/Y/op.cjs
src/Y/types/collection.cjs
src/ezl/layer/layerable.cjs
src/ezl/loc/boundingbox.cjs
src/ezl/math/index.cjs
src/ezl/math/rect.cjs
src/tanks/map/level.cjs

index 21021f7..5b7aeed 100644 (file)
@@ -43,9 +43,15 @@ var core = require('Y/core')
     K         : function(k){       return function(){ return k; }; },
     kObject   : function(){        return {}; },
     kArray    : function(){        return []; },
-    nth       : function(n){       return function(){ return arguments[n]; }; },
     val       : function(def,o){   return o !== undefined ? o : def; },
     ok        : function(o){       return o !== undefined && o !== null; },
+    first     : function(a){       return a; },
+    second    : function(_,a){     return a; },
+    nth       : function(n){
+        if (n === 0) return op.first;
+        if (n === 1) return op.second;
+        return function(){ return arguments[n]; };
+    },
     
     // reduce-ordered values & accessors
     khas      : function(k,o){     return k in o; },
@@ -74,11 +80,15 @@ var core = require('Y/core')
     
     // misc
     end : function end(o){ return ((o && o.__y__) ? o.end() : o); },
-    parseBool : function(s){
-        var i = parseInt(s);
-        return isNaN(i) ? (s && s.toLowerCase() !== 'false') : i;
-    }
+    parseBool : parseBool,
+    toBool : parseBool,
+    toInt : function(s){ return parseInt(s) }
     
 };
 
+function parseBool(s){
+    var i = parseInt(s);
+    return isNaN(i) ? (s && s.toLowerCase() !== 'false') : i;
+}
+
 core.extend(exports, op);
index 46bbff5..d30fbcf 100644 (file)
@@ -7,6 +7,8 @@ var Y      = require('Y/y').Y
 ,   slice  = core.slice
 
 ,   internal = require('Y/internal')
+,   first  = op.nth(0)
+,   second = op.nth(1)
 ;
 
 
@@ -76,8 +78,7 @@ YBase.subclass('YCollection', {
     'map' : function map( fn ){
         var cxt = arguments[1] || this;
         return this.reduce(function(acc, v, k, o){
-            var _v = fn.call(cxt, v, k, o);
-            return acc.attr(k, _v);
+            return acc.attr(k, fn.call(cxt, v, k, o));
         }, new this.__class__() );
     },
     
@@ -97,6 +98,13 @@ YBase.subclass('YCollection', {
         }, new this.__class__() );
     },
     
+    amap : function amap( fn ){
+        var cxt = arguments[1] || this;
+        return this.reduce(function(acc, v, k, o){
+            return acc.push(fn.call(cxt, v, k, o));
+        }, Y([]) );
+    },
+    
     // 'find' : function find( fn ){
     //     var cxt = arguments[1] || this;
     //     
@@ -140,19 +148,16 @@ YBase.subclass('YCollection', {
     },
     
     'pluck' : function pluck(key){
-        return this.map(function(v){
-            return del.attr(v, key);
-        });
+        return this.map(plucker, key);
     },
     
     'invoke' : function invoke(name){
         var args = slice.call(arguments,1);
-        return del.map(this, function(o){
-            return (o && type.isFunction(o[name])) ? o[name].apply(o, args) : null;
-        });
+        return del.map(this, invoker, [name, args]); // XXX: why is this del.map and not this.map?
     },
     
-    // FIXME: this._o[name].apply
+    
+    // FIXME: this._o[name].apply?
     'apply' : function apply(name, args){
         return this[name].apply(this, args);
     },
@@ -177,7 +182,16 @@ YBase.subclass('YCollection', {
         return this.filter(A.has, A);
     }
     
-    
 });
 
+// `this` == [name, args]
+function invoker(o){
+    var name = this[0], args = this[1];
+    return (o && (typeof o[name] == 'function')) ? o[name].apply(o, args) : null;
+}
+
+// `this` == key
+function plucker(v){
+    return del.attr(v, this);
+}
 
index 5c46bfc..40af4a6 100644 (file)
@@ -54,14 +54,15 @@ Mixin.subclass('Layerable', {
     animQueue  : null,
     _erased    : null,
     
-    layerWidth  : 0,  canvasWidth  : 0, realWidth  : 0,
-    layerHeight : 0,  canvasHeight : 0, realHeight : 0,
+    realWidth  : 0, layerWidth  : 0,  canvasWidth  : 0,
+    realHeight : 0, layerHeight : 0,  canvasHeight : 0,
     
-    x: 0, y: 0, loc : null, // Position relative to parent
+    loc : null,             // Position relative to parent (float)
+    layerX : 0, layerY: 0,  // Position cast to int and cached to avoid DOM hits
     
     // Bleeds are marks outside the declared layer-size
-    negBleed : null, // Loc
-    posBleed : null, // Loc
+    negBleed : null, // Loc(0,0)
+    posBleed : null, // Loc(0,0)
     
     // Transforms
     _origin : null, // rotational origin
@@ -247,37 +248,75 @@ Mixin.subclass('Layerable', {
         if (w === undefined && h === undefined)
             return new Vec(this.layerWidth,this.layerHeight);
         
-        if (w === null) w = this.realWidth;
-        if (h === null) h = this.realHeight;
-        
-        this.realWidth  = w;
-        this.realHeight = h;
+        var bb = this.bbox
+        ,   ox1 = bb.x1, oy1 = bb.y1
+        ,   ow = this.realWidth,  lw, olw = lw = this.layerWidth,  cw
+        ,   oh = this.realHeight, lh, olh = lh = this.layerHeight, ch
+        ,   changes = {}, cchanges = {}
+        ,   dirtied = false
+        ;
         
-        // HTMLElement.{width,height} is a long
-        this.layerWidth  = Math.round(w);
-        this.layerHeight = Math.round(h);
+        if (w === null || w === undefined) w = ow;
+        if (h === null || h === undefined) h = oh;
         
-        var bb = this.bbox.resize(w,h)
-        ,   nb = this.negBleed, pb = this.posBleed
+        if (w !== ow) {
+            this.realWidth = w;
+            lw = Math.round(w); // HTMLElement.{width,height} is a long
+            if (lw !== olw) {
+                this.layerWidth = changes.width = lw;
+                this.canvasWidth = cchanges.width = cw = Math.ceil(w);
+                dirtied = true;
+            }
+        }
         
-        ,   cw = this.canvasWidth  = Math.ceil(w + nb.x + pb.x)
-        ,   ch = this.canvasHeight = Math.ceil(h + nb.y + pb.y)
-        ;
+        if (h !== oh) {
+            this.realHeight = h;
+            lh = Math.round(h); // HTMLElement.{width,height} is a long
+            if (lh !== olh) {
+                this.layerHeight = changes.height = lh;
+                this.canvasHeight = cchanges.height = ch = Math.ceil(h);
+                dirtied = true;
+            }
+        }
         
-        this.layer.css({
-            'width' : w,            'height' : h,
-            'left'  : bb.x1,        'top' : bb.y1
-        });
-        this.canvas.css({
-            'width' : cw,           'height' : ch,
-            'margin-left' : -nb.x,  'margin-top' : -nb.y
-        });
-        var el = this.canvas[0];
-        if (el) {
-            el.width  = cw;
-            el.height = ch;
+        if (dirtied) {
+            bb.resize(w,h);
+            
+            var x1 = bb.x1 | 0
+            ,   y1 = bb.y1 | 0;
+            if (ox1 !== x1)
+                changes.left = x1;
+            if (oy1 !== y1)
+                changes.top = y1;
+            
+            this.layer.css(changes);
+            
+            var el = this.canvas[0];
+            if (el) {
+                this.canvas.css(cchanges);
+                el.width  = cw;
+                el.height = ch;
+            }
         }
         
+        // If I find I ever need bleeds again, I'll work out the caching implications of them changing
+        // var ocw = this.canvasWidth, och = this.canvasHeight
+        // ,   nb = this.negBleed,     pb = this.posBleed
+        // ,   cw = this.canvasWidth  = Math.ceil(w + nb.x + pb.x)
+        // ,   ch = this.canvasHeight = Math.ceil(h + nb.y + pb.y)
+        // ,   canvasDirtied = false
+        // ;
+        // 
+        // this.canvas.css({
+        //     'width' : cw,           'height' : ch,
+        //     'margin-left' : -nb.x,  'margin-top' : -nb.y
+        // });
+        // var el = this.canvas[0];
+        // if (el) {
+        //     el.width  = cw;
+        //     el.height = ch;
+        // }
+        
         return this;
     },
     
@@ -287,53 +326,13 @@ Mixin.subclass('Layerable', {
     width : function width(w){
         if (w === undefined)
             return this.layerWidth;
-        
-        this.layerWidth = w;
-        
-        var bb = this.bbox.resize(w, this.layerHeight)
-        // ,   ro = bb.relOrigin
-        ,   nb = this.negBleed
-        ,   cw = this.canvasWidth = Math.ceil(w + nb.x + this.posBleed.x); // HTMLCanvas.width is a long
-        
-        this.layer.css({
-            'width'       : w,
-            'left'        : bb.x1,
-            // 'margin-left' : -ro.x
-        });
-        
-        this.canvas.css({
-            'width'       : cw,
-            'margin-left' : -nb.x
-        });
-        if (this.canvas.length) this.canvas[0].width = cw;
-        
-        return this;
+        return this.size(w,null);
     },
     
     height : function height(h){
         if (h === undefined)
             return this.layerHeight;
-        
-        this.layerHeight = h;
-        
-        var bb = this.bbox.resize(this.layerWidth, h)
-        // ,   ro = bb.relOrigin
-        ,   nb = this.negBleed
-        ,   ch = this.canvasHeight = Math.ceil(h + nb.y + this.posBleed.y); // HTMLCanvas.height is a long
-        
-        this.layer.css({
-            'height'     : h,
-            'top'        : bb.y1,
-            // 'margin-top' : -ro.y
-        });
-        
-        this.canvas.css({
-            'height'     : ch,
-            'margin-top' : -nb.y
-        });
-        if (this.canvas.length) this.canvas[0].height = ch;
-        
-        return this;
+        return this.size(null,h);
     },
     
     /**
@@ -344,8 +343,8 @@ Mixin.subclass('Layerable', {
     /**
      * position(x,y) -> {this}
      * Sets the position of this node, and then returns it.
-     * @param {Number|String|undefined} x
-     * @param {Number|String|undefined} y If omitted, this method must be invoked with `undefined` as the first argument.
+     * @param {Number|String|undefined} [x]
+     * @param {Number|String|undefined} [y] If x is omitted, this method must be invoked with `undefined` as the first argument.
      * @return {this}
      */
     /**
@@ -356,20 +355,32 @@ Mixin.subclass('Layerable', {
      */
     position : function position(x,y){
         if (x === undefined && y === undefined)
-            return this.layer.position();
+            return this.loc;
         
         if ( x instanceof Array ) {
             y = x[_Y]; x = x[_X];
-        } else if ( Y.isPlainObject(x) ){
+        } else if ( x !== null && typeof x == 'object' && !(x instanceof Number) ){
             y = ('top'  in x ? x.top  : x.y);
             x = ('left' in x ? x.left : x.x);
         }
+        if (x === null || x === undefined) x = bb.x1;
+        if (y === null || y === undefined) y = bb.y1;
         
-        var bbox = this.bbox.relocate(x,y);
-        this.css({
-            'left' : bbox.x1,
-            'top'  : bbox.y1
-        });
+        var bb = this.bbox;
+        if (x === bb.x1 && y === bb.y1)
+            return this;
+        
+        bb.relocate(x,y);
+        this.loc = bb.loc;
+        var ix = bb.x1 | 0
+        ,   iy = bb.y1 | 0
+        ,   changes = {}, dirtied = false
+        ;
+        
+        if (ix !== this.layerX) { changes.left = this.layerX = ix; dirtied = true; }
+        if (iy !== this.layerY) { changes.top  = this.layerY = iy; dirtied = true; }
+        
+        if (dirtied) this.layer.css(changes);
         return this;
     },
     
@@ -773,4 +784,4 @@ $(function(){
             '.ezl.layer canvas { z-index:0; }'
         ].join('\n'))
         .appendTo('head');
-});
+});
\ No newline at end of file
index dc0b4ae..cff66a6 100644 (file)
@@ -20,6 +20,14 @@ Rect.subclass('BoundingBox', {
     },
     
     /**
+     * @protected
+     * Called by Rect and BoundingBox whenever dirtied.
+     */
+    _uncache : function _uncache(){
+        this._loc = this._relOrigin = null;
+    },
+    
+    /**
      * Changes origin position without moving the bounds.
      * Use relocate() to change the position by moving the origin.
      */
@@ -30,28 +38,50 @@ Rect.subclass('BoundingBox', {
      * Changes origin x-position without moving the bounds.
      * Use relocate() to change the position by moving the origin.
      */
-    set originX(v){ this._origin.x = v - this.x1; return v; },
-    get originX(){ return this.absOrigin.x; },
+    set originX(v){ this._origin.x = v - this[X1]; return v; },
+    get originX(){ return (this._loc || this.loc).x; },
     
     /**
      * Changes origin y-position without moving the bounds.
      * Use relocate() to change the position by moving the origin.
      */
-    set originY(v){ this._origin.y = v - this.y1; return v; },
-    get originY(){ return this.absOrigin.y; },
+    set originY(v){ this._origin.y = v - this[Y1]; return v; },
+    get originY(){ return (this._loc || this.loc).y; },
+    
+    // Accessors for the realized numeric origin relative to the coordinate system of the BoundingBox.
+    get loc() {
+        if (!this._loc)
+            this._loc = this._origin.absolute(this[X2]-this[X1], this[Y2]-this[Y1], this[X1],this[Y1]);
+        return this._loc;
+    },
+    set loc(v) {
+        this._origin.setXY(v.x - this[X1], v.y - this[Y1]);
+        this._uncache();
+        return this.loc;
+    },
     
-    // Accessors for the realized numeric origin.
-    get absOrigin() { return this._origin.absolute(this.width,this.height, this.x1,this.y1); }, // TODO: cache absolute and invalidate
-    set absOrigin(v) { this._origin.setXY(v.x - this.x1, v.y - this.y1); return this._origin; },
-    get relOrigin() { return this._origin.absolute(this.width,this.height); }, // TODO: cache absolute and invalidate
-    set relOrigin(v) { this._origin.setXY(v.x, v.y); return this._origin; },
+    // Alias to match relOrigin
+    get absOrigin(){  return this._loc || this.loc; },
+    set absOrigin(v){ return (this.loc = v); },
+    
+    // Accessors for the realized numeric origin relative to the box bounds.
+    get relOrigin() {
+        if (!this._relOrigin)
+            this._relOrigin = this._origin.absolute(this[X2]-this[X1],this[Y2]-this[Y1]);
+        return this._relOrigin;
+    },
+    set relOrigin(v) {
+        this._origin.setXY(v.x, v.y);
+        this._uncache();
+        return this.relOrigin;
+    },
     
     
     // Accessors for distance from origin to requested bound.
-    get originLeft(){   return this.relOrigin.x; },
-    get originTop(){    return this.relOrigin.y; },
-    get originRight(){  return this.width  - this.relOrigin.x; },
-    get originBottom(){ return this.height - this.relOrigin.y; },
+    get originLeft(){   return (this._relOrigin || this.relOrigin).x; },
+    get originTop(){    return (this._relOrigin || this.relOrigin).y; },
+    get originRight(){  return this.width  - (this._relOrigin || this.relOrigin).x; },
+    get originBottom(){ return this.height - (this._relOrigin || this.relOrigin).y; },
     
     /**
      * Calculates realized distance from origin to requested bound.
@@ -75,16 +105,16 @@ Rect.subclass('BoundingBox', {
      * FIXME: this is just wrong if delta-xy is big enough
      */
     collision : function collision(trj, from, to){
-        if (from.x2 <= this.x1 && to.x2 >= this.x1)
+        if (from.x2 <= this[X1] && to.x2 >= this[X1])
             return this.leftSide;
             
-        if (from.x1 >= this.x2 && to.x1 <= this.x2)
+        if (from.x1 >= this[X2] && to.x1 <= this[X2])
             return this.rightSide;
         
-        if (from.y2 <= this.y1 && to.y2 >= this.y1)
+        if (from.y2 <= this[Y1] && to.y2 >= this[Y1])
             return this.topSide;
         
-        if (from.y1 >= this.y2 && to.y1 <= this.y2)
+        if (from.y1 >= this[Y2] && to.y1 <= this[Y2])
             return this.bottomSide;
         
         return null;
@@ -96,17 +126,17 @@ Rect.subclass('BoundingBox', {
      * @return {Vec} Furthest point or null if no collision is detected.
      */
     furthest : function furthest(trj, from, to){
-        if (from.x2 <= this.x1 && to.x2 >= this.x1)
-            return tr.pointAtX(this.x1 - 1 - from.originRight);
+        if (from.x2 <= this[X1] && to.x2 >= this[X1])
+            return tr.pointAtX(this[X1] - 1 - from.originRight);
             
-        if (from.x1 >= this.x2 && to.x1 <= this.x2)
-            return tr.pointAtX(this.x2 + 1 + from.originLeft);
+        if (from.x1 >= this[X2] && to.x1 <= this[X2])
+            return tr.pointAtX(this[X2] + 1 + from.originLeft);
             
-        if (from.y2 <= this.y1 && to.y2 >= this.y1)
-            return tr.pointAtY(this.y1 - 1 - from.originTop);
+        if (from.y2 <= this[Y1] && to.y2 >= this[Y1])
+            return tr.pointAtY(this[Y1] - 1 - from.originTop);
             
-        if (from.y1 >= this.y2 && to.y1 <= this.y2)
-            return tr.pointAtY(this.y2 + 1 + from.originBottom );
+        if (from.y1 >= this[Y2] && to.y1 <= this[Y2])
+            return tr.pointAtY(this[Y2] + 1 + from.originBottom );
         
         return null;
     },
@@ -139,7 +169,7 @@ Rect.subclass('BoundingBox', {
     
     /**
      * Resizes bounds to proportionally maintain their distance from the origin.
-     * This is an in-place modification of the BoundingBox.
+     * nb. This is an in-place modification of the BoundingBox.
      */
     resize : function resize(w,h){
         if (w instanceof Array) { h=w[1]; w=w[0]; }
index a891b65..cc3dc45 100644 (file)
@@ -1,6 +1,7 @@
 //#exports clamp lerp
-require('Y').Y.core
-.extend(exports, {
+var Y = require('Y').Y;
+
+Y.core.extend(exports, {
     
     'clamp' : function clamp(value, min, max) {
       return Math.min(Math.max(value, min), max);
@@ -8,6 +9,18 @@ require('Y').Y.core
     
     'lerp' : function lerp(x, a, b) {
         return a + x*(b - a);
-    }
+    },
+    
+    // Arg-masked versions of Math functions
+    'min'   : Y(Math.min).limit(2),
+    'max'   : Y(Math.max).limit(2),
+    
+    'floor' : Y(Math.floor).limit(1),
+    'ceil'  : Y(Math.ceil).limit(1),
+    'round' : Y(Math.round).limit(1),
+    
+    'abs'   : Y(Math.abs).limit(1),
+    'sqrt'  : Y(Math.sqrt).limit(1)
+    
     
 });
index 75d3b76..6554269 100644 (file)
@@ -27,22 +27,29 @@ Y.subclass('Rect', [], {
     
     /**
      * @protected
+     * Called whenever the Rect is dirtied (a no-op by default).
+     */
+    _uncache : Y.op.nop,
+    
+    /**
+     * @protected
      */
     set4 : function set4(x1,y1, x2,y2){
         this[X1] = x1; this[Y1] = y1;
         this[X2] = x2; this[Y2] = y2;
+        this._uncache();
         return this;
     },
     
-    get x1(){ return this[X1]; }, set x1(v){ this[X1] = v; return v; },
-    get y1(){ return this[Y1]; }, set y1(v){ this[Y1] = v; return v; },
-    get x2(){ return this[X2]; }, set x2(v){ this[X2] = v; return v; },
-    get y2(){ return this[Y2]; }, set y2(v){ this[Y2] = v; return v; },
+    get x1(){ return this[X1]; }, set x1(v){ this[X1] = v; this._uncache(); return v; },
+    get y1(){ return this[Y1]; }, set y1(v){ this[Y1] = v; this._uncache(); return v; },
+    get x2(){ return this[X2]; }, set x2(v){ this[X2] = v; this._uncache(); return v; },
+    get y2(){ return this[Y2]; }, set y2(v){ this[Y2] = v; this._uncache(); return v; },
     
     get p1(){  return new Vec(this[X1],this[Y1]); },
-    set p1(v){ this[X1] = v.x; this[Y1] = this.y; return v; },
+    set p1(v){ this[X1] = v.x; this[Y1] = this.y; this._uncache(); return v; },
     get p2(){  return new Vec(this[X2],this[Y2]); },
-    set p2(v){ this[X2] = v.x; this[Y2] = this.y; return v; },
+    set p2(v){ this[X2] = v.x; this[Y2] = this.y; this._uncache(); return v; },
     
     get width(){  return this[X2] - this[X1]; },
     get height(){ return this[Y2] - this[Y1]; },
index 435f1ad..05c8f79 100644 (file)
@@ -1,21 +1,20 @@
 var Y          = require('Y').Y
 ,   op         = require('Y/op')
-,   proxy    = require('Y/utils').proxy
+,   proxy      = require('Y/utils').proxy
+
 ,   evt        = require('evt')
+
+,   math       = require('ezl/math')
+,   Speciated  = require('ezl/mixins/speciated').Speciated
 ,   Rect       = require('ezl/shape').Rect
 
 ,   Buff       = require('tanks/effects/buff').Buff
-,   Map    = require('tanks/map/pathing/map').Map
+,   Map        = require('tanks/map/pathing/map').Map
 ,   Thing      = require('tanks/thing/thing').Thing
 ,   Tank       = require('tanks/thing/tank').Tank
 ,   Item       = require('tanks/thing/item').Item
-,   Player = require('tanks/thing/player').Player
+,   Player     = require('tanks/thing/player').Player
 ,   Wall       = require('tanks/map/wall').Wall
-,   Speciated  = require('ezl/mixins/speciated').Speciated
-
-,   min = Y(Math.min).limit(2)
-,   max = Y(Math.max).limit(2)
-,   toInt = Y(parseInt).limit(1)
 ,
 
 
@@ -25,15 +24,12 @@ Rect.subclass('Level', {
     __mixins__ : [ Speciated ],
     _layerClasses : 'ezl layer level',
     
-    init : function init(game, capacity, buffer_size){
+    init : function initLevel(game, capacity, buffer_size){
         this.game    = game;
         this.map = new Map(0,0, this.levelWidth, this.levelHeight, capacity, buffer_size);
         
         Rect.init.call(this, this.levelWidth,this.levelHeight);
         this.fill('transparent');
-        // var shape = this.shape = new Rect(this.levelWidth,this.levelHeight).fill('transparent');
-        // shape.layer.attr('class', this._layerClasses);
-        // this.bbox = shape.bbox;
     },
     
     setup : function setup(repo){
@@ -61,9 +57,6 @@ Rect.subclass('Level', {
             return game.addUnit(obj, x.loc[0], x.loc[1]);
         });
         
-        // I =
-        // game.addUnit(Item.create('nitro'), 8,8);
-        
         return this;
     }
     
@@ -81,19 +74,19 @@ Level.on('speciate',
         ;
         
         if (size) {
-            size = size.split(' ').map(toInt);
-            proto.levelWidth = size[0];
+            size = size.split(' ').map(op.toInt);
+            proto.levelWidth  = size[0];
             proto.levelHeight = size[1];
         }
         
-        if (!proto.levelWidth) {
-            proto.levelWidth =
-                bounds.right.pluck(0).reduce(max,0)
-                - bounds.left.pluck(0).reduce(min,Infinity);
-        }
-        if (!proto.levelHeight) {
-            proto.levelHeight =
-                bounds.bottom.pluck(1).reduce(max,0)
-                - bounds.top.pluck(1).reduce(min,Infinity);
-        }
+        // if (!proto.levelWidth) {
+        //     proto.levelWidth =
+        //         bounds.right.pluck(0).reduce(math.max,0)
+        //         - bounds.left.pluck(0).reduce(math.min,Infinity);
+        // }
+        // if (!proto.levelHeight) {
+        //     proto.levelHeight =
+        //         bounds.bottom.pluck(1).reduce(math.max,0)
+        //         - bounds.top.pluck(1).reduce(math.min,Infinity);
+        // }
     });