Skip to content

Commit ae09da3

Browse files
Array walking optimizations in CCNode to make node by tag lookup O(logN) instead of O(N). This change gets us 1.4 seconds on 16,000 nodes for the remove test.
1 parent f723b75 commit ae09da3

File tree

11 files changed

+137
-8
lines changed

11 files changed

+137
-8
lines changed

cocos2d/base_nodes/CCNode.cs

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public class CCNode : ICCSelectorProtocol, ICCFocusable, ICCTargetedTouchDelegat
9999
protected CCActionManager m_pActionManager;
100100
protected CCCamera m_pCamera;
101101
protected CCRawList<CCNode> m_pChildren;
102+
private Dictionary<int, List<CCNode>> m_pChildrenByTag;
102103
protected CCGridBase m_pGrid;
103104
protected CCNode m_pParent;
104105
protected CCScheduler m_pScheduler;
@@ -623,10 +624,15 @@ public virtual void Update(float dt)
623624

624625
#endregion
625626

627+
private bool m_bCleaned = false;
626628
~CCNode()
627629
{
628630
//unregisterScriptHandler();
629631
Cleanup();
632+
if(m_pChildren != null)
633+
m_pChildren.Clear();
634+
if(m_pChildrenByTag != null)
635+
m_pChildrenByTag.Clear();
630636
}
631637

632638
public void GetPosition(out float x, out float y)
@@ -644,6 +650,10 @@ public void SetPosition(float x, float y)
644650

645651
public virtual void Cleanup()
646652
{
653+
if (m_bCleaned == true)
654+
{
655+
return;
656+
}
647657
// actions
648658
StopAllActions();
649659

@@ -658,6 +668,7 @@ public virtual void Cleanup()
658668
elements[i].Cleanup();
659669
}
660670
}
671+
m_bCleaned = true;
661672
}
662673

663674
public CCNode GetChildByTag(int tag)
@@ -666,6 +677,18 @@ public CCNode GetChildByTag(int tag)
666677

667678
if (m_pChildren != null && m_pChildren.count > 0)
668679
{
680+
if (m_pChildrenByTag.ContainsKey(tag))
681+
{
682+
List<CCNode> l = m_pChildrenByTag[tag];
683+
foreach (CCNode n in l)
684+
{
685+
if (n.Tag == tag)
686+
{
687+
return (n);
688+
}
689+
}
690+
}
691+
/*
669692
CCNode[] elements = m_pChildren.Elements;
670693
for (int i = 0, count = m_pChildren.count; i < count; i++)
671694
{
@@ -674,6 +697,7 @@ public CCNode GetChildByTag(int tag)
674697
return elements[i];
675698
}
676699
}
700+
*/
677701
}
678702

679703
return null;
@@ -701,9 +725,19 @@ public virtual void AddChild(CCNode child, int zOrder, int tag)
701725
{
702726
m_pChildren = new CCRawList<CCNode>();
703727
}
728+
if (m_pChildrenByTag == null)
729+
{
730+
m_pChildrenByTag = new Dictionary<int, List<CCNode>>();
731+
}
704732

705733
InsertChild(child, zOrder);
706734

735+
if (!m_pChildrenByTag.ContainsKey(tag))
736+
{
737+
m_pChildrenByTag[tag] = new List<CCNode>();
738+
}
739+
m_pChildrenByTag[tag].Add(child);
740+
707741
child.m_nTag = tag;
708742
child.Parent = this;
709743
child.m_uOrderOfArrival = s_globalOrderOfArrival++;
@@ -740,7 +774,19 @@ public virtual void RemoveChild(CCNode child, bool cleanup)
740774
{
741775
return;
742776
}
743-
777+
try
778+
{
779+
if (m_pChildrenByTag.ContainsKey(child.Tag))
780+
{
781+
m_pChildrenByTag[child.Tag].Remove(child);
782+
}
783+
}
784+
catch (Exception)
785+
{
786+
// Ignore this here, don't care about this exception. It likely means either
787+
// a concurrent modification has occured somewhere upstream, or the child was not
788+
// added to the tag list successfully.
789+
}
744790
if (m_pChildren.Contains(child))
745791
{
746792
DetachChild(child, cleanup);
@@ -845,6 +891,56 @@ public virtual void ReorderChild(CCNode child, int zOrder)
845891
child.m_uOrderOfArrival = s_globalOrderOfArrival++;
846892
child.m_nZOrder = zOrder;
847893
}
894+
#region Child Sorting
895+
896+
// Quick sort taken from http://snipd.net/quicksort-in-c
897+
public static void Quicksort(CCNode[] elements, int left, int right)
898+
{
899+
int i = left, j = right;
900+
CCNode pivot = elements[(left + right) / 2];
901+
902+
while (i <= j)
903+
{
904+
/*
905+
(pivot.m_nZOrder < elements[i].m_nZOrder ||
906+
(pivot.m_nZOrder == elements[i].m_nZOrder && pivot.m_uOrderOfArrival < elements[i].m_uOrderOfArrival)))
907+
*/
908+
while ((elements[i].m_nZOrder < pivot.m_nZOrder ||
909+
(pivot.m_nZOrder == elements[i].m_nZOrder && elements[i].m_uOrderOfArrival < pivot.m_uOrderOfArrival)))
910+
{
911+
i++;
912+
}
913+
914+
while ((elements[j].m_nZOrder > pivot.m_nZOrder ||
915+
(pivot.m_nZOrder == elements[j].m_nZOrder && elements[j].m_uOrderOfArrival > pivot.m_uOrderOfArrival)))
916+
{
917+
j--;
918+
}
919+
920+
if (i <= j)
921+
{
922+
// Swap
923+
CCNode tmp = elements[i];
924+
elements[i] = elements[j];
925+
elements[j] = tmp;
926+
927+
i++;
928+
j--;
929+
}
930+
}
931+
932+
// Recursive calls
933+
if (left < j)
934+
{
935+
Quicksort(elements, left, j);
936+
}
937+
938+
if (i < right)
939+
{
940+
Quicksort(elements, i, right);
941+
}
942+
}
943+
848944

849945
public virtual void SortAllChildren()
850946
{
@@ -854,6 +950,8 @@ public virtual void SortAllChildren()
854950
int length = m_pChildren.count;
855951
CCNode[] x = m_pChildren.Elements;
856952

953+
Quicksort(x, 0, length-1);
954+
/*
857955
// insertion sort
858956
for (i = 1; i < length; i++)
859957
{
@@ -870,13 +968,14 @@ public virtual void SortAllChildren()
870968
}
871969
x[j + 1] = tempItem;
872970
}
873-
971+
*/
874972
//don't need to check children recursively, that's done in visit of each child
875973

876974
m_bReorderChildDirty = false;
877975
}
878976
}
879977

978+
#endregion
880979
/// <summary>
881980
/// This is called from the Visit() method. This is where you DRAW your node. Only
882981
/// draw stuff from this method call.

cocos2d/sprite_nodes/CCSprite.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,8 +952,11 @@ public override void SortAllChildren()
952952
{
953953
int i, j, length = m_pChildren.count;
954954
CCNode[] x = m_pChildren.Elements;
955-
CCNode tempItem;
955+
// CCNode tempItem;
956956

957+
CCNode.Quicksort(x, 0, length - 1);
958+
959+
/*
957960
// insertion sort
958961
for (i = 1; i < length; i++)
959962
{
@@ -970,6 +973,7 @@ public override void SortAllChildren()
970973
}
971974
x[j + 1] = tempItem;
972975
}
976+
*/
973977

974978
if (m_pobBatchNode != null)
975979
{

cocos2d/sprite_nodes/CCSpriteBatchNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ public override void SortAllChildren()
272272
// CCNode tempItem;
273273

274274
//insertion sort - change to qsort per RIQ
275-
Quicksort(elements, 0, count - 1);
275+
CCNode.Quicksort(elements, 0, count - 1);
276276

277277
/*
278278
for (int i = 1; i < count; i++)

tests/tests/classes/tests/PerformanceTest/PerformanceNodeChildrenTest/AddRemoveSpriteSheet.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ public override void initWithQuantityOfNodes(int nNodes)
4949

5050
public override void Update(float dt)
5151
{
52-
throw new NotFiniteNumberException();
5352
}
5453

5554
public virtual string profilerName()

tests/tests/classes/tests/PerformanceTest/PerformanceNodeChildrenTest/AddSpriteSheet.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ namespace tests
99
{
1010
public class AddSpriteSheet : AddRemoveSpriteSheet
1111
{
12+
private bool bTested = false;
13+
1214
public override void Update(float dt)
1315
{
1416
// reset seed
1517
//srandom(0);
16-
18+
if (bTested)
19+
{
20+
return;
21+
}
1722
// 15 percent
1823
int totalToAdd = (int)(currentQuantityOfNodes * 0.15f);
1924

@@ -24,35 +29,45 @@ public override void Update(float dt)
2429
int[] zs = new int[totalToAdd];
2530

2631
// Don't include the sprite creation time and random as part of the profiling
32+
long lStart = DateTime.Now.Ticks;
2733
for (int i = 0; i < totalToAdd; i++)
2834
{
2935
CCSprite pSprite = new CCSprite(batchNode.Texture, new CCRect(0, 0, 32, 32));
3036
sprites.Add(pSprite);
3137
zs[i] = (int)(CCMacros.CCRandomBetweenNegative1And1() * 50);
3238

3339
}
40+
long ldiff = DateTime.Now.Ticks - lStart;
41+
CCLog.Log("Add Sprite took {0} sec", (new TimeSpan(ldiff)).TotalSeconds);
3442

3543
// add them with random Z (very important!)
3644
//#if CC_ENABLE_PROFILERS
3745
// CCProfilingBeginTimingBlock(_profilingTimer);
3846
//#endif
3947

48+
lStart = DateTime.Now.Ticks;
4049
for (int i = 0; i < totalToAdd; i++)
4150
{
4251
batchNode.AddChild((CCNode)(sprites[i]), zs[i], PerformanceNodeChildrenTest.kTagBase + i);
4352
}
53+
ldiff = DateTime.Now.Ticks - lStart;
54+
CCLog.Log("Add Child to Batch took {0} sec", (new TimeSpan(ldiff)).TotalSeconds);
4455

4556
//#if CC_ENABLE_PROFILERS
4657
// CCProfilingEndTimingBlock(_profilingTimer);
4758
//#endif
4859

4960
// remove them
61+
lStart = DateTime.Now.Ticks;
5062
for (int i = 0; i < totalToAdd; i++)
5163
{
5264
batchNode.RemoveChildByTag(PerformanceNodeChildrenTest.kTagBase + i, true);
5365
}
66+
ldiff = DateTime.Now.Ticks - lStart;
67+
CCLog.Log("Remove Child took {0} sec", (new TimeSpan(ldiff)).TotalSeconds);
5468

5569
}
70+
bTested = true;
5671
}
5772

5873
public override string title()

tests/tests/classes/tests/PerformanceTest/PerformanceNodeChildrenTest/IterateSpriteSheet.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public override void initWithQuantityOfNodes(int nNodes)
5050

5151
public override void Update(float dt)
5252
{
53-
throw new NotFiniteNumberException();
5453
}
5554

5655
public virtual string profilerName()

tests/tests/classes/tests/PerformanceTest/PerformanceNodeChildrenTest/IterateSpriteSheetCArray.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ namespace tests
88
{
99
public class IterateSpriteSheetCArray : IterateSpriteSheet
1010
{
11+
bool bDone = false;
1112
public override void Update(float dt)
1213
{
14+
if (bDone) return;
1315
// iterate using fast enumeration protocol
1416
var pChildren = batchNode.Children;
1517

@@ -26,6 +28,7 @@ public override void Update(float dt)
2628
//#if CC_ENABLE_PROFILERS
2729
// CCProfilingEndTimingBlock(_profilingTimer);
2830
//#endif
31+
bDone = true;
2932
}
3033

3134
public override string title()

tests/tests/classes/tests/PerformanceTest/PerformanceNodeChildrenTest/IterateSpriteSheetFastEnum.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ namespace tests
88
{
99
public class IterateSpriteSheetFastEnum : IterateSpriteSheet
1010
{
11+
bool bDone = false;
1112
public override void Update(float dt)
1213
{
14+
if (bDone) return;
1315
// iterate using fast enumeration protocol
1416
var pChildren = batchNode.Children;
1517

@@ -26,6 +28,7 @@ public override void Update(float dt)
2628
//#if CC_ENABLE_PROFILERS
2729
// CCProfilingEndTimingBlock(_profilingTimer);
2830
//#endif
31+
bDone = true;
2932
}
3033

3134
public override string title()

tests/tests/classes/tests/PerformanceTest/PerformanceNodeChildrenTest/PerformanceNodeChildrenTest..cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class PerformanceNodeChildrenTest
1616
public static int kTagBase = 20000;
1717
public static int TEST_COUNT = 4;
1818

19-
public static int kMaxNodes = 15000;
19+
public static int kMaxNodes = 50000;
2020
public static int kNodesIncrease = 500;
2121

2222
public static int s_nCurCase = 0;

tests/tests/classes/tests/PerformanceTest/PerformanceNodeChildrenTest/RemoveSpriteSheet.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ namespace tests
99
public class RemoveSpriteSheet : AddRemoveSpriteSheet
1010
{
1111

12+
bool bDone = false;
1213
public override void Update(float dt)
1314
{
15+
16+
if (bDone) return;
1417
//srandom(0);
1518

1619
// 15 percent
@@ -47,6 +50,7 @@ public override void Update(float dt)
4750
// CCProfilingEndTimingBlock(_profilingTimer);
4851
//#endif
4952
}
53+
bDone = true;
5054
}
5155

5256
public override string title()

0 commit comments

Comments
 (0)