Hello once again stackoverflow, so I am working on my latest project, an oil-tycoon game, after being inspired by a movie. It involves an overworld map which you can zoom in, and for that I have a function which scales the images to a certain size depending on how much you scrolled. I am used to C++ where you manually free up memory so please go easy on me as I don't fully understand the garbage collector and what not, English also isn't my native language.
The function is this (partly from stackoverflow, I love you guys):
public void scale(int dWidth, int dHeight) {
Image newImg = this.baseImg.getScaledInstance(dWidth, dHeight, Image.SCALE_DEFAULT);
// Create a buffered image with transparency
BufferedImage bimage = new BufferedImage(newImg.getWidth(null), newImg.getHeight(null), BufferedImage.TYPE_INT_ARGB);
// Draw the image on to the buffered image
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(newImg, 0, 0, null);
bGr.dispose();
this.img.flush();
this.img = bimage;
bimage.flush();
newImg.flush();
}
This function is part of a class, Bitmap, which has a BufferedImage member variable, img. This member variable is used to actually draw the images on screen. However upon testing my new scaling functionality, I noticed memory usage skyrocketed as the function was called. Moreover, the memory wasn't actually freed up. I tested it, and without the calls to the scale function, memory usage is stable at around ~200MB, but once I start calling this function (for one image only, called every time a MouseWheelMoved event fires) it skyrockets once you scroll. Every scroll increases the memory usage by a lot, and it never goes back down. I already added the flush calls, but it doesn't seem to have any effect. Neither does setting the local variables to null. I suspect there is some kind of memory leak in this function.
So my question to you guys: can anyone confirm this? Does anybody see a memory leak in this function? If not, what else could be causing this ridiculous increase in memory usage upon calling this function? I just want to stress again that I'm 100% sure this function is the cause of it.
I can post more code if needed (for instance the actual calls to the function), but I omitted it for simplicity for the time being.
Thanks in advance!
EDIT
Added extra code, as requested.
Lets dive down this rabbit hole, from top to bottom.
First off, I have a class, GameWindow, which extends JFrame. It has a MouseWheelListener like this:
this.addMouseWheelListener(new MouseWheelListener(){
#Override
public void mouseWheelMoved(MouseWheelEvent arg0) {
double scrollAmount = arg0.getPreciseWheelRotation();
curMapScale -= ((double)scrollAmount * 0.1);
// Reset zooming
if(curMapScale <= 0)
{
curMapScale = 1.0;
}
}
});
My game loop is as such (noobish, I know):
private void gameLoop()
{
boolean abort = false;
long curTime = 0;
long prevTime = System.currentTimeMillis();
while(!abort)
{
curTime = System.currentTimeMillis();
// 500ms for debugging the scaling issue
if(curTime - prevTime > (500))
{
prevTime = curTime;
wGame.render();
}
}
}
wGame is the GameWindow object, and it's render function is like this:
public void render()
{
mainPanel.updateGame();
mainPanel.render();
}
mainPanel is an object of GraphicsPanel, a class that extends JPanel.
It's updateGame() function:
public void updateGame()
{
em.update();
}
and it's render function:
public void render()
{
this.repaint();
}
And finally it's scaleEntities() function
public void scaleEntities()
{
// Deleted scaling of all entities except the map in order to debug the scaling issue
em.scaleMap(getWidth() * GameWindow.curMapScale, getHeight() * GameWindow.curMapScale );
}
em is an object of the class EntityManager, a class which contains ArrayLists with every entity in the game world. It's update function goes like this:
public void update()
{
// Loop through all generic entities en update them (entityList is empty
// for the moment
for(int i = 0; i < entityList.size(); i++)
{
entityList.get(i).update();
}
// Loop through all pointsOfInterests, which exist only on the overworld
// also empty for the moment
for(int i = 0; i < pointsOfInterest.size(); i++)
{
pointsOfInterest.get(i).update();
}
// Update the current map (note the update() function isn't actually used yet, it will be used to update an entities position if it has a velocity
mapList.get(curMap).update();
// Stuff pertaining to Window Resizing, which is irrelevant right now
GameWindow.resetOffset();
GameWindow.hasMouseDragged = false;
}
and it's scaleMap() function goes like this:
public void scaleMap(double width, double height)
{
mapList.get(curMap).scale((int)Math.ceil(width), (int)Math.ceil(height));
}
the call to scale in this function is passed down directly to the Bitmap object of the map. The scale function we started out with is thus called.
Last but not least, the paintComponent() function of the GraphicsPanel class, where the objects actually get drawn:
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, getWidth(), getHeight());
em.render(g2d);
}
The EntityManagers render function:
public void render(Graphics2D g2d)
{
// Only draw the map for debugging purposes
mapList.get(curMap).draw(g2d);
// for(int i = 0; i < entityList.size(); i++)
// {
// entityList.get(i).draw(g2d);
// }
// for(int i = 0; i < pointsOfInterest.size(); i++)
// {
// pointsOfInterest.get(i).draw(g2d);
// }
}
And the maps draw function:
#Override
public void draw(Graphics2D g2d) {
BufferedImage temp = bitm.getSource();
g2d.drawImage(temp, (int)this.loc.getX(), (int)this.loc.getY(), null);
temp.flush();
}
bitm is obviously the Bitmap member of the map, and it's getSource() function goes like this:
public BufferedImage getSource()
{
return this.img;
}
This should be all relevant code. Does anybody see where the memory leak might come from? I have a feeling the newImg and bimage variables in the Bitmaps scale function aren't properly deleted.
Thanks in advance, once again!
Related
I am trying to make an Uno game (not important), so I need to draw some rectangles and images at the start of the game to set up. However, when the deck is clicked, it needs to add a card to the player's hand. This would obviously be at a different time than the original setup, but since it involves drawing a card, doesn't it also need to be within paint()? I tried to fix this by creating booleans and changing them depending on if the drawing has already been made, but when I did that it now does not draw the start drawing at all. Is there an easier way to do this or at least a solution to this issue? There is a lot more code, but I think what I have below is all that is needed for this issue. Thanks!
public class ImageCreator {
private boolean hasStartedDrawing = false;
private boolean drawCardPlayer = false;
public void mouseClicked(MouseEvent e)
{
if ((e.getX() >= 472 && e.getX() <= 662) && (e.getY() >= 205 && e.getY() <= 455))
{
drawCardPlayer = true;
repaint();
}
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
if (hasStartedDrawing == false)
{
Rectangle rect0P = new Rectangle(50, 650, 95, 125); g2.draw(rect0P);
hasStartedDrawing = true;
{
if (drawCardPlayer)
{
game.drawCardPlayer(g); //a method in another class that actually draws the card
drawCardPlayer = false;
}
}
}
You are doing it just great! Just a little detail, I struggle with it too in the beggining.
The best aproach is to override paintComponent, and then call repaint every time what you need to update or "draw" the GUI again.
If you need a more deep explanation, here you have the "why": https://www.oracle.com/java/technologies/painting.html
And if you need some easy (but important) examples, here you have (also from Oracle Documentation): https://docs.oracle.com/javase/tutorial/uiswing/painting/index.html
I am currently working on an animation to compare two stock exchange algorithms. I am running the algorithms within the paint component extending JComponent. (not the best, but I don't care) I need to have the screen refresh half way through the paint component. I do not want it to have to get all the way through before it up dates the screen. The reason being is I have one algorithm with a nested while loop and the other without. How would I go about doing this?
public void paintComponent(Graphics g) {
//calls in the super class and calls the calibrate the graphics method
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
calibrateFrame( getHeight(), getWidth() );
//Clears the rectangle to avoid overlaying, makes it black
g2D.clearRect(0, 0, getWidth(), getHeight());
g2D.setColor(Color.BLACK);
g2D.fillRect(0, 0, getWidth(), getHeight());
//Draws the rectangles without the algorithm started
redraw(g2D, -1);
/**
*algorithms
*/
fastSpans[0] = 1;
slowSpans[0] = 1;
//Makes a new stack pushes 0 on the stack
Stack myStack = new Stack();
myStack.push(0);
//If it has not been sorted or paused, continue the algorithm
if (!(pause) && !(sorted)){
//The slower algorithm needs to start out at zero (j)
int j = indexValue-1;
g2D.setColor(Color.BLUE);
//Calculates the values for the X and Y coordinates for the
//new rectangle, along with the height
int slowY = calSlowY(j);
int slowX = calSlowX(j);
int curHeightSlow = (int) ((stocks[j]/maxStockValue)*maxHeight);
//Here is the actual algorithm
int k = 1;
boolean span_end = false;
//Nested While Loop
while (((j-k)>0) && !span_end){
if (stocks[j-k] <= stocks[j]){
k = k + 1;
// Draw the current component
// **********************
// DO REFRESH MID PAINT COMPONENT
}
else{ span_end = true; }
}
slowSpans[j] = k;
g2D.setColor(Color.WHITE);
for(int i = 0; i < numberOfStock ; i++){
}
if (!(indexValue >= numberOfStock)){
while (!( myStack.empty()) && (stocks[(int)myStack.peek()]) <= stocks[indexValue]){
myStack.pop();
}
if (myStack.empty()){
fastSpans[indexValue] = indexValue + 1;
}
else {
fastSpans[indexValue]= indexValue - (int) myStack.peek();
//System.out.println("Im in the else");
}
myStack.push(indexValue);
}
}
drawStrings(g2D);
}
I am running the algorithms within the paint component extending JComponent. (not the best, but I don't care)
But you should care, since this impacts on your problem and possible solution.
I need to have the screen refresh half way through the paint component. I do not want it to have to get all the way through before it up dates the screen. The reason being is I have one algorithm with a nested while loop and the other without. How would I go about doing this?
Then don't run the algorithm through paintComponent. Instead use a SwingWorker<Void, Image>, update a BufferedImage and pass that image to the GUI through the worker's publish/process method pair, calling repaint() at the same time. For more on how to use a SwingWorker, please have a look at: Lesson: Concurrency in Swing
Don't understand what half way means.
If it means half of the component, then the simple way is to using two JComponent.
If u means in same component one line updated but another line not updated.
What I understand the repaint is calling when it packs(), updateUI(), or caused by invalidate().
So in my view, the repaint() should only care about paint these lines/2D, and in another thread to execute these loops/generate these data. Once the data collection is finished just call updateUI()/paintImmediately.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
repaint();
}
});
I'm working on a Java program with Processing library, that plots/draws millions of galaxies (the coordinates do not change) in 3D-space (as simple points) and allows translating, rotating and scaling the view in real time.
I'm using Processing to have GPU acceleration on rendering the view, but the points have to be given to the graphics card by the CPU anyway. So currently my draw() method contains a for loop which "draws" the points one by one. Since nothing about the points changes, only the camera angle, position and scale, it is too slow for my needs.
Current draw():
transformSpace();
for (int i = 0; i < visibleBodies.length; i++) {
Body b = visibleBodies[i];
point(b.x, -b.z, -b.y); // Z-up world
}
Could all the points be handled somehow differently, so draw() wouldn't have to contain a massive loop? I've been unsuccessful in trying to find a way to feed the coordinates to the GPU in a separate one-time process or make all the data one big object.
You could draw the to a PGraphics ahead of time, and then just draw that PGraphics when you want to draw your bodies.
PGraphics image;
void setup() {
size(500, 500);
image = createGraphics(500, 500);
image.beginDraw();
for (int i = 0; i < visibleBodies.length; i++) {
Body b = visibleBodies[i];
image.point(b.x, -b.z, -b.y); // Z-up world
}
image.endDraw();
}
void draw() {
background(255, 0, 0);
image(image, 0, 0, width, height);
}
More info can be found in the Processing reference.
I've been experimenting with different ways of moving a image over a grid of tiles for a game, but I've been unable to get a working implementation.
First I tried using a grid layout to hold a bunch of Tiles that extended Canvas and drew themselves. This drew the tiles nicely, however it seems that I am unable to draw my Player object on top of them. Originally, the Player also extended Canvas and I intended to have the widget on top of the tiles. It seems like this is impossible.
I then tried to have the Tile simply extend nothing, and just hold the image. I then hold each Tile in a 2D array and draw each Tile by a nested for loop, using the int from the for loop, multiplied by the image size, to draw Tile's Image. I put this code in a PaintListener inside of my constructor for my Map class that extended Canvas and dropped my Map onto my Shell in a Fill layout, but the PaintListener never gets called (I tested with a print statement).
What implementation could I use to draw the Tiles at the start of the game, then allow me to control the movement of my Player image?
I did something similar.
Using a PaintListener I get the calls when the Widget needs to be repainted. In my paint function, I loop over a tile array (wrapped in a World class) and draw all tiles. Afterwards I use the same technique with a worldObjects array/class:
public class WorldWidget extends Canvas {
WorldWidget() {
addPaintListener(new PaintListener() {
#Override
public void paintControl(PaintEvent e) {
WorldWidget.this.paintControl(e);
}
});
}
protected void paintControl(PaintEvent e) {
GC gc = e.gc;
for (short y = 0; y < world.getHeight(); y++) {
for (short x = 0; x < world.getWidth(); x++) {
final ITile tile = world.getTile(x, y);
final Image image = ImageCache.getImage(tile);
gc.drawImage(image, x * tileSize, y * tileSize);
}
}
// Here is used a similar loop, to draw world objects
}
}
This is obviously a condensed code example, as the class is part of an editor and reacts on mouse clicks and movement amongst other things.
When I did a tile based simulation while ago I did it this way:
I had 2 layers of the tile map - one for the terrain and second for the units.
The map itself was represented by a JPanel.
So roughly you got this for the JPanel:
public void paintComponent(Graphics graphics) {
// create an offscreen buffer to render the map
if (buffer == null) {
buffer = new BufferedImage(SimulationMap.MAP_WIDTH, SimulationMap.MAP_HEIGHT, BufferedImage.TYPE_INT_ARGB);
}
Graphics g = buffer.getGraphics();
g.clearRect(0, 0, SimulationMap.MAP_WIDTH, SimulationMap.MAP_HEIGHT);
// cycle through the tiles in the map drawing the appropriate
// image for the terrain and units where appropriate
for (int x = 0; x < map.getWidthInTiles(); x++) {
for (int y = 0; y < map.getHeightInTiles(); y++) {
if (map.getTerrain(x, y) != null) {
g.drawImage(tiles[map.getTerrain(x, y).getType()], x * map.getTILE_WIDTH(), y * map.getTILE_HEIGHT(), null);
}
}
}
if (map.getSimulationUnits() != null) {
for (Unit unit : map.getSimulationUnits()) {
g.drawImage(tiles[unit.getUnitType()], (int) Math.round(unit.getActualXCor() * map.getTILE_WIDTH()), (int) Math.round(unit.getActualYCor() * map.getTILE_HEIGHT()),
null);
}
}
// ...
// draw the buffer
graphics.drawImage(buffer, 0, 0, null);
}
Logic:
private Terrain[][] terrain = new Terrain[WIDTH][HEIGHT];
/** The unit in each tile of the map */
private Unit[][] units = new Unit[WIDTH][HEIGHT];
Then you have your game loop where you update the position of the units and other things, basically render() and update() the game. Check the links I've provided below.
NOTE
Since you are making a simple game this post about making game loops will be definitely useful for you. This hopefully also answer your question about moving the object on the map.
This site will be also very helpful since you will probably need to detect collision at some point too.
I'm trying to make a tower of hanoi solver which simply solves the hanoi without any mouse events. The problem is when I move the rectangle the original remains, even after I repaint. I've searched the net and tried changing the code around but nonthing worked. I am using a JFrame with a JPanel inside of it if that changes anything.
I have my disk class here which is just a rectangle with colour.
class Disk extends Rectangle {
Color diskColour;
public Disk(int a, int b, int c, int d, Color colour) {
x = a;
y = b;
width = c;
height = d;
diskColour = colour;
}
public Color getColour() {
return diskColour;
}
public void paintSquare(Graphics g) {
repaint();
g.setColor(diskColour);
g.fillRect(x, y, width, height);
repaint();
}
}
Here is my code where I actually call the paintSquare method:
public void simpleMoveDisk(Disk[] disks, int n, Graphics g) {
disks[n].setLocation(30,25);
disks[n].paintSquare(g);
repaint();
}
The paintSquare method paints the disk, while the setLocation method changes its coordinates.
When this runs the rectangle occurs in the new location, however the old one still remains. Any help is appreciated, thanks in advance.
You are calling repaint() in several places and you shouldn't be.
Have your the top level class that is doing the painting, call the paintSquare method and any other method that is needed. Those methods should not be calling repaint().
Also your simple move disk is really strange in the fact that it passes an array of Disks, an index, and a graphics object. Instead make it just take in a Disk. Just pass it the one out of the array that is needed to be updated. Then let whatever class that calls simpleMoveDisk, separately make a call to repaint instead of trying to paint and update the model in the same method.