From c73e13cb62dc60d12f0550f2f7fe47f285d133ee Mon Sep 17 00:00:00 2001 From: Said Abou-Hallawa Date: Thu, 25 Apr 2024 17:51:35 -0700 Subject: [PATCH] Expand the grid size of the Multiply test automatically when needed https://github.com/WebKit/MotionMark/issues/52 Enlarge the grid size and reduce the tile size when a larger complexity is needed to lower the frame rate. Refactor the spiral iterator into a separate class. Keep calling its next() method to move to the next cell. When its isDone() returns true enlarge the iterator grid size and resize the already created tiles to fit in the new grid size. Add a new class for the roundedRect tile called "Tile". This class can handle the location, size and animation of the Tile. --- MotionMark/resources/extensions.js | 26 ++ MotionMark/tests/core/resources/multiply.js | 315 ++++++++++++++------ 2 files changed, 247 insertions(+), 94 deletions(-) diff --git a/MotionMark/resources/extensions.js b/MotionMark/resources/extensions.js index 3fb5794..8222c4f 100644 --- a/MotionMark/resources/extensions.js +++ b/MotionMark/resources/extensions.js @@ -265,18 +265,41 @@ Point = Utilities.createClass( return this.x; }, + // Used when the point object is used as a size object. + set width(w) + { + this.x = w; + }, + // Used when the point object is used as a size object. get height() { return this.y; }, + // Used when the point object is used as a size object. + set height(h) + { + this.y = h; + }, + // Used when the point object is used as a size object. get center() { return new Point(this.x / 2, this.y / 2); }, + // Used when the point object is used as a size object. + area: function() { + return this.x * this.y; + }, + + // Used when the point object is used as a size object. + expand(width, height) { + this.x += width; + this.y += height; + }, + str: function() { return "x = " + this.x + ", y = " + this.y; @@ -320,6 +343,9 @@ Point = Utilities.createClass( } }); +// FIXME: Add a seprate class for Size. +let Size = Point; + Utilities.extendObject(Point, { zero: new Point(0, 0), diff --git a/MotionMark/tests/core/resources/multiply.js b/MotionMark/tests/core/resources/multiply.js index c21febc..34045e9 100644 --- a/MotionMark/tests/core/resources/multiply.js +++ b/MotionMark/tests/core/resources/multiply.js @@ -24,120 +24,247 @@ */ (function() { +var Tile = Utilities.createClass( + function(stage, coordinate) + { + this.stage = stage; + this.coordinate = coordinate; + + this.roundedRect = Utilities.createElement('div', { + class: "div-" + Stage.randomInt(0, 5) + }, stage.element); + + this.distance = this.coordinate.length(); + this.step = Math.max(3, this.distance / 1.5); + this.rotate = Stage.randomInt(0, 359); + + this.move(); + this.resize(); + this.hide(); + }, { + + move: function() { + let tileSize = this.stage.tileSize; + + let location = this.stage.size.center; + location = location.add(this.coordinate.multiply(tileSize)); + location = location.subtract(tileSize.multiply(0.5)); + + this.roundedRect.style.left = location.x + 'px'; + this.roundedRect.style.top = location.y + 'px'; + }, + + resize: function() { + let tileSize = this.stage.tileSize; + + this.roundedRect.style.width = tileSize.width + 'px'; + this.roundedRect.style.height = tileSize.height + 'px'; + }, + + show: function() { + this.roundedRect.style.display = "block"; + }, + + hide: function() { + this.roundedRect.style.display = "none"; + }, + + backgroundColor: function() { + let influence = Math.max(.01, 1 - (this.distance * this.stage.distanceFactor)); + let l = this.stage.l * Math.tan(influence); + return this.stage.hslPrefix + l + "%," + influence + ")"; + }, + + animate: function(timestamp) { + this.rotate += this.step; + this.roundedRect.style.transform = "rotate(" + this.rotate + "deg)"; + this.roundedRect.style.backgroundColor = this.backgroundColor(); + } +}); + +var SpiralIterator = Utilities.createClass( + function(gridSize) + { + this.gridSize = gridSize; + this.current = Point.zero; + this.direction = this.directions.right; + this.size = new Size(1, 1); + this.count = 0; + }, { + + directions: { + top: 0, + left: 1, + bottom: 2, + right: 3 + }, + + moves: [ + new Size(0, -1), // top + new Size(-1, 0), // left + new Size(0, +1), // bottom + new Size(+1, 0) // right + ], + + isDone: function() { + return this.count >= this.gridSize.area(); + }, + + next: function() { + ++this.count; + + if (this.isDone()) + return; + + let direction = this.direction; + let move = this.moves[direction]; + + if (Math.abs(this.current.x) == Math.abs(this.current.y)) { + // Turn left. + direction = (direction + 1) % 4; + + if (this.current.x >= 0 && this.current.y >= 0) { + if (this.size.width < Math.min(this.gridSize.width, this.gridSize.height)) + this.size.expand(2, 2); + else if (this.size.width < this.gridSize.width) + ++this.size.width; + + move = this.moves[this.directions.right]; + } else + move = this.moves[direction]; + } + + if (this.count < this.size.area()) { + this.current = this.current.add(move); + this.direction = direction; + return; + } + + // Make a U-turn. + this.direction = (this.direction + 1) % 4; + + if (this.direction == this.directions.left || this.direction == this.directions.right) + this.current = this.current.add(this.moves[this.direction].multiply(this.size.width++)); + else + this.current = this.current.add(this.moves[this.direction].multiply(this.size.height++)); + + this.direction = (this.direction + 1) % 4; + } +}); + var MultiplyStage = Utilities.createSubclass(Stage, function() { Stage.call(this); this.tiles = []; - this._offsetIndex = 0; + this.activeLength = 0; }, { - visibleCSS: [ - ["display", "none", "block"] - ], - totalRows: 68, + initialRowsCount: 69, + extraExpandRows: 16, - initialize: function(benchmark, options) - { + initialize: function(benchmark, options) { Stage.prototype.initialize.call(this, benchmark, options); - var tileSize = Math.round(this.size.height / this.totalRows); - if (options.visibleCSS) - this.visibleCSS = options.visibleCSS; - - // Fill the scene with elements - var x = Math.round((this.size.width - tileSize) / 2); - var y = Math.round((this.size.height - tileSize) / 2); - var tileStride = tileSize; - var direction = 0; - var spiralCounter = 2; - var nextIndex = 1; - var maxSide = Math.floor(y / tileStride) * 2 + 1; - this._centerSpiralCount = maxSide * maxSide; - for (var i = 0; i < this._centerSpiralCount; ++i) { - this._addTile(x, y, tileSize, Stage.randomInt(0, 359)); - - if (i == nextIndex) { - direction = (direction + 1) % 4; - spiralCounter++; - nextIndex += spiralCounter >> 1; - } - if (direction == 0) - x += tileStride; - else if (direction == 1) - y -= tileStride; - else if (direction == 2) - x -= tileStride; - else - y += tileStride; - } - this._sidePanelCount = maxSide * Math.floor((this.size.width - x) / tileStride) * 2; - for (var i = 0; i < this._sidePanelCount; ++i) { - var sideX = x + Math.floor(Math.floor(i / maxSide) / 2) * tileStride; - var sideY = y - tileStride * (i % maxSide); + this.rowsCount = this.initialRowsCount; - if (Math.floor(i / maxSide) % 2 == 1) - sideX = this.size.width - sideX - tileSize + 1; - this._addTile(sideX, sideY, tileSize, Stage.randomInt(0, 359)); - } + this.calculateTileSize(); + this.calculateGridSize(); + this.iterator = new SpiralIterator(this.tileGrid); + + while (!this.iterator.isDone()) + this.tiles.push(this.createTile()); }, - _addTile: function(x, y, tileSize, rotateDeg) - { - var tile = Utilities.createElement("div", { class: "div-" + Stage.randomInt(0,6) }, this.element); - var halfTileSize = tileSize / 2; - tile.style.left = x + 'px'; - tile.style.top = y + 'px'; - tile.style.width = tileSize + 'px'; - tile.style.height = tileSize + 'px'; - var visibleCSS = this.visibleCSS[this.tiles.length % this.visibleCSS.length]; - tile.style[visibleCSS[0]] = visibleCSS[1]; - - var distance = 1 / tileSize * this.size.multiply(0.5).subtract(new Point(x + halfTileSize, y + halfTileSize)).length(); - this.tiles.push({ - element: tile, - rotate: rotateDeg, - step: Math.max(3, distance / 1.5), - distance: distance, - active: false, - visibleCSS: visibleCSS, - }); - }, - - complexity: function() - { - return this._offsetIndex; + calculateTileSize: function() { + let tileSide = Math.round(this.size.height / this.rowsCount); + this.tileSize = new Size(tileSide, tileSide); }, - tune: function(count) - { - this._offsetIndex = Math.max(0, Math.min(this._offsetIndex + count, this.tiles.length)); - this._distanceFactor = 1.5 * (1 - 0.5 * Math.max(this._offsetIndex - this._centerSpiralCount, 0) / this._sidePanelCount) / Math.sqrt(this._offsetIndex); + calculateGridSize: function() { + let columnsCount = Math.floor(this.size.width / this.tileSize.width); + if (columnsCount % 2 == 0) + --columnsCount; + this.tileGrid = new Size(columnsCount, this.rowsCount); }, - animate: function() - { - var progress = this._benchmark.timestamp % 10000 / 10000; - var bounceProgress = Math.sin(2 * Math.abs( 0.5 - progress)); - var l = Utilities.lerp(bounceProgress, 20, 50); - var hslPrefix = "hsla(" + Utilities.lerp(progress, 0, 360) + ",100%,"; - - for (var i = 0; i < this._offsetIndex; ++i) { - var tile = this.tiles[i]; - tile.active = true; - tile.element.style[tile.visibleCSS[0]] = tile.visibleCSS[2]; - tile.rotate += tile.step; - tile.element.style.transform = "rotate(" + tile.rotate + "deg)"; - - var influence = Math.max(.01, 1 - (tile.distance * this._distanceFactor)); - tile.element.style.backgroundColor = hslPrefix + l * Math.tan(influence / 1.25) + "%," + influence + ")"; + expandGrid: function(extraRows) { + this.rowsCount += this.extraExpandRows; + + this.calculateTileSize(); + this.calculateGridSize(); + this.iterator.gridSize = this.tileGrid; + + for (let tile of this.tiles) { + tile.move(); + tile.resize(); } + }, + + createTile: function() { + let tile = new Tile(this, this.iterator.current); - for (var i = this._offsetIndex; i < this.tiles.length && this.tiles[i].active; ++i) { - var tile = this.tiles[i]; - tile.active = false; - tile.element.style[tile.visibleCSS[0]] = tile.visibleCSS[1]; + if (this.iterator.isDone()) + this.expandGrid(); + + this.iterator.next(); + return tile; + }, + + complexity: function() { + return this.activeLength; + }, + + activeTiles: function() { + return this.tiles.slice(0, this.activeLength); + }, + + inactiveTiles: function(end) { + return this.tiles.slice(this.activeLength, end); + }, + + reusableTune: function(count) { + if (count == 0) + return; + + if (count < 0) { + this.activeLength = Math.max(this.activeLength + count, 0); + for (var i = this.activeLength; i < this.tiles.length; ++i) + this.tiles[i].hide(); + return; } + + let inactiveTiles = this.inactiveTiles(this.activeLength + count); + for (let tile of inactiveTiles) + tile.show(); + + for (let i = inactiveTiles.length; i < count; ++i) + this.tiles.push(this.createTile()); + + this.activeLength += count; + }, + + reusableAnimate: function(timestamp) { + for (let tile of this.activeTiles()) + tile.animate(timestamp); + }, + + tune: function(count) { + this.reusableTune(count); + + let centerSpiralCount = this.tileGrid.height * this.tileGrid.height; + let sidePanelCount = this.tileGrid.area() - centerSpiralCount; + let activeSidePanelCount = Math.max(this.activeLength - centerSpiralCount, 0); + this.distanceFactor = 1.5 * (1 - 0.5 * activeSidePanelCount / sidePanelCount) / Math.sqrt(this.activeLength); + }, + + animate: function(timestamp) { + let progress = timestamp % 10000 / 10000; + let bounceProgress = Math.sin(2 * Math.abs(0.5 - progress)); + this.l = Utilities.lerp(bounceProgress, 20, 50); + this.hslPrefix = "hsla(" + Utilities.lerp(progress, 0, 360) + ",100%,"; + + this.reusableAnimate(timestamp); } });