Browse Source

Add the useBucketSizes optimization

Popov72 5 năm trước cách đây
mục cha
commit
3270956af6

+ 25 - 0
gui/src/2D/controls/scrollViewers/scrollViewer.ts

@@ -103,6 +103,31 @@ export class ScrollViewer extends Rectangle {
         this._window.freezeControls = value;
         this._window.freezeControls = value;
     }
     }
 
 
+    /** Gets the bucket width */
+    public get bucketWidth(): number {
+        return this._window.bucketWidth;
+    }
+
+    /** Gets the bucket height */
+    public get bucketHeight(): number {
+        return this._window.bucketHeight;
+    }
+
+    /**
+     * Sets the bucket sizes.
+     * When freezeControls is true, setting a non-zero bucket size will improve performances by updating only
+     * controls that are visible. The bucket sizes is used to subdivide (internally) the window area to smaller areas into which
+     * controls are dispatched. So, the size should be roughly equals to the mean size of all the controls of
+     * the window. To disable the usage of buckets, sets either width or height (or both) to 0.
+     * Please note that using this option will raise the memory usage (the higher the bucket sizes, the less memory
+     * used), that's why it is not enabled by default.
+     * @param width width of the bucket
+     * @param height height of the bucket
+     */
+    public setBucketSizes(width: number, height: number): void {
+        this._window.setBucketSizes(width, height);
+    }
+
     private _forceHorizontalBar: boolean = false;
     private _forceHorizontalBar: boolean = false;
     private _forceVerticalBar: boolean = false;
     private _forceVerticalBar: boolean = false;
 
 

+ 104 - 4
gui/src/2D/controls/scrollViewers/scrollViewerWindow.ts

@@ -13,10 +13,9 @@ export class _ScrollViewerWindow extends Container {
 
 
     private _freezeControls = false;
     private _freezeControls = false;
     private _parentMeasure: Measure;
     private _parentMeasure: Measure;
+    private _oldLeft: number;
+    private _oldTop: number;
 
 
-    /**
-     * Freezes or unfreezes controls
-     */
     public get freezeControls(): boolean {
     public get freezeControls(): boolean {
         return this._freezeControls;
         return this._freezeControls;
     }
     }
@@ -44,6 +43,9 @@ export class _ScrollViewerWindow extends Container {
         // in freeze mode, prepare children measures accordingly
         // in freeze mode, prepare children measures accordingly
         if (value) {
         if (value) {
             this._updateMeasures();
             this._updateMeasures();
+            if (this._useBuckets()) {
+                this._makeBuckets();
+            }
         }
         }
 
 
         this._freezeControls = value;
         this._freezeControls = value;
@@ -51,6 +53,72 @@ export class _ScrollViewerWindow extends Container {
         this.host.markAsDirty(); // redraw with the (new) current settings
         this.host.markAsDirty(); // redraw with the (new) current settings
     }
     }
 
 
+    private _bucketWidth: number = 0;
+    private _bucketHeight: number = 0;
+    private _buckets: { [key: number]: Array<Control> } = {};
+    private _bucketLen: number;
+
+    public get bucketWidth(): number {
+        return this._bucketWidth;
+    }
+
+    public get bucketHeight(): number {
+        return this._bucketHeight;
+    }
+
+    public setBucketSizes(width: number, height: number): void {
+        this._bucketWidth = width;
+        this._bucketHeight = height;
+
+        if (this._useBuckets()) {
+            if (this._freezeControls) {
+                this._makeBuckets();
+            }
+        } else {
+            this._buckets = {};
+        }
+    }
+
+    private _useBuckets(): boolean {
+        return this._bucketWidth > 0 && this._bucketHeight > 0;
+    }
+
+    private _makeBuckets(): void {
+        this._buckets = {};
+        this._bucketLen = Math.ceil(this.widthInPixels / this._bucketWidth);
+        this._dispatchInBuckets(this._children);
+    }
+
+    private _dispatchInBuckets(children: Control[]): void {
+        for (let i = 0; i < children.length; ++i) {
+            let child = children[i];
+
+            let bStartX = Math.max(0, Math.floor((child._currentMeasure.left - this._currentMeasure.left) / this._bucketWidth)),
+                bEndX = Math.floor((child._currentMeasure.left - this._currentMeasure.left + child._currentMeasure.width - 1) / this._bucketWidth),
+                bStartY = Math.max(0, Math.floor((child._currentMeasure.top - this._currentMeasure.top) / this._bucketHeight)),
+                bEndY = Math.floor((child._currentMeasure.top - this._currentMeasure.top + child._currentMeasure.height - 1) / this._bucketHeight);
+
+            while (bStartY <= bEndY) {
+                for (let x = bStartX; x <= bEndX; ++x) {
+                    let bucket = bStartY * this._bucketLen + x,
+                        lstc = this._buckets[bucket];
+
+                    if (!lstc) {
+                        lstc = [];
+                        this._buckets[bucket] = lstc;
+                    }
+
+                    lstc.push(child);
+                }
+                bStartY++;
+            }
+
+            if (child instanceof Container && child._children.length > 0) {
+                this._dispatchInBuckets(child._children);
+            }
+        }
+    }
+
     // reset left and top measures for the window and all its children
     // reset left and top measures for the window and all its children
     private _updateMeasures(): void {
     private _updateMeasures(): void {
         let left = this.leftInPixels | 0,
         let left = this.leftInPixels | 0,
@@ -129,6 +197,30 @@ export class _ScrollViewerWindow extends Container {
         }
         }
     }
     }
 
 
+    private _scrollChildrenWithBuckets(left: number, top: number, scrollLeft: number, scrollTop: number): void {
+        let bStartX = Math.max(0, Math.floor(-left / this._bucketWidth)),
+            bEndX = Math.floor((-left + this._parentMeasure.width - 1) / this._bucketWidth),
+            bStartY = Math.max(0, Math.floor(-top / this._bucketHeight)),
+            bEndY = Math.floor((-top + this._parentMeasure.height - 1) / this._bucketHeight);
+
+        while (bStartY <= bEndY) {
+            for (let x = bStartX; x <= bEndX; ++x) {
+                let bucket = bStartY * this._bucketLen + x,
+                    lstc = this._buckets[bucket];
+
+                if (lstc) {
+                    for (let i = 0; i < lstc.length; ++i) {
+                        let child = lstc[i];
+                        child._currentMeasure.left = child._customData._origLeft + scrollLeft;
+                        child._currentMeasure.top = child._customData._origTop + scrollTop;
+                        child._isClipped = false; // clipping will be handled by _draw and the call to _intersectsRect()
+                    }
+                }
+            }
+            bStartY++;
+        }
+    }
+
     /** @hidden */
     /** @hidden */
     public _draw(context: CanvasRenderingContext2D, invalidatedRectangle?: Measure): void {
     public _draw(context: CanvasRenderingContext2D, invalidatedRectangle?: Measure): void {
         if (!this._freezeControls) {
         if (!this._freezeControls) {
@@ -145,7 +237,15 @@ export class _ScrollViewerWindow extends Container {
         let left = this.leftInPixels,
         let left = this.leftInPixels,
             top = this.topInPixels;
             top = this.topInPixels;
 
 
-        this._scrollChildren(this._children, left, top);
+        if (this._useBuckets()) {
+            this._scrollChildrenWithBuckets(this._oldLeft, this._oldTop, left, top);
+            this._scrollChildrenWithBuckets(left, top, left, top);
+        } else {
+            this._scrollChildren(this._children, left, top);
+        }
+
+        this._oldLeft = left;
+        this._oldTop = top;
 
 
         for (var child of this._children) {
         for (var child of this._children) {
             if (!child._intersectsRect(this._parentMeasure)) {
             if (!child._intersectsRect(this._parentMeasure)) {