Unity3D Building Damage Tutorial- Part VII - Optimalisation

So, we have to make code a little bit faster. To count the time taken by things we are going to speed up (Destroy method), I've written this: public float DebDestroyTime = 0f; public float DebGeneralDestroyTime = 0f; public int DebDestroyCount = 0; public int DebGeneralNumber = 0; ...    ...       ...       ...                 public void TellAliasesToUpdate{ DebDestroyCount = 0; DebDestroyTime = 0; if(time>3){ for(int k=0; k0){ DebGeneralNumber++; DebGeneralDestroyTime += DebDestroyTime; Debug.Log(DebGeneralNumber+" The overall time is "+DebDestroyTime+" And the General time is "+DebGeneralDestroyTime); } 	} Don't bother about the  variable. Things for counting optimalisation time have Deb prefix. This variables are set in Destroy method in Verticle class private void Destroy{ float time = 0f; time = Time.realtimeSinceStartup; //Here setting current time for(int i = 0; i<LinkedAliases.Count; i++){ OwnerManager.Aliases[ LinkedAliases[i]  ].RemoveLink(number); foreach(int l in Triangles){ OwnerManager.Aliases[ LinkedAliases[i]  ].RemoveLink(number); } 				RemoveLink( LinkedAliases[i]  ); } 			if((OwnerManager.IsMeshThick==true)&&IsATwin==false){ OwnerManager.Aliases[number+(OwnerManager.Aliases.Count/2)].Destroy; ProduceTrianglesBetweenWalls; } 			DestroyTriangles; OwnerManager.UpdateTrianglesList; time = Time.realtimeSinceStartup - time;  //Now counting deffrence //Debug.Log("Time made is "+time); OwnerManager.DebDestroyTime += time; //and adding to DebDestroyTime variables OwnerManager.DebDestroyCount++; } Okay, so now (for me) time after 10 "destroing" updates is  is 14.10584 seconds.
 * Is the number of Updates where some kind of destroying was done. It is not done allways, as in many "updates" the Aliases are just losing health
 * Time consumed for Destroy methods in every Alias in one Update.
 * Time consumed for Destroy methods in every Alias in all Updates
 * The number of times Destroy method was called in one Update

Making only 2 traingles for a wall
As you may remember from last part, for every wall I made 4 traingles, effectively making it visible for two sides. This is of course unnecessary, and time-consuming.

As we know traingle data in Unity in stored in mesh.traingles array. It consists of ints, every three of them mean one traingle with verticles of this 3 numbers. I discovered that to correctly face the traingle, we have to know the position of our destroyed Alias among the three. Than, knowing the order we can set the order of Aliases in AddTrinagleAndVerticles method. This is how I've done it:

private void ProduceTrianglesBetweenWalls{//fills the void between Alias and its twin int[] TempListOfAliasNumbers= new int[2]; //Stores the numbers of other (not this) aliases that form a triangle int DestroyedAliasPosition = 0; foreach(int k in Triangles){ if(k < OwnerManager.OrgTriangleCount){//This triangle is not a wall triangle Vector3 VertNumbers = OwnerManager.Triangles[k]; if(VertNumbers != Vector3.zero){ int i = 0; for(int j=0; j<3; j++){ float f = VertNumbers[j]; if(OwnerManager.VerticleToAliasArray[ (int) f]!= number){ int z = OwnerManager.VerticleToAliasArray[ (int) f]; TempListOfAliasNumbers[i] = z; 								i++; }else{ DestroyedAliasPosition = i; Debug.Log(i);} } 						if( (OwnerManager.Aliases[TempListOfAliasNumbers[0]].state != VerticleState.Destroyed) && (OwnerManager.Aliases[TempListOfAliasNumbers[1]].state != VerticleState.Destroyed) ){ AddTriangleInOrder(DestroyedAliasPosition, TempListOfAliasNumbers); } 					} 				} 			} 			OwnerManager.TranslateFromMeshToManager; } 		private void AddTriangleInOrder(int DestroyedAliasPosition, int[] TempListOfAliasNumbers){ switch (DestroyedAliasPosition){ case 0: OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0])  ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; break; case 1: OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[0]                              ,TempListOfAliasNumbers[1],  TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2)  ; break; case 2: OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] )  ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; break; } 		}
 * Stores this position. It can be either 0, 1 or 2. Than, it is passed with TempListOfAliasNumbers array to the  method. There, adequate order is found. (Order was found by just trying until it was good)

With that, the time of 10 "destroying" updates fell to 10.63108 for me. Betted, but still far to slow.

TranslateFromMeshToManager and UpdateTrianglesList
Let's analise the Destroy method in Verticle class. private void Destroy{ //DebEnlightenThisAlias; //DebEnlightenTriangles; //Debug.Log("liAl "+LinkedAliases[0]+" Da angle "+number); for(int i = 0; i<LinkedAliases.Count; i++){ //foreach(int k in LinkedAliases){//send info to other aliases about breaking links OwnerManager.Aliases[ LinkedAliases[i]  ].RemoveLink(number); foreach(int l in Triangles){ OwnerManager.Aliases[ LinkedAliases[i]  ].RemoveLink(number); } 				RemoveLink(  LinkedAliases[i]  ); } 			if((OwnerManager.IsMeshThick==true)&&IsATwin==false){ OwnerManager.Aliases[number+(OwnerManager.Aliases.Count/2)].Destroy; ProduceTrianglesBetweenWalls; } 			DestroyTriangles; OwnerManager.UpdateTrianglesList; } Well, it makes this:


 * 1) Sends info to linked Aliases, for it to remove the link to Alias being destroyed.
 * 2) Destroys the Twin
 * 3) Produces wall
 * 4) Destroys Triangles
 * 5) And Updates Trinagle List

Let's focus on the last one. During in MeshManager and Verticle class, there are often "translations" between my custom classes or arrays (Aliases, VTAA, Triangles list) and Mesh Class (mesh.traingles, mesh.vertices). DestroyTriangles method removes data from Triangle list only. Than, this cahnges have to be translated to mesh.traingles. It is done in  UpdateTrianglesList.

It is called each time the Alias is being Destroyed. This means that it is run many times in every "destroing" update. private void ProduceTrianglesBetweenWalls{//fills the void between Alias and its twin int[] TempListOfAliasNumbers= new int[2]{99,99}; //Stores the numbers of other (not this) aliases that form a triangle float[] fx = new float[2]; foreach(int k in Triangles){ if(k < OwnerManager.OrgTriangleCount){//This triangle is not a wall triangle Vector3 VertNumbers = OwnerManager.Triangles[k]; if(VertNumbers != Vector3.zero){ int i = 0; for(int j=0; j<3; j++){ float f = VertNumbers[j]; if(OwnerManager.VerticleToAliasArray[ (int) f]!= number){ fx[i] = f; 								int z = OwnerManager.VerticleToAliasArray[ (int) f]; TempListOfAliasNumbers[i] = z; 								i++; } 						} 						if( (OwnerManager.Aliases[TempListOfAliasNumbers[0]].state != VerticleState.Destroyed) && (OwnerManager.Aliases[TempListOfAliasNumbers[1]].state != VerticleState.Destroyed) ){ OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[0]                              ,TempListOfAliasNumbers[1],  TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] )  ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2)  ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; } 					} 				} 			} 			OwnerManager.TranslateFromMeshToManager; } This function, as we know manages the wall making. As triangles and vertices are maniplated directly in mesh class. After that, these changes have to be "translated" to my own custom classes. And this "translation", made in  is made every time a wall is bulid.

So, in an one "destroying" update, we have multiple calls to functions  and. Why not calling them only in the end of  method? This for sure will speed things up!

TellAliasesToUpdate method(MeshManager.cs)
public bool MeshWasChanged = false; ...   ...      ...     ...    ...      ...    ...          public void TellAliasesToUpdate{ DebDestroyCount = 0; DebDestroyTime = 0; if(time>1){ for(int k=0; k0){ DebGeneralNumber++; DebGeneralDestroyTime += DebDestroyTime; Debug.Log(DebGeneralNumber+" The overall time is "+DebDestroyTime+" And the General time is "+DebGeneralDestroyTime); } 	}

Well, two two new things: For now, we have to change the
 * Two make code even faster, i added MeshWasChanged bool. It works similarly to the DebDestroyCount. In Destroy method in Verticle, we change it to true. In this way, only when some changes are made, that part of code will work.
 * I will talk about it in a moment.

UpdateTrainglesList Method (MeshManager.cs)
public void UpdateTrianglesList{//gets the in-manager triangle list and then translates it to mesh.triangles format, and then sets it to be the mesh.triangles. If mesh.traingles is longer than Traingles*3, writes from 0 to traingles*3, rest of mesh.traingles is left unchanged //Debug.Log("UPL"); int LenghtOfTrianglesArray = Triangles.Count*3; int[] tempTrianglesArray = new int[LenghtOfTrianglesArray]; int i = 0; foreach(Vector3 vec in Triangles){ tempTrianglesArray[i] 	= (int)vec.x; 			tempTrianglesArray[i+1] = (int)vec.y; 			tempTrianglesArray[i+2] = (int)vec.z; 			i+=3; } 		int[] tempTrianglesArrayNumber2 = new int[mesh.triangles.Length]; mesh.triangles.CopyTo(tempTrianglesArrayNumber2,0); tempTrianglesArray.CopyTo(tempTrianglesArrayNumber2, 0);// we override the data in tempTrianglesArrayNumber2, but only to a lenght of TempTrianglesArray mesh.triangles = tempTrianglesArrayNumber2; //mesh.triangles = tempTrianglesArray; } Well, I've added a new  tempTrianglesArrayNumber2 array which is of current mesh.traingle lenght. Important here is that if here the lenght of mesh.triangles is bigger that Traingles list * 3, the additional traingles (from adding a wall) will be preserved, and not deleted.

Problem with walls.


Well, if we would start the game by now, without the  MakeWallsFromList, something like that would happen:

The reason for that is this: Sometimes, in the same update a new Wall is added, using lets say Alias A, and the same Alias is deleted. Becouse that the mesh->Classes translate is done in the end of update, the Alias A doesn't know that he "takes part" in wall, and in result fails to remove it.

This is the solution that i found for this problem:
 * 1) While being destroyed, the Aliases will not call AddTriangleAndVerticles directly, but will add data about this theoretical wall to a special list instead.
 * 2) After all update is done, code will check for every wall in list if Aliases taking part in it are still "undestroyed". If so, the wall will be made
 * 3) Clears the List.

Okey, let's go down to implementation.

Alternative wall making
// in Vertices Class private void AddTriangleInOrder(int DestroyedAliasPosition, int[] TempListOfAliasNumbers){ switch (DestroyedAliasPosition){ case 0: OwnerManager.AddWallToWallsList(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0])  ; OwnerManager.AddWallToWallsList(TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; break; case 1: OwnerManager.AddWallToWallsList(TempListOfAliasNumbers[0]                              ,TempListOfAliasNumbers[1],  TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; OwnerManager.AddWallToWallsList(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2)  ; break; case 2: OwnerManager.AddWallToWallsList(TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] )  ; OwnerManager.AddWallToWallsList(TempListOfAliasNumbers[1] + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers[1], TempListOfAliasNumbers[0] + OwnerManager.Aliases.Count/2)  ; break; } 		} Simple change from AddTriangleAndVerticles to  AddWallToWallsList.

AddWallToWallsList method (meshManager)
public List WallsWaitingToBeMade = new List ; ...  ...   ...   ...   ...   ...            public void AddWallToWallsList(int a, int b, int c){ WallsWaitingToBeMade.Add(a); WallsWaitingToBeMade.Add(b); WallsWaitingToBeMade.Add(c); } Well, just adding the Aliases numbers to the List. (the "format" of triangles in this list is similar to mesh.traingles: 3 ints one after another mean one traingle.

Than, in TellAliasesToUpdate method (MeshManager class) we call MakeWallsFromList

MakeWallsFromList method
private void MakeWallsFromList{//this check if verticles of teoretical Alias are still Ok. If so, sends info to make trinagle from them. TempMeshVerticlesArray = new Vector3[mesh.vertices.Length]; mesh.vertices.CopyTo(TempMeshVerticlesArray,0); TempMeshTrinaglesArray = new int[mesh.triangles.Length]; mesh.triangles.CopyTo(TempMeshTrinaglesArray,0); float time = Time.realtimeSinceStartup; for(int i=0; i=Aliases.Count/2){//it is a Twin!, so dont have state if(	Aliases[WallsWaitingToBeMade[i]-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } 			if(WallsWaitingToBeMade[i+1]=Aliases.Count/2){//it is a Twin!, so dont have state if(	Aliases[WallsWaitingToBeMade[i+1]-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } 			if(WallsWaitingToBeMade[i+2]=Aliases.Count/2){//it is a Twin!, so dont have state if(	Aliases[WallsWaitingToBeMade[i+2]-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } 			if(ItIsOk == true){ AddTriangleAndVerticles(	WallsWaitingToBeMade[i], WallsWaitingToBeMade[i+1], WallsWaitingToBeMade[i+2]); } 		} 		WallsWaitingToBeMade.Clear; mesh.vertices = TempMeshVerticlesArray; mesh.triangles = TempMeshTrinaglesArray; DebDestroyTime += (Time.realtimeSinceStartup - time); } Might look a bit complicated, but it is really simple 		for(int i=0; i=Aliases.Count/2){//it is a Twin!, so dont have state if(	Aliases[WallsWaitingToBeMade[i]-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } Here is a condition that checks if the Alias which will take part in wall-making is not destroyed. But is not so easy, as twins may take part too, and as we know they dont have states(or at least, states are not updated). If conditions went otherwise, I set a bool. After all 3 Aliases are checked in that manner, and ItIsOk is still true like in the beginning, we can make a wall. if(ItIsOk == true){ AddTriangleAndVerticles(	WallsWaitingToBeMade[i], WallsWaitingToBeMade[i+1], WallsWaitingToBeMade[i+2]); } Than, the list is cleared WallsWaitingToBeMade.Clear; With that optimalisation, the DebDestroyTime after 10 updates fell to 0.5745029 sec. Enormous change, but there is one more big thing to optimalise
 * This copying on the top is something else, will talk bout it later
 * Check if it is an Alias
 * which is not destroyed. If so, it is ok, lets do nothin
 * It is a twin, it don't have a state, so
 * We check the alias which twin it is, if it is destroyed. Accoring to rules i stated, the Alias and its twin have the same states.

Setting mesh.vertices and mesh.traingles
I suspect (but i am not sure), that when we set mesh.vertices or mesh.traingles not only reference to an array is changed, but there are some inter-Mesh calculations taking place. (like normals counting). Now, these small calculations are performed every time  is called. Therefore, it would be faster to first produce an big array with added verticle and traingle arrays, and that set the mesh.traingles and vertices.

So, I added two new temporary arrays private int[] TempMeshTrinaglesArray; private Vector3[] TempMeshVerticlesArray; Initialised them at begining of  method

private void MakeWallsFromList{//this check if verticles of teoretical Alias are still Ok. If so, sends info to make trinagle from them. TempMeshVerticlesArray = new Vector3[mesh.vertices.Length]; mesh.vertices.CopyTo(TempMeshVerticlesArray,0); TempMeshTrinaglesArray = new int[mesh.triangles.Length]; mesh.triangles.CopyTo(TempMeshTrinaglesArray,0); ...  ...   ...   ...   ...   ...   ...   ...   ...   ...   ... And Edited the   method, for it to operate on Temporary Arrays. ​	public int AddTriangleAndVerticles(int a, int b, int c){//gets 3 numbers of Aliases. Produces a triangle between them, and new verticles Vector3[] OldVerticleList = TempMeshVerticlesArray; Vector3[] NewVerticleList = new Vector3 [TempMeshVerticlesArray.Length+3]; Vector3[] LastThreeVerts = new Vector3[3]{Aliases[a].positionRelative,Aliases[b].positionRelative,Aliases[c].positionRelative }; OldVerticleList.CopyTo(NewVerticleList,0); LastThreeVerts.CopyTo(NewVerticleList, TempMeshVerticlesArray.Length); TempMeshVerticlesArray = NewVerticleList; int[] OldTriangleList = TempMeshTrinaglesArray; int[] NewTriangleList = new int [TempMeshTrinaglesArray.Length+3]; int[] NewTriangle = new int[3]{TempMeshVerticlesArray.Length-1, TempMeshVerticlesArray.Length-2, TempMeshVerticlesArray.Length-3}; OldTriangleList.CopyTo(NewTriangleList, 0); NewTriangle.CopyTo(NewTriangleList, OldTriangleList.Length); TempMeshTrinaglesArray = NewTriangleList; return (TempMeshTrinaglesArray.Length/3)-1; //the number of added triangle }

And then, on the end of MakeWallsFromList method,  we update the mesh ...  ...   ...   ...   ... 		WallsWaitingToBeMade.Clear; mesh.vertices = TempMeshVerticlesArray; mesh.triangles = TempMeshTrinaglesArray; DebDestroyTime += (Time.realtimeSinceStartup - time); } Okay, and it is end. Time of 10 updates is 0.1333022 sec. For now, it is good enough. Of course, there are many other places to optimalise, but for now, I'm done.

For files, goto Github, Version 5.