Skip to content

Commit

Permalink
feat(dia.Paper): add autoFreeze option (#2134)
Browse files Browse the repository at this point in the history
  • Loading branch information
Geliogabalus authored Apr 18, 2023
1 parent 169f26a commit ce1f70a
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 17 deletions.
1 change: 1 addition & 0 deletions demo/performance/async.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</style>
<input id="viewport" type="checkbox" checked="true"/>Custom viewport
<input id="padding" type="checkbox" checked="true"/>Padding
<input id="autofreeze" type="checkbox"/>autoFreeze
<input id="leave-rendered-in-viewport" type="checkbox"/>Keep rendered in viewport
<input id="leave-dragged-in-viewport" type="checkbox"/>Keep dragged in viewport
<input id="count" type="number" value="1000"/>Count
Expand Down
25 changes: 23 additions & 2 deletions demo/performance/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,19 @@ var windowBBox;
function setWindowBBox() {
windowBBox = paper.pageToLocalRect(window.scrollX, window.scrollY, window.innerWidth, window.innerHeight);
}
window.onscroll = function() { setWindowBBox(); };
window.onresize = function() { setWindowBBox(); };

window.onscroll = function() {
setWindowBBox();
if (autoFreeze && paper.isFrozen()) {
paper.unfreeze();
}
};
window.onresize = function() {
setWindowBBox();
if (autoFreeze && paper.isFrozen()) {
paper.unfreeze();
}
};

var viewportTemplate = new Rectangle({
size: { width: 200, height: 200 },
Expand Down Expand Up @@ -116,6 +127,16 @@ viewportInput.addEventListener('click', function(evt) {
viewportRect = evt.target.checked;
}, false);

var autoFreezeInput = document.getElementById('autofreeze');
var autoFreeze = autoFreezeInput.checked;
autoFreezeInput.addEventListener('click', function(evt) {
autoFreeze = evt.target.checked;
paper.options.autoFreeze = autoFreeze;
if (!autoFreeze) {
paper.unfreeze();
}
}, false);

var leaveRenderedInput = document.getElementById('leave-rendered-in-viewport');
var leaveRenderedInViewport = leaveRenderedInput.checked;
leaveRenderedInput.addEventListener('click', function(evt) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<code>autoFreeze</code> - By default, the paper periodically checks the viewport calling <a href="#dia.Paper.prototype.options.viewport"><code>viewport</code></a> callback for every view.
The options <code>autoFreeze</code> can be used to suppress this behavior. When the option is enabled, it freezes the paper as soon as there are no more updates and unfreezes it when there is an update scheduled.
(paper has no updates to perform when <a href="#dia.Paper.prototype.hasScheduledUpdates">hasScheduledUpdates()</a> returns <code>false</code>).
In that case, the user needs to <a href="#dia.Paper.prototype.checkViewport"><code>checkViewport</code></a> manually when he wants to reinvoke <a href="#dia.Paper.prototype.options.viewport"><code>viewport</code></a>
when external changes occurred, e. g. during scrolling and zooming. The freeze state will be set on the next frame cycle after processing all updates. On this cycle paper will call <a href="#dia.Paper.prototype.options.viewport"><code>viewport</code></a> callback
and freeze itself.
46 changes: 32 additions & 14 deletions src/dia/Paper.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ export const Paper = View.extend({

frozen: false,

autoFreeze: false,

// no docs yet
onViewUpdate: function(view, flag, priority, opt, paper) {
// Do not update connected links when:
Expand Down Expand Up @@ -415,7 +417,8 @@ export const Paper = View.extend({
keyFrozen: false,
freezeKey: null,
sort: false,
disabled: false
disabled: false,
idle: false
};
},

Expand Down Expand Up @@ -754,6 +757,12 @@ export const Paper = View.extend({

scheduleViewUpdate: function(view, type, priority, opt) {
const { _updates: updates, options } = this;
if (updates.idle) {
if (options.autoFreeze) {
updates.idle = false;
this.unfreeze();
}
}
const { FLAG_REMOVE, FLAG_INSERT, UPDATE_PRIORITY, cid } = view;
let priorityUpdates = updates.priorities[priority];
if (!priorityUpdates) priorityUpdates = updates.priorities[priority] = {};
Expand Down Expand Up @@ -904,23 +913,23 @@ export const Paper = View.extend({
updateViewsAsync: function(opt, data) {
opt || (opt = {});
data || (data = { processed: 0, priority: MIN_PRIORITY });
var updates = this._updates;
var id = updates.id;
const { _updates: updates, options } = this;
const id = updates.id;
if (id) {
cancelFrame(id);
if (data.processed === 0 && this.hasScheduledUpdates()) {
this.notifyBeforeRender(opt);
}
var stats = this.updateViewsBatch(opt);
var passingOpt = defaults({}, opt, {
const stats = this.updateViewsBatch(opt);
const passingOpt = defaults({}, opt, {
mountBatchSize: MOUNT_BATCH_SIZE - stats.mounted,
unmountBatchSize: MOUNT_BATCH_SIZE - stats.unmounted
});
var checkStats = this.checkViewport(passingOpt);
var unmountCount = checkStats.unmounted;
var mountCount = checkStats.mounted;
var processed = data.processed;
var total = updates.count;
const checkStats = this.checkViewport(passingOpt);
const unmountCount = checkStats.unmounted;
const mountCount = checkStats.mounted;
let processed = data.processed;
const total = updates.count;
if (stats.updated > 0) {
// Some updates have been just processed
processed += stats.updated + stats.unmounted;
Expand All @@ -937,16 +946,23 @@ export const Paper = View.extend({
} else {
data.processed = processed;
}
} else {
if (!updates.idle) {
if (options.autoFreeze) {
this.freeze();
updates.idle = true;
this.trigger('render:idle', opt);
}
}
}
// Progress callback
var progressFn = opt.progress;
const progressFn = opt.progress;
if (total && typeof progressFn === 'function') {
progressFn.call(this, stats.empty, processed, total, stats, this);
}
// The current frame could have been canceled in a callback
if (updates.id !== id) return;
}

if (updates.disabled) {
throw new Error('dia.Paper: can not unfreeze the paper after it was removed');
}
Expand Down Expand Up @@ -1590,11 +1606,13 @@ export const Paper = View.extend({
this._resetUpdates();
// clearing views removes any event listeners
this.removeViews();
this.freeze({ key: 'reset' });
// Allows to unfreeze normally while in the idle state using autoFreeze option
const key = this.options.autoFreeze ? null : 'reset';
this.freeze({ key });
for (var i = 0, n = cells.length; i < n; i++) {
this.renderView(cells[i], opt);
}
this.unfreeze({ key: 'reset' });
this.unfreeze({ key });
this.sortViews();
},

Expand Down
66 changes: 65 additions & 1 deletion test/jointjs/dia/Paper.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ QUnit.module('joint.dia.Paper', function(hooks) {
});
});

QUnit.module('transformToFitContent', function() {
QUnit.module('transformToFitContent', function(hooks) {

hooks.beforeEach(function() {
const testGraph = new joint.dia.Graph();
Expand Down Expand Up @@ -173,6 +173,11 @@ QUnit.module('joint.dia.Paper', function(hooks) {
});
});

hooks.afterEach(function() {
if (paper) paper.remove();
paper = null;
});

QUnit.test('transformToFitContent()', function(assert) {

const roundScale = function(scale, precision) {
Expand Down Expand Up @@ -1646,6 +1651,65 @@ QUnit.module('joint.dia.Paper', function(hooks) {
});
});

QUnit.module('async = TRUE, autoFreeze = TRUE', function(hooks) {

QUnit.test('autofreeze check', function(assert) {
const done = assert.async();

const testPaper = new Paper({
el: paperEl,
model: graph,
async: true,
autoFreeze: true,
sorting: Paper.sorting.APPROX
});

assert.ok(testPaper.isFrozen());
assert.ok(testPaper.isAsync());
assert.equal(cellNodesCount(testPaper), 0);
let idleCounter = 0;

const rect = new joint.shapes.standard.Rectangle();
const circle = new joint.shapes.standard.Circle();

testPaper.on('render:idle', () => {
assert.notOk(testPaper.hasScheduledUpdates(), 'has no updates');
assert.ok(testPaper.isFrozen(), 'frozen after updating');
switch (idleCounter) {
case 0: {
assert.equal(cellNodesCount(testPaper), 1, 'cell rendered');
rect.position(201, 202);
break;
}
case 1: {
const view = testPaper.findViewByModel(rect);
const viewBbox = view.getBBox();
assert.deepEqual({ x: viewBbox.x, y: viewBbox.y }, { x: 201, y: 202 }, 'view was correctly updated');
graph.addCell(circle);
break;
}
case 2: {
assert.equal(cellNodesCount(testPaper), 2, '2 cells rendered');
graph.removeCells(rect);
break;
}
case 3: {
assert.equal(cellNodesCount(testPaper), 1, 'cell rendered');
const circleView = testPaper.findViewByModel(circle);
const rectView = testPaper.findViewByModel(rect);
assert.ok(circleView.el.isConnected, 'circle is present');
assert.notOk(rectView, 'rect is removed');
done();
break;
}
}
idleCounter++;
});

graph.resetCells([rect]);
});
});

QUnit.module('async = TRUE, frozen = TRUE', function(hooks) {

hooks.beforeEach(function() {
Expand Down
1 change: 1 addition & 0 deletions types/joint.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,7 @@ export namespace dia {
async?: boolean;
sorting?: sorting;
frozen?: boolean;
autoFreeze?: boolean;
viewport?: ViewportCallback | null;
onViewUpdate?: (view: mvc.View<any, any>, flag: number, priority: number, opt: { [key: string]: any }, paper: Paper) => void;
onViewPostponed?: (view: mvc.View<any, any>, flag: number, paper: Paper) => boolean;
Expand Down

0 comments on commit ce1f70a

Please sign in to comment.