mirror of
https://github.com/google/blockly.git
synced 2026-01-13 20:07:08 +01:00
feat: add after queued renders promises to render management (#6981)
* feat: add promise to render queue system * chore: update docs for variables * chore: add export of render management * chore: add tests for resolving render management promises * fix: modify insertion marker manager to use render management promises * chore: format * chore: remove callback from afterQueuedRenders * chore: don't store resolver * chore: rename afterQueuedRenders -> finishQueuedRenders * chore: fix tests
This commit is contained in:
@@ -1611,10 +1611,13 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
/**
|
||||
* Triggers a rerender after a delay to allow for batching.
|
||||
*
|
||||
* @returns A promise that resolves after the currently queued renders have
|
||||
* been completed. Used for triggering other behavior that relies on
|
||||
* updated size/position location for the block.
|
||||
* @internal
|
||||
*/
|
||||
queueRender() {
|
||||
queueRender(this);
|
||||
queueRender(): Promise<void> {
|
||||
return queueRender(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -121,6 +121,7 @@ import * as uiPosition from './positionable_helpers.js';
|
||||
import * as Procedures from './procedures.js';
|
||||
import * as registry from './registry.js';
|
||||
import {RenderedConnection} from './rendered_connection.js';
|
||||
import * as renderManagement from './render_management.js';
|
||||
import * as blockRendering from './renderers/common/block_rendering.js';
|
||||
import * as constants from './constants.js';
|
||||
import * as geras from './renderers/geras/geras.js';
|
||||
@@ -711,6 +712,7 @@ export {Msg, setLocale};
|
||||
export {Names};
|
||||
export {Options};
|
||||
export {RenderedConnection};
|
||||
export {renderManagement};
|
||||
export {Scrollbar};
|
||||
export {ScrollbarPair};
|
||||
export {ShortcutRegistry};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.InsertionMarkerManager');
|
||||
|
||||
import {finishQueuedRenders} from './render_management.js';
|
||||
import * as blockAnimations from './block_animations.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as common from './common.js';
|
||||
@@ -188,9 +189,9 @@ export class InsertionMarkerManager {
|
||||
const inferiorConnection = local.isSuperior() ? closest : local;
|
||||
const rootBlock = this.topBlock.getRootBlock();
|
||||
|
||||
// bringToFront is incredibly expensive. Delay by at least a frame.
|
||||
requestAnimationFrame(() => {
|
||||
finishQueuedRenders().then(() => {
|
||||
blockAnimations.connectionUiEffect(inferiorConnection.getSourceBlock());
|
||||
// bringToFront is incredibly expensive. Delay until the next frame.
|
||||
setTimeout(() => {
|
||||
rootBlock.bringToFront();
|
||||
}, 0);
|
||||
|
||||
@@ -8,20 +8,51 @@ import {BlockSvg} from './block_svg.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
|
||||
|
||||
/** The set of all blocks in need of rendering which don't have parents. */
|
||||
const rootBlocks = new Set<BlockSvg>();
|
||||
|
||||
/** The set of all blocks in need of rendering. */
|
||||
let dirtyBlocks = new WeakSet<BlockSvg>();
|
||||
let pid = 0;
|
||||
|
||||
/**
|
||||
* The promise which resolves after the current set of renders is completed. Or
|
||||
* null if there are no queued renders.
|
||||
*
|
||||
* Stored so that we can return it from afterQueuedRenders.
|
||||
*/
|
||||
let afterRendersPromise: Promise<void>|null = null;
|
||||
|
||||
/**
|
||||
* Registers that the given block and all of its parents need to be rerendered,
|
||||
* and registers a callback to do so after a delay, to allowf or batching.
|
||||
*
|
||||
* @param block The block to rerender.
|
||||
* @return A promise that resolves after the currently queued renders have been
|
||||
* completed. Used for triggering other behavior that relies on updated
|
||||
* size/position location for the block.
|
||||
* @internal
|
||||
*/
|
||||
export function queueRender(block: BlockSvg) {
|
||||
export function queueRender(block: BlockSvg): Promise<void> {
|
||||
queueBlock(block);
|
||||
if (!pid) pid = window.requestAnimationFrame(doRenders);
|
||||
if (!afterRendersPromise) {
|
||||
afterRendersPromise = new Promise((resolve) => {
|
||||
window.requestAnimationFrame(() => {
|
||||
doRenders();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return afterRendersPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns A promise that resolves after the currently queued renders have
|
||||
* been completed.
|
||||
*/
|
||||
export function finishQueuedRenders(): Promise<void> {
|
||||
// If there are no queued renders, return a resolved promise so `then`
|
||||
// callbacks trigger immediately.
|
||||
return afterRendersPromise ? afterRendersPromise : Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +93,7 @@ function doRenders() {
|
||||
|
||||
rootBlocks.clear();
|
||||
dirtyBlocks = new Set();
|
||||
pid = 0;
|
||||
afterRendersPromise = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
'Blockly.test.procedureMap',
|
||||
'Blockly.test.procedures',
|
||||
'Blockly.test.registry',
|
||||
'Blockly.test.renderManagement',
|
||||
'Blockly.test.serialization',
|
||||
'Blockly.test.shortcutRegistry',
|
||||
'Blockly.test.touch',
|
||||
|
||||
64
tests/mocha/render_management_test.js
Normal file
64
tests/mocha/render_management_test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.declareModuleId('Blockly.test.renderManagement');
|
||||
|
||||
import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js';
|
||||
|
||||
|
||||
suite('Render Management', function() {
|
||||
setup(function() {
|
||||
this.clock = sharedTestSetup.call(this).clock;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
sharedTestTeardown.call(this);
|
||||
});
|
||||
|
||||
suite('finish queued renders callback', function() {
|
||||
function createMockBlock() {
|
||||
return {
|
||||
hasRendered: false,
|
||||
renderEfficiently: function() {this.hasRendered = true;},
|
||||
|
||||
// All of the APIs the render management system needs.
|
||||
getParent: () => null,
|
||||
getChildren: () => [],
|
||||
isDisposed: () => false,
|
||||
getConnections_: () => [],
|
||||
getRelativeToSurfaceXY: () => ({x: 0, y: 0}),
|
||||
workspace: {
|
||||
resizeContents: () => {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('the queueRender promise is properly resolved after rendering',
|
||||
function() {
|
||||
const block = createMockBlock();
|
||||
const promise = Blockly.renderManagement.queueRender(block)
|
||||
.then(() => {
|
||||
chai.assert.isTrue(
|
||||
block.hasRendered, 'Expected block to be rendered');
|
||||
});
|
||||
this.clock.runAll();
|
||||
return promise;
|
||||
});
|
||||
|
||||
test(
|
||||
'the finish queued renders promise is properly resolved after rendering',
|
||||
function() {
|
||||
const block = createMockBlock();
|
||||
Blockly.renderManagement.queueRender(block);
|
||||
const promise = Blockly.renderManagement.finishQueuedRenders(() => {
|
||||
chai.assert.isTrue(
|
||||
block.hasRendered, 'Expected block to be rendered');
|
||||
});
|
||||
this.clock.runAll();
|
||||
return promise;
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user