Reputation: 326
I have a dataset of almost 120,000 stars and a test dataset of 215 stars (taken from the main dataset) and I am making a program that reads the dataset and plots stars around a 3D Earth model.
void listStars()
{
int i = 0;
while (i < (StarDataBank.Instance.NumOfStars))
{
int primaryID = int.Parse(StarDataBank.Instance.StarIDID[i]);
string properName = StarDataBank.Instance.StarName[i];
string HIPID = StarDataBank.Instance.StarIDHIP[i];
string HDID = StarDataBank.Instance.StarIDHD[i];
string HRID = StarDataBank.Instance.StarIDHR[i];
string GLID = StarDataBank.Instance.StarIDGL[i];
string BFID = StarDataBank.Instance.StarIDBF[i];
decimal rightAscension = Convert.ToDecimal(StarDataBank.Instance.StarRA[i]);
decimal declination = Convert.ToDecimal(StarDataBank.Instance.StarDec[i]);
decimal Mag;
decimal CI;
float scale = 0;
int r = 0;
int g = 0;
int b = 0;
Decimal.TryParse((StarDataBank.Instance.StarMag[i]), out Mag);
Decimal.TryParse((StarDataBank.Instance.StarCI[i]), out CI);
if (PlayerPrefs.GetInt("dynamicSize") == 1)
{
if (Mag < -1)
{
scale = 77.5f;
}
else
{
if (Mag > -1 && Mag <= 5)
{
scale = 52.5f;
}
else
{
if (Mag > 5 && Mag <= 10)
{
scale = 32.5f;
}
else
{
if (Mag > 10 && Mag <= 15)
{
scale = 17.5f;
}
else
{
if (Mag > 15 && Mag <= 20)
{
scale = 7.5f;
}
else
{
if (Mag > 20 && Mag <= 25)
{
scale = 2.5f;
}
}
}
}
}
}
}
else
{
scale = 20;
}
StartCoroutine(placeStars(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, Mag, CI, scale));
i++;
}
DestroyImmediate(StarDataBank.Instance.gameObject);
}
IEnumerator placeStars(int primaryID, string properName, string HIPID, string HDID, string HRID, string GLID, string BFID, decimal rightAscension, decimal declination, decimal magnitude, decimal colourIndex, float scale)
{
var thisStar = (GameObject)Instantiate(prefabStar, transform.position + getVectors(Convert.ToDecimal(rightAscension), Convert.ToDecimal(declination)), Quaternion.identity);
thisStar.name = (primaryID).ToString();
thisStar.transform.parent = StarObject.transform;
thisStar.transform.localScale = new Vector3(scale, scale, scale);
thisStar.AddComponent<Star>().newStar(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, magnitude, colourIndex);
yield return null;
}
Using the smaller test dataset with only 215 stars, I am able to attach the Star class to each Instantiated star.
But with the large full dataset with ~120k stars, the Unity Editor just hangs after the play button is pressed. When I comment out this line: thisStar.AddComponent<Star>().newStar(...);
The program works as intended, each star is plotted in the scene, the only thing is that the Star class isn't getting attached to each newly instantiated star.
This means that the AddComponent function is not playing well with my code, especially when the large dataset is being used.
Is there a more efficient way of attaching the Star class to each instantiated star GameObject?
Also, does it make a difference when I destroy the singleton after I've finished using it? And does it make a difference that I've used IEnumerator to instantiate?
EDIT: Here's the Star class:
public class Star : MonoBehaviour
{
Simulation simulationInstance;
public int primaryID; // primary key NEEDS TO BE SET
public string properName; // some stars have names NEEDS TO BE SET
public string HIPID; // ID of star from Hipparcos catalogue NEEDS TO BE SET
public string HDID; // ID of star from Henry Draper catalogue NEEDS TO BE SET
public string HRID; // ID of star from Harvard Revised catalogue NEEDS TO BE SET
public string GLID; // ID of star from Gliese catalogue NEEDS TO BE SET
public string BFID; // ID of star from BayerFlamsteed catalogue NEEDS TO BE SET
public decimal rightAscension; // right ascension of star NEEDS TO BE SET
public decimal declination; // declination of star NEEDS TO BE SET
public decimal magnitude; // magnitude of the star NEEDS TO BE SET
public decimal colourIndex; // colour index of the star NEEDS TO BE SET
public int scale; // size of the sphere that will represent the star AUTOMATICALLY SET
public int red; // red colour (0-255) AUTOMATICALLY SET
public int green; // green colour (0-255) AUTOMATICALLY SET
public int blue; // blue colour (0-255) AUTOMATICALLY SET
public double x; // AUTOMATICALLY SET
public double y; // AUTOMATICALLY SET
public double z; // AUTOMATICALLY SET
void Start()
{
simulationInstance = FindObjectOfType<Simulation>();
}
public void newStar(int primaryID, string properName, string HIPID, string HDID, string HRID, string GLID, string BFID, decimal rightAscension, decimal declination, decimal magnitude, decimal colourIndex)
{
this.primaryID = primaryID;
this.properName = properName;
this.HIPID = HIPID;
this.HDID = HDID;
this.HRID = HRID;
this.GLID = GLID;
this.BFID = BFID;
this.rightAscension = rightAscension;
this.declination = declination;
this.magnitude = magnitude;
this.colourIndex = colourIndex;
}
public void tellStarInfoPanel()
{
simulationInstance.starInfoPanelManager(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, magnitude, colourIndex);
}
}
EDIT: I have made listStars() a coroutine and placeStars() a normal method
public void placeStars(int primaryID, string properName, string HIPID, string HDID, string HRID, string GLID, string BFID, decimal rightAscension, decimal declination, decimal magnitude, decimal colourIndex, float scale)
{
var thisStar = (GameObject)Instantiate(prefabStar, transform.position + getVectors(Convert.ToDecimal(rightAscension), Convert.ToDecimal(declination)), Quaternion.identity);
thisStar.name = (primaryID).ToString();
thisStar.transform.parent = StarObject.transform;
thisStar.transform.localScale = new Vector3(scale, scale, scale);
thisStar.GetComponent<Star>().newStar(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, magnitude, colourIndex);
starsRendered++;
}
Upvotes: 1
Views: 4180
Reputation: 659
Is there a more efficient way of attaching the Star class to each instantiated star GameObject?
Yes. Your Star prefab should already have the component
Star
attached. Then you can instantiate it asvar thisStar = (Star)Instantiate(prefabStar, transform.position + getVectors(Convert.ToDecimal(rightAscension), Convert.ToDecimal(declination)), Quaternion.identity);
, avoiding the AddComponent step.
Also, does it make a difference when I destroy the singleton after I've finished using it?
No. It's not consuming any processing, you will just free some memory.
And does it make a difference that I've used IEnumerator to instantiate?
Only if you want to instantiate the objects over multiple frames, as I've shown below.
Some things you can improve
listStars
method into a Coroutine, making the return type an IEnumerator and using yield return placeStars(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, Mag, CI, scale)
. You might want to instantiate multiple stars on the same frame, or it might take some time!Start
method becomes increasingly expensive at every star instantiated, it's better to pass it as an argument to the placeStars
method, acquiring it only once before the loop stars.dynamicSize
from PlayerPrefs
at every iteration. Store it before the loop starts. The same goes for the singleton instance. Cache it before the loop starts and use the cached reference.Pseudo code example:
IEnumerator listStars()
{
var starDataBank = StarDataBank.Instance;
var dynamicSize = PlayerPrefs.GetInt("dynamicSize");
var simulationInstance = FindObjectOfType<Simulation>();
var starsPerFrame = 10;
for (int i = 0; i < starDataBank.NumOfStars; i++)
{
// do your stuff
for (int j = 0; j < starsPerFrame; j++)
placeStars(simulationInstance, primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, Mag, CI, scale));
yield return null;
}
DestroyImmediate(starDataBank.gameObject);
}
Upvotes: 1
Reputation: 1637
You need to know that this is not a good use case for Monobehaviour/Component OOP model. You may want consider switching to preview ECS tech stack for this. Not long time ago I tested similar case and found out that instancing 100k entities from csv file is doable. In this situation my HDD I/O was the bottleneck and not CPU/memory (opposite of what happens with Monobehaviours). What's more - loading time can be optimized by pre-caching your raw data as memory segments serialized to binary files. But I won sugar-coat it - it's much more advanced programming than Monobehaviours and this tech stack is in unstable pre-release state at the moment.
Upvotes: 0
Reputation: 1775
If you think AddComponent() is computationally too heavy, it would be wise to have a closer look at Start component or even better running a Unity's profiler tool which should exactly pinpoint the culprit.
Also do your Star has to be a Component? Maybe you can go around this problem by simply utilising OO techniques.
Upvotes: 0
Reputation: 10137
Avoid usage of AddComponent
in performance critical code
Calling GameObject.AddComponent
is expensive, especially inside a performance critical context. Each time a component is added, the following must happen:
- Finding the component's script in the script cache, by name. This might also incur allocations if it's not already cached.
- Allocating the memory for the MonoBehaviour.
- Notifying other attached components that a new component has been added. The attached components can perform actions when a known component is added. The amount of work performed here depends on the number and type of attached components. E.g. a rigid body needs to know if a collider is added.
- Running the new component's Awake method. This inspection will highlight calls to AddComponent inside a performance critical context. It will also mark the calling method as expensive, and any usages of the calling method will also receive a performance indicator highlight.
Upvotes: 0