Skip to content

Commit

Permalink
added support for negative indices to 'getChildAt', 'addChildAt', and…
Browse files Browse the repository at this point in the history
… 'removeChildAt'
  • Loading branch information
PrimaryFeather committed Mar 23, 2015
1 parent 9462e87 commit 5232a92
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 9 deletions.
32 changes: 23 additions & 9 deletions starling/src/starling/display/DisplayObjectContainer.as
Expand Up @@ -73,9 +73,7 @@ package starling.display

/** Helper objects. */
private static var sHelperMatrix:Matrix = new Matrix();
private static var sHelperMatrix3D:Matrix3D = new Matrix3D();
private static var sHelperPoint:Point = new Point();
private static var sHelperPoint3D:Vector3D = new Vector3D();
private static var sBroadcastListeners:Vector.<DisplayObject> = new <DisplayObject>[];
private static var sSortBuffer:Vector.<DisplayObject> = new <DisplayObject>[];

Expand Down Expand Up @@ -107,14 +105,18 @@ package starling.display
/** Adds a child to the container. It will be at the frontmost position. */
public function addChild(child:DisplayObject):DisplayObject
{
addChildAt(child, numChildren);
addChildAt(child, -1);
return child;
}

/** Adds a child to the container at a certain index. */
/** Adds a child to the container at a certain index. If you pass a negative index,
* '-1' will add the child at the very end, '-2' at the second to last position, etc. */
public function addChildAt(child:DisplayObject, index:int):DisplayObject
{
var numChildren:int = mChildren.length;
var numChildren:int = mChildren.length;

if (index < 0)
index = numChildren + 1 + index;

if (index >= 0 && index <= numChildren)
{
Expand Down Expand Up @@ -158,10 +160,16 @@ package starling.display
return child;
}

/** Removes a child at a certain index. Children above the child will move down. If
* requested, the child will be disposed right away. */
/** Removes a child at a certain index. The index positions of any display objects above
* the child are decreased by 1. If requested, the child will be disposed right away.
* Pass the index '-1' to remove the last child, '-2' for the second to last, etc. */
public function removeChildAt(index:int, dispose:Boolean=false):DisplayObject
{
var numChildren:int = mChildren.length;

if (index < 0)
index = numChildren + index;

if (index >= 0 && index < numChildren)
{
var child:DisplayObject = mChildren[index];
Expand Down Expand Up @@ -197,10 +205,16 @@ package starling.display
for (var i:int=beginIndex; i<=endIndex; ++i)
removeChildAt(beginIndex, dispose);
}

/** Returns a child object at a certain index. */

/** Returns a child object at a certain index. If you pass a negative index,
* '-1' will return the last child, '-2' the second to last child, etc. */
public function getChildAt(index:int):DisplayObject
{
var numChildren:int = mChildren.length;

if (index < 0)
index = numChildren + index;

if (index >= 0 && index < numChildren)
return mChildren[index];
else
Expand Down
27 changes: 27 additions & 0 deletions tests/src/tests/display/DisplayObjectContainerTest.as
Expand Up @@ -189,6 +189,33 @@ package tests.display

Assert.assertEquals(3, parent.numChildren);
}

[Test]
public function testNegativeIndices():void
{
var parent:Sprite = new Sprite();
var childA:Sprite = new Sprite();
var childB:Sprite = new Sprite();
var childC:Sprite = new Sprite();

parent.addChildAt(childC, -1);
parent.addChildAt(childB, -2);
parent.addChildAt(childA, -3);

Assert.assertEquals(parent.getChildAt(0), childA);
Assert.assertEquals(parent.getChildAt(1), childB);
Assert.assertEquals(parent.getChildAt(2), childC);

Assert.assertEquals(parent.getChildAt(-3), childA);
Assert.assertEquals(parent.getChildAt(-2), childB);
Assert.assertEquals(parent.getChildAt(-1), childC);

Assert.assertEquals(parent.removeChildAt(-3), childA);
Assert.assertEquals(parent.removeChildAt(-2), childB);
Assert.assertEquals(parent.removeChildAt(-1), childC);

Assert.assertEquals(0, parent.numChildren);
}

[Test]
public function testSwapChildren():void
Expand Down

21 comments on commit 5232a92

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 23, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! After this change, a lot of mistakes, is not added to the parent of a child!

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm ... I don't understand how this can have a side effect; the changes should apply only if the given index is negative, which always resulted in an exception before.

Can you give me a code example of one such exception? I must be overlooking something.

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 23, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the problem! This is due to Feathers LayoutGroup

override public function addChildAt(child:DisplayObject, index:int):DisplayObject
{
    if(child is IFeathersControl)
    {
        child.addEventListener(FeathersEventType.RESIZE, child_resizeHandler);
    }
    if(child is ILayoutDisplayObject)
    {
        child.addEventListener(FeathersEventType.LAYOUT_DATA_CHANGE, child_layoutDataChangeHandler);
    }
    var oldIndex:int = this.items.indexOf(child);
    if(oldIndex == index)
    {
        return child;
    }
    if(oldIndex >= 0)
    {
        this.items.splice(oldIndex, 1);
    }
    var itemCount:int = this.items.length;
    if(index == itemCount)
    {
        //faster than splice because it avoids gc
        this.items[index] = child;
    }
    else
    {
        this.items.splice(index, 0, child);
    }
    this.invalidate(INVALIDATION_FLAG_LAYOUT);
    return super.addChildAt(child, index);
}

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 23, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please return back to the code, this change should be done in conjunction with Feathers.

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see! I didn't think of that side effect, you're right. Thanks for reporting that!

Thankfully, there's no need to remove that feature completely; I just needed to revert the implementation of "addChild()". See: 9fd6733

Please try it out and let me know if that helps!

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 23, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now everything is fine! :) Thank you for your efficiency!

@Oldes
Copy link

@Oldes Oldes commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I pretty don't like this change, as it's just syntactic sugar, which degradates performance a little bit (but every bit is counted one day).

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd agree if this code was part of the render loop; but it's in child management, which is far less critical. Furthermore, we're talking about one "if" per method, no additional function call, so I doubt that this would show any impact even on hundreds of iterations. In fact, when I'm able to reverse my very latest commit (9fd6733), "addChild" whould even be faster then before, because the function "numChildren" is called only once, not twice.

That said: this change can still be reverted if it causes any issues or is unpopular for some other reasons. I just don't think that performance speaks against it.

@Oldes
Copy link

@Oldes Oldes commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using timeline based animations and it is quite common that I call addChildAt multiple times per frame per object (as it's in classic Flash), so I still consider each "sugar" unnecessary - especially in cases where I don't see any real bonus.

But my version is quite different from your already, because I didn't wanted to add Sprite3D as I will never use it in current project.

@Oldes
Copy link

@Oldes Oldes commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding reducing numChildren call, it's questionable. You would have to count also the condition, which is in "addChildAt" even in cases, where it's called directly.

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've got one more condition, but spare one method call (numChildren). And the method call is definitely much more expensive than the condition.

As for Sprite3D: did you test the performance impact? I took great care to make the effect on pure 2D code as minimal as possible. That said: yes, there sure is some code that needs to be executed that hadn't been previously.

@Oldes
Copy link

@Oldes Oldes commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not tested, I don't have that much time.. and if you would check my version, I'm modifying your code quite drastically. This changes would not be accepted in official repo, but in my case I don't care. Like here: Oldes@a64bc57 ;-)

As I said, every bit is worth the effort in final.

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, that will definitely be faster. But for the official version, I did unfortunately have to add that test; you wouldn't believe how developers are able to bend the API, LOL. 😉

No worries, I know that you've got more important things to do than test different Starling iterations. Thanks for taking the time to give me feedback on this (and other) changes, though — it's appreciated! And the last word is not spoken about that "negative index" case. I might limit it to just "getChildAt".

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a problem with Feathers ScrollContainer!

override public function addChildAt(child:DisplayObject, index:int):DisplayObject
{
    if(!this.displayListBypassEnabled)
    {
        return super.addChildAt(child, index);
    }
    var result:DisplayObject = DisplayObjectContainer(this.viewPort).addChildAt(child, index);
    if(result is IFeathersControl)
    {
        result.addEventListener(Event.RESIZE, child_resizeHandler);
    }
    if(result is ILayoutDisplayObject)
    {
        result.addEventListener(FeathersEventType.LAYOUT_DATA_CHANGE, child_layoutDataChangeHandler);
    }
    this.invalidate(INVALIDATION_FLAG_SIZE);
    return result;
}

Number of children may be different !!!! In this.viewPort Because I could add children through addRawChild();

Please return all back!

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alevys — what do you mean, is there a new problem?

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES!

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess since this commit? eed8696
Is that right?

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on this.

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please speak in full sentences, Alexander, otherwise this is going to take a while. ;-)
What exactly is your problem, and what can I do to help?

@alevys
Copy link

@alevys alevys commented on 5232a92 Mar 24, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow this code. It reflects the essence of the problem.

    public class Test extends ScrollContainer
    {
        public function Test() {
            super();

            addRawChild(new Sprite());
            addRawChild(new Sprite());
            addChild(new Sprite());
        }
    }

@PrimaryFeather
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could reproduce the problem, thanks! Josh is already working on an update of Feathers to restore compatibility.

Please sign in to comment.