[SOLVED] Lemur ListBox versioned reference fails to update on removing object


#1

I seem to have found a problem, possibly created by me, not sure but it involves removing items from a listbox.

        if( selectionRef.update() ) {
            // Selection has changed
            if (selectionRef.get().isEmpty()) {
                //Load defaults here.
                LOG.info("Selection ref is NULL");
            } else {
                int selectedIndex = listActiveCrowds.getSelectionModel().getSelection();
                LOG.info("Crowd         [{}]", selectedIndex);
                Crowd crowd = getState(CrowdManagerAppstate.class).getCrowdManager().getCrowd(selectedIndex);
                for (int i = 0; i < 8; i++) {
                    ObstacleAvoidanceParams oap = crowd.getObstacleAvoidanceParams(i);
                    //Remove selected parameter.
                    remove(listBoxAvoidance, i);
                    //Insert the new parameters into the list.
                    insert(listBoxAvoidance, i, OavParamToString(oap, i));
                }
                listBoxAvoidance.getSelectionModel().setSelection(0);
                System.out.println("Update Loop-Visually Selected = " + listActiveCrowds.getSelectionModel().getSelection());
            }
        }

What is going on is I have 2 listBoxes. listActiveCrowds can have objects added to it or removed from it. Whenever a selection changes in listActiveCrowds, it should update the parameters in listBoxAvoidance with whatever it pulls in from the crowdManager.

Everything updates as expected when clicking an item in listActiveCrowds or if I remove the last item added to the list when there are others above it.

If I remove an item from any place other than the last added, the versioned reference never fires, null or otherwise.

For example,

3 items in list, (0-2), removal by starting from last item in list.

Crowd         [2]
Update Loop-Visually Selected = 2

removing selection = 2
Crowd         [1]
Update Loop-Visually Selected = 1

removing selection = 1
Crowd         [0]
Update Loop-Visually Selected = 0

removing selection = 0
Selection ref is NULL, loading defaults.

Removal by starting at first item in list. Selecting the item always works, otherwise not so.

Crowd         [0]
Update Loop-Visually Selected = 0

removing selection = 0

removing selection = 0

removing selection = 0
Selection ref is NULL, loading defaults

#2

I’m not sure what’s happening. In the Lemur demos, you can select items and remove them… I think one of them even has remove first, remove last, etc. buttons to really test it without selection.

In your code, I don’t actually see the code that is doing the removing, though. Just a remove(listBox, i) method of your own that the body is not included. So I can’t really say if something odd is happening in the meatier part of your code that isn’t shown.

I figure there must be some logic in that method or there wouldn’t be a reason to have it, eh?


#3
    //Insert a parameter string into the List.
    protected void insert(ListBox list, int idx, String txt) {
        list.getModel().add(idx, txt);
        list.getSelectionModel().setSelection(idx);
    }
    
    //Remove parameter string from a listBox.
    protected void remove(ListBox list, int idx) {
        list.getModel().remove(idx);
    }

I liked the names remove and insert.

The actual removing of listActiveCrowd items is like this,

    private void shutdown() {
        
        //Get the Crowd from selectedParam.
        Integer selectedCrowd = listActiveCrowds.getSelectionModel().getSelection();
        
        //Check to make sure the crowd has been selected.
        if (selectedCrowd == null) {
            GuiGlobals.getInstance().getPopupState()
                    .showModalPopup(getState(GuiUtilState.class)
                            .buildPopup("You must select a crowd from the "
                                    + "[ Active Crowds ] list.", 0));
            return;
        }
        
        //Get the crowds name from the listActiveCrowds selectedParam.
        String crowdName = listActiveCrowds.getModel().get(selectedCrowd).toString();

        //We check mapGrids to see if the key exists. If not, go no further.
        if (!mapCrowds.containsKey(crowdName)) {
            GuiGlobals.getInstance().getPopupState()
                    .showModalPopup(getState(GuiUtilState.class)
                            .buildPopup("No crowd found by that name.", 0));
            return;
        }
        
        //We have a valid crowd so remove it from the map, the CrowdManager and 
        //the listActiveCrowds listbox.
        Iterator<String> iterator = mapCrowds.keySet().iterator();
        while (iterator.hasNext()) {
            String entry = iterator.next();
            if (entry.equals(crowdName)) {
                //To fully remove the crowd we have to remove it from the 
                //CrowdManager, mapCrowds (removes the query object also), and 
                //the listActiveCrowds.
                System.out.println("removing selection = " + listActiveCrowds.getSelectionModel().getSelection());
                Crowd crowd = getState(CrowdManagerAppstate.class).getCrowdManager().getCrowd(selectedCrowd);
                getState(CrowdManagerAppstate.class).getCrowdManager().removeCrowd(crowd);
                remove(listActiveCrowds, selectedCrowd);
                iterator.remove();
                break;
            }
        } 
    }

Edit: didn’t realize I typoed on the oapToString till just now. blush.


#4

I’m not sure what to tell you… removal of list items has always worked for me.

If it were me, I’d add logging in remove(ListBox, int) to log the list before and after, etc…

Edit: I mean, the VersionedList removal code is super straight forward:


#5

I will try that. I will also try the demos again and see if it happens there since I have them.


#6

Well it seems you cant rely on getSelected since its not updated when removing or adding to a listbox.

Its only accurate if you click.

Remove getSelection before remove listActiveCrowds 1
Remove getSelection after remove listActiveCrowds 1

#7

I found that in order to fully cover the possible versioned reference updates, i had to have a reference for
VersionedList<T> model;
and
VersionedReference<Set<Integer>>

because sometimes modelRef.update() will update when selectionRef.update(); doesn’t


#8

Hmmm… is this a Lemur bug or just something folks need to be aware of? On quick read, I can’t tell exactly if it’s something I can/should fix.


#9

I don’t know about the getSelected part being a bug but its ok as long as your aware of how things work and make the user select before you try to use getSelection. Default upon creation is nothing selected unless you specificaly set it yourself.

As for the versioned reference, I think its like that for a reason, you are doing two different things with the references. I just wasn’t aware of it and once I accounted for that, all works fine. It’s also probably I am doing things that are unintended since bad java habits will always affect this. I prefer strings for some type of work where you allow for objects to be used and sometimes you cant really translate that into a string without user input.

By bad java habit, I mean the expecting that what you can do with swing will work for 3d and sometimes it just aint so.


#10

Can you describe the sequence of events that led to the trap?


#11

Yes,

I created the

listActiveCrowds = contActiveCrowds.addChild(new ListBox<>(), "growx, growy"); 

as an empty list.
I the used the

private VersionedReference<Set<Integer>> selectionRef; 

For the versionedRef because I found it while digging around in the forums for other stuff. I didnt read about it or know there was another type you could use,

private VersionedReference<List<String>> modelRef;

When I was adding to the list, as shown above, I was setting the selector, so everything worked fine until I tried to remove things. Since I did not set selector upon removing objects, there was a mismatch in the update loop when grabbing the selected on the crowd list. Selcted shows one thing but in reality the object was already gone so index out of bounds exception.

So I started digging into ListBox and found that

boolean selectionUpdate = selectionRef.update(); 

would update only for specific things using the selected whereas

modelRef.update()

would update for the actual objects in the list being removed. modelRef is for list objects, the selectionRef is for the selection.

Once I accounted for both objects and selected and removed procedural setting of object selected in the list. everything aligned and works. I basically let the listBox do what is was intended to do i guess.

        //Have to check for both selectionRef(the selections themselves) and 
        //modelRef(the list of crowd names) updates to fully know whether the 
        //reference has been updated.
        if( selectionRef.update() || modelRef.update()) {
            // Selection has changed and the model or selection is empty.
            if ( selectionRef.get().isEmpty() ||  modelRef.get().isEmpty()) {
                //Load defaults since there is no data to update.
                LOG.info("Update Loop empty reference - selectionRef [{}] modelRef [{}]", selectionRef.get().isEmpty(), modelRef.get().isEmpty());
                //The ObstacleAvoidanceParams string for the listbox.      
                for (int i = 0; i < 8; i++) {
                    String params = "<=====    " + i + "    =====>\n" + defaultOAP; 
                    //Remove selected parameter from listBoxAvoidance.
                    remove(listBoxAvoidance, i);
                    //Insert the new parameters into listBoxAvoidance.
                    insert(listBoxAvoidance, i, params);
                }
            } else {
                Integer selectedIndex = listActiveCrowds.getSelectionModel().getSelection();
                int numberOfCrowds = getState(CrowdManagerAppstate.class).getCrowdManager().getNumberOfCrowds();
                //The selectedIndex does not update when removing or adding 
                //objects to the listBox. This leads to situations where 
                //selectedIndex will be the same as the numberOfCrowds. in those 
                //cases we skip updating listBoxAvoidance. Failure to make the 
                //check throws an index out of bounds exception.
                if (selectedIndex != null && selectedIndex <  numberOfCrowds) {
                    LOG.info("Update Loop updated OAP - Crowd [{}] selectedIndex [{}]", listActiveCrowds.getModel().get(selectedIndex), selectedIndex);

                    Crowd crowd = getState(CrowdManagerAppstate.class).getCrowdManager().getCrowd(selectedIndex);
                    for (int i = 0; i < 8; i++) {
                        ObstacleAvoidanceParams oap = crowd.getObstacleAvoidanceParams(i);
                        //Remove selected parameter.
                        remove(listBoxAvoidance, i);
                        //Insert the new parameters into the list by converting
                        //the oap to string.
                        insert(listBoxAvoidance, i, oapToString(oap, i));
                    }
                }
            }
        }

#12

My reading of the ListBox code around the line you posted is that I should probably be checking for this case else the visual selection will be wrong too, eh?

I will think about if there is an easy test for this I might add to the demos.


#13

Visual is always correct actually.

edit, works as expected I would say where if selecting any item and doing a removal, the next in list shows as selected as should visually or if none left the list shows empty.


#14

I found that my solution for getSelection() was user specific and also breakable.

The problem is with how getSelection() works for SelectionModel.

Since getSelection() will only update using the lastAdd, the true and only real solution when inserting or removing items from the list is to setSelection(-1) and force the user to select an item themselves.

If the list size does not change, ie a removal followed by an insertion, you can ignore this. Anything else and its only safe to do as above unless its the last added item of the list or implement your own SelectionModel.

As always, I could be wrong and not aware of other things I should be doing so feel free to slap me down.

Edit: By my solution I mean checking like this where I am checking selectedIndex against a different number.

if (selectedIndex != null && selectedIndex <  numberOfCrowds) {

If the list where I pull the numberOfCrowds from in this instance were of a different size than the ListBox size where the selectedIndex comes from, things become mismatched. When I wrote this, this wasn’t possible but I later made changes that allowed the two sizes to diverge.


#15

Rereading the code for selection model it looks like it doesn’t properly handle remove in the single-selection case. I don’t know if that’s related to your issue or not.


#16

I tried to tackle it on the user side by setting the selection but I always wound up defensive coding some other part of the code till it look real Italian…

So I opted for never updating the selection other than clearing it.