RocketScienceGuy
RocketScienceGuy

Reputation: 161

Java libGDX Array corruption

I'm trying to make a fancy new ui widget, an edge-on carousel to scroll around and around through actors:

carousel

In the picture above it should be counting in from the left up to 99 and then starting at 0 and moving to the right. Instead, I'm getting some apparent array corruption.

The code tracks which index should be front in center in the display (getIndexOfFocus()). Due to challenges with rendering order, I go through all the elements above this element first in one loop, then have a second loop starting from the beginning of the array up to and including the center element (in the example picture shown, the center element is 6):

    SnapshotArray<Actor> children = getChildren();
    children.end();

    int n = children.size;
    for (int i = getIndexOfFocus() + 1; i < n; i++) {
        actorSubProcessing(children, i, rowHeight, startY, x);
    }
    
    for (int f = 0; f<= getIndexOfFocus(); f++) {
        actorSubProcessing(children, f, rowHeight, startY, x);
        //children.get(i).toFront();
    }

If I comment out the first loop, elements 0-6 are shown correctly, instead of 56-50 and 6 popping up in the corner. Within the actorSubProcessing method I do three things to directly affect the elements of the children array:

    child.setScaleX(child.getScaleX() * scalingAgent); 
    child.setScaleY(child.getScaleY() * scalingAgent);  

    if (round)
        child.setBounds(Math.round(x), Math.round(y), Math.round(width), Math.round(height));
    else
        child.setBounds(x, y, width, height);

    if (offset > 0)
        child.toBack();

I understand that libGDX arrays are special in that they limit garbage collection, but why is working with elements later in the array changing elements early on in the array? getChildren(), btw, is coming from ui.HorizontalDisplay, which I mirrored before modifying to be this new widget

One of my attempts at a solution is figuring out how to clone the array, but even if I could I imagine it would be messy for the widget to determine what to render. I've also been going through the API. I'd like to be able to rearrange the ordering of my array elements; I think there would be a certain elegance to having the element to display in the center be the last element in the array. Then I could just do a single loop through the array, from beginning to array. I see that you can sort arrays with a "comparator", but don't comprehend what I'm reading there yet to know if that's a path towards rearranging the array order.

Upvotes: 0

Views: 102

Answers (1)

Tenfour04
Tenfour04

Reputation: 93789

When you call toBack() on a child, it is reordering the Group's array of children. This can get messy if you're doing it while iterating the children. SnapshotArray is designed so you can work with a temporary copy of the array that is safe from modifications to the SnapshotArray. The temporary copy goes back to a pool when you're done with it so it minimizes allocations/deallocations.

I'm not exactly sure what the context of your code is, because it's weird that you're calling children.end() on it right before using it.

You should use SnapshotArray kind of like this:

Actor[] children = getChildren().begin();

int n = children.size;
for (int i = getIndexOfFocus() + 1; i < n; i++) {
    actorSubProcessing(children, i, rowHeight, startY, x);
}

for (int f = 0; f<= getIndexOfFocus(); f++) {
    actorSubProcessing(children, f, rowHeight, startY, x);
    //children.get(i).toFront();
}

children.end();

where your actorSubProcessing needs to be modified to work with Actor[] instead of a SnapshotArray<Actor>.

The other area where you may be having an issue is the indices you're using. If you're simply using the index of each child from the children array, that's going to be constantly changing as you move their Z order around. You need to give them each a permanent identifier. Your parent could assign their indices to their userObject property right after you've added all of them:

for (int i=0; i<getChildren().size; i++)
    getChildren().get(i).setUserObject((Integer)i);

And retrieve them like this:

int originalIndex = (Integer)child.getUserObject();

I suggest a simpler algorithm where you sort the children by what will be closest and fill them all back into the children SnapshotArray in one operation. Then it won't have to call toBack() on each of them, which is really expensive because you're reordering the array once for every child.

Actor[] children = getChildren().begin();

// Iterate all children to scale and position them here like you were doing with 
// your actorSubProcessing method, but don't call toBack() on them.

Arrays.sort(children, comparator); 
// where comparator compares -abs(originalIndex - indexOfFocus) (psuedo code)
// This will sort them from farthest to nearest.

getChildren().clear();
getChildren().addAll(children); // adding back the same ones, but in sorted order
getChildren().end();

Upvotes: 2

Related Questions