Search code examples
javaarraysgenericsintellij-idealibgdx

Access of a gdx.utils.Array launches a ClassCastException


I'm trying to select certain objects from an Array containing all my game objects inside my render method. Because It is inside my render method, I'm trying to avoid the garbage collector by only using member variables. My goal is to put in my tmpRenderArray GameObjects that are instances of Renderable. I then need to sort theses objects and render them.

Here's my code:

private Array<GameObject> gameObjects = new Array<>();
private Array<Renderable> tmpRenderArray = new Array<>();

//...

@Override
public void render(float delta)
{
    Gdx.gl.glClearColor(1f, 0.7f, 0.7f, 1f);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    batch.setProjectionMatrix(camera.combined);
    batch.begin();

    int i = 0;
    for(GameObject object : gameObjects)
        if(object instanceof Renderable)
            tmpRenderArray.set(i++, (Renderable)object);

    tmpRenderArray.sort(Renderable.PRIORITY_COMPARATOR); 
    //PRIORITY_COMPARATOR = (a, b) -> Float.compare(a.getPriority(), b.getPriority());

    for(; i-- > 0; )
        tmpRenderArray.items[i].render(); //<-- That line

    batch.end();

    //...
}

Previously, I've set my tmpRenderArray size to the gameObjects size to ensure there's enough place. tmpRenderArray.setSize(gameObjects.size); Both tmpRenderArray and gameObjects are normal Array objects (com.badlogic.gdx.utils.Array) created with default constructor. No other operations than specified have been made on these arrays. (Except creating GameObjects by calling the add method)

Since I'm looping backward to render, there's no chance of rendering null objects. However I'm getting a Class Cast exception on the specified line which is "too late" in my opinion. Exception in thread "LWJGL Application" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Lme.winter.project2d.Renderable;

If I'm right, [L means an array but I'm not casting an array. items is a member variable so no operations are done accessing it. How can I launch an exception by accessing an array ?

Edit

Thanks to Tenfour04 for fixing a side bug (index mistake) but the main issue is still alive. I will also consider using clear and add methods of the Array object but for now I would like to fix this bug, or at least understand it. I've updated my code below with that small fix. I am not arguing that this method of rendering is the best or it is better than the one given by the Tenfour04. I just want to understand what's going on.

I used the debugger to see what's going on: debug

As we can see, tmpRenderArray.items[i] when i = 1, returns a Car (which is a Renderable) but when I step in, I'm redirected to LwjglApplication::initialize right in the catch clause and I get the original exception. The question remains the same, how is my array casted when accessing it ? The array I'm accessing is plain java, it is the member variable inside the Array object so there's not any other code of me or of LibGDX running.


Solution

  • It states in the documentation of Array that you cannot access items if you did not use the Array constructor that specifies the class explicitly. This is because generic types are stripped at runtime by Java, so the array doesn't know the type of its objects that you access from items. LibGDX Array provides access to stuff under the hood that you can easily misuse, I suppose so you can highly optimize code if you need to.

    Replace

    tmpRenderArray.items[i].render();
    

    with

    tmpRenderArray.get(i).render();
    

    and it should work fine, except for the IndexOutOfBoundsException on the first element from your i being one too high (see below).


    Original answer--these bugs were not the source of the CCE

    Your bugs are that you don't clear your temp array, and that your i is one bigger than it should be due to the i++ in the upper for loop. This object is not null, but some left over object from a previous frame. You need to start i at -1 and use ++i instead of i++. Or decrement by 1 before the second loop.

    That said, some of your code is a bit convoluted. You could simplify it like this:

    Gdx.gl.glClearColor(1f, 0.7f, 0.7f, 1f);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    
    batch.setProjectionMatrix(camera.combined);
    batch.begin();
    
    tmpRenderArray.clear();
    for(Renderable object : gameObjects)
        tmpRenderArray.add(object);
    
    tmpRenderArray.sort(Renderable.PRIORITY_COMPARATOR); //invert this comparator
    //PRIORITY_COMPARATOR = (b, a) -> Float.compare(b.getPriority(), a.getPriority());
    
    for(Renderable renderable : tmpRenderArray)
        renderable.render();
    
    batch.end();
    
    //...