vbrepo Posted June 29, 2017 Share Posted June 29, 2017 Hey, I am new to Pixi and that's probably the real issue :-) We created a game called blobber.io and have an enourmous memory leak with pixi.CocoonText not releasing its memory. Is probably a destroy that is not called but I can't find and see how and where to actually set it. We have a simple gameloop and at the beginning we call the graph.clear(). Within this gameloop we call the drawPlayers functions which is calling the setText in renderer.js. I am completely lost here. I tried everything but am really blocked here. export default () => { const scale = window.innerHeight / vars.screenHeight; graph.clear(); const textStyle = { font: '30px bold sans-serif', fill: playerConfig.textColor, stroke: playerConfig.textBorder, x: vars.screenWidth / 2, y: vars.screenHeight / 2, }; if (!vars.disconnected) { if (vars.gameStart) { renderer.setText(vars.died ? 'You died!' : '', 'state', textStyle); renderer.ground.tilePosition.set( -player.x * scale, -player.y * scale ); if (vars.borderDraw) { drawborder(); } const orderMass = []; // console.log(`Users: ${objs.users.length}`); for (let i = 0; i < objs.users.length; i++) { if (objs.users.type === 'player') { orderMass.push(i); } } if (objs.playerCells.length > 0) { // calcViewZoom(); let c = 0; let a = 0; for (let i = 0; i < objs.playerCells.length; i++) { // playerCells.updatePos(); a += objs.playerCells.x / objs.playerCells.length; c += objs.playerCells.y / objs.playerCells.length; } // vars.posX = a; // vars.posY = c; // vars.posSize = vars.viewZoom; vars.nodeX = (vars.nodeX + a) / 2; vars.nodeY = (vars.nodeY + c) / 2; } else { // vars.nodeX = (29 * vars.nodeX + vars.posX) / 30; // vars.nodeY = (29 * vars.nodeY + vars.posY) / 30; // vars.viewZoom = (9 * vars.viewZoom + vars.posSize * viewRange()) / 10; } drawPlayers(orderMass); } if (vars.died) { renderer.setText('Game Over!', 'state', textStyle); } } else { graph.beginFill(0x171717); graph.lineStyle(0); graph.drawRect(0, 0, vars.screenWidth, vars.screenHeight); graph.endFill(); if (vars.kicked) { if (vars.reason !== '') { renderer.setText(`You were kicked for:\r\n${vars.reason}`, 'state', textStyle); } else { renderer.setText('You were kicked!', 'state', textStyle); } } else { renderer.setText('Disconnected!', 'state', textStyle); } } renderer && renderer.render(); }; in renderer.js export default class Rendered { /** * @param {HTMLCanvasElement} canvas */ constructor(canvas) { this.canvas = canvas; this.renderer = PIXI.autoDetectRenderer(innerWidth, innerHeight, { view: canvas, transparent: true, antialias: false }); this.container = new PIXI.Container(); this.stage = new PIXI.Container(); this.graph = new PIXI.Graphics(); this.graphInfo = new PIXI.Graphics(); this.ground = new PIXI.extras.TilingSprite(LightGroundTexture); this.ground.uvRespectAnchor = true; this.ground.anchor.set(0.5,0.5); this.container.addChild(this.ground); this.container.addChild(this.graph); this.container.addChild(this.stage); this.container.addChild(this.graphInfo); this.stage.displayList = new PIXI.DisplayList(); this._sprite = this.graphInfo; this.zoom = 1; this.render(); } setText(...args) { SpriteProxyPrototype.setText.bind(this)(...args); for (let i in this) { if (i.startsWith('_text@') && this) { const textSprite = this; textSprite.scale.set(1); textSprite.resolution = 1; textSprite.position.x = this.renderer.width / 2; textSprite.position.y = this.renderer.height / 2; } } } render() { this._updateZoom(); this.renderer.render(this.container); } static createSpriteFromeImage(src) { if (!textuteCache[src]) { const tmp = getTexture(src); if (tmp instanceof PIXI.Texture) { // console.log('MATCH SPRITE ' ,src) textuteCache[src] = tmp; return new PIXI.Sprite(tmp); } else if (tmp instanceof Promise) { // console.log('MATCH ASYNC SPRITE ' ,src) const sprite = new PIXI.Sprite(); tmp.then(e => (textuteCache[src] = sprite.texture = e)); return sprite; } else { // console.warn('MISSING SPRITE ALTATS:' +src) textuteCache[src] = PIXI.Texture.fromImage(src); return new PIXI.Sprite(textuteCache[src]); } } else { return new PIXI.Sprite(textuteCache[src]); } } set scale(value) { this._scale = value; this.stage.scale.set(this._scale); } get scale() { return this._scale; } set zoom(zoom) { if (this._zoom === undefined) { this.__zoom = this.___zoom = zoom; } this._zoom = zoom; } get zoom() { return this.___zoom || 1; } updateSkin() { const theme = getTheme(); this.ground.texture = { 'light': LightGroundTexture, 'dark': DarkGroundTexture, }[theme]; } _updateZoom() { this.___zoom += (this.__zoom - this.___zoom) * 0.5; this.__zoom += (this._zoom - this.__zoom) * 0.5; const zoom = this.zoom || 1; this.container.scale.set(zoom); this.container.position.set( this.renderer.width * (1 - zoom) * 0.5, this.renderer.height * (1 - zoom) * 0.5 ); this.ground.width = this.renderer.width / zoom; this.ground.height = this.renderer.height / zoom; this.ground.position.set( this.renderer.width * 0.5, this.renderer.height * 0.5 ); } } const SpriteProxyPrototype = { render(x, y, width, height, property = '_spriteImage') { this._sprite.position.set(x, y); this._sprite.zOrder = -width - height; // this._spriteImage.zOrder = -width - height if (this._spriteImage.width != width || this._spriteImage.height != height) { // console.log('setWidthHeight Run') this._spriteImage.width = width; this._spriteImage.height = height; if (this._maskSprite) { this._maskSprite.width = width; this._maskSprite.height = height; } } }, setSrc(src, property = '_spriteImage') { if (!textuteCache[src]) { const tmp = getTexture(src); if (tmp instanceof PIXI.Texture) { // console.log('MATCH SPRITE ' ,src) textuteCache[src] = tmp; this[property].texture = tmp; } else if (tmp instanceof Promise) { // console.log('MATCH ASYNC SPRITE ' ,src) tmp.then(e => (textuteCache[src] = this[property].texture = e)); } else { // console.warn('MISSING SPRITE ALTATS:' +src) this[property].texture = textuteCache[src] = PIXI.Texture.fromImage(src); } } else if (this[property].texture != textuteCache[src]) { this[property].texture = textuteCache[src]; // console.log('setSrc Run') } }, setText(text, key, { font = 'bold 20px Arial', fill = '#ffffff', align = 'center', stroke = '#888888', thin = 2, x = 0, y = 0, } = {}) { const scale = (this._stage || this.stage).scale.x; const sym = `_text@${key}`; const textStyle = { font, fill, align, stroke, strokeThickness: thin }; if (!text) { if (this[sym]) { this._sprite && this._sprite.removeChild(this[sym]); this[sym] = null; } } else if (!this[sym]) { const textSprite = new PIXI.cocoontext.CocoonText(text, textStyle); // console.log(textSprite); // var textSprite = new PIXI.Text(text, textStyle); textSprite.resolution = scale * devicePixelRatio * 2; textSprite.anchor.set(0.5, 0.5); // textSprite.displayGroup = TextGroup; textSprite.position.set(x, y); this[sym] = textSprite; textSprite.scale.set(1 / scale); this._sprite && this._sprite.addChild(textSprite); // console.log(this._sprite && this._sprite.addChild(textSprite)); } else { const textSprite = this[sym]; let changeStyle = false; if (textSprite.text != text) { textSprite.text = text; } textSprite.position.set(x, y); for (const i in textStyle) { changeStyle = changeStyle || (textStyle != textSprite.style); } textSprite.style = textStyle; textSprite.scale.set(1 / scale); textSprite.resolution = scale * devicePixelRatio * 2; } }, setMask(src) { if (src == '') { this._sprite.removeChild(this._maskSprite); this._spriteImage.mask = null; this._maskSprite = null; } else if (!this._maskSprite) { this._maskSprite = Rendered.createSpriteFromeImage(src); this._maskSprite.anchor.set(0.5); this._sprite.addChildAt(this._maskSprite, 0); this._spriteImage.mask = this._maskSprite; // console.log('set mask done',this._maskSprite) } else { this.setSrc(src, '_maskSprite'); } } }; export function onDel(e) { if (e && e.cells && Object.keys(e.cells).length) { for (const i in e.cells) { onDel(e.cells); } } if (e) { e._stage && e._sprite && e._stage.removeChild(e._sprite); e._sprite = null; e._spriteImage = null; } } /** * @param {PIXI.Container} stage * @param {String} imageSrc */ export function SpriteProxy(stage, imageSrc = '') { return { onNew(e) { const sprite = new PIXI.Sprite(); const imageSprite = Rendered.createSpriteFromeImage((e.sprite + '').endsWith('.png') ? e.sprite : imageSrc); imageSprite.anchor.set(0.5, 0.5); sprite.addChild(imageSprite); stage.addChild(sprite); for (const i in SpriteProxyPrototype) { e = SpriteProxyPrototype.bind(e); } e._stage = stage; e._sprite = sprite; e._spriteImage = imageSprite; e._sprite.displayGroup = ObjectGroup; }, onDel(e) { onDel(e); }, onChange(e) {} }; } in draw.js function drawName(name, cell) { const fontSize = Math.max(cell.radius / 3 | 0, 20) | 0; const textStyle = { font: `bold ${fontSize}px Arial`, fill: playerConfig.textColor, stroke: playerConfig.textBorder }; cell.setText(!Config.toggleShowNameState ? name : '', 'name', textStyle); const massString = Config.toggleMassState !== 0 ? Math.round(cell.mass | 0) + '' : ''; cell.setText(massString, 'mass', { ...textStyle, font: `bold ${fontSize * 0.7 | 0}px Arial`, y: fontSize, }); } draw.js gameLoop.js renderer.js Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted June 29, 2017 Share Posted June 29, 2017 I don't understand. You just said that you know that "destroy()" method must be called on every text that you dont use anymore. At the same time, there are no "destroy" calls in the code. What is CocoonText? We have some "ifs" for Cocoon.js but there's no such class. Quote Link to comment Share on other sites More sharing options...
vbrepo Posted June 29, 2017 Author Share Posted June 29, 2017 I thought graph.clear() was doing that competely Quote Link to comment Share on other sites More sharing options...
vbrepo Posted June 29, 2017 Author Share Posted June 29, 2017 https://github.com/JiDW/pixi-cocoontext is used for the rendering of the text pixi-cocoontext.js Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted June 29, 2017 Share Posted June 29, 2017 Does it even support pixi4? It was updated long time ago, you have to ask that guy on github and post it in their issues. I used some of his plugins, and i even based pixi-compressedTextures on his code. Anyway, you have to call "destroy()" on every text that is removed and not used anymore. In pixiv4, "renderer.textureGC" takes care of that, by default its enabled to run every two minutes or so, but there's no guarantee it works with old CocoonText object. Quote Link to comment Share on other sites More sharing options...
vbrepo Posted June 29, 2017 Author Share Posted June 29, 2017 Thanks Ivan, I will give that a try. ivan.popelyshev 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.