FYI, we do have a baked-in mechanism for this sort of thing in v2 since all DOM updates go via the scheduler which is “throttled” by a rAF loop. Post-alpha we’ll be adding a timeslicing api that provides more fine-grained control over how frequently and in what priority updates are propagated.
Hi @bigopon, I’m finally back into this. I have some really odd memory growth in the chromium GPU when the screen is off only, and I’ve narrowed this down to my component page. If I run my services without the view bound there is no memory growth with the screen off. So I’m thinking that the browser is rendering and buffering (or something) when the screen is off based in DOM changes, and when the screen comes on it draws/paints (whatever) but does not clear it’s bitmap buffers. To be honest I don’t really know what I’m talking about… it’s all just guesses at the moment.
Anyhow, to eliminate the DOM updates I really need to suspend all binding when the screen is off. The code above this that you provided previously seems to work well for normal bindings, but I also need to suspend InterpolationBinding/ChildInterpolationBinding as most of my UI is dynamic and using compose with view.bind=“viewStrategy” and Interpolation. How would I modify this code to also suspend (and resume/flush) ChildInterpolationBinding. I’ve explored the view tree and can see the compose bindings but can’t seem to find the ChildInterpolationBindings??
ChildInterpolationBinding is an optimization done when the interpolation has only 1 dynamic parts.
For your use case, you can do it like this, probably:
if (binding instanceof ChildInterpolationBinding) {
binding.updateTarget = ... // here
} else if (binding instanceof InterpolationBinding) {
let parts = binding.parts;
for (let i = 1, ii = parts.length; i < ii; i += 2) {
let childBinding = binding[`childBinding${i}`];
childBinding.updateTarget = ... // here
}
}
thank you @bigopon, this helps clarify. But, these binding don’t seem to be accessible via the chain above being controller.view.bindings[]. How would I access these bindings from the view (or elsewhere) so I can flush when the screen comes on?
would this make a difference? maybe I need a model on this compose and access the view via model.created? could I just do view-model.bind="$this".
Or is there a better way to attach a dynamic template? I tried getViewStrategy returning an inlineViewStrategy but the viewstrategy is determined by some bindings.
@bigopon, I came up with this. I struggled to get to all the bindings I needed to pause, so I started from the other end (Binding prototypes) and worked back to the DOM. It seems to work well but I’m sure there are probably all sorts of anti-patterns going on here. What do you think? Any feedback on gotchas, making it more efficient, simpler etc etc.?
// @ts-ignore
import { Binding } from 'aurelia-binding';
import { ChildInterpolationBinding } from 'aurelia-templating-binding';
import { isTizen } from 'core/utils';
import { remove } from 'lodash';
declare var tizen;
export function auShim() {
let isPaused = false
const containedInSelecor = 'layout-block'
const managedBindings: any[] = []
const cibPrototype = ((<any>ChildInterpolationBinding.prototype))
const bPrototype = ((<any>Binding.prototype))
//modify binding prototypes to make "pausable" on dom elements
function setupBindingPrototype(bProto) {
bProto.findDOMTarget = function (target = this) {
while (target && typeof target.closest !== "function") {
target = target.target ?? target.parent ?? target.parentElement
if (target) {
target = this.findDOMTarget(target)
}
}
return target
}
bProto.getDOMTarget = function () {
//some doc frags seem to be loaded late so recheck fragments incase they've since been attached
if (!this.DOMTarget && this.source?.fragment instanceof DocumentFragment) {
this.DOMTarget = this.findDOMTarget()
}
return this.DOMTarget
}
bProto.originalUpdateTarget = bProto.updateTarget
bProto.updateTarget = function (value) {
const DOMTarget = this.getDOMTarget()
if (!this.isPausable) {
this.isPausable = DOMTarget?.closest(containedInSelecor) ? true : false
}
if (isPaused && this.isPausable && !this.isManaged && this.isBound) {
this.isManaged = true
managedBindings.push(this)
console.log('managedBindings add:', managedBindings)
}
if (this.isManaged && isPaused) {
this.lastSupressedValue = value
} else {
this.lastSupressedValue = undefined
return this.originalUpdateTarget.apply(this, arguments);
}
}
bProto.flushChanges = function () {
if (this.isBound && this.lastSupressedValue) {
this.originalUpdateTarget(this.lastSupressedValue)
}
}
}
setupBindingPrototype(cibPrototype)
setupBindingPrototype(bPrototype)
function flushAllChanges() {
managedBindings.forEach(b => {
b.flushChanges()
})
let removed = remove(managedBindings, b => {
if (!b.isBound) {
b.isManaged = false
return true
} else {
return false
}
})
console.log('managedBindings del:', removed.length, 'remain:', managedBindings)
}
tizen.power.setScreenStateChangeListener((prevState: string, currState: string) => {
if (currState === 'SCREEN_NORMAL' && prevState === 'SCREEN_OFF') {
flushAllChanges();
isPaused = false
} else if (currState === 'SCREEN_OFF' && prevState === 'SCREEN_NORMAL') {
isPaused = true
}
});
//for debug testing purposes only
(window as any).togglePause = function togglePause() {
if (isPaused) {
flushAllChanges();
isPaused = false
} else {
isPaused = true
}
}
}