Unless I’m not understanding the issue…
Can you not just suspend updating the UI by… not pushing any new data to the UI state?
So you keep some of your app state separate from the UI state and only push changes between them when the page is visible?
Unless I’m not understanding the issue…
Can you not just suspend updating the UI by… not pushing any new data to the UI state?
So you keep some of your app state separate from the UI state and only push changes between them when the page is visible?
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??
Any help appreciated.
Cheers
I guess i can do something like this:
ChildInterpolationBinding.prototype.originalUpdateTarget = ChildInterpolationBinding.prototype.updateTarget
ChildInterpolationBinding.prototype.updateTarget = function (value) {
if (document.visibilityState == “hidden”) return
this.originalUpdateTarget(value)
}
Which seems to work, but I don’t know how to access these bindings from the view to flushChanges when the the document.visibilityState == “visible”…?
The code for constructing interpolation bindings is here templating-binding/interpolation-binding-expression.js at f865e5934546536629aae75d1b0c9e56e953bc26 · aurelia/templating-binding · GitHub
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?
with this template

Doing this

Gives me this

It seems to be accessible. Can you check again?
I’m using compose to pull in a dynamic view strategy like this:
<template ref="myElement">
<slot></slot>
<compose view.bind="view" containerless></compose>
</template>
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.
For compose, it’s stored internally of the compose view model.
With this template

Doing this

Gives me this

I guess it’s the timing that is a bit unfortunate, as compose itself delays things on its own, hence the use of the timeout.
Excellent, I can work with this. I’ll see how I go. Thanks again.
@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
}
}
}