Konva Image on heroku server eats RSS Memory - node.js

I have been working on a Discord bot to generate image attachments and send them into channels; and it all works a treat. However I have now encountered a problem in that when I use images, and in this case 32 images for an attachment the 'RSS' memory as per process.memoryUsage.rss() hits the roof; if I run this process 2-3 times, the serve dies due to lack of Memory.
So this is all in a Node Process, on Heroku.
The code in principle:
let stage = new Konva.Stage({
width: width,
height: height,
});
let canvas1 = createCanvas(stage.width(), stage.height());
const ctx1 = canvas1.getContext('2d'); // I draw to this
let canvas2 = createCanvas(stage.width(), stage.height());
const ctx2 = canvas2.getContext('2d'); // I draw to this
let layer = new Konva.Layer();
...
// It's these images that seem to cause the most Memory Bloat
Konva.Image.fromURL(
imagePath,
imageNode => {
imageNode.setAttrs(attrs); // attrs = { image, x, y, width, height }
layer.add(imageNode);
// imageNode.destroy(); doesn't save memory
}
);
...
// Add the canvas to the Konva, as an image
layer.add(new Konva.Image({
image: canvas1,
x: 0,
y: 0,
width: stage.width(),
height: stage.height(),
opacity: 0.5,
}));
// Add the canvas to the Konva, as an image
layer.add(new Konva.Image({
image: canvas2,
x: 0,
y: 0,
width: stage.width(),
height: stage.height(),
opacity: 0.5,
}));
...
stage.add(layer);
layer.draw();
let asCanvas = stage.toCanvas(); // I use this for the attachment
stage.destroy();
stage = null;
layer = null;
return asCanvas;
...
let attachment = new Discord.MessageAttachment(
fromAsCanvas.toBuffer(), // fromAsCanvas is from the asCanvas above.
'imageName.png'
);
message.channel
.send({
files: [attachment],
content: 'Message with the Attachment',
});
My belief at this time, is that the images loaded from the system, and added to the layer, then to canvas, are not freeded up from memory and just sit there for a significant amount of time with no consistincy.
I have tried:
Running the Garbage Collector afterwards to confirm if this would help (it does not)
Destroying the image post-add to layer (this removed the image itself)
Destroying the Stage
Destroying the Layer
Nulling the Stage, Layer, and all 4 Canvas related variables
I have lots of logging, e.g:
Start: 74MB
Post ctx1: 75MB
Post ctx2: 75MB
Post Reduce: 77MB
Post forEach: 237MB // adds all 32 the images +160MB
Post Mark Destroyed Guns (spread arrays): 237MB
Post Added Some Things 1: 247MB +10MB
Post Added Some Things 2: 249MB
Post Added Some Things 3: 259MB
Post Added Some Things 4: 260MB
Post Add canvas1 Canvas to Layer: 260MB
Post Add canvas2 to Layer: 260MB
Post Add Layer to Stage: 293MB +33MB
Post Layer.draw: 294MB
Post toCanvas: 321MB +27MB
Post Destroy Stage/etc: 308MB -13MB
Sends message
5 Seconds later RSS is at: 312MB +4MB
As you can see, once I have this solved, I might still have 50MB of extra Memory usage to debug too.

I believe I have now solved this, based on this article: https://github.com/Automattic/node-canvas/issues/785 Basically, I added inside 'onload' the statement img.onload = null; as I had refactored to a promise/onload system during my attempts to solve this.
Followup 1:
In addition to setting img.src = null I then followed up with the following:
process.nextTick(() => {
img.onload = null;
img.onerror = null;
img = null;
})
The setting of img = null brought back a lot more memory, but still left plenty taken up.
Followup 2:
I discovered that destroying the layer and image also helped free up even more memory, to the point that it seems I actually end up recovering memory between processes:
layer.destroyChildren();
layer.destroy();
stage.destroyChildren();
stage.destroy();
stage = null;
layer = null;
I am hoping, this has finally solved it.

Related

How to make a sprite moving at the same speed of tileSprite in Phaser3

I am new in Phaser.
I want to make a background which represent the ground moving and there should be a bunch of rocks above it which I made them as a sprite group.
I made the ground as a tileSprite, and on each update I changed the tilePositionX. my problem is how to set the velocity of the rocks to match the update in the background so they appear to be on the ground and moving at the same speed of the ground.
theoretically you could measure the passed time, from the last update function call, and calculate the difference, or just eye-ball the needed velocity. (aka. try different velocities until it matches up with the background-movement):
Update:
If no physics object is needed you could move the "rock" "manually", this is very easy.
Here a quick demo, that I eye-balled:
The red mushroom, uses physics and velocity. Warning: the speed of the object can/will vary depending on the browser and hardware.
Update: The green mushroom, is moved manually via the position.
// Minor formating for stackoverflow
document.body.style = "display: flex;flex-direction: column;";
var config = {
type: Phaser.AUTO,
width: 536,
height: 150,
physics:{
default: 'arcade',
arcade: { debug: true }
},
scene: {
preload,
create,
update
}
};
var tilesprite;
var redRock;
var greenRock;
function preload ()
{
this.load.image('mushroom', 'https://labs.phaser.io/assets/sprites/mushroom16x16.png');
}
function create ()
{
tilesprite = this.add.tileSprite(400, 300, 800, 600, 'mushroom');
redRock = this.add.image(400, 40, 'mushroom').setScale(2);
redRock.setTint(0xff0000);
this.physics.add.existing(redRock);
redRock.body.setVelocityX(-60)
greenRock = this.add.image(400, 90, 'mushroom').setScale(2);
greenRock.setTint(0x00ff00);
}
function update (time, delta){
tilesprite.tilePositionX += 1;
let speedCorrection = (1000/60)/delta;
redRock.body.setVelocityX(-60 * speedCorrection )
greenRock.x -= 1;
// extra "reset" Rock
if( redRock.x + 16 < 0 ){
redRock.x = this.sys.game.canvas.width + 16;
greenRock.x = this.sys.game.canvas.width + 16;
}
}
var game = new Phaser.Game(config);
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.min.js">
</script>
Disclaimer: It is not the most elegant solution, but it took me literally 30 Seconds to find a matching velocity.
Minor Update:
I improved the eye-ball-method with a speed correction, it can cause minor flickering/slipping(max 1-2 pixel skips), but should move constant on "all" device.
The change is just adding a small calculation let speedCorrection = (1000/60)/delta;

How can I load an exported Tileset (image collection) from Tiled in Phaser 3?

I want to load an image collection tileset into my phaser game. I know that with tilesets that are just one image you can just load that image into phaser, but what about an image collection? In Tiled I saw the options to export that tileset as either .tsx or .json, but I don't know if that helps in my case. The reason I need this is because I have some objects that are too big to be used as tiles. I can load them into Tiled and place them like I want to, but obviously they don't show up in my game, unless I can import that tileset into phaser. Does anyone know how to do that, or maybe you know a better option than using an image collection?
Well after some tests, and updating my Tiled version to 1.9.2, it seems there is an pretty simple way.
As long as the tileset collection is marked as "Embeded in map"
(I could have sworn, this checkbox was hidden/deactivated, when selecting "Collection of Images", in my early Tiled version)
Export the map as json
Load map and tile-images
preload() {
this.load.tilemapTiledJSON('map', 'export.tmj');
this.load.image('tile01', 'tile01.png');
this.load.image('tile02', 'tile02.png');
...
}
create Phaser TileSets, just use the filename from the json as the tilsetName (this is the "tricky" part, atleast for me)
create() {
var map = this.make.tilemap({ key: 'map' });
var img1 = map.addTilesetImage( 'tile01.png', 'tile01');
var img2 = map.addTilesetImage( 'tile02.png', 'tile02');
...
// create the layer with all tilesets
map.createLayer('Tile Layer 1', [img1, img2, ...]);
...
}
This should work, atleast with a "Collection of images", with images that have a size of 8x8 pixel (since I don't know the needed/intended Images size, I didn't want to waste time testing various images-sizes needlessly).
Here a small demo:
(due to CORS-issues the map data is inserted as jsonObject and the textures are generate and not loaded)
const mapJsonExport = {"compressionlevel":-1,"height":10,"infinite":false,"layers":[{"compression":"","data":"AQAAAAEAAAACAAAAAgAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAIAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAQAAAAEAAAACAAAAAgAAAAEAAAABAAAAAgAAAAIAAAABAAAAAgAAAAIAAAACAAAAAgAAAAEAAAACAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAgAAAAEAAAACAAAAAgAAAAEAAAACAAAAAQAAAAEAAAACAAAAAQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAEAAAABAAAAAgAAAAEAAAABAAAAAQAAAAEAAAACAAAAAQAAAAIAAAACAAAAAgAAAAEAAAABAAAAAgAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAEAAAACAAAAAQAAAA==","encoding":"base64","height":10,"id":1,"name":"Tile Layer 1","opacity":1,"type":"tilelayer","visible":true,"width":10,"x":0,"y":0}],"nextlayerid":2,"nextobjectid":1,"orientation":"orthogonal","renderorder":"right-down","tiledversion":"1.9.2","tileheight":8,"tilesets":[{"columns":0,"firstgid":1,"grid":{"height":1,"orientation":"orthogonal","width":1},"margin":0,"name":"tiles","spacing":0,"tilecount":2,"tileheight":8,"tiles":[{"id":0,"image":"tile01.png","imageheight":8,"imagewidth":8},{"id":1,"image":"tile02.png","imageheight":8,"imagewidth":8}],"tilewidth":8}],"tilewidth":8,"type":"map","version":"1.9","width":10};
var config = {
width: 8 * 10,
height: 8 * 10,
zoom: 2.2,
scene: { preload, create }
};
function preload() {
// loading inline JSON due to CORS-issues with the code Snippets
this.load.tilemapTiledJSON('map', mapJsonExport);
// generating texture instead of loading them due to CORS-issues with the code Snippets
let graphics = this.make.graphics({add: false});
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 8, 8);
graphics.generateTexture('tile01', 8, 8);
graphics.fillStyle(0x000000);
graphics.fillRect(0, 0, 8, 8);
graphics.generateTexture('tile02', 8, 8);
}
function create () {
let map = this.make.tilemap({ key: 'map' });
let img1 = map.addTilesetImage( 'tile01.png', 'tile01');
let img2 = map.addTilesetImage( 'tile02.png', 'tile02');
map.createLayer('Tile Layer 1', [img1, img2], 0, 0);
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

PhaserJS: After Rotation of camera dragging a Sprite gives strange coords

Basically the problem is that after you rotate the camera, the points that are given as arguments in the callback for dragging are not what I expected. I'm guessing I have to Rotate the given points also but I just couldn't.
Can Someone explain what is going on, is this some kind of bug or what should I do in order the sprite to follow the mouse cursor?
Easiest way to explain the problem is to reproduce it:
1) Go to Phaser Example Runner
2) Copy- Paste this code:
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
}
function create ()
{
var image = this.add.sprite(200, 300, 'eye').setInteractive();
this.cameras.main.setRotation(Math.PI);
image.on('pointerover', function () {
this.setTint(0x00ff00);
});
image.on('pointerout', function () {
this.clearTint();
});
this.input.setDraggable(image);
this.input.on('dragstart', function (pointer, gameObject) {
gameObject.setTint(0xff0000);
});
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
console.log(`x: ${dragX}, y: ${dragY}`);
gameObject.x = dragX;
gameObject.y = dragY;
});
this.input.on('dragend', function (pointer, gameObject) {
gameObject.clearTint();
});
}
3) Open the console, drag around the Eye and look at what coordinates are given.
4) If you remove line 24 (the rotation of the camera) Everything works as expected.
(The example is taken from Phaser 3 Official examples and a bit changed for the bug)
According to Phaser's API Documentation on the setRotation() method, the rotation given in radians applies to everything rendered by the camera. Unfortunately, your pointer isn't rendered by the camera so it doesn't get the same rotated coordinates. Not sure if this is a bug with the library or just a poorly documented exception, but I believe there is a workaround.
Create 2 variables to hold an initial position and a final position:
var image = this.add.sprite(200, 300, 'eye').setInteractive();
var initial = [];
var final = [];
Populate the initial position in your .on('dragstart') method:
this.input.on('dragstart', function (pointer, gameObject) {
initial = [
gameObject.x,
gameObject.y,
pointer.x,
pointer.y
];
gameObject.setTint(0xff0000);
});
Then, populate the final variable in your .on('drag') method:
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
final = [
gameObject.x, // not necessary but keeping for variable shape consistency
gameObject.y, // not necessary but keeping for variable shape consistency
pointer.x,
pointer.y
];
gameObject.x = initial[0] + (initial[2] - final[2]);
gameObject.y = initial[1] + (initial[3] - final[3]);
});
All we're doing here is keeping track of the change in pointer position and mimicking that change in our gameObject.

background image repeat effect in cocos creater

How can background image be set in repeat effect in cocos creater ?
If character runs, back image should be flow backwards, but I want to background repeatedly continuously repeated by using one device size image file.
I think you can do this by using two background images, placed one after the other, with anchors 0,0, size assumed to be same as canvas size (Images not placed inside the canvas). This script applied to both images can achieve that effect, in simplest form.
cc.Class({
extends: cc.Component,
properties: {
// speed of scrolling
speed: 0,
// reset to position
bgwidth: 0
},
// use this for initialization
onLoad: function () {
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
var x = this.node.x;
x -= this.speed * dt;
if (x <= -this.bgwidth) {
x += this.bgwidth*2;
}
this.node.x = x;
},
});

Is it possible to animate filter in Fabric.js?

Is it possible to animate the images filter in Fabric.js? Such as a "pixelate" filter.
I solved it in the same way like the demo.
Unfortunately filters aren't able to be animated - they need too much processing time.
Here's my Code:
image = ... //Image, where the filter should be applied
var filter = new fabric.Image.filters.RemoveWhite({
threshold: 0,
distance: 140
});
image.filters.push(filter);
image.applyFilters(canvas.renderAll.bind(canvas));
animate(image,1, 400); //Start the Animation
function animate(image,value, stop){
value += ((stop-value)*0.02); //Change the threshold-value
if (image.filters[0]) {
image.filters[0]['threshold'] = value;
console.log(value);
image.applyFilters(canvas.renderAll.bind(canvas)); //Start creating the new image
if(value<stop-100){
setTimeout(function(){act(image,value,stop);},1);
}
}
}
I know the code isn't the most efficient one, but it works. And you can see that Animating filters consumes too much time.
(I tested it with a 1920x1080px image, maybe you can use it with smaller images)
Here is a updated version for the brightness filter
var brightnessValue = 0.9;
var brightnessFilter = new fabric.Image.filters.Brightness({
brightness: brightnessValue
});
fabricImage.filters.push(brightnessFilter);
fabric.util.requestAnimFrame(function brightnessFilterAnimation() {
brightnessValue = brightnessValue - 0.04;
brightnessFilter.brightness = brightnessValue;
fabricImage.applyFilters();
if (brightnessValue > 0) {
fabric.util.requestAnimFrame(brightnessFilterAnimation);
}
});

Resources