PDA

View Full Version : Heap gone huge because of Array



mishoboss
15 Dec 2011, 9:31 AM
Hi, in my app I continuously create and destroy field items. However the Heap is getting bigger and bigger and the app gets laggy.

I have attached listeners for deactivate and destroy all child items:

Ext.getCmp('card1').on('deactivate', function(oldCard) { Ext.getCmp('card1').removeAll(true); });
Ext.getCmp('card2').on('deactivate', function(oldCard) { Ext.getCmp('card2').removeAll(true); });

Is there something more to do? If the items have handlers attached, do they destroy too?

mitchellsimoens
15 Dec 2011, 10:05 AM
I often listen for the activeitemchange on the wrapping component as usually I have a back button. This event sends the new item and old item in it's arguments.

mishoboss
16 Dec 2011, 12:47 AM
Thank you for that suggestion. I did it, but the Heap keeps going larger and larger and the app gets slower:

Ext.getCmp('content').on('activeitemchange', function(container, newcard, oldcard, opts) { oldcard.removeAll(true); });

I think there are other objects created that I don't remove. But I don't know why. This is the app - http://m-design.bg/sencha/. Click on the first 4 rows to go to other pages. This way the app creates and removes items. And here is the whole app code. I would be very thankful if you take a snap look at it and tell me if you see something wrong :) Thank you.


Ext.require(['Ext.Panel', 'Ext.Button', 'Ext.Toolbar', 'Ext.util.JSONP', 'Ext.dataview.ComponentView']);



var UIobjects = new Object();
var currentCard = "card1";
var broadCrumb = new Array();

function pushWidget(widget, container) {
if (widget) {
container.push(widget);
}
}

function buildUIArray(json, type, parent) {
if (json) {




var container;

if (type == 'page') {
UIobjects[json.id] = new Array();
container = UIobjects[json.id];
} else if (type == 'frame') {
parent.push(new Object({
xtype: 'fieldset',
title: json.label
}));
container = parent[parent.length - 1]['items'] = new Array();
}

var page_data = json.widget;
if (Ext.isArray(page_data)) {
for (var i in page_data) {
pushWidget(addsWidget(page_data[i], container), container);
}
} else {
pushWidget(addsWidget(page_data, container), container);
}

json = null;




} else {
alert('There was an error retrieving the UI.');
}
}




function addsWidget(data, container) {
var widget;
if (data.type == "Switch") {
widget = new Object({
xtype: 'togglefield',
label: data.label
});
} else if (data.type == "Slider") {
widget = new Object({
xtype: 'sliderfield',
label: data.label,
value: data.item.state,
minValue: 0,
maxValue: 100
});
} else if (data.type == "Text") {
widget = new Object({
xtype: 'textfield',
label: data.label,
value: data.item ? data.item.state : data.label,
readOnly: true
});
} else if (data.type == "Group") {
widget = new Object({
xtype: 'button',
text: data.label,
style: 'display:block',
page_id: data.linkedPage.id,
handler: tapHandler,
baseCls: 'x-form-label x-field',
labelCls: ''
});
buildUIArray(data.linkedPage, 'page', '');
} else if (data.type == "Frame") {

buildUIArray(data, 'frame', container);
}
return widget;

}








var tapHandler = function (btn, evt) {
broadCrumb.push(btn.page_id);
Ext.getCmp("content").getLayout().setAnimation({
type: 'slide',
direction: 'left'
});
goToPage(btn.page_id);

}


var goToPage = function (page) {
if (currentCard == "card1") {
currentCard = "card2";
} else {
currentCard = "card1";
}

Ext.getCmp(currentCard).setItems(UIobjects[page]);
Ext.getCmp("content").setActiveItem(currentCard);
}


Ext.setup({
tabletStartupScreen: 'tablet_startup.png',
phoneStartupScreen: 'phone_startup.png',
icon: 'icon.png',
glossOnIcon: false,





onReady: function () {
//var server_url = 'http://192.168.90.176:8080';
var server_url = 'http://m-design.bg/sencha';




var backPage = function () {
if (broadCrumb.length > 1) {
broadCrumb.pop();
Ext.getCmp("content").getLayout().setAnimation({
type: 'slide',
direction: 'right'
});
goToPage(broadCrumb[broadCrumb.length - 1]);
}
}




var makeJSONPRequest = function () {
Ext.getCmp('content').setMask({
message: 'Loading...'
});
Ext.util.JSONP.request({
url: 'http://m-design.bg/sencha/json.txt',
//url: server_url+'/rest/sitemaps/demo',
callbackKey: 'jsoncallback',
params: {
key: Math.random()
},

callback: function (result) {

buildUIArray(result.homepage, 'page', '');
Ext.getCmp('content').unmask();
broadCrumb[0] = result.homepage.id;
//console.log(UIobjects);
Ext.getCmp(currentCard).setItems(UIobjects[result.homepage.id]);
result = null;
}
});
};





var panel = Ext.create('Ext.Panel', {
layout: {
type: 'card',
animation: {
type: 'slide',
direction: 'left'
}
},
fullscreen: true,
id: 'content',
autoDestroy: true,
animation: {
type: 'slide',
direction: 'left'
},
items: [{
id: 'card1',
scrollable: 'vertical',
autoDestroy: true
}, {
id: 'card2',
scrollable: 'vertical',
autoDestroy: true
}, {
docked: 'top',
xtype: 'toolbar',
ui: 'light',
items: [{
text: 'Back',
handler: backPage
}, {
xtype: 'spacer'
}, {
text: 'Load UI',
handler: makeJSONPRequest
},

]
}]
});
Ext.getCmp('content').on('activeitemchange', function (container, newcard, oldcard, opts) {
oldcard.removeAll(true);
});


}
});

mishoboss
17 Dec 2011, 6:12 AM
I just can't get through this problem. I debug the app via Google Chrome. I take heap snapshots continuously and compare them. It seems that there are created way more Object, Array and String entries than deleted.


I've done some changes since my previous post. I've attached listeners to the buttons with delegation and I also remove listeners from the old card on card change. I thought maybe listeners are my problem, but it seems they're not.

Please, take a look at the app HERE
(http://m-design.bg/sencha/)Don't enter anything in the Server field, just press "Login", then select the Demo interface.

And the code:

Ext.require(['Ext.Panel', 'Ext.Button', 'Ext.Toolbar', 'Ext.util.JSONP', 'Ext.dataview.ComponentView']);


//-------- PATCH FOR BUTTON TO ACCEPT HTML TEXT -----------
//----- won't be needed in the next Sencha releases -------
Ext.define('Ext.overrides.button.updateHtml', {
override: 'Ext.Button',

updateHtml: function (html) {
var element = this.textElement;

if (html) {
element.show();
element.update(html);
} else {
element.hide();
}
}
});
//-------------------- END PATH -----------------------

var UIobjects = new Object();
var currentCard = "card1";
var broadCrumb = new Array();
var broadCrumbText = '';
var server_url;









var sitemapsStore = new Ext.data.Store({
fields: ['name', 'link']

});



function showLoginWindow() {
var login = Ext.create('Ext.Panel', {
floating: true,
modal: true,
centered: true,
width: '90%',
height: 300,

items: [{
title: 'Login',
xtype: 'toolbar',
ui: 'light',
docked: 'top'
}, {
xtype: 'urlfield',
label: 'Server',
id: 'server_settings',
autoCorrect: true,
placeHolder: 'http://localhost:8080',
required: true,
value: localStorage.getItem('openHAB_server')
}, {
xtype: 'textfield',
label: 'Username',
id: 'username_settings',
placeHolder: 'username',
disabled: true
}, {
xtype: 'passwordfield',
label: 'Password',
id: 'password_settings',
placeHolder: 'password',
disabled: true,
style: 'margin-bottom:10px;'
}, {
xtype: 'button',
text: 'Login',
style: 'display: block; margin: auto; width:150px;',
handler: function () {
server_url = Ext.getCmp('server_settings').getValue();
if (server_url.substring(0, 7) != "http://") {
server_url = "http://" + server_url;
}
localStorage.setItem('openHAB_server', server_url);
Ext.util.JSONP.request({

url: server_url + '/rest/sitemaps/jsonp',
//url: 'http://m-design.bg/sencha/sitemaps.txt',
callbackKey: 'jsoncallback',
headers: {
'Accept': 'application/json',
},
params: {
key: Math.random()
},

callback: function (result) {
login.destroy(true);
sitemapsStore.loadData(result.sitemaps);
showSitemapsWindow();

},
onFailure: function (result) {
console.log('error');
}
});

}
}]
});


Ext.getCmp('content').add(login);
}


function showSitemapsWindow() {



var sitemapSelect = Ext.create('Ext.Panel', {
floating: true,
modal: true,
centered: true,
width: '90%',
height: 300,

layout: 'fit',
scrollable: 'vertical',
items: [{
title: 'Interfaces',
xtype: 'toolbar',
ui: 'light',
docked: 'top'
}, {
xtype: 'list',
store: sitemapsStore,
singleSelect: true,
itemTpl: '{name}',
onItemDisclosure: function (record, btn, index) {
loadUIData(record.data.link);
sitemapSelect.destroy(true);
}
}]
});

Ext.getCmp('content').add(sitemapSelect);


}



function loadUIData(url) {
Ext.getCmp('content').setMask({
message: 'Loading...'
});
Ext.util.JSONP.request({

url: server_url + '/rest/sitemaps/demo/jsonp',
//url,
//url: 'http://m-design.bg/sencha/ui.txt',
callbackKey: 'jsoncallback',
headers: {
'Accept': 'application/json',
},
params: {
key: Math.random()
},

callback: function (result) {

buildUIArray(result.homepage, 'page', '');
Ext.getCmp('content').unmask();
broadCrumb[0] = new Array(result.homepage.id, result.name);
Ext.getCmp('title').setHtml(result.name);
//console.log(UIobjects);
Ext.getCmp(currentCard).setItems(UIobjects[result.homepage.id]);
Ext.getCmp(currentCard).on({
delegate: 'button',

tap: tapHandler
});

result = null;
}
});
};



function backPage() {
if (broadCrumb.length > 1) {
broadCrumb.pop();
Ext.getCmp("content").getLayout().setAnimation({
type: 'slide',
direction: 'right'
});
goToPage(broadCrumb[broadCrumb.length - 1][0]);
}
}

function pushWidget(widget, container) {
if (widget) {
container.push(widget);
}
}

function buildUIArray(json, type, parent) {
if (json) {




var container;

if (type == 'page') {
UIobjects[json.id] = new Array();
container = UIobjects[json.id];
} else if (type == 'frame') {
parent.push(new Object({
xtype: 'fieldset',
title: json.label
}));
container = parent[parent.length - 1]['items'] = new Array();
}

var page_data = json.widget;
if (Ext.isArray(page_data)) {
for (var i in page_data) {
pushWidget(addsWidget(page_data[i], container), container);
}
} else {
pushWidget(addsWidget(page_data, container), container);
}

json = null;




} else {
alert('There was an error retrieving the UI.');
}
}




function addsWidget(data, container) {
var widget;
if (data.type == "Switch") {
if (data.item.type == "RollershutterItem") {
widget = createRollershutterWidget(data.label, data.icon, data.item.state)
} else {
widget = createToggleWidget(data.label, data.icon, data.item.state);
}

} else if (data.type == "Slider") {
widget = createSliderWidget(data.label, data.icon, data.item.state);

} else if (data.type == "Text") {
if (data.linkedPage) {
widget = createLinkWidget(data.label, data.icon, data.linkedPage.id, data.linkedPage.label);
buildUIArray(data.linkedPage, 'page', '');
} else {
widget = createTextWidget(data.label, data.icon, data.item.state);
}
} else if (data.type == "Group") {
widget = createLinkWidget(data.label, data.icon, data.linkedPage.id, data.linkedPage.label);
buildUIArray(data.linkedPage, 'page', '');

} else if (data.type == "Selection") {
widget = createSelectionWidget(data.label, data.icon, data.item.state, data.mapping);
} else if (data.type == "Frame") {

buildUIArray(data, 'frame', container);
}
return widget;

}








var tapHandler = function (btn, evt) {

broadCrumb.push([btn.page_id, btn.page_label]);
Ext.getCmp("content").getLayout().setAnimation({
type: 'slide',
direction: 'left'
});
goToPage(btn.page_id);

}


var goToPage = function (page) {
if (currentCard == "card1") {
currentCard = "card2";
} else {
currentCard = "card1";
}

Ext.getCmp(currentCard).setItems(UIobjects[page]);
Ext.getCmp("content").setActiveItem(currentCard);
}










Ext.setup({
tabletStartupScreen: 'tablet_startup.png',
phoneStartupScreen: 'phone_startup.png',
icon: 'icon.png',
glossOnIcon: false,





onReady: function () {



var panel = Ext.create('Ext.Panel', {
layout: {
type: 'card',
animation: {
type: 'slide',
direction: 'left'
}
},
fullscreen: true,
id: 'content',
autoDestroy: true,


items: [{
id: 'card1',
scrollable: 'vertical',
autoDestroy: true,
padding: 6,
cls: 'card1',

}, {
id: 'card2',
scrollable: 'vertical',
autoDestroy: true,
padding: 6,
cls: 'card2',

}, {
docked: 'top',
xtype: 'toolbar',
ui: 'light',
items: [{
id: 'back_btn',
ui: 'back',
text: 'Back',
handler: backPage,
hidden: true
}, {
xtype: 'spacer'
}, {
id: 'title',
xtype: 'label',
cls: 'oph_title'
}, {
xtype: 'spacer'
}

]
}]
});



Ext.getCmp('content').on('activeitemchange', function (container, newcard, oldcard, opts) {
oldcard.removeAll(true);
oldcard.clearListeners();
newcard.on({
delegate: 'button',

tap: tapHandler
});
if (broadCrumb.length == 1) {
Ext.getCmp('back_btn').hide();
} else {
Ext.getCmp('back_btn').show();
}
broadCrumbText = '';
for (var k in broadCrumb) {
broadCrumbText += broadCrumb[k][1];
if (k != broadCrumb.length - 1) {
broadCrumbText += ' > '
}
}
Ext.getCmp('title').setHtml(broadCrumbText);
});
showLoginWindow();


}
});



function createSliderWidget(label, icon, state) {
return {
xtype: 'sliderfield',
label: '<img class="oph_icon" src="' + server_url + '/images/' + icon + '.png" />' + label,
labelWidth: '60%',
value: 45,
minValue: 0,
maxValue: 100
};
}

function createTextWidget(label, icon, state) {
return {
xtype: 'textfield',
label: '<img class="oph_icon" src="' + server_url + '/images/' + icon + '.png" />' + label.replace(/\[(.*?)\]/, ''),
labelWidth: '60%',
value: label.match(/\[(.*?)\]/)[1],
readOnly: true
};
}

function createToggleWidget(label, icon, state) {
return {
xtype: 'togglefield',
label: '<img class="oph_icon" src="' + server_url + '/images/' + icon + '.png" />' + label,
labelWidth: '60%',
value: 1 //state == 'ON' ? 1 : 0
};

}

function createRollershutterWidget(label, icon, state) {
return {
xtype: 'field',
label: '<img class="oph_icon" src="' + server_url + '/images/' + icon + '.png" />' + label,
labelWidth: '60%',
html: '<div class="x-button-normal x-button x-layout-box-item oph_rollershutter_btn" style="margin-left:7px;"><span class="x-button-icon arrow_up x-icon-mask" ></span></div><div class="x-button-normal x-button x-layout-box-item oph_rollershutter_btn"><span class="x-button-icon delete x-icon-mask" ></span></div><div class="x-button-normal x-button x-layout-box-item oph_rollershutter_btn"><span class="x-button-icon arrow_down x-icon-mask" ></span></div>'
};

}

function createLinkWidget(label, icon, page_id, page_label) {
return {
xtype: 'button',
html: '<img class="oph_icon" src="' + server_url + '/images/' + icon + '.png" />' + label,
style: 'display:block',
baseCls: 'x-form-label x-field oph_group_btn',
page_id: page_id,
page_label: page_label,
labelCls: ''
};
}

function createSelectionWidget(label, icon, state, options) {
return {
xtype: 'selectfield',
labelWidth: '60%',
//value: state == "Undefined" ? options[1].command : state,
label: '<img class="oph_icon" src="' + server_url + '/images/' + icon + '.png" />' + label,
options: options,
displayField: 'label',
valueField: 'command'
}
};

mishoboss
18 Dec 2011, 9:23 AM
OK, I have done some more tests and tried some more things based on what I've found on the Sencha forums.

I have a Panel with a Card layout and with no items on initialize. Then I do this to change the card and load dynamic items content on that card:


sitemapUIpanel.setActiveItem(new Ext.Panel({scrollable: 'vertical', autoDestroy: true, padding: 6, items:UIobjects[page]}));

The UIobjects array holds the items data in a ready to use format.


I listen for activeitemchange event to destroy the old panel and attach button listeners to the new one:

sitemapUIpanel.onAfter('activeitemchange', function (container, newcard, oldcard, opts) {

if (oldcard) {
setTimeout(function () {


oldcard.clearListeners();
oldcard.destroy();
console.log(oldcard);
console.log(Ext.query('*').length);
}, 1000);

}

newcard.on({
delegate: 'button',

tap: tapHandler
});
}, sitemapUIpanel);

The result is even more tragic than before...
There are thousands of new Arrays and Objects on every page created that are not deleted on destroy(). Could anyone give me some advise where I'm wrong? :(

mishoboss
20 Dec 2011, 1:54 AM
Anyone? :(